Index: ps/trunk/source/lib/allocators/shared_ptr.h =================================================================== --- ps/trunk/source/lib/allocators/shared_ptr.h (revision 19898) +++ ps/trunk/source/lib/allocators/shared_ptr.h (revision 19899) @@ -1,75 +1,75 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_ALLOCATORS_SHARED_PTR #define INCLUDED_ALLOCATORS_SHARED_PTR #include "lib/alignment.h" #include "lib/sysdep/rtl.h" // rtl_AllocateAligned struct DummyDeleter { template void operator()(T*) { } }; template inline shared_ptr DummySharedPtr(T* ptr) { return shared_ptr(ptr, DummyDeleter()); } struct ArrayDeleter { template void operator()(T* p) { delete[] p; } }; // (note: uses CheckedArrayDeleter) LIB_API shared_ptr Allocate(size_t size); struct AlignedDeleter { template void operator()(T* t) { rtl_FreeAligned(t); } }; template static inline Status AllocateAligned(shared_ptr& p, size_t size, size_t alignment = cacheLineSize) { void* mem = rtl_AllocateAligned(size, alignment); if(!mem) WARN_RETURN(ERR::NO_MEM); p.reset((T*)mem, AlignedDeleter()); return INFO::OK; } #endif // #ifndef INCLUDED_ALLOCATORS_SHARED_PTR Index: ps/trunk/binaries/data/mods/mod/hwdetect/hwdetect.js =================================================================== --- ps/trunk/binaries/data/mods/mod/hwdetect/hwdetect.js (revision 19898) +++ ps/trunk/binaries/data/mods/mod/hwdetect/hwdetect.js (revision 19899) @@ -1,382 +1,382 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ var g_IntelMesaChipsets = [ "Intel(R) 845G", "Intel(R) 830M", "Intel(R) 852GM/855GM", "Intel(R) 865G", "Intel(R) 915G", "Intel (R) E7221G (i915)", "Intel(R) 915GM", "Intel(R) 945G", "Intel(R) 945GM", "Intel(R) 945GME", "Intel(R) G33", "Intel(R) Q35", "Intel(R) Q33", "Intel(R) IGD", "Intel(R) 965Q", "Intel(R) 965G", "Intel(R) 946GZ", "Intel(R) GMA500", // not in current Mesa "Intel(R) 965GM", "Intel(R) 965GME/GLE", "Mobile Intel\xC2\xAE GM45 Express Chipset", // utf-8 decoded as iso-8859-1 "Intel(R) Integrated Graphics Device", "Intel(R) G45/G43", "Intel(R) Q45/Q43", "Intel(R) G41", "Intel(R) B43", "Intel(R) IGDNG_D", // not in current Mesa; looks somewhat like Ironlake "Intel(R) IGDNG_M", // not in current Mesa; looks somewhat like Ironlake "Intel(R) Ironlake Desktop", "Intel(R) Ironlake Mobile", "Intel(R) Sandybridge Desktop", "Intel(R) Sandybridge Mobile", "Intel(R) Sandybridge Server", "Intel(R) Ivybridge Desktop", "Intel(R) Ivybridge Mobile", "Intel(R) Ivybridge Server", "Intel(R) Haswell Desktop", "Intel(R) Haswell Mobile", "Intel(R) Haswell Server", "Unknown Intel Chipset", "*", // dummy value to support IsWorseThanIntelMesa("*") to detect all Intel Mesa devices ]; // Originally generated from Mesa with // perl -lne'print "\t$1," if /chipset = (".*")/' src/mesa/drivers/dri/intel/intel_context.c // Assumed to be roughly ordered by performance. var g_IntelWindowsChipsets = [ "Intel 845G", "Intel 855GM", "Intel 865G", "Intel 915G", "Intel 915GM", "Intel 945G", "Intel 945GM", "Intel 965/963 Graphics Media Accelerator", "Intel Broadwater G", "Intel Bear Lake B", "Intel Pineview Platform", "Intel Eaglelake", "Intel(R) G41 Express Chipset", // Eaglelake "Intel(R) G45/G43 Express Chipset", // Eaglelake "Intel Cantiga", "Mobile Intel(R) 4 Series Express Chipset Family", // probably Cantiga "Intel(R) HD Graphics", // probably Ironlake "Intel(R) Graphics Media Accelerator HD", // no idea "*", ]; // Determined manually from data reports. // See http://en.wikipedia.org/wiki/Intel_GMA for useful listing. var g_IntelMacChipsets = [ "Intel GMA 950", "Intel GMA X3100", "Intel HD Graphics", "Intel HD Graphics 3000", "Unknown Intel Chipset", "*", ]; // Determined manually from data reports. // See http://support.apple.com/kb/HT3246 for useful listing. function IsWorseThanIntelMesa(renderer, chipset) { var target = g_IntelMesaChipsets.indexOf(chipset); if (target == -1) error("Invalid chipset "+chipset); // GL_RENDERER is "Mesa DRI $chipset" or "Mesa DRI $chipset $otherstuff" for (var i = 0; i < target; ++i) { var str = "Mesa DRI " + g_IntelMesaChipsets[i]; if (renderer == str || renderer.substr(0, str.length+1) == str+" ") return true; } return false; } function IsWorseThanIntelWindows(renderer, chipset) { var target = g_IntelWindowsChipsets.indexOf(chipset); if (target == -1) error("Invalid chipset "+chipset); var match = g_IntelWindowsChipsets.indexOf(renderer); if (match != -1 && match < target) return true; return false; } function IsWorseThanIntelMac(renderer, chipset) { var target = g_IntelMacChipsets.indexOf(chipset); if (target == -1) error("Invalid chipset "+chipset); // GL_RENDERER is "$chipset OpenGL Engine" for (var i = 0; i < target; ++i) { var str = g_IntelMacChipsets[i]+" OpenGL Engine"; if (renderer == str) return true; } return false; } function RunDetection(settings) { // This function should have no side effects, it should just // set these output properties: // List of warning strings to display to the user // in an ugly GUI dialog box var dialog_warnings = []; // List of warning strings to log var warnings = []; // If variable value is not undefined it overrides default.cfg, // local preferences have a higher priority anyway var disable_audio = undefined; var disable_s3tc = undefined; var disable_shadows = undefined; var disable_shadowpcf = undefined; var disable_allwater = undefined; var disable_fancywater = undefined; var enable_glsl = undefined; var enable_postproc = undefined; var enable_smoothlos = undefined; var override_renderpath = undefined; // 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 in the matching code will be caught as references to // undefined variables.) // 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(" "); // Enable GLSL on OpenGL 3+, which should be able to properly // manage GLSL shaders, needed for effects like windy trees if (GL_VERSION.match(/^[3-9]/)) enable_glsl = true; // Enable most graphics options on OpenGL 4+, which should be // able to properly manage them if (GL_VERSION.match(/^[4-9]/)) { enable_postproc = true; enable_smoothlos = true; // enable all water effects disable_allwater = false; } // Disable most graphics features on software renderers if (GL_RENDERER.match(/^(Software Rasterizer|.*(llvm|soft)pipe.*|Mesa X11|Apple Software Renderer|GDI Generic)$/)) { warnings.push("You are using '" + GL_RENDERER + "' graphics driver, expect very poor performance!"); warnings.push("If possible install a proper graphics driver for your hardware."); enable_glsl = false; enable_postproc = false; enable_smoothlos = false; // s3tc on software renderers halves fps and makes textures weird disable_s3tc = true; disable_shadows = true; disable_shadowpcf = true; disable_allwater = true; } // 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)$/)) { dialog_warnings.push("You are using 260.19.* series NVIDIA drivers, which may crash the game. Please upgrade to 260.19.21 or later."); } // http://trac.wildfiregames.com/ticket/684 // https://bugs.freedesktop.org/show_bug.cgi?id=24047 // R600 drivers will advertise support for S3TC but not actually support it, // and will draw everything in grey instead, so forcibly disable S3TC. // (We should add a version check once there's a version that does support it properly.) if (os_unix && GL_RENDERER.match(/^Mesa DRI R600 /)) disable_s3tc = true; // http://trac.wildfiregames.com/ticket/623 // Shadows are reportedly very slow on various drivers: // r300 classic // Intel 945 // Shadows are also known to be quite slow on some others: // Intel 4500MHD // In the interests of performance, we'll disable them on lots of devices // (with a fairly arbitrary cutoff for Intels) if ( (os_unix && GL_RENDERER.match(/^Mesa DRI R[123]00 /)) || (os_macosx && IsWorseThanIntelMac(GL_RENDERER, "Intel HD Graphics 3000")) || (os_unix && IsWorseThanIntelMesa(GL_RENDERER, "Intel(R) Ironlake Desktop")) || (os_win && IsWorseThanIntelWindows(GL_RENDERER, "Intel(R) HD Graphics")) ) { disable_shadows = true; disable_shadowpcf = true; } // Fragment-shader water is really slow on most Intel devices (especially the // "use actual depth" option), so disable it on all of them if ( (os_macosx && IsWorseThanIntelMac(GL_RENDERER, "*")) || (os_unix && IsWorseThanIntelMesa(GL_RENDERER, "*")) || (os_win && IsWorseThanIntelWindows(GL_RENDERER, "*")) ) { disable_fancywater = true; disable_shadowpcf = true; } // http://trac.wildfiregames.com/ticket/780 // r300 classic has problems with shader mode, so fall back to non-shader if (os_unix && GL_RENDERER.match(/^Mesa DRI R[123]00 /)) { override_renderpath = "fixed"; warnings.push("Some graphics features are disabled, due to bugs in old graphics drivers. Upgrading to a Gallium-based driver might help."); } // http://www.wildfiregames.com/forum/index.php?showtopic=15058 // GF FX has poor shader performance, so fall back to non-shader if (GL_RENDERER.match(/^GeForce FX /)) { override_renderpath = "fixed"; disable_allwater = true; } // http://trac.wildfiregames.com/ticket/964 // SiS Mirage 3 drivers apparently crash with shaders, so fall back to non-shader // (The other known SiS cards don't advertise GL_ARB_fragment_program so we // don't need to do anything special for them) if (os_win && GL_RENDERER.match(/^Mirage Graphics3$/)) { override_renderpath = "fixed"; } return { "dialog_warnings": dialog_warnings, "warnings": warnings, "disable_audio": disable_audio, "disable_s3tc": disable_s3tc, "disable_shadows": disable_shadows, "disable_shadowpcf": disable_shadowpcf, "disable_allwater": disable_allwater, "disable_fancywater": disable_fancywater, "enable_glsl": enable_glsl, "enable_postproc": enable_postproc, "enable_smoothlos": enable_smoothlos, "override_renderpath": override_renderpath, }; } global.RunHardwareDetection = function(settings) { //print(JSON.stringify(settings, null, 1)+"\n"); var output = RunDetection(settings); //print(JSON.stringify(output, null, 1)+"\n"); for (var i = 0; i < output.warnings.length; ++i) warn(output.warnings[i]); if (output.dialog_warnings.length) { var msg = output.dialog_warnings.join("\n\n"); Engine.DisplayErrorDialog(msg); } if (output.disable_audio !== undefined) Engine.SetDisableAudio(output.disable_audio); if (output.disable_s3tc !== undefined) Engine.SetDisableS3TC(output.disable_s3tc); if (output.disable_shadows !== undefined) Engine.SetDisableShadows(output.disable_shadows); if (output.disable_shadowpcf !== undefined) Engine.SetDisableShadowPCF(output.disable_shadowpcf); if (output.disable_allwater !== undefined) Engine.SetDisableAllWater(output.disable_allwater); if (output.disable_fancywater !== undefined) Engine.SetDisableFancyWater(output.disable_fancywater); if (output.enable_glsl !== undefined) Engine.SetEnableGLSL(output.enable_glsl); if (output.enable_postproc !== undefined) Engine.SetEnablePostProc(output.enable_postproc); if (output.enable_smoothlos !== undefined) Engine.SetEnableSmoothLOS(output.enable_smoothlos); if (output.override_renderpath !== undefined) Engine.SetRenderPath(output.override_renderpath); }; Index: ps/trunk/source/lib/bits.cpp =================================================================== --- ps/trunk/source/lib/bits.cpp (revision 19898) +++ ps/trunk/source/lib/bits.cpp (revision 19899) @@ -1,42 +1,42 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bit-twiddling. */ #include "precompiled.h" #include "lib/bits.h" static inline u32 get_float_bits(const float x) { u32 ret; memcpy(&ret, &x, 4); return ret; } int floor_log2(const float x) { const u32 i = get_float_bits(x); const u32 biased_exp = (i >> 23) & 0xFF; return (int)biased_exp - 127; } Index: ps/trunk/source/lib/byte_order.h =================================================================== --- ps/trunk/source/lib/byte_order.h (revision 19898) +++ ps/trunk/source/lib/byte_order.h (revision 19899) @@ -1,166 +1,166 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * byte order (endianness) support routines. */ #ifndef INCLUDED_BYTE_ORDER #define INCLUDED_BYTE_ORDER #include "lib/sysdep/cpu.h" // detect byte order via predefined macros. #ifndef BYTE_ORDER # define LITTLE_ENDIAN 0x4321 # define BIG_ENDIAN 0x1234 # if ARCH_IA32 || ARCH_IA64 || ARCH_AMD64 || ARCH_ALPHA || ARCH_ARM || ARCH_AARCH64 || ARCH_MIPS || defined(__LITTLE_ENDIAN__) # define BYTE_ORDER LITTLE_ENDIAN # else # define BYTE_ORDER BIG_ENDIAN # endif #endif /** * convert 4 characters to u32 (at compile time) for easy comparison. * output is in native byte order; e.g. FOURCC_LE can be used instead. **/ #define FOURCC(a,b,c,d) // real definition is below #undef FOURCC // implementation rationale: // - can't pass code as string, and use s[0]..s[3], because // VC6/7 don't realize the macro is constant // (it should be usable as a switch{} expression) // - the casts are ugly but necessary. u32 is required because u8 << 8 == 0; // the additional u8 cast ensures each character is treated as unsigned // (otherwise, they'd be promoted to signed int before the u32 cast, // which would break things). /// big-endian version of FOURCC #define FOURCC_BE(a,b,c,d) ( ((u32)(u8)a) << 24 | ((u32)(u8)b) << 16 | \ ((u32)(u8)c) << 8 | ((u32)(u8)d) << 0 ) /// little-endian version of FOURCC #define FOURCC_LE(a,b,c,d) ( ((u32)(u8)a) << 0 | ((u32)(u8)b) << 8 | \ ((u32)(u8)c) << 16 | ((u32)(u8)d) << 24 ) #if BYTE_ORDER == BIG_ENDIAN # define FOURCC FOURCC_BE #else # define FOURCC FOURCC_LE #endif #if BYTE_ORDER == BIG_ENDIAN // convert a little-endian number to/from native byte order. # define to_le16(x) swap16(x) # define to_le32(x) swap32(x) # define to_le64(x) swap64(x) // convert a big-endian number to/from native byte order. # define to_be16(x) (x) # define to_be32(x) (x) # define to_be64(x) (x) #else // LITTLE_ENDIAN // convert a little-endian number to/from native byte order. # define to_le16(x) (x) # define to_le32(x) (x) # define to_le64(x) (x) // convert a big-endian number to/from native byte order. # define to_be16(x) swap16(x) # define to_be32(x) swap32(x) # define to_be64(x) swap64(x) #endif /// read a little-endian number from memory into native byte order. LIB_API u16 read_le16(const void* p); LIB_API u32 read_le32(const void* p); /// see read_le16 LIB_API u64 read_le64(const void* p); /// see read_le16 /// read a big-endian number from memory into native byte order. LIB_API u16 read_be16(const void* p); LIB_API u32 read_be32(const void* p); /// see read_be16 LIB_API u64 read_be64(const void* p); /// see read_be16 /// write a little-endian number to memory in native byte order. LIB_API void write_le16(void* p, u16 x); LIB_API void write_le32(void* p, u32 x); /// see write_le16 LIB_API void write_le64(void* p, u64 x); /// see write_le16 /// write a big-endian number to memory in native byte order. LIB_API void write_be16(void* p, u16 x); LIB_API void write_be32(void* p, u32 x); /// see write_be16 LIB_API void write_be64(void* p, u64 x); /// see write_be16 /** * zero-extend \ (truncated to 8) bytes of little-endian data to u64, * starting at address \ (need not be aligned). **/ LIB_API u64 movzx_le64(const u8* p, size_t size); LIB_API u64 movzx_be64(const u8* p, size_t size); /** * sign-extend \ (truncated to 8) bytes of little-endian data to i64, * starting at address \ (need not be aligned). **/ LIB_API i64 movsx_le64(const u8* p, size_t size); LIB_API i64 movsx_be64(const u8* p, size_t size); #if ICC_VERSION #define swap32 _bswap #define swap64 _bswap64 #elif MSC_VERSION extern unsigned short _byteswap_ushort(unsigned short); extern unsigned long _byteswap_ulong(unsigned long); extern unsigned __int64 _byteswap_uint64(unsigned __int64); #pragma intrinsic(_byteswap_ushort) #pragma intrinsic(_byteswap_ulong) #pragma intrinsic(_byteswap_uint64) # define swap16 _byteswap_ushort # define swap32 _byteswap_ulong # define swap64 _byteswap_uint64 #elif defined(linux) # include # if defined(__arch__swab16) && !defined(swap16) # define swap16 __arch__swab16 # endif # if defined(__arch__swab32) && !defined(swap32) # define swap32 __arch__swab32 # endif # if defined(__arch__swab64) && !defined(swap64) # define swap64 __arch__swab64 # endif #endif #ifndef swap16 LIB_API u16 swap16(const u16 x); #endif #ifndef swap32 LIB_API u32 swap32(const u32 x); #endif #ifndef swap64 LIB_API u64 swap64(const u64 x); #endif #endif // #ifndef INCLUDED_BYTE_ORDER Index: ps/trunk/source/lib/config.h =================================================================== --- ps/trunk/source/lib/config.h (revision 19898) +++ ps/trunk/source/lib/config.h (revision 19899) @@ -1,74 +1,74 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * compile-time configuration for the entire project */ #ifndef INCLUDED_CONFIG #define INCLUDED_CONFIG // notes: // - this file is included in the PCH and thus affects the entire project. // to avoid unnecessary full rebuilds, place settings of more limited // applicability in config2.h and explicitly include that header. // - config macros are always defined; their values (1 or 0) are tested // with #if instead of #ifdef. this protects against typos by at least // causing a warning if the tested macro is undefined. // - allow override via compiler settings by checking #ifndef. // precompiled headers #ifndef CONFIG_ENABLE_PCH # define CONFIG_ENABLE_PCH 1 // improve build performance #endif // frame pointers #ifndef CONFIG_OMIT_FP # ifdef NDEBUG # define CONFIG_OMIT_FP 1 // improve performance # else # define CONFIG_OMIT_FP 0 // enable use of ia32's fast stack walk # endif #endif // try to prevent any exceptions from being thrown - even by the C++ // standard library. useful only for performance tests. #ifndef CONFIG_DISABLE_EXCEPTIONS # define CONFIG_DISABLE_EXCEPTIONS 0 #endif // enable additional debug checks (potentially rather slow). #ifndef CONFIG_ENABLE_CHECKS # define CONFIG_ENABLE_CHECKS 0 #endif // static type checking with Dehydra #ifndef CONFIG_DEHYDRA # define CONFIG_DEHYDRA 0 #endif // allow the use of Boost? (affects PCH and several individual modules) #ifndef CONFIG_ENABLE_BOOST # define CONFIG_ENABLE_BOOST 1 #endif #endif // #ifndef INCLUDED_CONFIG Index: ps/trunk/source/lib/allocators/tests/test_headerless.h =================================================================== --- ps/trunk/source/lib/allocators/tests/test_headerless.h (revision 19898) +++ ps/trunk/source/lib/allocators/tests/test_headerless.h (revision 19899) @@ -1,148 +1,148 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/bits.h" // round_down #include "lib/allocators/headerless.h" void* const null = 0; class TestHeaderless: public CxxTest::TestSuite { public: void test_Basic() { HeaderlessAllocator a(8192); // (these are disabled because they raise an assert) #if 0 // can't Allocate unaligned sizes TS_ASSERT_EQUALS(a.Allocate(HeaderlessAllocator::minAllocationSize+1), null); // can't Allocate too small amounts TS_ASSERT_EQUALS(a.Allocate(HeaderlessAllocator::minAllocationSize/2), null); #endif // can Allocate the entire pool char* p1 = (char*)a.Allocate(4096); char* p2 = (char*)a.Allocate(4096); TS_ASSERT_DIFFERS(p1, null); TS_ASSERT_DIFFERS(p2, null); // back-to-back (non-freelist) allocations should be contiguous TS_ASSERT_EQUALS(p1+4096, p2); // allocations are writable p1[0] = 11; p1[4095] = 12; } void test_Free() { // Deallocate allows immediate reuse of the freed pointer HeaderlessAllocator a(4096); void* p1 = a.Allocate(1024); a.Deallocate(p1, 1024); void* p2 = a.Allocate(1024); TS_ASSERT_EQUALS(p1, p2); } void test_Coalesce() { HeaderlessAllocator a(0x10000); // can Allocate non-power-of-two sizes (note: the current // implementation only allows sizes that are multiples of 0x10) void* p1 = a.Allocate(0x5680); void* p2 = a.Allocate(0x78A0); void* p3 = a.Allocate(0x1240); TS_ASSERT_DIFFERS(p1, null); TS_ASSERT_DIFFERS(p2, null); TS_ASSERT_DIFFERS(p3, null); // after freeing, must be able to allocate the total amount // note: we don't insist on being able to fill the entire // memory range. in this case, the problem is that the pool has some // virtual address space left, but the allocator doesn't grab that // and add it to the freelist. that feature is currently not // implemented. a.Deallocate(p1, 0x5680); a.Deallocate(p2, 0x78A0); a.Deallocate(p3, 0x1240); void* p4 = a.Allocate(0xE140); TS_ASSERT_DIFFERS(p4, null); } void test_Reset() { // after Reset, must return the same pointer as a freshly constructed instance HeaderlessAllocator a(4096); void* p1 = a.Allocate(128); a.Reset(); void* p2 = a.Allocate(128); TS_ASSERT_EQUALS(p1, p2); } // will the allocator survive a series of random but valid Allocate/Deallocate? void test_Randomized() { const size_t poolSize = 1024*1024; HeaderlessAllocator a(poolSize); typedef std::map AllocMap; AllocMap allocs; srand(1); for(int i = 0; i < 1000; i++) { // allocate if(rand() >= RAND_MAX/2) { const size_t maxSize = (size_t)((rand() / (float)RAND_MAX) * poolSize); const size_t size = std::max((size_t)HeaderlessAllocator::minAllocationSize, round_down(maxSize, HeaderlessAllocator::allocationAlignment)); // (the size_t cast on minAllocationSize prevents max taking a reference to the non-defined variable) void* p = a.Allocate(size); if(!p) continue; TS_ASSERT(allocs.find(p) == allocs.end()); allocs[p] = size; } // free else { if(allocs.empty()) continue; // find random allocation to deallocate AllocMap::iterator it = allocs.begin(); const int numToSkip = rand() % (int)allocs.size(); for(int skip = 0; skip < numToSkip; skip++) ++it; void* p = (*it).first; size_t size = (*it).second; allocs.erase(it); a.Deallocate(p, size); } } } }; Index: ps/trunk/libraries/source/fcollada/src/FCollada/FUtils/FUAssert.cpp =================================================================== --- ps/trunk/libraries/source/fcollada/src/FCollada/FUtils/FUAssert.cpp (revision 19898) +++ ps/trunk/libraries/source/fcollada/src/FCollada/FUtils/FUAssert.cpp (revision 19899) @@ -1,67 +1,67 @@ /* Copyright (C) 2005-2007 Feeling Software Inc. Portions of the code are: - Copyright (C) 2005-2007 Sony Computer Entertainment America + Copyright (C) 2005-2007 Sony Computer Entertainment America. Portions of the code are: - Copyright (C) 2013 Wildfire Games + Copyright (C) 2013 Wildfire Games. MIT License: http://www.opensource.org/licenses/mit-license.php */ #include "StdAfx.h" #include "FUAssert.h" #ifndef WIN32 #include "sys/signal.h" // Used for throw(SIGTRAP) on UNIX-like systems #endif #ifdef __APPLE__ #include #endif static FUAssertion::FUAssertCallback* curAssertCallback = NULL; void FUAssertion::SetAssertionFailedCallback(FUAssertCallback* assertionCallback) { curAssertCallback = assertionCallback; } bool FUAssertion::OnAssertionFailed(const char* file, uint32 line) { char message[1024]; snprintf(message, 1024, "[%s@%u] Assertion failed.\nAbort: Enter debugger.\nRetry: Continue execution.\nIgnore: Do not assert at this line for the duration of the application.", file, (unsigned int) line); message[1023] = 0; if (curAssertCallback != NULL) return (*curAssertCallback)(message); #ifdef _DEBUG else { #ifdef WIN32 // Use windows builtins int32 buttonPressed = MessageBoxA(NULL, message, "Assertion failed.", MB_ABORTRETRYIGNORE | MB_ICONWARNING); if (buttonPressed == IDABORT) { __debugbreak(); } else if (buttonPressed == IDRETRY) {} else if (buttonPressed == IDIGNORE) { return true; } #elif defined(__APPLE__) // Use apple builtins Debugger(); #elif defined(__i386__) or defined(__x86_64__) // Use hardware breakpoints on UNIX-like x86 based hardware __asm__("int $0x03"); #else // Use software breakpoints as a fallback on UNIX-like systems throw(SIGTRAP); #endif return false; } #else // _DEBUG return false; #endif // _DEBUG } Index: ps/trunk/source/lib/adts/bit_buf.h =================================================================== --- ps/trunk/source/lib/adts/bit_buf.h (revision 19898) +++ ps/trunk/source/lib/adts/bit_buf.h (revision 19899) @@ -1,64 +1,64 @@ -/* Copyright (c) 2011 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 * "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. */ /* * FIFO bit queue */ #ifndef INCLUDED_ADTS_BIT_BUF #define INCLUDED_ADTS_BIT_BUF #include "lib/bits.h" struct BitBuf { uintptr_t buf; uintptr_t cur; // bit to be appended (toggled by add()) size_t len; // |buf| [bits] void reset() { buf = 0; cur = 0; len = 0; } // toggle current bit if desired, and add to buffer (new bit is LSB) void add(uintptr_t toggle) { cur ^= toggle; buf <<= 1; buf |= cur; len++; } // extract LS n bits size_t extract(uintptr_t n) { const uintptr_t bits = buf & bit_mask(n); buf >>= n; return bits; } }; #endif // #ifndef INCLUDED_ADTS_BIT_BUF Index: ps/trunk/source/lib/adts/cache_adt.h =================================================================== --- ps/trunk/source/lib/adts/cache_adt.h (revision 19898) +++ ps/trunk/source/lib/adts/cache_adt.h (revision 19899) @@ -1,745 +1,745 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Customizable cache data structure. */ #ifndef INCLUDED_CACHE_ADT #define INCLUDED_CACHE_ADT #include #include #include #include // std::priority_queue #if CONFIG_ENABLE_BOOST # include # define MAP boost::unordered_map #else # define MAP stdext::hash_map #endif /* Cache for items of variable size and value/"cost". underlying displacement algorithm is pluggable; default is "Landlord". template reference: Entry provides size, cost, credit and credit_density(). rationale: - made a template instead of exposing Cache::Entry because that would drag a lot of stuff out of Cache. - calculates its own density since that entails a Divider functor, which requires storage inside Entry. Entries is a collection with iterator and begin()/end() and "static Entry& entry_from_it(iterator)". rationale: - STL map has pair as its value_type, so this function would return it->second. however, we want to support other container types (where we'd just return *it). Manager is a template parameterized on typename Key and class Entry. its interface is as follows: // is the cache empty? bool empty() const; // add (key, entry) to cache. void add(const Key& key, const Entry& entry); // if the entry identified by is not in cache, return false; // otherwise return true and pass back a pointer to it. bool find(const Key& key, const Entry** pentry) const; // remove an entry from cache, which is assumed to exist! // this makes sense because callers will typically first use find() to // return info about the entry; this also checks if present. void remove(const Key& key); // mark as just accessed for purpose of cache management. // it will tend to be kept in cache longer. void on_access(Entry& entry); // caller's intent is to remove the least valuable entry. // in implementing this, you have the latitude to "shake loose" // several entries (e.g. because their 'value' is equal). // they must all be push_back-ed into the list; Cache will dole // them out one at a time in FIFO order to callers. // // rationale: // - it is necessary for callers to receive a copy of the // Entry being evicted - e.g. file_cache owns its items and // they must be passed back to allocator when evicted. // - e.g. Landlord can potentially see several entries become // evictable in one call to remove_least_valuable. there are // several ways to deal with this: // 1) generator interface: we return one of { empty, nevermind, // removed, remove-and-call-again }. this greatly complicates // the call site. // 2) return immediately after finding an item to evict. // this changes cache behavior - entries stored at the // beginning would be charged more often (unfair). // resuming charging at the next entry doesn't work - this // would have to be flushed when adding, at which time there // is no provision for returning any items that may be evicted. // 3) return list of all entries "shaken loose". this incurs // frequent mem allocs, which can be alleviated via suballocator. // note: an intrusive linked-list doesn't make sense because // entries to be returned need to be copied anyway (they are // removed from the manager's storage). void remove_least_valuable(std::list& entry_list) */ // // functors to calculate minimum credit density (MCD) // // MCD is required for the Landlord algorithm's evict logic. // [Young02] calls it '\delta'. // scan over all entries and return MCD. template float ll_calc_min_credit_density(const Entries& entries) { float min_credit_density = FLT_MAX; for(typename Entries::const_iterator it = entries.begin(); it != entries.end(); ++it) { const float credit_density = Entries::entry_from_it(it).credit_density(); min_credit_density = std::min(min_credit_density, credit_density); } return min_credit_density; } // note: no warning is given that the MCD entry is being removed! // (reduces overhead in remove_least_valuable) // these functors must account for that themselves (e.g. by resetting // their state directly after returning MCD). // determine MCD by scanning over all entries. // tradeoff: O(N) time complexity, but all notify* calls are no-ops. template class McdCalc_Naive { public: void notify_added(const Entry&) const {} void notify_decreased(const Entry&) const {} void notify_impending_increase_or_remove(const Entry&) const {} void notify_increased_or_removed(const Entry&) const {} float operator()(const Entries& entries) const { const float mcd = ll_calc_min_credit_density(entries); return mcd; } }; // cache previous MCD and update it incrementally (when possible). // tradeoff: amortized O(1) time complexity, but notify* calls must // perform work whenever something in the cache changes. template class McdCalc_Cached { public: McdCalc_Cached() : min_credit_density(FLT_MAX), min_valid(false) {} void notify_added(const Entry& entry) { // when adding a new item, the minimum credit density can only // decrease or remain the same; acting as if entry's credit had // been decreased covers both cases. notify_decreased(entry); } void notify_decreased(const Entry& entry) { min_credit_density = std::min(min_credit_density, entry.credit_density()); } void notify_impending_increase_or_remove(const Entry& entry) { // remember if this entry had the smallest density is_min_entry = feq(min_credit_density, entry.credit_density()); } void notify_increased_or_removed(const Entry& UNUSED(entry)) { // .. it did and was increased or removed. we must invalidate // MCD and recalculate it next time. if(is_min_entry) { min_valid = false; min_credit_density = -1.0f; } } float operator()(const Entries& entries) { if(min_valid) { // the entry that has MCD will be removed anyway by caller; // we need to invalidate here because they don't call // notify_increased_or_removed. min_valid = false; return min_credit_density; } // this is somewhat counterintuitive. since we're calculating // MCD directly, why not mark our cached version of it valid // afterwards? reason is that our caller will remove the entry with // MCD, so it'll be invalidated anyway. // instead, our intent is to calculate MCD for the *next time*. const float ret = ll_calc_min_credit_density(entries); min_valid = true; min_credit_density = FLT_MAX; return ret; } private: float min_credit_density; bool min_valid; // temporary flag set by notify_impending_increase_or_remove bool is_min_entry; }; // // Landlord cache management policy: see [Young02]. // // in short, each entry has credit initially set to cost. when wanting to // remove an item, all are charged according to MCD and their size; // entries are evicted if their credit is exhausted. accessing an entry // restores "some" of its credit. template class McdCalc = McdCalc_Cached> class Landlord { public: bool empty() const { return map.empty(); } void add(const Key& key, const Entry& entry) { // adapter for add_ (which returns an iterator) (void)add_(key, entry); } bool find(const Key& key, const Entry** pentry) const { MapCIt it = map.find(key); if(it == map.end()) return false; *pentry = &it->second; return true; } void remove(const Key& key) { MapIt it = map.find(key); // note: don't complain if not in the cache: this happens after // writing a file and invalidating its cache entry, which may // or may not exist. if(it != map.end()) remove_(it); } void on_access(Entry& entry) { mcd_calc.notify_impending_increase_or_remove(entry); // Landlord algorithm calls for credit to be reset to anything // between its current value and the cost. const float gain = 0.75f; // restore most credit entry.credit = gain*entry.cost + (1.0f-gain)*entry.credit; mcd_calc.notify_increased_or_removed(entry); } void remove_least_valuable(std::list& entry_list) { // we are required to evict at least one entry. one iteration // ought to suffice, due to definition of min_credit_density and // tolerance; however, we provide for repeating if necessary. again: // messing with this (e.g. raising if tiny) would result in // different evictions than Landlord_Lazy, which is unacceptable. // nor is doing so necessary: if mcd is tiny, so is credit. const float min_credit_density = mcd_calc(map); ENSURE(min_credit_density > 0.0f); for(MapIt it = map.begin(); it != map.end();) // no ++it { Entry& entry = it->second; charge(entry, min_credit_density); if(should_evict(entry)) { entry_list.push_back(entry); // annoying: we have to increment before erasing MapIt it_to_remove = it++; map.erase(it_to_remove); } else { mcd_calc.notify_decreased(entry); ++it; } } if(entry_list.empty()) goto again; } protected: class Map : public MAP { public: static Entry& entry_from_it(typename Map::iterator it) { return it->second; } static const Entry& entry_from_it(typename Map::const_iterator it) { return it->second; } }; typedef typename Map::iterator MapIt; typedef typename Map::const_iterator MapCIt; Map map; // add entry and return iterator pointing to it. MapIt add_(const Key& key, const Entry& entry) { typedef std::pair PairIB; typename Map::value_type val = std::make_pair(key, entry); PairIB ret = map.insert(val); ENSURE(ret.second); // must not already be in map mcd_calc.notify_added(entry); return ret.first; } // remove entry (given by iterator) directly. void remove_(MapIt it) { const Entry& entry = it->second; mcd_calc.notify_impending_increase_or_remove(entry); mcd_calc.notify_increased_or_removed(entry); map.erase(it); } void charge(Entry& entry, float delta) { entry.credit -= delta * entry.size; // don't worry about entry.size being 0 - if so, cost // should also be 0, so credit will already be 0 anyway. } // for each entry, 'charge' it (i.e. reduce credit by) delta * its size. // delta is typically MCD (see above); however, several such updates // may be lumped together to save time. Landlord_Lazy does this. void charge_all(float delta) { for(MapIt it = map.begin(); it != map.end(); ++it) { Entry& entry = it->second; entry.credit -= delta * entry.size; if(!should_evict(entry)) mcd_calc.notify_decreased(entry); } } // is entry's credit exhausted? if so, it should be evicted. bool should_evict(const Entry& entry) { // we need a bit of leeway because density calculations may not // be exact. choose value carefully: must not be high enough to // trigger false positives. return entry.credit < 0.0001f; } private: McdCalc mcd_calc; }; // Cache manger policies. (these are partial specializations of Landlord, // adapting it to the template params required by Cache) template class Landlord_Naive : public Landlord {}; template class Landlord_Cached: public Landlord {}; // variant of Landlord that adds a priority queue to directly determine // which entry to evict. this allows lumping several charge operations // together and thus reduces iteration over all entries. // tradeoff: O(logN) removal (instead of N), but additional O(N) storage. template class Landlord_Lazy : public Landlord_Naive { typedef typename Landlord_Naive::Map Map; typedef typename Landlord_Naive::MapIt MapIt; typedef typename Landlord_Naive::MapCIt MapCIt; public: Landlord_Lazy() { pending_delta = 0.0f; } void add(const Key& key, const Entry& entry) { // we must apply pending_delta now - otherwise, the existing delta // would later be applied to this newly added item (incorrect). commit_pending_delta(); MapIt it = Parent::add_(key, entry); pri_q.push(it); } void remove(const Key& key) { Parent::remove(key); // reconstruct pri_q from current map. this is slow (N*logN) and // could definitely be done better, but we don't bother since // remove is a very rare operation (e.g. invalidating entries). while(!pri_q.empty()) pri_q.pop(); for(MapCIt it = this->map.begin(); it != this->map.end(); ++it) pri_q.push(it); } void on_access(Entry& entry) { Parent::on_access(entry); // entry's credit was changed. we now need to reshuffle the // pri queue to reflect this. pri_q.ensure_heap_order(); } void remove_least_valuable(std::list& entry_list) { MapIt least_valuable_it = pri_q.top(); pri_q.pop(); Entry& entry = Map::entry_from_it(least_valuable_it); entry_list.push_back(entry); // add to pending_delta the MCD that would have resulted // if removing least_valuable_it normally. // first, calculate actual credit (i.e. apply pending_delta to // this entry); then add the resulting density to pending_delta. entry.credit -= pending_delta*entry.size; const float credit_density = entry.credit_density(); ENSURE(credit_density > 0.0f); pending_delta += credit_density; Parent::remove_(least_valuable_it); } private: typedef Landlord_Naive Parent; // sort iterators by credit_density of the Entry they reference. struct CD_greater { bool operator()(MapIt it1, MapIt it2) const { return Map::entry_from_it(it1).credit_density() > Map::entry_from_it(it2).credit_density(); } }; // wrapper on top of priority_queue that allows 'heap re-sift' // (see on_access). // notes: // - greater comparator makes pri_q.top() the one with // LEAST credit_density, which is what we want. // - deriving from an STL container is a bit dirty, but we need this // to get at the underlying data (priority_queue interface is not // very capable). class PriQ: public std::priority_queue, CD_greater> { public: void ensure_heap_order() { // TODO: this is actually N*logN - ouch! that explains high // CPU cost in profile. this is called after only 1 item has // changed, so a logN "sift" operation ought to suffice. // that's not supported by the STL heap functions, so we'd // need a better implementation. pending.. std::make_heap(this->c.begin(), this->c.end(), this->comp); } }; PriQ pri_q; // delta values that have accumulated over several // remove_least_valuable() calls. applied during add(). float pending_delta; void commit_pending_delta() { if(pending_delta > 0.0f) { this->charge_all(pending_delta); pending_delta = 0.0f; // we've changed entry credit, so the heap order *may* have been // violated; reorder the pri queue. (I don't think so, // due to definition of delta, but we'll play it safe) pri_q.ensure_heap_order(); } } }; // // functor that implements division of first arg by second // // this is used to calculate credit_density(); performance matters // because this is called for each entry during each remove operation. // floating-point division (fairly slow) class Divider_Naive { public: Divider_Naive() {} // needed for default CacheEntry ctor Divider_Naive(float UNUSED(x)) {} float operator()(float val, float divisor) const { return val / divisor; } }; // caches reciprocal of divisor and multiplies by that. // tradeoff: only 4 clocks (instead of 20), but 4 bytes extra per entry. class Divider_Recip { float recip; public: Divider_Recip() {} // needed for default CacheEntry ctor Divider_Recip(float x) { recip = 1.0f / x; } float operator()(float val, float UNUSED(divisor)) const { return val * recip; } }; // initial implementation for testing purposes; quite inefficient. template class LRU { public: bool empty() const { return lru.empty(); } void add(const Key& key, const Entry& entry) { lru.push_back(KeyAndEntry(key, entry)); } bool find(const Key& key, const Entry** pentry) const { CIt it = std::find(lru.begin(), lru.end(), KeyAndEntry(key)); if(it == lru.end()) return false; *pentry = &it->entry; return true; } void remove(const Key& key) { lru.remove(KeyAndEntry(key)); } void on_access(Entry& entry) { for(It it = lru.begin(); it != lru.end(); ++it) { if(&entry == &it->entry) { add(it->key, it->entry); lru.erase(it); return; } } DEBUG_WARN_ERR(ERR::LOGIC); // entry not found in list } void remove_least_valuable(std::list& entry_list) { entry_list.push_back(lru.front().entry); lru.pop_front(); } private: struct KeyAndEntry { KeyAndEntry(const Key& key): key(key) {} KeyAndEntry(const Key& key, const Entry& entry): key(key), entry(entry) {} bool operator==(const KeyAndEntry& rhs) const { return key == rhs.key; } bool operator!=(const KeyAndEntry& rhs) const { return !operator==(rhs); } Key key; Entry entry; }; typedef std::list List; typedef typename List::iterator It; typedef typename List::const_iterator CIt; List lru; }; // this is applicable to all cache management policies and stores all // required information. a Divider functor is used to implement // division for credit_density. template struct CacheEntry { Item item; size_t size; size_t cost; float credit; Divider divider; // needed for mgr.remove_least_valuable's entry_copy CacheEntry() { } CacheEntry(const Item& item_, size_t size_, size_t cost_) : item(item_), divider((float)size_) { size = size_; cost = cost_; credit = (float)cost; // else divider will fail ENSURE(size != 0); } float credit_density() const { return divider(credit, (float)size); } }; // // Cache // template < typename Key, typename Item, // see documentation above for Manager's interface. template class Manager = Landlord_Cached, class Divider = Divider_Naive > class Cache { public: Cache() : mgr() {} void add(const Key& key, const Item& item, size_t size, size_t cost) { return mgr.add(key, Entry(item, size, cost)); } // remove the entry identified by . expected usage is to check // if present and determine size via retrieve(), so no need for // error checking. // useful for invalidating single cache entries. void remove(const Key& key) { mgr.remove(key); } // if there is no entry for in the cache, return false. // otherwise, return true and pass back item and (optionally) size. // // if refill_credit (default), the cache manager 'rewards' this entry, // tending to keep it in cache longer. this parameter is not used in // normal operation - it's only for special cases where we need to // make an end run around the cache accounting (e.g. for cache simulator). bool retrieve(const Key& key, Item& item, size_t* psize = 0, bool refill_credit = true) { const Entry* entry; if(!mgr.find(key, &entry)) return false; item = entry->item; if(psize) *psize = entry->size; if(refill_credit) mgr.on_access((Entry&)*entry); return true; } bool peek(const Key& key, Item& item, size_t* psize = 0) { return retrieve(key, item, psize, false); } // toss out the least valuable entry. return false if cache is empty, // otherwise true and (optionally) pass back its item and size. bool remove_least_valuable(Item* pItem = 0, size_t* pSize = 0) { // as an artefact of the cache eviction policy, several entries // may be "shaken loose" by one call to remove_least_valuable. // we cache them in a list to disburden callers (they always get // exactly one). if(entries_awaiting_eviction.empty()) { if(empty()) return false; mgr.remove_least_valuable(entries_awaiting_eviction); ENSURE(!entries_awaiting_eviction.empty()); } const Entry& entry = entries_awaiting_eviction.front(); if(pItem) *pItem = entry.item; if(pSize) *pSize = entry.size; entries_awaiting_eviction.pop_front(); return true; } bool empty() const { return mgr.empty(); } private: typedef CacheEntry Entry; // see note in remove_least_valuable(). std::list entries_awaiting_eviction; Manager mgr; }; #endif // #ifndef INCLUDED_CACHE_ADT Index: ps/trunk/source/lib/adts/dyn_hash_tbl.h =================================================================== --- ps/trunk/source/lib/adts/dyn_hash_tbl.h (revision 19898) +++ ps/trunk/source/lib/adts/dyn_hash_tbl.h (revision 19899) @@ -1,238 +1,238 @@ -/* Copyright (c) 2011 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 * "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. */ /* * dynamic (grow-able) hash table */ #ifndef INCLUDED_ADTS_DYN_HASH_TBL #define INCLUDED_ADTS_DYN_HASH_TBL #include // strcmp #include "lib/fnv_hash.h" template class DHT_Traits { public: static const size_t initial_entries = 16; size_t hash(Key key) const; bool equal(Key k1, Key k2) const; Key get_key(T t) const; }; template<> class DHT_Traits { public: static const size_t initial_entries = 512; size_t hash(const char* key) const { return (size_t)fnv_lc_hash(key); } bool equal(const char* k1, const char* k2) const { return !strcmp(k1, k2); } const char* get_key(const char* t) const { return t; } }; // intended for pointer types template > class DynHashTbl { T* tbl; u16 num_entries; u16 max_entries; // when initialized, = 2**n for faster modulo Traits tr; T& get_slot(Key key) const { size_t hash = tr.hash(key); ENSURE(max_entries != 0); // otherwise, mask will be incorrect const size_t mask = max_entries-1; for(;;) { T& t = tbl[hash & mask]; // empty slot encountered => not found if(!t) return t; // keys are actually equal => found it if(tr.equal(key, tr.get_key(t))) return t; // keep going (linear probing) hash++; } } void expand_tbl() { // alloc a new table (but don't assign it to unless successful) T* old_tbl = tbl; tbl = (T*)calloc(max_entries*2, sizeof(T)); if(!tbl) { tbl = old_tbl; throw std::bad_alloc(); } max_entries *= 2; // must be set before get_slot // newly initialized, nothing to copy - done if(!old_tbl) return; // re-hash from old table into the new one for(size_t i = 0; i < max_entries/2u; i++) { T t = old_tbl[i]; if(t) get_slot(tr.get_key(t)) = t; } free(old_tbl); } public: DynHashTbl() { tbl = 0; clear(); } ~DynHashTbl() { free(tbl); } void clear() { // must remain usable after calling clear, so shrink the table to // its initial size but don't deallocate it completely SAFE_FREE(tbl); num_entries = 0; // rationale: must not set to 0 because expand_tbl only doubles the size. // don't keep the previous size when clearing because it may have become // huge and there is no provision for shrinking. max_entries = tr.initial_entries/2; // will be doubled in expand_tbl expand_tbl(); } void insert(const Key key, const T t) { // more than 75% full - increase table size. // do so before determining slot; this will invalidate previous pnodes. if(num_entries*4 >= max_entries*3) expand_tbl(); T& slot = get_slot(key); ENSURE(slot == 0); // not already present slot = t; num_entries++; } T find(Key key) const { return get_slot(key); } size_t size() const { return num_entries; } class iterator { public: typedef std::forward_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t difference_type; typedef const T* pointer; typedef const T& reference; iterator() { } iterator(T* pos_, T* end_) : pos(pos_), end(end_) { } T& operator*() const { return *pos; } iterator& operator++() // pre { do pos++; while(pos != end && *pos == 0); return (*this); } bool operator==(const iterator& rhs) const { return pos == rhs.pos; } bool operator<(const iterator& rhs) const { return (pos < rhs.pos); } // derived const T* operator->() const { return &**this; } bool operator!=(const iterator& rhs) const { return !(*this == rhs); } iterator operator++(int) // post { iterator tmp = *this; ++*this; return tmp; } protected: T* pos; T* end; // only used when incrementing (avoid going beyond end of table) }; iterator begin() const { T* pos = tbl; while(pos != tbl+max_entries && *pos == 0) pos++; return iterator(pos, tbl+max_entries); } iterator end() const { return iterator(tbl+max_entries, 0); } }; #endif // #ifndef INCLUDED_ADTS_DYN_HASH_TBL Index: ps/trunk/source/lib/adts/ring_buf.h =================================================================== --- ps/trunk/source/lib/adts/ring_buf.h (revision 19898) +++ ps/trunk/source/lib/adts/ring_buf.h (revision 19899) @@ -1,212 +1,212 @@ -/* Copyright (c) 2011 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 * "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. */ /* * static array, accessible modulo n */ #ifndef INCLUDED_ADTS_RING_BUF #define INCLUDED_ADTS_RING_BUF template class RingBuf { size_t size_; // # of entries in buffer size_t head; // index of oldest item size_t tail; // index of newest item T data[n]; public: RingBuf() : data() { clear(); } void clear() { size_ = 0; head = 0; tail = n-1; } size_t size() const { return size_; } bool empty() const { return size_ == 0; } const T& operator[](int ofs) const { ENSURE(!empty()); size_t idx = (size_t)(head + ofs); return data[idx % n]; } T& operator[](int ofs) { ENSURE(!empty()); size_t idx = (size_t)(head + ofs); return data[idx % n]; } T& front() { ENSURE(!empty()); return data[head]; } const T& front() const { ENSURE(!empty()); return data[head]; } T& back() { ENSURE(!empty()); return data[tail]; } const T& back() const { ENSURE(!empty()); return data[tail]; } void push_back(const T& item) { if(size_ < n) size_++; // do not complain - overwriting old values is legit // (e.g. sliding window). else head = (head + 1) % n; tail = (tail + 1) % n; data[tail] = item; } void pop_front() { if(size_ != 0) { size_--; head = (head + 1) % n; } else DEBUG_WARN_ERR(ERR::LOGIC); // underflow } class iterator { public: typedef std::random_access_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t difference_type; typedef T* pointer; typedef T& reference; iterator() : data(0), pos(0) {} iterator(T* data_, size_t pos_) : data(data_), pos(pos_) {} T& operator[](int idx) const { return data[(pos+idx) % n]; } T& operator*() const { return data[pos % n]; } T* operator->() const { return &**this; } iterator& operator++() // pre { ++pos; return (*this); } iterator operator++(int) // post { iterator tmp = *this; ++*this; return tmp; } bool operator==(const iterator& rhs) const { return data == rhs.data && pos == rhs.pos; } bool operator!=(const iterator& rhs) const { return !(*this == rhs); } bool operator<(const iterator& rhs) const { return (pos < rhs.pos); } iterator& operator+=(difference_type ofs) { pos += ofs; return *this; } iterator& operator-=(difference_type ofs) { return (*this += -ofs); } iterator operator+(difference_type ofs) const { iterator tmp = *this; return (tmp += ofs); } iterator operator-(difference_type ofs) const { iterator tmp = *this; return (tmp -= ofs); } difference_type operator-(const iterator right) const { return (difference_type)(pos - right.pos); } protected: T* data; size_t pos; // not mod-N so that begin != end when buffer is full. }; class const_iterator { public: typedef std::random_access_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t difference_type; typedef const T* pointer; typedef const T& reference; const_iterator() : data(0), pos(0) {} const_iterator(const T* data_, size_t pos_) : data(data_), pos(pos_) {} const T& operator[](int idx) const { return data[(pos+idx) % n]; } const T& operator*() const { return data[pos % n]; } const T* operator->() const { return &**this; } const_iterator& operator++() // pre { ++pos; return (*this); } const_iterator operator++(int) // post { const_iterator tmp = *this; ++*this; return tmp; } bool operator==(const const_iterator& rhs) const { return data == rhs.data && pos == rhs.pos; } bool operator!=(const const_iterator& rhs) const { return !(*this == rhs); } bool operator<(const const_iterator& rhs) const { return (pos < rhs.pos); } iterator& operator+=(difference_type ofs) { pos += ofs; return *this; } iterator& operator-=(difference_type ofs) { return (*this += -ofs); } iterator operator+(difference_type ofs) const { iterator tmp = *this; return (tmp += ofs); } iterator operator-(difference_type ofs) const { iterator tmp = *this; return (tmp -= ofs); } difference_type operator-(const iterator right) const { return (difference_type)(pos - right.pos); } protected: const T* data; size_t pos; // not mod-N so that begin != end when buffer is full. }; iterator begin() { return iterator(data, head); } const_iterator begin() const { return const_iterator(data, head); } iterator end() { return iterator(data, head+size_); } const_iterator end() const { return const_iterator(data, head+size_); } }; #endif // #ifndef INCLUDED_ADTS_RING_BUF Index: ps/trunk/source/lib/alignment.h =================================================================== --- ps/trunk/source/lib/alignment.h (revision 19898) +++ ps/trunk/source/lib/alignment.h (revision 19899) @@ -1,106 +1,106 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_ALIGNMENT #define INCLUDED_ALIGNMENT #include "lib/sysdep/compiler.h" // MSC_VERSION #include "lib/sysdep/arch.h" // ARCH_AMD64 template inline bool IsAligned(T t, uintptr_t multiple) { return (uintptr_t(t) % multiple) == 0; } template inline size_t Align(size_t n) { cassert(multiple != 0 && ((multiple & (multiple-1)) == 0)); // is power of 2 return (n + multiple-1) & ~(multiple-1); } // bridge the differences between MSC and GCC alignment definitions. // example: ALIGNED(int, 8) myAlignedVariable = 0; #if MSC_VERSION # define ALIGNED(type, multiple) __declspec(align(multiple)) type #elif GCC_VERSION # define ALIGNED(type, multiple) type __attribute__((aligned(multiple))) #else # define ALIGNED(type, multiple) type #endif // // SIMD vector // static const size_t vectorSize = 16; #define VECTOR_ALIGNED(type) ALIGNED(type, 16) // ALIGNED() requires a literal; keep in sync with vectorSize #define ASSERT_VECTOR_MULTIPLE(size)\ ASSERT(IsAligned(size, vectorSize)) #define ASSERT_VECTOR_ALIGNED(pointer)\ ASSERT_VECTOR_MULTIPLE(pointer);\ ASSUME_ALIGNED(pointer, vectorSize) // // CPU cache // static const size_t cacheLineSize = 64; // (L2) #define CACHE_ALIGNED(type) ALIGNED(type, 64) // ALIGNED() requires a literal; keep in sync with cacheLineSize // // MMU pages // static const size_t pageSize = 0x1000; // 4 KB static const size_t largePageSize = 0x200000; // 2 MB // // misc // static const size_t allocationAlignment = 16; static const size_t KiB = size_t(1) << 10; static const size_t MiB = size_t(1) << 20; static const size_t GiB = size_t(1) << 30; // waio opens files with FILE_FLAG_NO_BUFFERING, so Windows requires // file offsets / buffers and sizes to be sector-aligned. querying the // actual sector size via GetDiskFreeSpace is inconvenient and slow. // we always request large blocks anyway, so just check whether inputs // are aligned to a `maximum' sector size. this catches common mistakes // before they cause scary "IO failed" errors. if the value turns out // to be too low, the Windows APIs will still complain. static const uintptr_t maxSectorSize = 0x1000; #endif // #ifndef INCLUDED_ALIGNMENT Index: ps/trunk/source/lib/debug.h =================================================================== --- ps/trunk/source/lib/debug.h (revision 19898) +++ ps/trunk/source/lib/debug.h (revision 19899) @@ -1,578 +1,578 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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_api.h" #include "lib/types.h" // intptr_t #include "lib/status.h" #include "lib/alignment.h" #include "lib/code_annotation.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 //----------------------------------------------------------------------------- // 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 char* fmt, ...) PRINTF_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, /** * 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. * use 0 as the initial value to avoid allocating .rdata space. **/ static const atomic_bool DEBUG_SUPPRESS = 0xAB; /** * choices offered by the error dialog that are returned * by debug_DisplayError. **/ 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 }; /** * all choices offered by the error dialog. those not defined in * ErrorReaction are acted upon by debug_DisplayError and * never returned to callers. * (this separation avoids enumerator-not-handled warnings) **/ enum ErrorReactionInternal { ERI_CONTINUE = ER_CONTINUE, ERI_BREAK = ER_BREAK, /** * ignore and do not report again. * note: non-persistent; only applicable during this program run. **/ ERI_SUPPRESS, /** * exit the program immediately. **/ ERI_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. **/ ERI_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); // simplified version for just displaying an error message #define DEBUG_DISPLAY_ERROR(description)\ do\ {\ CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE];\ (void)debug_CaptureContext(context);\ (void)debug_DisplayError(description, 0, context, L"debug_DisplayError", WIDEN(__FILE__), __LINE__, __func__, 0);\ }\ while(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 char* tag); /** * in future, discard output with the given tag. * no effect if not currently added. **/ LIB_API void debug_filter_remove(const char* 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 char* text); /** * call debug_puts if debug_filter_allows allows the string. **/ LIB_API void debug_puts_filtered(const char* 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 Status; ERR::REENTERED if reentered via recursion or * multithreading (not allowed since an infinite loop may result). **/ LIB_API Status debug_WriteCrashlog(const char* text); //----------------------------------------------------------------------------- // assertions //----------------------------------------------------------------------------- /** * ensure 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! * * to pass more information to users at runtime, you can write * ENSURE(expression && "descriptive string"). **/ #define ENSURE(expr)\ do\ {\ static atomic_bool suppress__;\ if(!(expr))\ {\ switch(debug_OnAssertionFailure(WIDEN(#expr), &suppress__, WIDEN(__FILE__), __LINE__, __func__))\ {\ case ER_CONTINUE:\ break;\ case ER_BREAK:\ default:\ debug_break();\ break;\ }\ }\ }\ while(0) /** * same as ENSURE in debug mode, does nothing in release mode. * (we don't override the `assert' macro because users may * inadvertently include \ afterwards) * (we do not provide an MFC-style VERIFY macro because the distinction * between ENSURE and VERIFY is unclear. to always run code but only * check for success in debug builds without raising unused-variable warnings, * use ASSERT + UNUSED2.) **/ #define ASSERT(expr) ENSURE(expr) #ifdef NDEBUG # undef ASSERT # define ASSERT(expr) #endif /** * display the error dialog with the given text. this is less error-prone than * ENSURE(0 && "text"). note that "conditional expression is constant" warnings * are disabled anyway. * * if being able to suppress the warning is desirable (e.g. for self-tests), * then use DEBUG_WARN_ERR instead. **/ #define debug_warn(expr) ENSURE(0 && (expr)) /** * display the error dialog with text corresponding to the given error code. * used by WARN_RETURN_STATUS_IF_ERR et al., which wrap function calls and automatically * raise warnings and return to the caller. **/ #define DEBUG_WARN_ERR(status)\ do\ {\ static atomic_bool suppress__;\ switch(debug_OnError(status, &suppress__, WIDEN(__FILE__), __LINE__, __func__))\ {\ case ER_CONTINUE:\ break;\ case ER_BREAK:\ default:\ debug_break();\ break;\ }\ }\ while(0) /** * called when a ENSURE/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) ANALYZER_NORETURN; /** * called when a DEBUG_WARN_ERR indicates an error occurred; * notifies the user via debug_DisplayError. * * @param err Status 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(Status err, atomic_bool* suppress, const wchar_t* file, int line, const char* func) ANALYZER_NORETURN; /** * suppress (prevent from showing) the error dialog from subsequent * debug_OnError for the given Status. * * 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 Status to skip. * * note: only one concurrent skip request is allowed; call * debug_StopSkippingErrors before the next debug_SkipErrors. */ LIB_API void debug_SkipErrors(Status err); /** * @return how many errors were skipped since the call to debug_SkipErrors() **/ LIB_API size_t debug_StopSkippingErrors(); //----------------------------------------------------------------------------- // symbol access //----------------------------------------------------------------------------- namespace ERR { const Status SYM_NO_STACK_FRAMES_FOUND = -100400; const Status SYM_UNRETRIEVABLE_STATIC = -100401; const Status SYM_UNRETRIEVABLE = -100402; const Status SYM_TYPE_INFO_UNAVAILABLE = -100403; const Status SYM_INTERNAL_ERROR = -100404; const Status SYM_UNSUPPORTED = -100405; const Status SYM_CHILD_NOT_FOUND = -100406; // this limit is to prevent infinite recursion. const Status 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 Status 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 Status SYM_SUPPRESS_OUTPUT = +100409; } /** * Maximum number of characters (including null terminator) written to * user's buffers by debug_ResolveSymbol. **/ static const size_t DEBUG_SYMBOL_CHARS = 1000; static const size_t DEBUG_FILE_CHARS = 100; /** * read and return symbol information for the given address. * * NOTE: the PDB implementation is rather slow (~500 us). * * @param ptr_of_interest address of symbol (e.g. function, variable) * @param sym_name optional out; holds at least DEBUG_SYMBOL_CHARS; * receives symbol name returned via debug info. * @param file optional out; holds at least DEBUG_FILE_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 Status; INFO::OK iff any information was successfully * retrieved and stored. **/ LIB_API Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line); static const size_t DEBUG_CONTEXT_SIZE = 2048; // Win32 CONTEXT is currently 1232 bytes /** * @param context must point to an instance of the platform-specific type * (e.g. CONTEXT) or CACHE_ALIGNED storage of DEBUG_CONTEXT_SIZE bytes. **/ LIB_API Status debug_CaptureContext(void* context); /** * 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). either specify an SEH exception's * context record or use debug_CaptureContext to retrieve the current state. * Rationale: intermediates such as debug_DisplayError change the * context, so it should be captured as soon as possible. * @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 Status; ERR::REENTERED if reentered via recursion or * multithreading (not allowed since static data is used). **/ LIB_API Status 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 char* 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. // instead, "lib/sysdep/vm.h" functions 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/external_libraries/curl.h =================================================================== --- ps/trunk/source/lib/external_libraries/curl.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/curl.h (revision 19899) @@ -1,52 +1,52 @@ -/* Copyright (c) 2011 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 * "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. */ /* * bring in libcurl header, with compatibility fixes */ #ifndef INCLUDED_CURL #define INCLUDED_CURL #if OS_WIN // curl.h wants to include winsock2.h which causes conflicts. // provide some required definitions from winsock.h, then pretend // we already included winsock.h typedef uintptr_t SOCKET; struct sockaddr { unsigned short sa_family; char sa_data[14]; }; struct fd_set; #define _WINSOCKAPI_ // winsock.h include guard #endif // OS_WIN #include #endif // #ifndef INCLUDED_CURL Index: ps/trunk/source/lib/external_libraries/dbghelp_funcs.h =================================================================== --- ps/trunk/source/lib/external_libraries/dbghelp_funcs.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/dbghelp_funcs.h (revision 19899) @@ -1,36 +1,36 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ FUNC(BOOL, SymInitializeW, (__in HANDLE hProcess, __in_opt PCWSTR UserSearchPath, __in BOOL fInvadeProcess)) FUNC(DWORD, SymGetOptions, (VOID)) FUNC(DWORD, SymSetOptions, (__in DWORD SymOptions)) FUNC(DWORD64, SymGetModuleBase64, (__in HANDLE hProcess, __in DWORD64 qwAddr)) FUNC(BOOL, SymFromAddrW, (__in HANDLE hProcess, __in DWORD64 Address, __out_opt PDWORD64 Displacement, __inout PSYMBOL_INFOW Symbol)) FUNC(BOOL, SymGetLineFromAddrW64, (__in HANDLE hProcess, __in DWORD64 qwAddr, __out PDWORD pdwDisplacement, __out PIMAGEHLP_LINEW64 Line64)) FUNC(PVOID, SymFunctionTableAccess64, (__in HANDLE hProcess, __in DWORD64 AddrBase)) FUNC(BOOL, SymGetTypeInfo, (__in HANDLE hProcess, __in DWORD64 ModBase, __in ULONG TypeId, __in IMAGEHLP_SYMBOL_TYPE_INFO GetType, __out PVOID pInfo)) FUNC(BOOL, SymFromIndexW, (__in HANDLE hProcess, __in ULONG64 BaseOfDll, __in DWORD Index, __inout PSYMBOL_INFOW Symbol)) FUNC(BOOL, SymSetContext, (__in HANDLE hProcess, __in PIMAGEHLP_STACK_FRAME StackFrame, __in_opt PIMAGEHLP_CONTEXT Context)) FUNC(BOOL, SymEnumSymbolsW, (__in HANDLE hProcess, __in ULONG64 BaseOfDll, __in_opt PCWSTR Mask, __in PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback, __in_opt PVOID UserContext)) FUNC(PIMAGE_NT_HEADERS, ImageNtHeader, (__in PVOID Base)) FUNC(BOOL, MiniDumpWriteDump, (IN HANDLE hProcess, IN DWORD ProcessId, IN HANDLE hFile, IN MINIDUMP_TYPE DumpType, IN CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, OPTIONAL IN CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, OPTIONAL IN CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL)) FUNC(BOOL, StackWalk64, (__in DWORD MachineType, __in HANDLE hProcess, __in HANDLE hThread, __inout LPSTACKFRAME64 StackFrame, __inout PVOID ContextRecord, __in_opt PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, __in_opt PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, __in_opt PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, __in_opt PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress)) Index: ps/trunk/source/lib/allocators/unique_range.h =================================================================== --- ps/trunk/source/lib/allocators/unique_range.h (revision 19898) +++ ps/trunk/source/lib/allocators/unique_range.h (revision 19899) @@ -1,220 +1,220 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_ALLOCATORS_UNIQUE_RANGE #define INCLUDED_ALLOCATORS_UNIQUE_RANGE #include "lib/lib_api.h" #include "lib/alignment.h" // allocationAlignment #include "lib/sysdep/vm.h" // we usually don't hold multiple references to allocations, so unique_ptr // can be used instead of the more complex (ICC generated incorrect code on // 2 occasions) and expensive shared_ptr. // a custom deleter is required because allocators such as ReserveAddressSpace need to // pass the size to their deleter. we want to mix pointers from various allocators, but // unique_ptr's deleter is fixed at compile-time, so it would need to be general enough // to handle all allocators. // storing the size and a function pointer would be one such solution, with the added // bonus of no longer requiring a complete type at the invocation of ~unique_ptr. // however, this inflates the pointer size to 3 words. if only a few allocator types // are needed, we can replace the function pointer with an index stashed into the // lower bits of the pointer (safe because all allocations' addresses are multiples // of allocationAlignment). typedef intptr_t IdxDeleter; // no-op deleter (use when returning part of an existing allocation) static const IdxDeleter idxDeleterNone = 0; typedef void (*UniqueRangeDeleter)(void* pointer, size_t size); /** * register a deleter, returning its index within the table. * * @param deleter function pointer. must be uniquely associated with * the idxDeleter storage location. * @param idxDeleter location where to store the next available index. * if it is already non-zero, skip the call to this function to * avoid overhead. * * thread-safe. idxDeleter is used for mutual exclusion between * multiple callers for the same deleter. concurrent registration of * different deleters is also safe due to atomic increments. * * halts the program if more than allocationAlignment deleters are * to be registered. **/ LIB_API void RegisterUniqueRangeDeleter(UniqueRangeDeleter deleter, volatile IdxDeleter* idxDeleter); LIB_API NOTHROW_DECLARE void CallUniqueRangeDeleter(void* pointer, size_t size, IdxDeleter idxDeleter); // unfortunately, unique_ptr allows constructing without a custom deleter. to ensure callers can // rely upon pointers being associated with a size, we introduce a `UniqueRange' replacement. // its interface is identical to unique_ptr except for the constructors, the addition of // size() and the removal of operator bool (which avoids implicit casts to int). class UniqueRange { public: typedef void* pointer; typedef void element_type; UniqueRange() { Clear(); } UniqueRange(pointer p, size_t size, IdxDeleter deleter) { Set(p, size, deleter); } UniqueRange(UniqueRange&& rvalue) { Pilfer(rvalue); } UniqueRange& operator=(UniqueRange&& rvalue) { UniqueRange& lvalue = rvalue; if(this != &lvalue) { Delete(); Pilfer(lvalue); } return *this; } ~UniqueRange() { Delete(); } pointer get() const { return pointer(address_ & ~(allocationAlignment-1)); } IdxDeleter get_deleter() const { return IdxDeleter(address_ % allocationAlignment); } size_t size() const { return size_; } // side effect: subsequent get_deleter will return idxDeleterNone pointer release() // relinquish ownership { pointer ret = get(); Clear(); return ret; } void reset() { Delete(); Clear(); } void reset(pointer p, size_t size, IdxDeleter deleter) { Delete(); Set(p, size, deleter); } void swap(UniqueRange& rhs) { std::swap(address_, rhs.address_); std::swap(size_, rhs.size_); } // don't define construction and assignment from lvalue, // but the declarations must be accessible UniqueRange(const UniqueRange&); UniqueRange& operator=(const UniqueRange&); private: void Set(pointer p, size_t size, IdxDeleter deleter) { ASSERT((uintptr_t(p) % allocationAlignment) == 0); ASSERT(size_t(deleter) < allocationAlignment); address_ = uintptr_t(p) | deleter; size_ = size; ASSERT(get() == p); ASSERT(get_deleter() == deleter); ASSERT(this->size() == size); } void Clear() { Set(0, 0, idxDeleterNone); } void Pilfer(UniqueRange& victim) { const size_t size = victim.size(); const IdxDeleter idxDeleter = victim.get_deleter(); pointer p = victim.release(); Set(p, size, idxDeleter); victim.Clear(); } void Delete() { CallUniqueRangeDeleter(get(), size(), get_deleter()); } // (IdxDeleter is stored in the lower bits of address since size might not even be a multiple of 4.) uintptr_t address_; size_t size_; }; namespace std { static inline void swap(UniqueRange& p1, UniqueRange& p2) { p1.swap(p2); } static inline void swap(UniqueRange&& p1, UniqueRange& p2) { p2.swap(p1); } static inline void swap(UniqueRange& p1, UniqueRange&& p2) { p1.swap(p2); } } LIB_API UniqueRange AllocateAligned(size_t size, size_t alignment); LIB_API UniqueRange AllocateVM(size_t size, vm::PageType pageSize = vm::kDefault, int prot = PROT_READ|PROT_WRITE); #endif // #ifndef INCLUDED_ALLOCATORS_UNIQUE_RANGE Index: ps/trunk/source/lib/app_hooks.h =================================================================== --- ps/trunk/source/lib/app_hooks.h (revision 19898) +++ ps/trunk/source/lib/app_hooks.h (revision 19899) @@ -1,209 +1,209 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * hooks to allow customization / app-specific behavior. */ /* Background ---------- This codebase is shared between several projects, each with differing needs. Some of them have e.g. complicated i18n/translation facilities and require all text output to go through it; others strive to minimize size and therefore do not want to include that. Since commenting things out isn't an option with shared source and conditional compilation is ugly, we bridge the differences via "hooks". These are functions whose behavior is expected to differ between projects; they are defined by the app, registered here and called from lib code via our trampolines. Introduction ------------ This module provides a clean interface for other code to call hooks and allows the app to register them. It also defines default stub implementations. Usage ----- In the simplest case, the stubs are already acceptable. Otherwise, you need to implement a new version of some hooks, fill an AppHooks struct with pointers to those functions (zero the rest), and call app_hooks_update. Adding New Functions -------------------- Several steps are needed (see below for rationale): 0) HOOKNAME is the name of the desired procedure (e.g. "bundle_logs") 1) add a 'trampoline' (user visible function) declaration to this header (typically named ah_HOOKNAME) 2) add the corresponding implementation, i.e. call to ah.HOOKNAME 3) add a default implementation of the new functionality (typically named def_HOOKNAME) 4) add HOOKNAME member to struct AppHooks declaration 5) set HOOKNAME member to def_HOOKNAME in initialization of 'struct AppHooks ah' 6) add HOOKNAME to list in app_hooks_update code Rationale --------- While X-Macros would reduce the amount of work needed when adding new functions, they confuse static code analysis and VisualAssist X (the function definitions are not visible to them). We prefer convenience during usage instead of in the rare cases where new app hook functions are defined. note: an X-Macro would define the app hook as such: extern const wchar_t*, translate, (const wchar_t* text), (text), return) .. and in its various invocations perform the above steps automatically. */ #ifndef INCLUDED_APP_HOOKS #define INCLUDED_APP_HOOKS #include "lib/os_path.h" // trampolines for user code to call the hooks. they encapsulate // the details of how exactly to do this. /** * override default decision on using OpenGL extensions relating to * texture upload. * * this should call ogl_tex_override to disable/force their use if the * current card/driver combo respectively crashes or * supports it even though the extension isn't advertised. * * the default implementation works but is hardwired in code and therefore * not expandable. **/ extern void ah_override_gl_upload_caps(); /** * return path to directory into which crash dumps should be written. * * must be callable at any time - in particular, before VFS init. * paths are typically relative to sys_ExecutablePathname. * * @return path ending with directory separator (e.g. '/'). **/ extern const OsPath& ah_get_log_dir(); /** * gather all app-related logs/information and write it to file. * * used when writing a crash log so that all relevant info is in one file. * * the default implementation attempts to gather 0ad data, but is * fail-safe (doesn't complain if file not found). * * @param f file into which to write. **/ extern void ah_bundle_logs(FILE* f); /** * translate text to the current locale. * * @param text to translate. * @return pointer to localized text; must be freed via translate_free. * * the default implementation just returns the pointer unchanged. **/ extern const wchar_t* ah_translate(const wchar_t* text); /** * free text that was returned by translate. * * @param text to free. * * the default implementation does nothing. **/ extern void ah_translate_free(const wchar_t* text); /** * write text to the app's log. * * @param text to write. * * the default implementation uses stdout. **/ extern void ah_log(const wchar_t* text); /** * display an error dialog, thus overriding sys_display_error. * * @param text error message. * @param flags see DebugDisplayErrorFlags. * @return ErrorReactionInternal. * * the default implementation just returns ERI_NOT_IMPLEMENTED, which * causes the normal sys_display_error to be used. **/ extern ErrorReactionInternal ah_display_error(const wchar_t* text, size_t flags); /** * holds a function pointer (allowed to be NULL) for each hook. * passed to app_hooks_update. **/ struct AppHooks { void (*override_gl_upload_caps)(); const OsPath& (*get_log_dir)(); void (*bundle_logs)(FILE* f); const wchar_t* (*translate)(const wchar_t* text); void (*translate_free)(const wchar_t* text); void (*log)(const wchar_t* text); ErrorReactionInternal (*display_error)(const wchar_t* text, size_t flags); }; /** * update the app hook function pointers. * * @param ah AppHooks struct. any of its function pointers that are non-zero * override the previous function pointer value * (these default to the stub hooks which are functional but basic). **/ LIB_API void app_hooks_update(AppHooks* ah); /** * was the app hook changed via app_hooks_update from its default value? * * @param offset_in_struct byte offset within AppHooks (determined via * offsetof) of the app hook function pointer. **/ extern bool app_hook_was_redefined(size_t offset_in_struct); // name is identifier of the function pointer within AppHooks to test. #define AH_IS_DEFINED(name) app_hook_was_redefined(offsetof(AppHooks, name)) #endif // #ifndef INCLUDED_APP_HOOKS Index: ps/trunk/source/lib/base32.h =================================================================== --- ps/trunk/source/lib/base32.h (revision 19898) +++ ps/trunk/source/lib/base32.h (revision 19899) @@ -1,40 +1,40 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * base32 conversion */ #ifndef INCLUDED_BASE32 #define INCLUDED_BASE32 /** * generate the base32 textual representation of a buffer. * * @param len Size [bytes] of input * @param in Big-endian input data (assumed to be integral number of bytes) * @param out Output string; zero-terminated. must be big enough * (i.e. at least ceil(len*CHAR_BIT/5) + 1 chars) **/ extern void base32(const size_t len, const u8* in, u8* out); #endif // #ifndef INCLUDED_BASE32 Index: ps/trunk/source/lib/byte_order.cpp =================================================================== --- ps/trunk/source/lib/byte_order.cpp (revision 19898) +++ ps/trunk/source/lib/byte_order.cpp (revision 19899) @@ -1,200 +1,200 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * byte order (endianness) support routines. */ #include "precompiled.h" #include "lib/byte_order.h" #include "lib/bits.h" #ifndef swap16 u16 swap16(const u16 x) { return (u16)(((x & 0xff) << 8) | (x >> 8)); } #endif #ifndef swap32 u32 swap32(const u32 x) { return (x << 24) | (x >> 24) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00); } #endif #ifndef swap64 u64 swap64(const u64 x) { const u32 lo = (u32)(x & 0xFFFFFFFF); const u32 hi = (u32)(x >> 32); u64 ret = swap32(lo); ret <<= 32; // careful: must shift var of type u64, not u32 ret |= swap32(hi); return ret; } #endif //----------------------------------------------------------------------------- u16 read_le16(const void* p) { u16 n; memcpy(&n, p, sizeof(n)); return to_le16(n); } u32 read_le32(const void* p) { u32 n; memcpy(&n, p, sizeof(n)); return to_le32(n); } u64 read_le64(const void* p) { u64 n; memcpy(&n, p, sizeof(n)); return to_le64(n); } u16 read_be16(const void* p) { u16 n; memcpy(&n, p, sizeof(n)); return to_be16(n); } u32 read_be32(const void* p) { u32 n; memcpy(&n, p, sizeof(n)); return to_be32(n); } u64 read_be64(const void* p) { u64 n; memcpy(&n, p, sizeof(n)); return to_be64(n); } void write_le16(void* p, u16 x) { u16 n = to_le16(x); memcpy(p, &n, sizeof(n)); } void write_le32(void* p, u32 x) { u32 n = to_le32(x); memcpy(p, &n, sizeof(n)); } void write_le64(void* p, u64 x) { u64 n = to_le64(x); memcpy(p, &n, sizeof(n)); } void write_be16(void* p, u16 x) { u16 n = to_be16(x); memcpy(p, &n, sizeof(n)); } void write_be32(void* p, u32 x) { u32 n = to_be32(x); memcpy(p, &n, sizeof(n)); } void write_be64(void* p, u64 x) { u64 n = to_be64(x); memcpy(p, &n, sizeof(n)); } u64 movzx_le64(const u8* p, size_t size_bytes) { u64 number = 0; for(size_t i = 0; i < std::min(size_bytes, (size_t)8u); i++) number |= ((u64)p[i]) << (i*8); return number; } u64 movzx_be64(const u8* p, size_t size_bytes) { u64 number = 0; for(size_t i = 0; i < std::min(size_bytes, (size_t)8u); i++) { number <<= 8; number |= p[i]; } return number; } static inline i64 SignExtend(u64 bits, size_t size_bytes) { // no point in sign-extending if >= 8 bytes were requested if(size_bytes < 8) { const u64 sign_bit = Bit((size_bytes*8)-1); // number would be negative in the smaller type, // so sign-extend, i.e. set all more significant bits. if(bits & sign_bit) { const u64 valid_bit_mask = (sign_bit+sign_bit)-1; bits |= ~valid_bit_mask; } } const i64 number = static_cast(bits); return number; } i64 movsx_le64(const u8* p, size_t size_bytes) { const u64 number = movzx_le64(p, size_bytes); return SignExtend(number, size_bytes); } i64 movsx_be64(const u8* p, size_t size_bytes) { const u64 number = movzx_be64(p, size_bytes); return SignExtend(number, size_bytes); } Index: ps/trunk/source/lib/code_generation.h =================================================================== --- ps/trunk/source/lib/code_generation.h (revision 19898) +++ ps/trunk/source/lib/code_generation.h (revision 19899) @@ -1,137 +1,137 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #ifndef INCLUDED_CODE_GENERATION #define INCLUDED_CODE_GENERATION /** * package code into a single statement. * * @param STMT_code__ code to be bundled. (must be interpretable as * a macro argument, i.e. sequence of tokens). * the argument name is chosen to avoid conflicts. * * notes: * - for(;;) { break; } and {} don't work because invocations of macros * implemented with STMT often end with ";", thus breaking if() expressions. * - we'd really like to eliminate "conditional expression is constant" * warnings. replacing 0 literals with extern volatile variables fools * VC7 but isn't guaranteed to be free of overhead. we will just * squelch the warning (unfortunately non-portable). **/ #define STMT(STMT_code__) do { STMT_code__; } while(false) /** * execute the code passed as a parameter only the first time this is * reached. * may be called at any time (in particular before main), but is not * thread-safe. if that's important, use pthread_once() instead. **/ #define ONCE(ONCE_code__)\ STMT(\ static bool ONCE_done__ = false;\ if(!ONCE_done__)\ {\ ONCE_done__ = true;\ ONCE_code__;\ }\ ) /** * execute the code passed as a parameter except the first time this is * reached. * may be called at any time (in particular before main), but is not * thread-safe. **/ #define ONCE_NOT(ONCE_code__)\ STMT(\ static bool ONCE_done__ = false;\ if(!ONCE_done__)\ ONCE_done__ = true;\ else\ ONCE_code__;\ ) /** * execute the code passed as a parameter before main is entered. * * WARNING: if the source file containing this is not directly referenced * from anywhere, linkers may discard that object file (unless linking * statically). see http://www.cbloom.com/3d/techdocs/more_coding.txt **/ #define AT_STARTUP(code__)\ namespace { struct UID__\ {\ UID__() { code__; }\ } UID2__; } /** * C++ new wrapper: allocates an instance of the given type and stores a * pointer to it. sets pointer to 0 on allocation failure. * * this simplifies application code when on VC6, which may or * may not throw/return 0 on failure. **/ #define SAFE_NEW(type, ptr)\ type* ptr;\ try\ {\ ptr = new type();\ }\ catch(std::bad_alloc&)\ {\ ptr = 0;\ } /** * delete memory ensuing from new and set the pointer to zero * (thus making double-frees safe / a no-op) **/ #define SAFE_DELETE(p)\ STMT(\ delete (p); /* if p == 0, delete is a no-op */ \ (p) = 0;\ ) /** * delete memory ensuing from new[] and set the pointer to zero * (thus making double-frees safe / a no-op) **/ #define SAFE_ARRAY_DELETE(p)\ STMT(\ delete[] (p); /* if p == 0, delete is a no-op */ \ (p) = 0;\ ) /** * free memory ensuing from malloc and set the pointer to zero * (thus making double-frees safe / a no-op) **/ #define SAFE_FREE(p)\ STMT(\ free((void*)p); /* if p == 0, free is a no-op */ \ (p) = 0;\ ) #endif // #ifndef INCLUDED_CODE_GENERATION Index: ps/trunk/source/lib/debug.cpp =================================================================== --- ps/trunk/source/lib/debug.cpp (revision 19898) +++ ps/trunk/source/lib/debug.cpp (revision 19899) @@ -1,564 +1,564 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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/alignment.h" #include "lib/app_hooks.h" #include "lib/fnv_hash.h" #include "lib/sysdep/vm.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 static const StatusDefinition debugStatusDefinitions[] = { { ERR::SYM_NO_STACK_FRAMES_FOUND, L"No stack frames found" }, { ERR::SYM_UNRETRIEVABLE_STATIC, L"Value unretrievable (stored in external module)" }, { ERR::SYM_UNRETRIEVABLE, L"Value unretrievable" }, { ERR::SYM_TYPE_INFO_UNAVAILABLE, L"Error getting type_info" }, { ERR::SYM_INTERNAL_ERROR, L"Exception raised while processing a symbol" }, { ERR::SYM_UNSUPPORTED, L"Symbol type not (fully) supported" }, { ERR::SYM_CHILD_NOT_FOUND, L"Symbol does not have the given child" }, { ERR::SYM_NESTING_LIMIT, L"Symbol nesting too deep or infinite recursion" }, { ERR::SYM_SINGLE_SYMBOL_LIMIT, L"Symbol has produced too much output" }, { INFO::SYM_SUPPRESS_OUTPUT, L"Symbol was suppressed" } }; STATUS_ADD_DEFINITIONS(debugStatusDefinitions); // 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 char* tag) { const u32 hash = fnv_hash(tag, strlen(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_WARN_ERR(ERR::LOGIC); // increase MAX_TAGS return; } tags[num_tags++] = hash; } void debug_filter_remove(const char* tag) { const u32 hash = fnv_hash(tag, strlen(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 char* 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 char* fmt, ...) { char buf[16384]; va_list ap; va_start(ap, fmt); const int numChars = vsprintf_s(buf, ARRAY_SIZE(buf), fmt, ap); if (numChars < 0) debug_break(); // poor man's assert - avoid infinite loop because ENSURE also uses debug_printf va_end(ap); debug_puts_filtered(buf); } void debug_puts_filtered(const char* text) { if(debug_filter_allows(text)) debug_puts(text); } //----------------------------------------------------------------------------- Status 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 OsPath pathname = ah_get_log_dir()/"crashlog.txt"; FILE* f = sys_OpenFile(pathname, "w"); if(!f) { 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) { vm::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(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) { // retrieve errno (might be relevant) before doing anything else // that might overwrite it. wchar_t description_buf[100] = L"?"; wchar_t os_error[100] = L"?"; Status errno_equiv = StatusFromErrno(); // NOWARN if(errno_equiv != ERR::FAIL) // meaningful translation StatusDescription(errno_equiv, description_buf, ARRAY_SIZE(description_buf)); sys_StatusDescription(0, os_error, ARRAY_SIZE(os_error)); // rationale: see ErrorMessageMem emm->pa_mem = vm::Allocate(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 Status 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 error_buf[100] = {'?'}; if(!writer( L"(error while dumping stack: %ls)", StatusDescription(ret, error_buf, ARRAY_SIZE(error_buf)) )) goto fail; } else // success { writer.CountAddedChars(); } // append errno 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 ErrorReactionInternal CallDisplayError(const wchar_t* text, size_t flags) { // first try app hook implementation ErrorReactionInternal er = ah_display_error(text, flags); // .. it's only a stub: default to normal implementation if(er == ERI_NOT_IMPLEMENTED) er = sys_display_error(text, flags); return er; } static ErrorReaction PerformErrorReaction(ErrorReactionInternal er, size_t flags, atomic_bool* suppress) { const bool shouldHandleBreak = (flags & DE_MANUAL_BREAK) == 0; switch(er) { case ERI_CONTINUE: return ER_CONTINUE; case ERI_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(); return ER_CONTINUE; } else return ER_BREAK; case ERI_SUPPRESS: (void)cpu_CAS(suppress, 0, DEBUG_SUPPRESS); return ER_CONTINUE; case ERI_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); case ERI_NOT_IMPLEMENTED: default: debug_break(); // not expected to be reached return ER_CONTINUE; } } 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 ErrorReactionInternal 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("%s(%d): %s\n", utf8_from_wstring(filename).c_str(), line, utf8_from_wstring(description).c_str()); ErrorMessageMem emm; const wchar_t* text = debug_BuildErrorMessage(description, filename, line, func, context, lastFuncToSkip, &emm); (void)debug_WriteCrashlog(text); ErrorReactionInternal 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 Status errorToSkip; static size_t numSkipped; void debug_SkipErrors(Status 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(Status err) { if(cpu_CAS(&skipStatus, VALID, BUSY)) { numSkipped++; const bool ret = (err == errorToSkip); COMPILER_FENCE; skipStatus = VALID; return ret; } return false; } ErrorReaction debug_OnError(Status err, atomic_bool* suppress, const wchar_t* file, int line, const char* func) { CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE]; (void)debug_CaptureContext(context); if(ShouldSkipError(err)) return ER_CONTINUE; const wchar_t* lastFuncToSkip = L"debug_OnError"; wchar_t buf[400]; wchar_t err_buf[200]; StatusDescription(err, err_buf, ARRAY_SIZE(err_buf)); swprintf_s(buf, ARRAY_SIZE(buf), L"Function call failed: return value was %lld (%ls)", (long long)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) { CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE]; (void)debug_CaptureContext(context); 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/source/lib/debug_stl.h =================================================================== --- ps/trunk/source/lib/debug_stl.h (revision 19898) +++ ps/trunk/source/lib/debug_stl.h (revision 19899) @@ -1,86 +1,86 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * portable debugging helper functions specific to the STL. */ #ifndef INCLUDED_DEBUG_STL #define INCLUDED_DEBUG_STL namespace ERR { const Status STL_CNT_UNKNOWN = -100500; const Status STL_CNT_UNSUPPORTED = -100501; // likely causes: not yet initialized or memory corruption. const Status STL_CNT_INVALID = -100502; } /** * reduce complicated STL symbol names to human-readable form. * * algorithm: remove/replace undesired substrings in one pass (fast). * example: "std::basic_string\, * std::allocator\ \>" => "string". * * @param name Buffer holding input symbol name; modified in-place. * There is no length limit; must be large enough to hold typical STL * strings. DEBUG_SYMBOL_CHARS chars is a good measure. * @return name for convenience. **/ extern wchar_t* debug_stl_simplify_name(wchar_t* name); /** * abstraction of all STL iterators used by debug_stl. **/ typedef const u8* (*DebugStlIterator)(void* internal, size_t el_size); /** * no STL iterator is larger than this; see below. **/ const size_t DEBUG_STL_MAX_ITERATOR_SIZE = 64; /** * prepare to enumerate the elements of arbitrarily typed STL containers. * * works by retrieving element count&size via debug information and hiding * the container's iterator implementation behind a common interface. * a basic sanity check is performed to see if the container memory is * valid and appears to be initialized. * * @param type_name exact type of STL container (see example above) * @param p raw memory holding the container * @param size sizeof(container) * @param el_size sizeof(value_type) * @param el_count out; number of valid elements in container * @param el_iterator out; callback function that acts as an iterator * @param it_mem out; buffer holding the iterator state. must be * at least DEBUG_STL_MAX_ITERATOR_SIZE bytes. * @return Status (ERR::STL_*) **/ extern Status debug_stl_get_container_info(const wchar_t* type_name, const u8* p, size_t size, size_t el_size, size_t* el_count, DebugStlIterator* el_iterator, void* it_mem); #endif // #ifndef INCLUDED_DEBUG_STL Index: ps/trunk/source/lib/external_libraries/dbghelp.h =================================================================== --- ps/trunk/source/lib/external_libraries/dbghelp.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/dbghelp.h (revision 19899) @@ -1,149 +1,149 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bring in dbghelp library. */ #ifndef INCLUDED_DBGHELP #define INCLUDED_DBGHELP #include "lib/sysdep/os/win/win.h" #include // VARIANT #if MSC_VERSION # pragma comment(lib, "oleaut32.lib") // VariantChangeType #endif //----------------------------------------------------------------------------- // fix omissions in the VC PSDK's dbghelp.h // the macros defined "for those without specstrings.h" are incorrect - // parameter definition is missing. #ifndef __specstrings # define __specstrings // prevent dbghelp from changing these # define __in # define __out # define __inout # define __in_opt # define __out_opt # define __inout_opt # define __in_ecount(s) # define __out_ecount(s) # define __inout_ecount(s) # define __in_bcount(s) # define __out_bcount(s) # define __inout_bcount(s) # define __deref_opt_out # define __deref_out #endif // (VC2005 defines __specstrings, but doesn't define (or use) __out_xcount, // so this is not inside the above #ifndef section) // // missing from dbghelp's list #ifndef __out_xcount # define __out_xcount(s) #endif // // not defined by dbghelp; these values are taken from DIA cvconst.h // enum BasicType { btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8, btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26, btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31 }; enum DataKind { DataIsUnknown, DataIsLocal, DataIsStaticLocal, DataIsParam, DataIsObjectPtr, DataIsFileStatic, DataIsGlobal, DataIsMember, DataIsStaticMember, DataIsConstant }; //----------------------------------------------------------------------------- #if ICC_VERSION # pragma warning(push) # pragma warning(disable:94) // the size of an array must be greater than zero # pragma warning(disable:271) // trailing comma is nonstandard # pragma warning(disable:418) // declaration requires a typedef name # pragma warning(disable:791) // calling convention specified more than once #endif #pragma pack(push, 8) // seems to be required #define _NO_CVCONST_H // request SymTagEnum be defined #include // must come after win.h and the above definitions #pragma pack(pop) #if ICC_VERSION # pragma warning(pop) #endif //----------------------------------------------------------------------------- // rationale: Debugging Tools For Windows includes newer header/lib files, // but they're not redistributable. since we don't want everyone to // have to install that, it's preferable to link dynamically. // declare function pointers #define FUNC(ret, name, params) EXTERN_C ret (__stdcall *p##name) params; #include "lib/external_libraries/dbghelp_funcs.h" #undef FUNC extern void dbghelp_ImportFunctions(); #endif // #ifndef INCLUDED_DBGHELP Index: ps/trunk/source/lib/allocators/aligned_allocator.h =================================================================== --- ps/trunk/source/lib/allocators/aligned_allocator.h (revision 19898) +++ ps/trunk/source/lib/allocators/aligned_allocator.h (revision 19899) @@ -1,148 +1,148 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * STL allocator for aligned memory */ #ifndef ALIGNED_ALLOCATOR #define ALIGNED_ALLOCATOR #include "lib/bits.h" // round_up #include "lib/sysdep/arch/x86_x64/cache.h" #include "lib/sysdep/rtl.h" // rtl_AllocateAligned /** * stateless STL allocator that aligns elements to the L1 cache line size. * * note: the alignment is hard-coded to avoid any allocator state. * this avoids portability problems, which is important since allocators * are rather poorly specified. * * references: * http://www.tantalon.com/pete/customallocators.ppt * http://www.flipcode.com/archives/Aligned_Block_Allocation.shtml * http://www.josuttis.com/cppcode/allocator.html * * derived from code that bears the following copyright notice: * (C) Copyright Nicolai M. Josuttis 1999. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. **/ template class AlignedAllocator { public: // type definitions typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; // rebind allocator to type U template struct rebind { typedef AlignedAllocator other; }; pointer address(reference value) const { return &value; } const_pointer address(const_reference value) const { return &value; } NOTHROW_DEFINE AlignedAllocator() { } NOTHROW_DEFINE AlignedAllocator(const AlignedAllocator&) { } template NOTHROW_DEFINE AlignedAllocator (const AlignedAllocator&) { } NOTHROW_DEFINE ~AlignedAllocator() { } NOTHROW_DEFINE size_type max_size() const { // maximum number of *elements* that can be allocated return std::numeric_limits::max() / sizeof(T); } // allocate uninitialized storage pointer allocate(size_type numElements) { const size_type alignment = x86_x64::Caches(x86_x64::L1D)->entrySize; const size_type elementSize = round_up(sizeof(T), alignment); const size_type size = numElements * elementSize; pointer p = (pointer)rtl_AllocateAligned(size, alignment); return p; } // deallocate storage of elements that have been destroyed void deallocate(pointer p, size_type UNUSED(num)) { rtl_FreeAligned((void*)p); } void construct(pointer p, const T& value) { new((void*)p) T(value); } void destroy(pointer p) { p->~T(); UNUSED2(p); // otherwise, warning is raised for reasons unknown } }; // indicate that all specializations of this allocator are interchangeable template NOTHROW_DEFINE bool operator==(const AlignedAllocator&, const AlignedAllocator&) { return true; } template NOTHROW_DEFINE bool operator!=(const AlignedAllocator&, const AlignedAllocator&) { return false; } #endif // #ifndef ALIGNED_ALLOCATOR Index: ps/trunk/source/lib/allocators/allocator_adapters.h =================================================================== --- ps/trunk/source/lib/allocators/allocator_adapters.h (revision 19898) +++ ps/trunk/source/lib/allocators/allocator_adapters.h (revision 19899) @@ -1,190 +1,190 @@ -/* Copyright (c) 2011 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 * "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. */ /* * adapters for allocators; provides a minimal subset of the * STL allocator interface. */ #ifndef ALLOCATOR_ADAPTERS #define ALLOCATOR_ADAPTERS #include #include "lib/sysdep/rtl.h" #include "lib/sysdep/vm.h" // NB: STL allocators are parameterized on the object type and indicate // the number of elements to [de]allocate. however, these adapters are // only used for allocating storage and receive the number of bytes. struct Allocator_Heap { void* allocate(size_t size) { return malloc(size); } void deallocate(void* p, size_t UNUSED(size)) { return free(p); } }; template struct Allocator_Aligned { void* allocate(size_t size) { return rtl_AllocateAligned(size, alignment); } void deallocate(void* p, size_t UNUSED(size)) { return rtl_FreeAligned(p); } }; template struct Allocator_VM { void* allocate(size_t size) { return vm::Allocate(size, pageType, prot); } void deallocate(void* p, size_t size) { vm::Free(p, size); } }; template struct Allocator_AddressSpace { void* allocate(size_t size) { return vm::ReserveAddressSpace(size, commitSize, pageType, prot); } void deallocate(void* p, size_t size) { vm::ReleaseAddressSpace(p, size); } }; /** * fully STL-compatible allocator that simply draws upon another Allocator. * this allows a single allocator to serve multiple STL containers. */ template class ProxyAllocator { public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; template struct rebind { typedef ProxyAllocator other; }; // (required to be declared by boost::unordered_map, but should never be called) explicit NOTHROW_DEFINE ProxyAllocator(); explicit NOTHROW_DEFINE ProxyAllocator(Allocator& allocator) : allocator(&allocator) { } template NOTHROW_DEFINE ProxyAllocator(const ProxyAllocator& rhs) : allocator(rhs.allocator) { } // (required by VC2010 std::vector) bool operator==(const ProxyAllocator& rhs) const { return allocator == rhs.allocator; } bool operator!=(const ProxyAllocator& rhs) const { return !operator==(rhs); } pointer address(reference r) { return &r; } const_pointer address(const_reference s) { return &s; } size_type max_size() const throw () { return std::numeric_limits::max() / sizeof(T); } void construct(const pointer ptr, const value_type& t) { new(ptr) T(t); } void destroy(pointer ptr) { ptr->~T(); UNUSED2(ptr); // silence MSVC warnings } pointer allocate(size_type n) { // safely handle zero-sized allocations (happens with GCC STL - see ticket #909). if(n == 0) n = 1; return (pointer)allocator->allocate(n*sizeof(T)); } pointer allocate(size_type n, const void* const) { return allocate(n); } void deallocate(const pointer ptr, const size_type n) { return allocator->deallocate(ptr, n*sizeof(T)); } //private: // otherwise copy ctor cannot access it Allocator* allocator; }; #endif // #ifndef ALLOCATOR_ADAPTERS Index: ps/trunk/source/lib/allocators/allocator_checker.h =================================================================== --- ps/trunk/source/lib/allocators/allocator_checker.h (revision 19898) +++ ps/trunk/source/lib/allocators/allocator_checker.h (revision 19899) @@ -1,71 +1,71 @@ -/* Copyright (c) 2011 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 * "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. */ #ifndef INCLUDED_ALLOCATORS_ALLOCATOR_CHECKER #define INCLUDED_ALLOCATORS_ALLOCATOR_CHECKER #include /** * allocator test rig. * call from each allocator operation to sanity-check them. * should only be used during debug mode due to serious overhead. **/ class AllocatorChecker { public: void OnAllocate(void* p, size_t size) { const Allocs::value_type item = std::make_pair(p, size); std::pair ret = allocs.insert(item); ENSURE(ret.second == true); // wasn't already in map } void OnDeallocate(void* p, size_t size) { Allocs::iterator it = allocs.find(p); if(it == allocs.end()) DEBUG_WARN_ERR(ERR::LOGIC); // freeing invalid pointer else { // size must match what was passed to OnAllocate const size_t allocated_size = it->second; ENSURE(size == allocated_size); allocs.erase(it); } } /** * allocator is resetting itself, i.e. wiping out all allocs. **/ void OnClear() { allocs.clear(); } private: typedef std::map Allocs; Allocs allocs; }; #endif // #ifndef INCLUDED_ALLOCATORS_ALLOCATOR_CHECKER Index: ps/trunk/source/lib/allocators/allocator_policies.h =================================================================== --- ps/trunk/source/lib/allocators/allocator_policies.h (revision 19898) +++ ps/trunk/source/lib/allocators/allocator_policies.h (revision 19899) @@ -1,354 +1,354 @@ -/* Copyright (c) 2011 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 * "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. */ /* * policy class templates for allocators. */ #ifndef ALLOCATOR_POLICIES #define ALLOCATOR_POLICIES #include "lib/alignment.h" // pageSize #include "lib/allocators/allocator_adapters.h" #include "lib/allocators/freelist.h" namespace Allocators { //----------------------------------------------------------------------------- // Growth // O(N) allocations, O(1) wasted space. template struct Growth_Linear { size_t operator()(size_t oldSize) const { return oldSize + increment; } }; // O(log r) allocations, O(N) wasted space. NB: the common choice of // expansion factor r = 2 (e.g. in the GCC STL) prevents // Storage_Reallocate from reusing previous memory blocks, // thus constantly growing the heap and decreasing locality. // Alexandrescu [C++ and Beyond 2010] recommends r < 33/25. // we approximate this with a power of two divisor to allow shifting. // C++ does allow reference-to-float template parameters, but // integer arithmetic is expected to be faster. // (Storage_Commit should use 2:1 because it is cheaper to // compute and retains power-of-two sizes.) template struct Growth_Exponential { size_t operator()(size_t oldSize) const { const size_t product = oldSize * multiplier; // detect overflow, but allow equality in case oldSize = 0, // which isn't a problem because Storage_Commit::Expand // raises it to requiredCapacity. ASSERT(product >= oldSize); return product / divisor; } }; //----------------------------------------------------------------------------- // Storage // a contiguous region of memory (not just an "array", because // allocators such as Arena append variable-sized intervals). // // we don't store smart pointers because storage usually doesn't need // to be copied, and ICC 11 sometimes wasn't able to inline Address(). struct Storage { // @return starting address (alignment depends on the allocator). uintptr_t Address() const; // @return size [bytes] of currently accessible memory. size_t Capacity() const; // @return largest possible capacity [bytes]. size_t MaxCapacity() const; // expand Capacity() to at least requiredCapacity (possibly more // depending on GrowthPolicy). // @param requiredCapacity > Capacity() // @return false and leave Capacity() unchanged if expansion failed, // which is guaranteed to happen if requiredCapacity > MaxCapacity(). bool Expand(size_t requiredCapacity); }; // allocate once and refuse subsequent expansion. template > class Storage_Fixed { NONCOPYABLE(Storage_Fixed); public: Storage_Fixed(size_t size) : maxCapacity(size) , storage(allocator.allocate(maxCapacity)) { } ~Storage_Fixed() { allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return maxCapacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t UNUSED(requiredCapacity)) { return false; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; }; // unlimited expansion by allocating larger storage and copying. // (basically equivalent to std::vector, although Growth_Exponential // is much more cache and allocator-friendly than the GCC STL) template > class Storage_Reallocate { NONCOPYABLE(Storage_Reallocate); public: Storage_Reallocate(size_t initialCapacity) : capacity(initialCapacity) , storage(allocator.allocate(initialCapacity)) { } ~Storage_Reallocate() { allocator.deallocate(storage, capacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return capacity; } size_t MaxCapacity() const { return std::numeric_limits::max(); } bool Expand(size_t requiredCapacity) { size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity)); void* newStorage = allocator.allocate(newCapacity); if(!newStorage) return false; memcpy(newStorage, storage, capacity); std::swap(capacity, newCapacity); std::swap(storage, newStorage); allocator.deallocate(newStorage, newCapacity); // free PREVIOUS storage return true; } private: Allocator allocator; size_t capacity; // must be initialized before storage void* storage; }; // expand up to the limit of the allocated address space by // committing physical memory. this avoids copying and // reduces wasted physical memory. template, class GrowthPolicy = Growth_Exponential<2,1> > class Storage_Commit { NONCOPYABLE(Storage_Commit); public: Storage_Commit(size_t maxCapacity_) : maxCapacity(Align(maxCapacity_)) // see Expand , storage(allocator.allocate(maxCapacity)) , capacity(0) { } ~Storage_Commit() { allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return capacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t requiredCapacity) { size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity)); // reduce the number of expensive commits by accurately // reflecting the actual capacity. this is safe because // we also round up maxCapacity. newCapacity = Align(newCapacity); if(newCapacity > maxCapacity) return false; if(!vm::Commit(Address()+capacity, newCapacity-capacity)) return false; capacity = newCapacity; return true; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; size_t capacity; }; // implicitly expand up to the limit of the allocated address space by // committing physical memory when a page is first accessed. // this is basically equivalent to Storage_Commit with Growth_Linear, // except that there is no need to call Expand. template > class Storage_AutoCommit { NONCOPYABLE(Storage_AutoCommit); public: Storage_AutoCommit(size_t maxCapacity_) : maxCapacity(Align(maxCapacity_)) // match user's expectation , storage(allocator.allocate(maxCapacity)) { vm::BeginOnDemandCommits(); } ~Storage_AutoCommit() { vm::EndOnDemandCommits(); allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return maxCapacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t UNUSED(requiredCapacity)) { return false; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; }; // reserve and return a pointer to space at the end of storage, // expanding it if need be. // @param end total number of previously reserved bytes; will be // increased by size if the allocation succeeds. // @param size [bytes] to reserve. // @return address of allocated space, or 0 if storage is full // and cannot expand any further. template static inline uintptr_t StorageAppend(Storage& storage, size_t& end, size_t size) { size_t newEnd = end + size; if(newEnd > storage.Capacity()) { if(!storage.Expand(newEnd)) // NB: may change storage.Address() return 0; } std::swap(end, newEnd); return storage.Address() + newEnd; } // invoke operator() on default-constructed instantiations of // Functor for reasonable combinations of Storage and their parameters. template class Functor> static void ForEachStorage() { Functor >()(); Functor > >()(); Functor > >()(); Functor > >()(); Functor, Growth_Linear<> > >()(); Functor, Growth_Exponential<> > >()(); Functor, Growth_Linear<> > >()(); Functor, Growth_Exponential<> > >()(); Functor >()(); } } // namespace Allocators #endif // #ifndef ALLOCATOR_POLICIES Index: ps/trunk/source/lib/allocators/arena.cpp =================================================================== --- ps/trunk/source/lib/allocators/arena.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/arena.cpp (revision 19899) @@ -1,75 +1,75 @@ -/* Copyright (c) 2011 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 * "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. */ /* * arena allocator (variable-size blocks, no deallocation). */ #include "precompiled.h" #include "lib/allocators/arena.h" namespace Allocators { template struct BasicArenaTest { void operator()() const { Arena a(100); const size_t initialSpace = a.RemainingBytes(); void* p = a.allocate(100); ENSURE(p != 0); ENSURE(a.Contains(uintptr_t(p))); ENSURE(a.RemainingBytes() == initialSpace-100); ENSURE(a.Contains(uintptr_t(p)+1)); ENSURE(a.Contains(uintptr_t(p)+99)); ENSURE(!a.Contains(uintptr_t(p)-1)); ENSURE(!a.Contains(uintptr_t(p)+100)); if(a.RemainingBytes() == 0) ENSURE(a.allocate(1) == 0); // full else ENSURE(a.allocate(1) != 0); // can still expand a.DeallocateAll(); ENSURE(!a.Contains(uintptr_t(p))); p = a.allocate(36); ENSURE(p != 0); ENSURE(a.Contains(uintptr_t(p))); ENSURE(a.RemainingBytes() == initialSpace-36); void* p2 = a.allocate(64); ENSURE(p2 != 0); ENSURE(a.Contains(uintptr_t(p2))); ENSURE(a.RemainingBytes() == initialSpace-36-64); ENSURE(p2 == (void*)(uintptr_t(p)+36)); if(a.RemainingBytes() == 0) ENSURE(a.allocate(1) == 0); // full else ENSURE(a.allocate(1) != 0); // can still expand } }; void TestArena() { ForEachStorage(); } } // namespace Allocators Index: ps/trunk/source/lib/allocators/arena.h =================================================================== --- ps/trunk/source/lib/allocators/arena.h (revision 19898) +++ ps/trunk/source/lib/allocators/arena.h (revision 19899) @@ -1,173 +1,173 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * arena allocator (variable-size blocks, no deallocation). */ #ifndef INCLUDED_ALLOCATORS_ARENA #define INCLUDED_ALLOCATORS_ARENA #include "lib/allocators/allocator_policies.h" namespace Allocators { /** * allocator design parameters: * - O(1) allocation; * - variable-size blocks; * - support for deallocating all objects; * - consecutive allocations are back-to-back; * - no extra alignment nor padding. **/ template > class Arena { NONCOPYABLE(Arena); public: Arena(size_t maxSize) : storage(maxSize) { DeallocateAll(); } size_t RemainingBytes() const { return storage.MaxCapacity() - end; } void* allocate(size_t size) { return (void*)StorageAppend(storage, end, size); } void deallocate(void* UNUSED(p), size_t UNUSED(size)) { // ignored } void DeallocateAll() { end = 0; } // @return whether the address lies within the previously allocated range. bool Contains(uintptr_t address) const { return (address - storage.Address()) < end; } private: Storage storage; size_t end; }; LIB_API void TestArena(); /** * allocator design parameters: * - grow dynamically with a fixed chunkSize * - for frequent allocations of size << chunkSize * - no reallocations, pointers remain valid **/ class DynamicArena { struct ArenaChunk { bool Available(size_t size) const { return size <= (capacity - end); } // Must check Available first or this may return an invalid address uintptr_t Allocate(size_t size) { uintptr_t ptr = storage + end; end += size; return ptr; } uintptr_t storage; size_t end; size_t capacity; ArenaChunk* next; }; NONCOPYABLE(DynamicArena); public: DynamicArena(size_t chunkSize) : chunkSize(chunkSize), head(NULL) { AllocateNewChunk(); } ~DynamicArena() { ArenaChunk* chunk = head; while (chunk != NULL) { ArenaChunk* next = chunk->next; free(chunk); chunk = next; } } void AllocateNewChunk() { // For efficiency, do a single allocation with the ArenaChunk and its storage ArenaChunk* next = head; head = (ArenaChunk*)malloc(sizeof(ArenaChunk) + chunkSize); ENSURE(head); head->storage = sizeof(ArenaChunk) + uintptr_t(head); head->end = 0; head->capacity = chunkSize; head->next = next; } void* allocate(size_t size) { if (size > chunkSize) { debug_warn(L"DynamicArena cannot allocate more than chunk size"); throw std::bad_alloc(); } else if (!head->Available(size)) AllocateNewChunk(); return (void*)head->Allocate(size); } void deallocate(void* UNUSED(p), size_t UNUSED(size)) { // ignored } private: const size_t chunkSize; ArenaChunk *head; }; } // namespace Allocators #endif // #ifndef INCLUDED_ALLOCATORS_ARENA Index: ps/trunk/source/lib/allocators/dynarray.cpp =================================================================== --- ps/trunk/source/lib/allocators/dynarray.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/dynarray.cpp (revision 19899) @@ -1,145 +1,145 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * dynamic (expandable) array */ #include "precompiled.h" #include "lib/allocators/dynarray.h" #include "lib/alignment.h" #include "lib/sysdep/vm.h" static Status validate_da(DynArray* da) { if(!da) WARN_RETURN(ERR::INVALID_POINTER); // u8* const base = da->base; const size_t max_size_pa = da->max_size_pa; const size_t cur_size = da->cur_size; const size_t pos = da->pos; // note: this happens if max_size == 0 // if(debug_IsPointerBogus(base)) // WARN_RETURN(ERR::_1); // note: don't check if base is page-aligned - // might not be true for 'wrapped' mem regions. if(!IsAligned(max_size_pa, pageSize)) WARN_RETURN(ERR::_3); if(cur_size > max_size_pa) WARN_RETURN(ERR::_4); if(pos > cur_size || pos > max_size_pa) WARN_RETURN(ERR::_5); return INFO::OK; } #define CHECK_DA(da) RETURN_STATUS_IF_ERR(validate_da(da)) Status da_alloc(DynArray* da, size_t max_size) { ENSURE(max_size != 0); const size_t max_size_pa = Align(max_size); u8* p = (u8*)vm::ReserveAddressSpace(max_size_pa); if(!p) return ERR::NO_MEM; // NOWARN (already done in vm) da->base = p; da->max_size_pa = max_size_pa; da->cur_size = 0; da->cur_size_pa = 0; da->pos = 0; CHECK_DA(da); return INFO::OK; } Status da_free(DynArray* da) { CHECK_DA(da); vm::ReleaseAddressSpace(da->base, da->max_size_pa); // wipe out the DynArray for safety memset(da, 0, sizeof(*da)); return INFO::OK; } Status da_set_size(DynArray* da, size_t new_size) { CHECK_DA(da); // determine how much to add/remove const size_t cur_size_pa = Align(da->cur_size); const size_t new_size_pa = Align(new_size); const ssize_t size_delta_pa = (ssize_t)new_size_pa - (ssize_t)cur_size_pa; // not enough memory to satisfy this expand request: abort. // note: do not complain - some allocators (e.g. file_cache) // legitimately use up all available space. if(new_size_pa > da->max_size_pa) return ERR::LIMIT; // NOWARN u8* end = da->base + cur_size_pa; bool ok = true; // expanding if(size_delta_pa > 0) { ok = vm::Commit(uintptr_t(end), size_delta_pa); if(!ok) debug_printf("Commit failed (%p %lld)\n", (void *)end, (long long)size_delta_pa); } // shrinking else if(size_delta_pa < 0) ok = vm::Decommit(uintptr_t(end+size_delta_pa), -size_delta_pa); // else: no change in page count, e.g. if going from size=1 to 2 // (we don't want mem_* to have to handle size=0) da->cur_size = new_size; da->cur_size_pa = new_size_pa; CHECK_DA(da); return ok? INFO::OK : ERR::FAIL; } Status da_reserve(DynArray* da, size_t size) { if(da->pos+size > da->cur_size_pa) RETURN_STATUS_IF_ERR(da_set_size(da, da->cur_size_pa+size)); da->cur_size = std::max(da->cur_size, da->pos+size); return INFO::OK; } Status da_append(DynArray* da, const void* data, size_t size) { RETURN_STATUS_IF_ERR(da_reserve(da, size)); memcpy(da->base+da->pos, data, size); da->pos += size; return INFO::OK; } Index: ps/trunk/source/lib/allocators/dynarray.h =================================================================== --- ps/trunk/source/lib/allocators/dynarray.h (revision 19898) +++ ps/trunk/source/lib/allocators/dynarray.h (revision 19899) @@ -1,107 +1,107 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * dynamic (expandable) array */ #ifndef INCLUDED_ALLOCATORS_DYNARRAY #define INCLUDED_ALLOCATORS_DYNARRAY #include "lib/posix/posix_mman.h" // PROT_* /** * provides a memory range that can be expanded but doesn't waste * physical memory or relocate itself. * * works by preallocating address space and committing as needed. * used as a building block for other allocators. **/ struct DynArray { u8* base; size_t max_size_pa; /// reserved size_t cur_size; /// committed size_t cur_size_pa; size_t pos; }; /** * ready the DynArray object for use. * * no virtual memory is actually committed until calls to da_set_size. * * @param da DynArray. * @param max_size size [bytes] of address space to reserve (*); * the DynArray can never expand beyond this. * (* rounded up to next page size multiple) * @return Status. **/ LIB_API Status da_alloc(DynArray* da, size_t max_size); /** * free all memory (address space + physical) that constitutes the * given array. * * use-after-free is impossible because the memory is unmapped. * * @param da DynArray* zeroed afterwards. * @return Status **/ LIB_API Status da_free(DynArray* da); /** * expand or shrink the array: changes the amount of currently committed * (i.e. usable) memory pages. * * @param da DynArray. * @param new_size target size (rounded up to next page multiple). * pages are added/removed until this is met. * @return Status. **/ LIB_API Status da_set_size(DynArray* da, size_t new_size); /** * Make sure at least \ bytes starting at da->pos are committed and * ready for use. * * @param da DynArray* * @param size Minimum amount to guarantee [bytes] * @return Status **/ LIB_API Status da_reserve(DynArray* da, size_t size); /** * "write" to array, i.e. copy from the given buffer. * * starts at offset DynArray.pos and advances this. * * @param da DynArray. * @param data_src source memory * @param size [bytes] to copy * @return Status. **/ LIB_API Status da_append(DynArray* da, const void* data_src, size_t size); #endif // #ifndef INCLUDED_ALLOCATORS_DYNARRAY Index: ps/trunk/source/lib/allocators/freelist.cpp =================================================================== --- ps/trunk/source/lib/allocators/freelist.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/freelist.cpp (revision 19899) @@ -1,31 +1,31 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/allocators/freelist.h" void* mem_freelist_Sentinel() { // sentinel storing its own address static void* storageForPrevPtr = &storageForPrevPtr; return &storageForPrevPtr; } Index: ps/trunk/source/lib/allocators/freelist.h =================================================================== --- ps/trunk/source/lib/allocators/freelist.h (revision 19898) +++ ps/trunk/source/lib/allocators/freelist.h (revision 19899) @@ -1,64 +1,64 @@ -/* Copyright (c) 2011 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 * "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. */ #ifndef INCLUDED_ALLOCATORS_FREELIST #define INCLUDED_ALLOCATORS_FREELIST // "freelist" is a pointer to the first unused element or a sentinel. // their memory holds a pointer to the previous element in the freelist // (or its own address in the case of sentinels to avoid branches) // // rationale for the function-based interface: a class encapsulating the // freelist pointer would force each header to include this header, // whereas this approach only requires a void* pointer and calling // mem_freelist_Sentinel from the implementation. // // these functions are inlined because allocation is sometimes time-critical. // @return the address of a sentinel element, suitable for initializing // a freelist pointer. subsequent mem_freelist_Detach on that freelist // will return 0. LIB_API void* mem_freelist_Sentinel(); static inline void mem_freelist_AddToFront(void*& freelist, void* el) { ASSERT(freelist != 0); ASSERT(el != 0); memcpy(el, &freelist, sizeof(void*)); freelist = el; } // @return 0 if the freelist is empty, else a pointer that had // previously been passed to mem_freelist_AddToFront. static inline void* mem_freelist_Detach(void*& freelist) { ASSERT(freelist != 0); void* prev_el; memcpy(&prev_el, freelist, sizeof(void*)); void* el = (freelist == prev_el)? 0 : freelist; freelist = prev_el; return el; } #endif // #ifndef INCLUDED_ALLOCATORS_FREELIST Index: ps/trunk/source/lib/allocators/headerless.cpp =================================================================== --- ps/trunk/source/lib/allocators/headerless.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/headerless.cpp (revision 19899) @@ -1,773 +1,773 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * (header-less) pool-based heap allocator */ #include "precompiled.h" #include "lib/allocators/headerless.h" #include "lib/bits.h" #include "lib/allocators/pool.h" static const bool performSanityChecks = true; // shared by Impl::Allocate and FreedBlock::Validate static bool IsValidSize(size_t size); //----------------------------------------------------------------------------- // this combines the boundary tags and link fields into one structure, // which is safer than direct pointer arithmetic. // // it is written to freed memory, which is fine because IsValidSize ensures // the allocations are large enough. class FreedBlock { friend class RangeList; // manipulates link fields directly public: // (required for RangeList::m_sentinel) FreedBlock() { } void Setup(uintptr_t id, size_t size) { m_magic = s_magic; m_size = size; m_id = id; } void Reset() { // clear all fields to prevent accidental reuse prev = next = 0; m_id = 0; m_size = ~size_t(0); m_magic = 0; } size_t Size() const { return m_size; } /** * @return whether this appears to be a FreedBlock instance with the * desired ID. for additional safety, also call Validate(). **/ bool IsFreedBlock(uintptr_t id) const { if(m_id != id) return false; if(m_magic != s_magic) return false; return true; } /** * warn if any invariant doesn't hold. **/ void Validate(uintptr_t id) const { if(!performSanityChecks) return; // note: RangeList::Validate implicitly checks the prev and next // fields by iterating over the list. // note: we can't check for prev != next because we're called for // footers as well, and they don't have valid pointers. ENSURE(IsValidSize(m_size)); ENSURE(IsFreedBlock(id)); } private: // note: the magic and ID fields are stored at both ends of this // class to increase the chance of detecting memory corruption. static const u64 s_magic = 0xFF55AA00BBCCDDEEull; u64 m_magic; FreedBlock* prev; FreedBlock* next; // size [bytes] of the entire memory block, including header and footer size_t m_size; // this differentiates between headers and footers. uintptr_t m_id; }; static bool IsValidSize(size_t size) { // note: we disallow the questionable practice of zero-byte allocations // because they may be indicative of bugs. if(size < HeaderlessAllocator::minAllocationSize) return false; if(size % HeaderlessAllocator::allocationAlignment) return false; return true; } //----------------------------------------------------------------------------- // freelists //----------------------------------------------------------------------------- // policy: address-ordered good fit // mechanism: segregated range lists of power-of-two size classes struct AddressOrder { static bool ShouldInsertBefore(FreedBlock* current, FreedBlock* successor) { return current < successor; } }; // "range list" is a freelist of similarly-sized blocks. class RangeList { public: RangeList() { Reset(); } void Reset() { m_sentinel.prev = &m_sentinel; m_sentinel.next = &m_sentinel; m_freeBlocks = 0; m_freeBytes = 0; } template void Insert(FreedBlock* freedBlock) { // find freedBlock before which to insert FreedBlock* successor; for(successor = m_sentinel.next; successor != &m_sentinel; successor = successor->next) { if(InsertPolicy::ShouldInsertBefore(freedBlock, successor)) break; } freedBlock->prev = successor->prev; freedBlock->next = successor; successor->prev->next = freedBlock; successor->prev = freedBlock; m_freeBlocks++; m_freeBytes += freedBlock->Size(); } /** * @return the first freed block of size >= minSize or 0 if none exists. **/ FreedBlock* Find(size_t minSize) { for(FreedBlock* freedBlock = m_sentinel.next; freedBlock != &m_sentinel; freedBlock = freedBlock->next) { if(freedBlock->Size() >= minSize) return freedBlock; } // none found, so average block size is less than the desired size ENSURE(m_freeBytes/m_freeBlocks < minSize); return 0; } void Remove(FreedBlock* freedBlock) { freedBlock->next->prev = freedBlock->prev; freedBlock->prev->next = freedBlock->next; ENSURE(m_freeBlocks != 0); ENSURE(m_freeBytes >= freedBlock->Size()); m_freeBlocks--; m_freeBytes -= freedBlock->Size(); } void Validate(uintptr_t id) const { if(!performSanityChecks) return; size_t freeBlocks = 0, freeBytes = 0; for(FreedBlock* freedBlock = m_sentinel.next; freedBlock != &m_sentinel; freedBlock = freedBlock->next) { freedBlock->Validate(id); freeBlocks++; freeBytes += freedBlock->Size(); } for(FreedBlock* freedBlock = m_sentinel.prev; freedBlock != &m_sentinel; freedBlock = freedBlock->prev) { freedBlock->Validate(id); freeBlocks++; freeBytes += freedBlock->Size(); } // our idea of the number and size of free blocks is correct ENSURE(freeBlocks == m_freeBlocks*2 && freeBytes == m_freeBytes*2); // if empty, state must be as established by Reset ENSURE(!IsEmpty() || (m_sentinel.next == &m_sentinel && m_sentinel.prev == &m_sentinel)); } bool IsEmpty() const { return (m_freeBlocks == 0); } size_t FreeBlocks() const { return m_freeBlocks; } size_t FreeBytes() const { return m_freeBytes; } private: // a sentinel simplifies Insert and Remove. we store it here instead of // in a separate array to improve locality (it is actually accessed). mutable FreedBlock m_sentinel; size_t m_freeBlocks; size_t m_freeBytes; }; //----------------------------------------------------------------------------- class SegregatedRangeLists { public: SegregatedRangeLists() { Reset(); } void Reset() { m_bitmap = 0; for(size_t i = 0; i < numRangeLists; i++) m_rangeLists[i].Reset(); } void Insert(FreedBlock* freedBlock) { const size_t sizeClass = SizeClass(freedBlock->Size()); m_rangeLists[sizeClass].Insert(freedBlock); m_bitmap |= Bit(sizeClass); } /** * @return the first freed block of size >= minSize or 0 if none exists. **/ FreedBlock* Find(size_t minSize) { // iterate over all large enough, non-empty size classes // (zero overhead for empty size classes) const size_t minSizeClass = SizeClass(minSize); size_t sizeClassBits = m_bitmap & ((~size_t(0)) << minSizeClass); while(sizeClassBits) { const size_t size = ValueOfLeastSignificantOneBit(sizeClassBits); sizeClassBits &= ~size; // remove from sizeClassBits const size_t sizeClass = SizeClass(size); FreedBlock* freedBlock = m_rangeLists[sizeClass].Find(minSize); if(freedBlock) return freedBlock; } // apparently all classes above minSizeClass are empty, // or the above would have succeeded. ENSURE(m_bitmap < Bit(minSizeClass+1)); return 0; } void Remove(FreedBlock* freedBlock) { const size_t sizeClass = SizeClass(freedBlock->Size()); m_rangeLists[sizeClass].Remove(freedBlock); // (masking with !IsEmpty() << sizeClass would probably be faster) if(m_rangeLists[sizeClass].IsEmpty()) m_bitmap &= ~Bit(sizeClass); } void Validate(uintptr_t id) const { for(size_t i = 0; i < numRangeLists; i++) { m_rangeLists[i].Validate(id); // both bitmap and list must agree on whether they are empty ENSURE(((m_bitmap & Bit(i)) == 0) == m_rangeLists[i].IsEmpty()); } } size_t FreeBlocks() const { size_t freeBlocks = 0; for(size_t i = 0; i < numRangeLists; i++) freeBlocks += m_rangeLists[i].FreeBlocks(); return freeBlocks; } size_t FreeBytes() const { size_t freeBytes = 0; for(size_t i = 0; i < numRangeLists; i++) freeBytes += m_rangeLists[i].FreeBytes(); return freeBytes; } private: /** * @return "size class" of a given size. * class i > 0 contains blocks of size (2**(i-1), 2**i]. **/ static size_t SizeClass(size_t size) { return ceil_log2(size); } static uintptr_t ValueOfLeastSignificantOneBit(uintptr_t x) { return (x & -(intptr_t)x); } // segregated, i.e. one list per size class. static const size_t numRangeLists = sizeof(uintptr_t)*CHAR_BIT; RangeList m_rangeLists[numRangeLists]; // bit i set <==> size class i's freelist is not empty. // this allows finding a non-empty list in O(1). uintptr_t m_bitmap; }; //----------------------------------------------------------------------------- // coalescing //----------------------------------------------------------------------------- // policy: immediately coalesce // mechanism: boundary tags // note: the id and magic values are all that differentiates tags from // user data. this isn't 100% reliable, but as with headers, we don't want // to insert extra boundary tags into the allocated memory. // note: footers consist of Tag{magic, ID, size}, while headers also // need prev/next pointers. this could comfortably fit in 64 bytes, // but we don't want to inherit headers from a base class because its // prev/next pointers should reside between the magic and ID fields. // maintaining separate FreedBlock and Footer classes is also undesirable; // we prefer to use FreedBlock for both, which increases the minimum // allocation size to 64 + allocationAlignment, e.g. 128. // that's not a problem because the allocator is designed for // returning pages or IO buffers (4..256 KB). cassert(HeaderlessAllocator::minAllocationSize >= 2*sizeof(FreedBlock)); class BoundaryTagManager { public: BoundaryTagManager() : m_freeBlocks(0), m_freeBytes(0) { } FreedBlock* WriteTags(u8* p, size_t size) { FreedBlock* freedBlock = (FreedBlock*)p; freedBlock->Setup(s_headerId, size); Footer(freedBlock)->Setup(s_footerId, size); m_freeBlocks++; m_freeBytes += size; Validate(freedBlock); return freedBlock; } void RemoveTags(FreedBlock* freedBlock) { Validate(freedBlock); ENSURE(m_freeBlocks != 0); ENSURE(m_freeBytes >= freedBlock->Size()); m_freeBlocks--; m_freeBytes -= freedBlock->Size(); FreedBlock* footer = Footer(freedBlock); freedBlock->Reset(); footer->Reset(); } FreedBlock* PrecedingBlock(u8* p, u8* beginningOfPool) const { if(p == beginningOfPool) // avoid accessing invalid memory return 0; FreedBlock* precedingBlock; { FreedBlock* const footer = (FreedBlock*)(p - sizeof(FreedBlock)); if(!footer->IsFreedBlock(s_footerId)) return 0; footer->Validate(s_footerId); precedingBlock = (FreedBlock*)(p - footer->Size()); } Validate(precedingBlock); return precedingBlock; } FreedBlock* FollowingBlock(u8* p, size_t size, u8* endOfPool) const { if(p+size == endOfPool) // avoid accessing invalid memory return 0; FreedBlock* const followingBlock = (FreedBlock*)(p + size); if(!followingBlock->IsFreedBlock(s_headerId)) return 0; Validate(followingBlock); return followingBlock; } size_t FreeBlocks() const { return m_freeBlocks; } size_t FreeBytes() const { return m_freeBytes; } // (generated via GUID) static const uintptr_t s_headerId = 0x111E8E6Fu; static const uintptr_t s_footerId = 0x4D745342u; private: void Validate(FreedBlock* freedBlock) const { if(!performSanityChecks) return; // the existence of freedBlock means our bookkeeping better have // records of at least that much memory. ENSURE(m_freeBlocks != 0); ENSURE(m_freeBytes >= freedBlock->Size()); freedBlock->Validate(s_headerId); Footer(freedBlock)->Validate(s_footerId); } static FreedBlock* Footer(FreedBlock* freedBlock) { u8* const p = (u8*)freedBlock; return (FreedBlock*)(p + freedBlock->Size() - sizeof(FreedBlock)); } size_t m_freeBlocks; size_t m_freeBytes; }; //----------------------------------------------------------------------------- // stats //----------------------------------------------------------------------------- class Stats { public: void OnReset() { if(!performSanityChecks) return; m_totalAllocatedBlocks = m_totalAllocatedBytes = 0; m_totalDeallocatedBlocks = m_totalDeallocatedBytes = 0; m_currentExtantBlocks = m_currentExtantBytes = 0; m_currentFreeBlocks = m_currentFreeBytes = 0; } void OnAllocate(size_t size) { if(!performSanityChecks) return; m_totalAllocatedBlocks++; m_totalAllocatedBytes += size; m_currentExtantBlocks++; m_currentExtantBytes += size; } void OnDeallocate(size_t size) { if(!performSanityChecks) return; m_totalDeallocatedBlocks++; m_totalDeallocatedBytes += size; ENSURE(m_totalDeallocatedBlocks <= m_totalAllocatedBlocks); ENSURE(m_totalDeallocatedBytes <= m_totalAllocatedBytes); ENSURE(m_currentExtantBlocks != 0); ENSURE(m_currentExtantBytes >= size); m_currentExtantBlocks--; m_currentExtantBytes -= size; } void OnAddToFreelist(size_t size) { m_currentFreeBlocks++; m_currentFreeBytes += size; } void OnRemoveFromFreelist(size_t size) { if(!performSanityChecks) return; ENSURE(m_currentFreeBlocks != 0); ENSURE(m_currentFreeBytes >= size); m_currentFreeBlocks--; m_currentFreeBytes -= size; } void Validate() const { if(!performSanityChecks) return; ENSURE(m_totalDeallocatedBlocks <= m_totalAllocatedBlocks); ENSURE(m_totalDeallocatedBytes <= m_totalAllocatedBytes); ENSURE(m_currentExtantBlocks == m_totalAllocatedBlocks-m_totalDeallocatedBlocks); ENSURE(m_currentExtantBytes == m_totalAllocatedBytes-m_totalDeallocatedBytes); } size_t FreeBlocks() const { return m_currentFreeBlocks; } size_t FreeBytes() const { return m_currentFreeBytes; } private: u64 m_totalAllocatedBlocks, m_totalAllocatedBytes; u64 m_totalDeallocatedBlocks, m_totalDeallocatedBytes; u64 m_currentExtantBlocks, m_currentExtantBytes; u64 m_currentFreeBlocks, m_currentFreeBytes; }; //----------------------------------------------------------------------------- // HeaderlessAllocator::Impl //----------------------------------------------------------------------------- static void AssertEqual(size_t x1, size_t x2, size_t x3) { ENSURE(x1 == x2 && x2 == x3); } class HeaderlessAllocator::Impl { public: Impl(size_t poolSize) { (void)pool_create(&m_pool, poolSize, 0); Reset(); } ~Impl() { Validate(); (void)pool_destroy(&m_pool); } void Reset() { pool_free_all(&m_pool); m_segregatedRangeLists.Reset(); m_stats.OnReset(); Validate(); } NOTHROW_DEFINE void* Allocate(size_t size) { ENSURE(IsValidSize(size)); Validate(); void* p = TakeAndSplitFreeBlock(size); if(!p) { p = pool_alloc(&m_pool, size); if(!p) // both failed; don't throw bad_alloc because return 0; // this often happens with the file cache. } // (NB: we must not update the statistics if allocation failed) m_stats.OnAllocate(size); Validate(); ENSURE((uintptr_t)p % allocationAlignment == 0); return p; } void Deallocate(u8* p, size_t size) { ENSURE((uintptr_t)p % allocationAlignment == 0); ENSURE(IsValidSize(size)); ENSURE(pool_contains(&m_pool, p)); ENSURE(pool_contains(&m_pool, p+size-1)); Validate(); m_stats.OnDeallocate(size); Coalesce(p, size); AddToFreelist(p, size); Validate(); } void Validate() const { if(!performSanityChecks) return; m_segregatedRangeLists.Validate(BoundaryTagManager::s_headerId); m_stats.Validate(); AssertEqual(m_stats.FreeBlocks(), m_segregatedRangeLists.FreeBlocks(), m_boundaryTagManager.FreeBlocks()); AssertEqual(m_stats.FreeBytes(), m_segregatedRangeLists.FreeBytes(), m_boundaryTagManager.FreeBytes()); } private: void AddToFreelist(u8* p, size_t size) { FreedBlock* freedBlock = m_boundaryTagManager.WriteTags(p, size); m_segregatedRangeLists.Insert(freedBlock); m_stats.OnAddToFreelist(size); } void RemoveFromFreelist(FreedBlock* freedBlock) { m_stats.OnRemoveFromFreelist(freedBlock->Size()); m_segregatedRangeLists.Remove(freedBlock); m_boundaryTagManager.RemoveTags(freedBlock); } /** * expand a block by coalescing it with its free neighbor(s). **/ void Coalesce(u8*& p, size_t& size) { { FreedBlock* precedingBlock = m_boundaryTagManager.PrecedingBlock(p, m_pool.da.base); if(precedingBlock) { p -= precedingBlock->Size(); size += precedingBlock->Size(); RemoveFromFreelist(precedingBlock); } } { FreedBlock* followingBlock = m_boundaryTagManager.FollowingBlock(p, size, m_pool.da.base+m_pool.da.pos); if(followingBlock) { size += followingBlock->Size(); RemoveFromFreelist(followingBlock); } } } void* TakeAndSplitFreeBlock(size_t size) { u8* p; size_t leftoverSize = 0; { FreedBlock* freedBlock = m_segregatedRangeLists.Find(size); if(!freedBlock) return 0; p = (u8*)freedBlock; leftoverSize = freedBlock->Size() - size; RemoveFromFreelist(freedBlock); } if(IsValidSize(leftoverSize)) AddToFreelist(p+size, leftoverSize); return p; } Pool m_pool; SegregatedRangeLists m_segregatedRangeLists; BoundaryTagManager m_boundaryTagManager; Stats m_stats; }; //----------------------------------------------------------------------------- HeaderlessAllocator::HeaderlessAllocator(size_t poolSize) : impl(new Impl(poolSize)) { } void HeaderlessAllocator::Reset() { return impl->Reset(); } NOTHROW_DEFINE void* HeaderlessAllocator::Allocate(size_t size) { return impl->Allocate(size); } void HeaderlessAllocator::Deallocate(void* p, size_t size) { return impl->Deallocate((u8*)p, size); } void HeaderlessAllocator::Validate() const { return impl->Validate(); } Index: ps/trunk/source/lib/allocators/headerless.h =================================================================== --- ps/trunk/source/lib/allocators/headerless.h (revision 19898) +++ ps/trunk/source/lib/allocators/headerless.h (revision 19899) @@ -1,99 +1,99 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * (header-less) pool-based heap allocator */ #ifndef INCLUDED_ALLOCATORS_HEADERLESS #define INCLUDED_ALLOCATORS_HEADERLESS /** * (header-less) pool-based heap allocator * provides Allocate and Deallocate without requiring in-band headers; * this is useful when allocating page-aligned I/O buffers * (headers would waste an entire page per buffer) * * policy: * - allocation: first exhaust the freelist, then allocate more * - freelist: address-ordered good fit, always split blocks * - coalescing: immediate * mechanism: * - coalescing: boundary tags in freed memory with distinct bit patterns * - freelist: segregated range lists of power-of-two size classes * * note: this module basically implements a (rather complex) freelist and * could be made independent of the Pool allocation scheme. however, reading * neighboring boundary tags may cause segmentation violations; knowing the * bounds of valid committed memory (i.e. Pool extents) avoids this. **/ class HeaderlessAllocator { public: // allocators must 'naturally' align pointers, i.e. ensure they are // multiples of the largest native type (currently __m128). // since there are no headers, we can guarantee alignment by // requiring sizes to be multiples of allocationAlignment. static const size_t allocationAlignment = 16; // allocations must be large enough to hold our boundary tags // when freed. (see rationale above BoundaryTagManager) static const size_t minAllocationSize = 128; /** * @param poolSize maximum amount of memory that can be allocated. * this much virtual address space is reserved up-front (see Pool). **/ HeaderlessAllocator(size_t poolSize); /** * restore the original state (as if newly constructed). * this includes reclaiming all extant allocations. **/ void Reset(); /** * @param size [bytes] (= minAllocationSize + i*allocationAlignment). * (this allocator is designed for requests on the order of several KiB) * @return allocated memory or 0 if the pool is too fragmented or full. **/ NOTHROW_DECLARE void* Allocate(size_t size); /** * deallocate memory. * @param p must be exactly as returned by Allocate (in particular, * evenly divisible by allocationAlignment) * @param size must be exactly as specified to Allocate. **/ void Deallocate(void* p, size_t size); /** * perform sanity checks; ensure allocator state is consistent. **/ void Validate() const; private: class Impl; shared_ptr impl; }; #endif // #ifndef INCLUDED_ALLOCATORS_HEADERLESS Index: ps/trunk/source/lib/allocators/overrun_protector.h =================================================================== --- ps/trunk/source/lib/allocators/overrun_protector.h (revision 19898) +++ ps/trunk/source/lib/allocators/overrun_protector.h (revision 19899) @@ -1,101 +1,101 @@ -/* Copyright (c) 2011 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 * "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. */ #ifndef INCLUDED_ALLOCATORS_OVERRUN_PROTECTOR #define INCLUDED_ALLOCATORS_OVERRUN_PROTECTOR #include "lib/config2.h" // CONFIG2_ALLOCATORS_OVERRUN_PROTECTION #include "lib/sysdep/vm.h" /** OverrunProtector wraps an arbitrary object in isolated page(s) and can detect inadvertent writes to it. this is useful for tracking down memory overruns. the basic idea is to require users to request access to the object and notify us when done; memory access permission is temporarily granted. (similar in principle to Software Transaction Memory). since this is quite slow, the protection is disabled unless CONFIG2_ALLOCATORS_OVERRUN_PROTECTION == 1; this avoids having to remove the wrapper code in release builds and re-write when looking for overruns. example usage: OverrunProtector\ wrapper; .. T* p = wrapper.get(); // unlock, make ready for use if(!p) // wrapper's one-time alloc of a T- abort(); // instance had failed - can't continue. DoSomethingWith(p); // (read/write access) wrapper.lock(); // disallow further access until next .get() .. **/ template class OverrunProtector { NONCOPYABLE(OverrunProtector); // const member public: OverrunProtector() #if CONFIG2_ALLOCATORS_OVERRUN_PROTECTION : object(new(vm::Allocate(sizeof(T))) T()) #else : object(new T()) #endif { lock(); } ~OverrunProtector() { unlock(); #if CONFIG2_ALLOCATORS_OVERRUN_PROTECTION object->~T(); // call dtor (since we used placement new) vm::Free(object, sizeof(T)); #else delete object; #endif } T* get() const { unlock(); return object; } void lock() const { #if CONFIG2_ALLOCATORS_OVERRUN_PROTECTION vm::Protect(object, sizeof(T), PROT_NONE); #endif } private: void unlock() const { #if CONFIG2_ALLOCATORS_OVERRUN_PROTECTION vm::Protect(object, sizeof(T), PROT_READ|PROT_WRITE); #endif } T* const object; }; #endif // #ifndef INCLUDED_ALLOCATORS_OVERRUN_PROTECTOR Index: ps/trunk/source/lib/allocators/page_aligned.cpp =================================================================== --- ps/trunk/source/lib/allocators/page_aligned.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/page_aligned.cpp (revision 19899) @@ -1,112 +1,112 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/allocators/page_aligned.h" #include "lib/alignment.h" #include "lib/sysdep/cpu.h" // cpu_CAS //----------------------------------------------------------------------------- static inline Status StatusFromMap(void* ret) { if(ret != MAP_FAILED) return INFO::OK; WARN_RETURN(StatusFromErrno()); } // "anonymous" effectively means mapping /dev/zero, but is more efficient. // MAP_ANONYMOUS is not in SUSv3, but is a very common extension. // unfortunately, MacOS X only defines MAP_ANON, which Solaris says is // deprecated. workaround there: define MAP_ANONYMOUS in terms of MAP_ANON. #ifndef MAP_ANONYMOUS # define MAP_ANONYMOUS MAP_ANON #endif static const int mmap_flags = MAP_PRIVATE|MAP_ANONYMOUS; Status mem_Reserve(size_t size, u8** pp) { errno = 0; void* ret = mmap(0, size, PROT_NONE, mmap_flags|MAP_NORESERVE, -1, 0); *pp = (u8*)ret; return StatusFromMap(ret); } Status mem_Release(u8* p, size_t size) { errno = 0; if(munmap(p, size) != 0) WARN_RETURN(StatusFromErrno()); return 0; } Status mem_Commit(u8* p, size_t size, int prot) { // avoid misinterpretation by mmap. if(prot == PROT_NONE) WARN_RETURN(ERR::INVALID_PARAM); errno = 0; void* ret = mmap(p, size, prot, mmap_flags|MAP_FIXED, -1, 0); return StatusFromMap(ret); } Status mem_Decommit(u8* p, size_t size) { errno = 0; void* ret = mmap(p, size, PROT_NONE, mmap_flags|MAP_NORESERVE|MAP_FIXED, -1, 0); return StatusFromMap(ret); } Status mem_Protect(u8* p, size_t size, int prot) { errno = 0; if(mprotect(p, size, prot) != 0) WARN_RETURN(StatusFromErrno()); return 0; } //----------------------------------------------------------------------------- void* page_aligned_alloc(size_t size) { const size_t alignedSize = Align(size); u8* p = 0; RETURN_0_IF_ERR(mem_Reserve(alignedSize, &p)); RETURN_0_IF_ERR(mem_Commit(p, alignedSize, PROT_READ|PROT_WRITE)); return p; } void page_aligned_free(void* p, size_t size) { if(!p) return; ENSURE(IsAligned(p, pageSize)); const size_t alignedSize = Align(size); (void)mem_Release((u8*)p, alignedSize); } Index: ps/trunk/source/lib/allocators/page_aligned.h =================================================================== --- ps/trunk/source/lib/allocators/page_aligned.h (revision 19898) +++ ps/trunk/source/lib/allocators/page_aligned.h (revision 19899) @@ -1,62 +1,62 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_ALLOCATORS_PAGE_ALIGNED #define INCLUDED_ALLOCATORS_PAGE_ALIGNED #include "lib/posix/posix_mman.h" // PROT_* // very thin wrapper on top of sys/mman.h that makes the intent more obvious // (its commit/decommit semantics are difficult to tell apart) LIB_API Status mem_Reserve(size_t size, u8** pp); LIB_API Status mem_Release(u8* p, size_t size); LIB_API Status mem_Commit(u8* p, size_t size, int prot); LIB_API Status mem_Decommit(u8* p, size_t size); LIB_API Status mem_Protect(u8* p, size_t size, int prot); /** * allocate memory aligned to the system page size. * * this is useful for file_cache_alloc, which uses this allocator to * get sector-aligned (hopefully; see sys_max_sector_size) IO buffers. * * note that this allocator is stateless and very little error checking * can be performed. * * the memory is initially writable and you can use mprotect to set other * access permissions if desired. * * @param unaligned_size minimum size [bytes] to allocate. * @return page-aligned and -padded memory or 0 on error / out of memory. **/ LIB_API void* page_aligned_alloc(size_t unaligned_size); /** * free a previously allocated page-aligned region. * * @param p Exact value returned from page_aligned_alloc * @param unaligned_size Exact value passed to page_aligned_alloc **/ LIB_API void page_aligned_free(void* p, size_t unaligned_size); #endif // #ifndef INCLUDED_ALLOCATORS_PAGE_ALIGNED Index: ps/trunk/source/lib/allocators/pool.cpp =================================================================== --- ps/trunk/source/lib/allocators/pool.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/pool.cpp (revision 19899) @@ -1,177 +1,177 @@ -/* Copyright (c) 2011 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 * "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. */ /* * pool allocator */ #include "precompiled.h" #include "lib/allocators/pool.h" #include "lib/alignment.h" #include "lib/allocators/freelist.h" #include "lib/allocators/allocator_adapters.h" #include "lib/timer.h" namespace Allocators { template struct BasicPoolTest { void operator()() const { Pool p(100); const size_t initialSpace = p.RemainingObjects(); double* p1 = p.Allocate(); ENSURE(p1 != 0); ENSURE(p.Contains(uintptr_t(p1))); ENSURE(p.RemainingObjects() == initialSpace-1); ENSURE(p.Contains(uintptr_t(p1)+1)); ENSURE(p.Contains(uintptr_t(p1)+sizeof(double)-1)); ENSURE(!p.Contains(uintptr_t(p1)-1)); ENSURE(!p.Contains(uintptr_t(p1)+sizeof(double))); if(p.RemainingObjects() == 0) ENSURE(p.Allocate() == 0); // full else ENSURE(p.Allocate() != 0); // can still expand p.DeallocateAll(); ENSURE(!p.Contains(uintptr_t(p1))); p1 = p.Allocate(); ENSURE(p1 != 0); ENSURE(p.Contains(uintptr_t(p1))); ENSURE(p.RemainingObjects() == initialSpace-1); double* p2 = p.Allocate(); ENSURE(p2 != 0); ENSURE(p.Contains(uintptr_t(p2))); ENSURE(p.RemainingObjects() == initialSpace-2); ENSURE(p2 == (double*)(uintptr_t(p1)+sizeof(double))); if(p.RemainingObjects() == 0) ENSURE(p.Allocate() == 0); // full else ENSURE(p.Allocate() != 0); // can still expand } }; void TestPool() { ForEachStorage(); } } // namespace Allocators TIMER_ADD_CLIENT(tc_pool_alloc); Status pool_create(Pool* p, size_t max_size, size_t el_size) { if(el_size == POOL_VARIABLE_ALLOCS) p->el_size = 0; else p->el_size = Align(el_size); p->freelist = mem_freelist_Sentinel(); RETURN_STATUS_IF_ERR(da_alloc(&p->da, max_size)); return INFO::OK; } Status pool_destroy(Pool* p) { // don't be picky and complain if the freelist isn't empty; // we don't care since it's all part of the da anyway. // however, zero it to prevent further allocs from succeeding. p->freelist = mem_freelist_Sentinel(); return da_free(&p->da); } bool pool_contains(const Pool* p, void* el) { // outside of our range if(!(p->da.base <= el && el < p->da.base+p->da.pos)) return false; // sanity check: it should be aligned (if pool has fixed-size elements) if(p->el_size) ENSURE((uintptr_t)((u8*)el - p->da.base) % p->el_size == 0); return true; } void* pool_alloc(Pool* p, size_t size) { TIMER_ACCRUE(tc_pool_alloc); // if pool allows variable sizes, go with the size parameter, // otherwise the pool el_size setting. const size_t el_size = p->el_size? p->el_size : Align(size); ASSERT(el_size != 0); // note: freelist is always empty in pools with variable-sized elements // because they disallow pool_free. void* el = mem_freelist_Detach(p->freelist); if(!el) // freelist empty, need to allocate a new entry { // expand, if necessary if(da_reserve(&p->da, el_size) < 0) return 0; el = p->da.base + p->da.pos; p->da.pos += el_size; } ASSERT(pool_contains(p, el)); // paranoia return el; } void pool_free(Pool* p, void* el) { // only allowed to free items if we were initialized with // fixed el_size. (this avoids having to pass el_size here and // check if requested_size matches that when allocating) if(p->el_size == 0) { DEBUG_WARN_ERR(ERR::LOGIC); // cannot free variable-size items return; } if(pool_contains(p, el)) mem_freelist_AddToFront(p->freelist, el); else DEBUG_WARN_ERR(ERR::LOGIC); // invalid pointer (not in pool) } void pool_free_all(Pool* p) { p->freelist = mem_freelist_Sentinel(); // must be reset before da_set_size or CHECK_DA will complain. p->da.pos = 0; da_set_size(&p->da, 0); } size_t pool_committed(Pool* p) { return p->da.cur_size; } Index: ps/trunk/source/lib/allocators/pool.h =================================================================== --- ps/trunk/source/lib/allocators/pool.h (revision 19898) +++ ps/trunk/source/lib/allocators/pool.h (revision 19899) @@ -1,220 +1,220 @@ -/* Copyright (c) 2011 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 * "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. */ /* * pool allocator (fixed-size blocks, freelist). */ #ifndef INCLUDED_ALLOCATORS_POOL #define INCLUDED_ALLOCATORS_POOL #include "lib/bits.h" // ROUND_UP #include "lib/allocators/allocator_policies.h" namespace Allocators { /** * allocator design parameters: * - O(1) allocation and deallocation; * - fixed-size objects; * - support for deallocating all objects; * - consecutive allocations are back-to-back; * - objects are aligned to the pointer size. **/ template > class Pool { NONCOPYABLE(Pool); public: // (must round up because freelist stores pointers inside objects) static const size_t objectSize = ROUND_UP(sizeof(T), sizeof(intptr_t)); Pool(size_t maxObjects) : storage(maxObjects*objectSize) { DeallocateAll(); } size_t RemainingObjects() { return (storage.MaxCapacity() - end) / objectSize; } T* Allocate() { void* p = mem_freelist_Detach(freelist); if(p) { ASSERT(Contains((uintptr_t)p)); return (T*)p; } return (T*)StorageAppend(storage, end, objectSize); } void Deallocate(T* p) { ASSERT(Contains((uintptr_t)p)); mem_freelist_AddToFront(freelist, p); } void DeallocateAll() { freelist = mem_freelist_Sentinel(); end = 0; } // @return whether the address lies within the previously allocated range. bool Contains(uintptr_t address) const { return (address - storage.Address()) < end; } private: Storage storage; size_t end; void* freelist; }; LIB_API void TestPool(); } // namespace Allocators #include "lib/allocators/dynarray.h" /** * allocator design parameters: * - O(1) alloc and free; * - either fixed- or variable-sized blocks; * - doesn't preallocate the entire pool; * - returns sequential addresses. * * opaque! do not read/write any fields! **/ struct Pool { DynArray da; /** * size of elements. = 0 if pool set up for variable-sized * elements, otherwise rounded up to pool alignment. **/ size_t el_size; /** * pointer to freelist (opaque); see freelist_*. * never used (remains 0) if elements are of variable size. **/ void* freelist; }; /** * pass as pool_create's \ param to indicate variable-sized allocs * are required (see below). **/ const size_t POOL_VARIABLE_ALLOCS = ~(size_t)0u; /** * Ready Pool for use. * * @param p Pool* * @param max_size Max size [bytes] of the Pool; this much * (rounded up to next page multiple) virtual address space is reserved. * no virtual memory is actually committed until calls to pool_alloc. * @param el_size Number of bytes that will be returned by each * pool_alloc (whose size parameter is then ignored). Can be 0 to * allow variable-sized allocations, but pool_free is then unusable. * @return Status **/ LIB_API Status pool_create(Pool* p, size_t max_size, size_t el_size); /** * free all memory (address space + physical) that constitutes the * given Pool. * * future alloc and free calls on this pool will fail. * continued use of the allocated memory (*) is * impossible because it is marked not-present via MMU. * (* no matter if in freelist or unused or "allocated" to user) * * @param p Pool* * @return Status. **/ LIB_API Status pool_destroy(Pool* p); /** * indicate whether a pointer was allocated from the given pool. * * this is useful for callers that use several types of allocators. * * @param p Pool* * @param el * @return bool. **/ LIB_API bool pool_contains(const Pool* p, void* el); /** * Dole out memory from the pool. * exhausts the freelist before returning new entries to improve locality. * * @param p Pool* * @param size bytes to allocate; ignored if pool_create's el_size was not 0. * @return allocated memory, or 0 if the Pool would have to be expanded and * there isn't enough memory to do so. **/ LIB_API void* pool_alloc(Pool* p, size_t size); /** * Make a fixed-size element available for reuse in the given Pool. * * this is not allowed if the Pool was created for variable-size elements. * rationale: avoids having to pass el_size here and compare with size when * allocating; also prevents fragmentation and leaking memory. * * @param p Pool* * @param el Element returned by pool_alloc. **/ LIB_API void pool_free(Pool* p, void* el); /** * "free" all user allocations that ensued from the given Pool. * * this resets it as if freshly pool_create-d, but doesn't release the * underlying reserved virtual memory. * * @param p Pool* **/ LIB_API void pool_free_all(Pool* p); /** * Return the number of bytes committed in the pool's backing array. * * This is roughly the number of bytes allocated in this pool plus the * unused freelist entries. * * @param p Pool* * @return number of bytes **/ LIB_API size_t pool_committed(Pool* p); #endif // #ifndef INCLUDED_ALLOCATORS_POOL Index: ps/trunk/source/lib/allocators/shared_ptr.cpp =================================================================== --- ps/trunk/source/lib/allocators/shared_ptr.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/shared_ptr.cpp (revision 19899) @@ -1,65 +1,65 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/allocators/shared_ptr.h" #include "lib/allocators/allocator_checker.h" #ifndef NDEBUG static AllocatorChecker s_allocatorChecker; #endif class CheckedArrayDeleter { public: CheckedArrayDeleter(size_t size) : m_size(size) { } void operator()(u8* p) { ENSURE(m_size != 0); #ifndef NDEBUG s_allocatorChecker.OnDeallocate(p, m_size); #endif delete[] p; m_size = 0; } private: size_t m_size; }; shared_ptr Allocate(size_t size) { ENSURE(size != 0); u8* p = new u8[size]; #ifndef NDEBUG s_allocatorChecker.OnAllocate(p, size); #endif return shared_ptr(p, CheckedArrayDeleter(size)); } Index: ps/trunk/source/lib/allocators/tests/test_allocators.h =================================================================== --- ps/trunk/source/lib/allocators/tests/test_allocators.h (revision 19898) +++ ps/trunk/source/lib/allocators/tests/test_allocators.h (revision 19899) @@ -1,40 +1,40 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/allocators/dynarray.h" #include "lib/byte_order.h" class TestAllocators : public CxxTest::TestSuite { public: void test_da() { DynArray da; // basic test of functionality (not really meaningful) TS_ASSERT_OK(da_alloc(&da, 1000)); TS_ASSERT_OK(da_set_size(&da, 1000)); TS_ASSERT_OK(da_free(&da)); } }; Index: ps/trunk/source/lib/allocators/unique_range.cpp =================================================================== --- ps/trunk/source/lib/allocators/unique_range.cpp (revision 19898) +++ ps/trunk/source/lib/allocators/unique_range.cpp (revision 19899) @@ -1,104 +1,104 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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 "lib/allocators/unique_range.h" #include "lib/bits.h" // is_pow2, round_up #include "lib/sysdep/cpu.h" // cpu_AtomicAdd #include "lib/sysdep/rtl.h" // rtl_FreeAligned static void FreeNone(void* UNUSED(pointer), size_t UNUSED(size)) { // (providing a deleter function for idxDeleterNone avoids // having to check whether deleters[idxDeleter] == 0) } static void FreeAligned(void* pointer, size_t UNUSED(size)) { return rtl_FreeAligned(pointer); } static UniqueRangeDeleter deleters[allocationAlignment] = { FreeNone, FreeAligned }; static IdxDeleter numDeleters = 2; // NB: callers should skip this if *idxDeleterOut != 0 (avoids the overhead // of an unnecessary indirect function call) void RegisterUniqueRangeDeleter(UniqueRangeDeleter deleter, volatile IdxDeleter* idxDeleterOut) { ENSURE(deleter); if(!cpu_CAS(idxDeleterOut, idxDeleterNone, -1)) // not the first call for this deleter { // wait until an index has been assigned while(*idxDeleterOut <= 0) cpu_Pause(); return; } const IdxDeleter idxDeleter = cpu_AtomicAdd(&numDeleters, 1); ENSURE(idxDeleter < (IdxDeleter)ARRAY_SIZE(deleters)); deleters[idxDeleter] = deleter; COMPILER_FENCE; *idxDeleterOut = idxDeleter; } NOTHROW_DEFINE void CallUniqueRangeDeleter(void* pointer, size_t size, IdxDeleter idxDeleter) { ASSERT(idxDeleter < numDeleters); // (some deleters do not tolerate null pointers) if(pointer) deleters[idxDeleter](pointer, size); } UniqueRange AllocateAligned(size_t size, size_t alignment) { ENSURE(is_pow2(alignment)); alignment = std::max(alignment, allocationAlignment); const size_t alignedSize = round_up(size, alignment); const UniqueRange::pointer p = rtl_AllocateAligned(alignedSize, alignment); static volatile IdxDeleter idxDeleterAligned; if(idxDeleterAligned == 0) // (optional optimization) RegisterUniqueRangeDeleter(FreeAligned, &idxDeleterAligned); return UniqueRange(p, size, idxDeleterAligned); } UniqueRange AllocateVM(size_t size, vm::PageType pageType, int prot) { const UniqueRange::pointer p = vm::Allocate(size, pageType, prot); static volatile IdxDeleter idxDeleter; if(idxDeleter == 0) // (optional optimization) RegisterUniqueRangeDeleter(vm::Free, &idxDeleter); return UniqueRange(p, size, idxDeleter); } Index: ps/trunk/source/lib/app_hooks.cpp =================================================================== --- ps/trunk/source/lib/app_hooks.cpp (revision 19898) +++ ps/trunk/source/lib/app_hooks.cpp (revision 19899) @@ -1,180 +1,180 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * hooks to allow customization / app-specific behavior. */ #include "precompiled.h" #include "lib/app_hooks.h" #include "lib/sysdep/sysdep.h" #include //----------------------------------------------------------------------------- // default implementations //----------------------------------------------------------------------------- static void def_override_gl_upload_caps() { } static const OsPath& def_get_log_dir() { static OsPath logDir; if(logDir.empty()) logDir = sys_ExecutablePathname().Parent(); return logDir; } static void def_bundle_logs(FILE* UNUSED(f)) { } static const wchar_t* def_translate(const wchar_t* text) { return text; } static void def_translate_free(const wchar_t* UNUSED(text)) { // no-op - translate() doesn't own the pointer. } static void def_log(const wchar_t* text) { #if ICC_VERSION #pragma warning(push) #pragma warning(disable:181) // "invalid printf conversion" - but wchar_t* and %ls are legit #endif printf("%ls", text); // must not use wprintf, since stdout on Unix is byte-oriented #if ICC_VERSION #pragma warning(pop) #endif } static ErrorReactionInternal def_display_error(const wchar_t* UNUSED(text), size_t UNUSED(flags)) { return ERI_NOT_IMPLEMENTED; } //----------------------------------------------------------------------------- // contains the current set of hooks. starts with the default values and // may be changed via app_hooks_update. // // rationale: we don't ever need to switch "hook sets", so one global struct // is fine. by always having one defined, we also avoid the trampolines // having to check whether their function pointer is valid. static AppHooks ah = { def_override_gl_upload_caps, def_get_log_dir, def_bundle_logs, def_translate, def_translate_free, def_log, def_display_error }; // separate copy of ah; used to determine if a particular hook has been // redefined. the additional storage needed is negligible and this is // easier than comparing each value against its corresponding def_ value. static AppHooks default_ah = ah; // register the specified hook function pointers. any of them that // are non-zero override the previous function pointer value // (these default to the stub hooks which are functional but basic). void app_hooks_update(AppHooks* new_ah) { ENSURE(new_ah); #define OVERRIDE_IF_NONZERO(HOOKNAME) if(new_ah->HOOKNAME) ah.HOOKNAME = new_ah->HOOKNAME; OVERRIDE_IF_NONZERO(override_gl_upload_caps) OVERRIDE_IF_NONZERO(get_log_dir) OVERRIDE_IF_NONZERO(bundle_logs) OVERRIDE_IF_NONZERO(translate) OVERRIDE_IF_NONZERO(translate_free) OVERRIDE_IF_NONZERO(log) OVERRIDE_IF_NONZERO(display_error) #undef OVERRIDE_IF_NONZERO } bool app_hook_was_redefined(size_t offset_in_struct) { const u8* ah_bytes = (const u8*)&ah; const u8* default_ah_bytes = (const u8*)&default_ah; typedef void(*FP)(); // a bit safer than comparing void* pointers if(*(FP)(ah_bytes+offset_in_struct) != *(FP)(default_ah_bytes+offset_in_struct)) return true; return false; } //----------------------------------------------------------------------------- // trampoline implementations // (boilerplate code; hides details of how to call the app hook) //----------------------------------------------------------------------------- void ah_override_gl_upload_caps() { if(ah.override_gl_upload_caps) ah.override_gl_upload_caps(); } const OsPath& ah_get_log_dir() { return ah.get_log_dir(); } void ah_bundle_logs(FILE* f) { ah.bundle_logs(f); } const wchar_t* ah_translate(const wchar_t* text) { return ah.translate(text); } void ah_translate_free(const wchar_t* text) { ah.translate_free(text); } void ah_log(const wchar_t* text) { ah.log(text); } ErrorReactionInternal ah_display_error(const wchar_t* text, size_t flags) { return ah.display_error(text, flags); } Index: ps/trunk/source/lib/base32.cpp =================================================================== --- ps/trunk/source/lib/base32.cpp (revision 19898) +++ ps/trunk/source/lib/base32.cpp (revision 19899) @@ -1,60 +1,60 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * base32 conversion */ #include "precompiled.h" // big endian! void base32(const size_t in_len, const u8* in, u8* out) { u32 pool = 0; // of bits from buffer ssize_t pool_bits = 0; // # bits currently in buffer static const u8 tbl[33] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; size_t in_bytes_left = in_len; // to avoid overrunning input buffer const size_t out_chars = (in_len*8 + 4) / 5; // = ceil(# 5-bit blocks) for(size_t i = 0; i < out_chars; i++) { if(pool_bits < 5 && in_bytes_left) { pool <<= 8; pool |= *in++; pool_bits += 8; in_bytes_left--; } pool_bits -= 5; size_t c; if (pool_bits < 0) c = (pool << -pool_bits) & 31; else c = (pool >> pool_bits) & 31; *out++ = tbl[c]; } *out++ = '\0'; } Index: ps/trunk/source/lib/bits.h =================================================================== --- ps/trunk/source/lib/bits.h (revision 19898) +++ ps/trunk/source/lib/bits.h (revision 19899) @@ -1,302 +1,302 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bit-twiddling. */ #ifndef INCLUDED_BITS #define INCLUDED_BITS /** * value of bit number \. * * @param n bit index. * * requirements: * - T should be an unsigned type * - n must be in [0, CHAR_BIT*sizeof(T)), else the result is undefined! **/ template inline T Bit(size_t n) { const T one = T(1); return (T)(one << n); } /** * pretty much the same as Bit\. * this is intended for the initialization of enum values, where a * compile-time constant is required. **/ #define BIT(n) (1u << (n)) template inline bool IsBitSet(T value, size_t index) { const T bit = Bit(index); return (value & bit) != 0; } // these are declared in the header and inlined to aid compiler optimizations // (they can easily end up being time-critical). // note: GCC can't inline extern functions, while VC's "Whole Program // Optimization" can. /** * a mask that includes the lowest N bits * * @param numBits Number of bits in mask. **/ template inline T bit_mask(size_t numBits) { const T bitsInT = sizeof(T)*CHAR_BIT; const T allBits = (T)~T(0); // (shifts of at least bitsInT are undefined) if(numBits >= bitsInT) return allBits; // (note: the previous allBits >> (bitsInT-numBits) is not safe // because right-shifts of negative numbers are undefined.) const T mask = (T)((T(1) << numBits)-1); return mask; } /** * extract the value of bits hi_idx:lo_idx within num * * example: bits(0x69, 2, 5) == 0x0A * * @param num number whose bits are to be extracted * @param lo_idx bit index of lowest bit to include * @param hi_idx bit index of highest bit to include * @return value of extracted bits. **/ template inline T bits(T num, size_t lo_idx, size_t hi_idx) { const size_t numBits = (hi_idx - lo_idx)+1; // # bits to return T result = T(num >> lo_idx); result = T(result & bit_mask(numBits)); return result; } /** * set the value of bits hi_idx:lo_idx * * @param lo_idx bit index of lowest bit to include * @param hi_idx bit index of highest bit to include * @param value new value to be assigned to these bits **/ template inline T SetBitsTo(T num, size_t lo_idx, size_t hi_idx, size_t value) { const size_t numBits = (hi_idx - lo_idx)+1; ASSERT(value < (T(1) << numBits)); const T mask = bit_mask(numBits) << lo_idx; T result = num & ~mask; result = T(result | (value << lo_idx)); return result; } /** * @return number of 1-bits in mask. * execution time is proportional to number of 1-bits in mask. **/ template inline size_t SparsePopulationCount(T mask) { size_t num1Bits = 0; while(mask) { mask &= mask-1; // clear least significant 1-bit num1Bits++; } return num1Bits; } /** * @return number of 1-bits in mask. * execution time is logarithmic in the total number of bits. * supports up to 128-bit integers (if their arithmetic operators are defined). * [http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel] **/ template static inline size_t PopulationCount(T x) { cassert(!std::numeric_limits::is_signed); const T mask = T(~T(0)); x -= (x >> 1) & (mask/3); // count 2 bits x = (x & (mask/15*3)) + ((x >> 2) & (mask/15*3)); // count 4 bits x = (x + (x >> 4)) & (mask/255*15); // count 8 bits return T(x * (mask/255)) >> ((sizeof(T)-1)*CHAR_BIT); } /** * @return whether the given number is a power of two. **/ template inline bool is_pow2(T n) { // 0 would pass the test below but isn't a POT. if(n == 0) return false; return (n & (n-1)) == 0; } // as above; intended for use in static_assert #define IS_POW2(n) (((n) != 0) && ((n) & ((n)-1)) == 0) template inline T LeastSignificantBit(T x) { const T negX = T(~x + 1); // 2's complement (avoids 'negating unsigned type' warning) return x & negX; } template inline T ClearLeastSignificantBit(T x) { return x & (x-1); } /** * ceil(log2(x)) * * @param x (unsigned integer) * @return ceiling of the base-2 logarithm (i.e. rounded up) or * zero if the input is zero. **/ template inline size_t ceil_log2(T x) { T bit = 1; size_t log = 0; while(bit < x && bit != 0) // must detect overflow { log++; bit *= 2; } return log; } // compile-time variant of the above template struct CeilLog2 { enum { value = 1 + CeilLog2<(N+1)/2>::value }; }; template<> struct CeilLog2<1> { enum { value = 0 }; }; template<> struct CeilLog2<0> { enum { value = 0 }; }; /** * floor(log2(f)) * fast, uses the FPU normalization hardware. * * @param x (float) input; MUST be > 0, else results are undefined. * @return floor of the base-2 logarithm (i.e. rounded down). **/ extern int floor_log2(const float x); /** * round up to next larger power of two. **/ template inline T round_up_to_pow2(T x) { return T(1) << ceil_log2(x); } /** * round down to next larger power of two. **/ template inline T round_down_to_pow2(T x) { return T(1) << floor_log2(x); } /** * round number up/down to the next given multiple. * * @param n Number to round. * @param multiple Must be a power of two. **/ template inline T round_up(T n, T multiple) { ASSERT(is_pow2(multiple)); const T result = (n + multiple-1) & ~(multiple-1); ASSERT(n <= result && result < n+multiple); return result; } template inline T round_down(T n, T multiple) { ASSERT(is_pow2(multiple)); const T result = n & ~(multiple-1); ASSERT(result <= n && n < result+multiple); return result; } // evaluates to an expression suitable as an initializer // for constant static data members. #define ROUND_UP(n, multiple) (((n) + (multiple)-1) & ~((multiple)-1)) template inline T MaxPowerOfTwoDivisor(T value) { ASSERT(value != T(0)); for(size_t log2 = 0; log2 < sizeof(T)*CHAR_BIT; log2++) { if(IsBitSet(value, log2)) return T(1) << log2; } DEBUG_WARN_ERR(ERR::LOGIC); // unreachable (!= 0 => there is a set bit) return 0; } #endif // #ifndef INCLUDED_BITS Index: ps/trunk/source/lib/code_annotation.h =================================================================== --- ps/trunk/source/lib/code_annotation.h (revision 19898) +++ ps/trunk/source/lib/code_annotation.h (revision 19899) @@ -1,390 +1,390 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * macros for code annotation. */ #ifndef INCLUDED_CODE_ANNOTATION #define INCLUDED_CODE_ANNOTATION #include "lib/sysdep/compiler.h" #include "lib/sysdep/arch.h" // ARCH_AMD64 /** * mark a function parameter as unused and avoid * the corresponding compiler warning. * wrap around the parameter name, e.g. void f(int UNUSED(x)) **/ #define UNUSED(param) /** * mark a function local variable or parameter as unused and avoid * the corresponding compiler warning. * note that UNUSED is not applicable to variable definitions that * involve initialization, nor is it sufficient in cases where * an argument is unused only in certain situations. * example: void f(int x) { ASSERT(x == 0); UNUSED2(x); } * this asserts in debug builds and avoids warnings in release. **/ #if HAVE_C99 && GCC_VERSION // _Pragma from C99, unused from GCC # define UNUSED2(param) _Pragma("unused " #param) #elif ICC_VERSION // ICC 12 still doesn't recognize pragma unused, casting to void // isn't sufficient, and self-assignment doesn't work for references. # define UNUSED2(param) do{ if(¶m) {} } while(false) #else # define UNUSED2(param) ((void)(param)) #endif /** * indicate a function will not throw any synchronous exceptions, * thus hopefully generating smaller and more efficient code. * * must be placed BEFORE return types because "The [VC++] compiler * ignores, without warning, any __declspec keywords placed after *". * such syntax is apparently also legal in GCC, per the example * "__attribute__((noreturn)) void d0 (void)". * * example: * NOTHROW_DECLARE void function(); * NOTHROW_DEFINE void function() {} **/ #if GCC_VERSION # define NOTHROW_DECLARE __attribute__((nothrow)) # define NOTHROW_DEFINE // not supported for definitions #elif MSC_VERSION // Kevin Frei, 2006-03-23: "I work on the Visual C++ compiler team, // and agree completely with Paul Parks: don't use throw(), because // there's a chance that we'll eventually implement it according to the standard". # define NOTHROW_DECLARE __declspec(nothrow) # define NOTHROW_DEFINE __declspec(nothrow) #else // don't use throw() because it might result in ADDITIONAL checks // (the standard mandates calling unexpected()) # define NOTHROW_DECLARE # define NOTHROW_DEFINE #endif /** * mark a function as noreturn for static analyzer purposes. * currently only for clang-analyzer. */ #if __has_feature(attribute_analyzer_noreturn) # define ANALYZER_NORETURN __attribute__((analyzer_noreturn)) #else # define ANALYZER_NORETURN #endif /** * "unreachable code" helpers * * unreachable lines of code are often the source or symptom of subtle bugs. * they are flagged by compiler warnings; however, the opposite problem - * erroneously reaching certain spots (e.g. due to missing return statement) * is worse and not detected automatically. * * to defend against this, the programmer can annotate their code to * indicate to humans that a particular spot should never be reached. * however, that isn't much help; better is a sentinel that raises an * error if if it is actually reached. hence, the UNREACHABLE macro. * * ironically, if the code guarded by UNREACHABLE works as it should, * compilers may flag the macro's code as unreachable. this would * distract from genuine warnings, which is unacceptable. * * even worse, compilers differ in their code checking: GCC only complains if * non-void functions end without returning a value (i.e. missing return * statement), while VC checks if lines are unreachable (e.g. if they are * preceded by a return on all paths). * * the implementation below enables optimization and automated checking * without raising warnings. **/ #define UNREACHABLE // actually defined below.. this is for # undef UNREACHABLE // CppDoc's benefit only. // this macro should not generate any fallback code; it is merely the // compiler-specific backend for UNREACHABLE. // #define it to nothing if the compiler doesn't support such a hint. #define HAVE_ASSUME_UNREACHABLE 1 #if MSC_VERSION && !ICC_VERSION // (ICC ignores this) # define ASSUME_UNREACHABLE __assume(0) #elif GCC_VERSION # define ASSUME_UNREACHABLE __builtin_unreachable() #else # define ASSUME_UNREACHABLE # undef HAVE_ASSUME_UNREACHABLE # define HAVE_ASSUME_UNREACHABLE 0 #endif // compiler supports ASSUME_UNREACHABLE => allow it to assume the code is // never reached (improves optimization at the cost of undefined behavior // if the annotation turns out to be incorrect). #if HAVE_ASSUME_UNREACHABLE && !CONFIG_ENABLE_CHECKS # define UNREACHABLE ASSUME_UNREACHABLE // otherwise (or if CONFIG_ENABLE_CHECKS is set), add a user-visible // warning if the code is reached. note that abort() fails to stop // ICC from warning about the lack of a return statement, so we // use an infinite loop instead. #else # define UNREACHABLE\ STMT(\ DEBUG_WARN_ERR(ERR::LOGIC); /* hit supposedly unreachable code */\ for(;;){};\ ) #endif /** convenient specialization of UNREACHABLE for switch statements whose default can never be reached. example usage: int x; switch(x % 2) { case 0: break; case 1: break; NODEFAULT; } **/ #define NODEFAULT default: UNREACHABLE // generate a symbol containing the line number of the macro invocation. // used to give a unique name (per file) to types or variables. // we can't prepend __FILE__ to make it globally unique - the filename // may be enclosed in quotes. PASTE3_HIDDEN__ is needed to make sure // __LINE__ is expanded correctly. #define PASTE3_HIDDEN__(a, b, c) a ## b ## c #define PASTE3__(a, b, c) PASTE3_HIDDEN__(a, b, c) #define UID__ PASTE3__(LINE_, __LINE__, _) #define UID2__ PASTE3__(LINE_, __LINE__, _2) //----------------------------------------------------------------------------- // cassert /** * Compile-time assertion. Causes a compile error if the expression * evaluates to zero/false. * * No runtime overhead; may be used anywhere, including file scope. * Especially useful for testing sizeof types. * * @param expr Expression that is expected to evaluate to non-zero at compile-time. **/ #define cassert(expr) static_assert((expr), #expr) /** * Indicates that a class is noncopyable (usually due to const or reference * members, or because the class works as a singleton). * * For example: * * @code * class ClassName { * NONCOPYABLE(ClassName); * public: // etc. * }; * @endcode * * This is preferable to inheritance from boost::noncopyable because it avoids * ICC 11 W4 warnings about non-virtual dtors and suppression of the copy * assignment operator. */ #define NONCOPYABLE(className) \ className(const className&) = delete; \ const className& operator=(const className&) = delete #if ICC_VERSION # define ASSUME_ALIGNED(ptr, multiple) __assume_aligned(ptr, multiple) #else # define ASSUME_ALIGNED(ptr, multiple) #endif // annotate printf-style functions for compile-time type checking. // fmtpos is the index of the format argument, counting from 1 or // (if it's a non-static class function) 2; the '...' is assumed // to come directly after it. #if GCC_VERSION # define PRINTF_ARGS(fmtpos) __attribute__ ((format (printf, fmtpos, fmtpos+1))) # define VPRINTF_ARGS(fmtpos) __attribute__ ((format (printf, fmtpos, 0))) # if CONFIG_DEHYDRA # define WPRINTF_ARGS(fmtpos) __attribute__ ((user("format, w, printf, " #fmtpos ", +1"))) # else # define WPRINTF_ARGS(fmtpos) /* not currently supported in GCC */ # endif # define VWPRINTF_ARGS(fmtpos) /* not currently supported in GCC */ #else # define PRINTF_ARGS(fmtpos) # define VPRINTF_ARGS(fmtpos) # define WPRINTF_ARGS(fmtpos) # define VWPRINTF_ARGS(fmtpos) // TODO: support _Printf_format_string_ for VC9+ #endif // annotate vararg functions that expect to end with an explicit NULL #if GCC_VERSION # define SENTINEL_ARG __attribute__ ((sentinel)) #else # define SENTINEL_ARG #endif /** * prevent the compiler from reordering loads or stores across this point. **/ #if ICC_VERSION # define COMPILER_FENCE __memory_barrier() #elif MSC_VERSION # include # pragma intrinsic(_ReadWriteBarrier) # define COMPILER_FENCE _ReadWriteBarrier() #elif GCC_VERSION # define COMPILER_FENCE asm volatile("" : : : "memory") #else # define COMPILER_FENCE #endif // try to define _W64, if not already done // (this is useful for catching pointer size bugs) #ifndef _W64 # if MSC_VERSION # define _W64 __w64 # elif GCC_VERSION # define _W64 __attribute__((mode (__pointer__))) # else # define _W64 # endif #endif // C99-like restrict (non-standard in C++, but widely supported in various forms). // // May be used on pointers. May also be used on member functions to indicate // that 'this' is unaliased (e.g. "void C::m() RESTRICT { ... }"). // Must not be used on references - GCC supports that but VC doesn't. // // We call this "RESTRICT" to avoid conflicts with VC's __declspec(restrict), // and because it's not really the same as C99's restrict. // // To be safe and satisfy the compilers' stated requirements: an object accessed // by a restricted pointer must not be accessed by any other pointer within the // lifetime of the restricted pointer, if the object is modified. // To maximise the chance of optimisation, any pointers that could potentially // alias with the restricted one should be marked as restricted too. // // It would probably be a good idea to write test cases for any code that uses // this in an even very slightly unclear way, in case it causes obscure problems // in a rare compiler due to differing semantics. // // .. GCC #if GCC_VERSION # define RESTRICT __restrict__ // .. VC8 provides __restrict #elif MSC_VERSION # define RESTRICT __restrict // .. ICC supports the keyword 'restrict' when run with the /Qrestrict option, // but it always also supports __restrict__ or __restrict to be compatible // with GCC/MSVC, so we'll use the underscored version. One of {GCC,MSC}_VERSION // should have been defined in addition to ICC_VERSION, so we should be using // one of the above cases (unless it's an old VS7.1-emulating ICC). #elif ICC_VERSION # error ICC_VERSION defined without either GCC_VERSION or an adequate MSC_VERSION // .. unsupported; remove it from code #else # define RESTRICT #endif // // number of array elements // // (function taking a reference to an array and returning a pointer to // an array of characters. it's only declared and never defined; we just // need it to determine n, the size of the array that was passed.) template char (*ArraySizeDeducer(T (&)[n]))[n]; // (although requiring C++, this method is much better than the standard // sizeof(name) / sizeof(name[0]) because it doesn't compile when a // pointer is passed, which can easily happen under maintenance.) #define ARRAY_SIZE(name) (sizeof(*ArraySizeDeducer(name))) // C99-style __func__ // .. newer GCC already have it #if GCC_VERSION // nothing need be done // .. MSVC have __FUNCTION__ #elif MSC_VERSION # define __func__ __FUNCTION__ // .. unsupported #else # define __func__ "(unknown)" #endif // extern "C", but does the right thing in pure-C mode #if defined(__cplusplus) # define EXTERN_C extern "C" #else # define EXTERN_C extern #endif #if MSC_VERSION # define INLINE __forceinline #else # define INLINE inline #endif #if MSC_VERSION # define CALL_CONV __cdecl #else # define CALL_CONV #endif #if MSC_VERSION && !ARCH_AMD64 # define DECORATED_NAME(name) _##name #else # define DECORATED_NAME(name) name #endif // workaround for preprocessor limitation: macro args aren't expanded // before being pasted. #define STRINGIZE2(id) # id #define STRINGIZE(id) STRINGIZE2(id) // 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) #endif // #ifndef INCLUDED_CODE_ANNOTATION Index: ps/trunk/source/lib/config2.h =================================================================== --- ps/trunk/source/lib/config2.h (revision 19898) +++ ps/trunk/source/lib/config2.h (revision 19899) @@ -1,110 +1,110 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * compile-time configuration for isolated spots */ #ifndef INCLUDED_CONFIG2 #define INCLUDED_CONFIG2 // rationale: a centralized header makes it much easier to see what all // can be changed. it is assumed that only a few modules will need // configuration choices, so rebuilding them all is acceptable. // use config.h when settings must apply to the entire project. // allow use of RDTSC for raw tick counts (otherwise, the slower but // more reliable on MP systems wall-clock will be used). #ifndef CONFIG2_TIMER_ALLOW_RDTSC # define CONFIG2_TIMER_ALLOW_RDTSC 1 #endif // this enables/disables the actual checking done by OverrunProtector // (quite slow, entailing mprotect() before/after each access). // define to 1 here or in the relevant module if you suspect mem corruption. // we provide this option because OverrunProtector requires some changes to // the object being wrapped, and we want to leave those intact but not // significantly slow things down except when needed. #ifndef CONFIG2_ALLOCATORS_OVERRUN_PROTECTION # define CONFIG2_ALLOCATORS_OVERRUN_PROTECTION 0 #endif // zero-copy IO means all clients share the cached buffer; changing their // contents is forbidden. this flag causes the buffers to be marked as // read-only via MMU (writes would cause an exception), which takes a // bit of extra time. #ifndef CONFIG2_CACHE_READ_ONLY #define CONFIG2_CACHE_READ_ONLY 1 #endif #ifndef CONFIG2_FILE_ENABLE_AIO // work around a bug introduced in Linux 2.6.38 // (http://www.wildfiregames.com/forum/index.php?showtopic=14561&view=findpost&p=217710) // OpenBSD doesn't provide aio.h so we disable its use # if OS_LINUX || OS_OPENBSD # define CONFIG2_FILE_ENABLE_AIO 0 # else # define CONFIG2_FILE_ENABLE_AIO 1 # endif #endif // allow an attempt to start the Aken driver (i.e. service) at runtime. // enable at your own risk on WinXP systems to allow access to // better timers than Windows provides. on newer Windows versions, // attempts to start the service from code fail unless the process // is elevated, and definitely fail due to lack of cross-signing unless // test-signing mode is active. // if the user has taken explicit action to install and start the // service via aken_install.bat, mahaf.cpp will be able to access it // even if this is defined to 0. #ifndef CONFIG2_MAHAF_ATTEMPT_DRIVER_START # define CONFIG2_MAHAF_ATTEMPT_DRIVER_START 0 #endif // build in OpenGL ES 2.0 mode, instead of the default mode designed for // GL 1.1 + extensions. // this disables various features that are not supported by GLES. #ifndef CONFIG2_GLES # define CONFIG2_GLES 0 #endif // allow use of OpenAL/Ogg/Vorbis APIs #ifndef CONFIG2_AUDIO # define CONFIG2_AUDIO 1 #endif // allow use of NVTT #ifndef CONFIG2_NVTT # define CONFIG2_NVTT 1 #endif // allow use of lobby #ifndef CONFIG2_LOBBY # define CONFIG2_LOBBY 1 #endif // allow use of miniupnpc #ifndef CONFIG2_MINIUPNPC # define CONFIG2_MINIUPNPC 1 #endif #endif // #ifndef INCLUDED_CONFIG2 Index: ps/trunk/source/lib/debug_stl.cpp =================================================================== --- ps/trunk/source/lib/debug_stl.cpp (revision 19898) +++ ps/trunk/source/lib/debug_stl.cpp (revision 19899) @@ -1,607 +1,607 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * portable debugging helper functions specific to the STL. */ #include "precompiled.h" #include "lib/debug_stl.h" #include #include #include #include #include #include "lib/regex.h" static const StatusDefinition debugStlStatusDefinitions[] = { { ERR::STL_CNT_UNKNOWN, L"Unknown STL container type_name" }, { ERR::STL_CNT_UNSUPPORTED, L"Unsupported STL container" }, { ERR::STL_CNT_INVALID, L"Container type is known but contents are invalid" } }; STATUS_ADD_DEFINITIONS(debugStlStatusDefinitions); // used in debug_stl_simplify_name. // note: wcscpy_s is safe because replacement happens in-place and // is longer than (otherwise, we wouldn't be replacing). #define REPLACE(what, with)\ else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\ {\ src += ARRAY_SIZE(what)-1-1; /* see preincrement rationale*/\ wcscpy_s(dst, ARRAY_SIZE(with), (with));\ dst += ARRAY_SIZE(with)-1;\ } #define STRIP(what)\ else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\ {\ src += ARRAY_SIZE(what)-1-1;/* see preincrement rationale*/\ } #define STRIP_NESTED(what)\ else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\ {\ /* remove preceding comma (if present) */\ if(src != name && src[-1] == ',')\ dst--;\ src += ARRAY_SIZE(what)-1;\ /* strip everything until trailing > is matched */\ ENSURE(nesting == 0);\ nesting = 1;\ } // reduce complicated STL names to human-readable form (in place). // e.g. "std::basic_string, std::allocator >" => // "string". algorithm: strip undesired strings in one pass (fast). // called from symbol_string_build. // // see http://www.bdsoft.com/tools/stlfilt.html and // http://www.moderncppdesign.com/publications/better_template_error_messages.html wchar_t* debug_stl_simplify_name(wchar_t* name) { // used when stripping everything inside a < > to continue until // the final bracket is matched (at the original nesting level). int nesting = 0; const wchar_t* src = name-1; // preincremented; see below. wchar_t* dst = name; // for each character: (except those skipped as parts of strings) for(;;) { wchar_t c = *(++src); // preincrement rationale: src++ with no further changes would // require all comparisons to subtract 1. incrementing at the // end of a loop would require a goto, instead of continue // (there are several paths through the loop, for speed). // therefore, preincrement. when skipping strings, subtract // 1 from the offset (since src is advanced directly after). // end of string reached - we're done. if(c == '\0') { *dst = '\0'; break; } // we're stripping everything inside a < >; eat characters // until final bracket is matched (at the original nesting level). if(nesting) { if(c == '<') nesting++; else if(c == '>') { nesting--; ENSURE(nesting >= 0); } continue; } // start if chain (REPLACE and STRIP use else if) if(0) {} else if(!wcsncmp(src, L"::_Node", 7)) { // add a space if not already preceded by one // (prevents replacing ">::_Node>" with ">>") if(src != name && src[-1] != ' ') *dst++ = ' '; src += 7; } REPLACE(L"unsigned short", L"u16") REPLACE(L"unsigned int", L"size_t") REPLACE(L"unsigned __int64", L"u64") STRIP(L",0> ") // early out: all tests after this start with s, so skip them else if(c != 's') { *dst++ = c; continue; } REPLACE(L"std::_List_nod", L"list") REPLACE(L"std::_Tree_nod", L"map") REPLACE(L"std::basic_string, ") STRIP(L"std::char_traits, ") STRIP(L"std::char_traits<__wchar_t>, ") STRIP(L"std::_Tmap_traits") STRIP(L"std::_Tset_traits") STRIP_NESTED(L"std::allocator<") STRIP_NESTED(L"std::less<") STRIP_NESTED(L"stdext::hash_compare<") STRIP(L"std::") STRIP(L"stdext::") else *dst++ = c; } return name; } //----------------------------------------------------------------------------- // STL container debugging //----------------------------------------------------------------------------- // provide an iterator interface for arbitrary STL containers; this is // used to display their contents in stack traces. their type and // contents aren't known until runtime, so this is somewhat tricky. // // we assume STL containers aren't specialized on their content type and // use their int instantiations's memory layout. vector will therefore // not be displayed correctly, but it is frowned upon anyway (since // address of its elements can't be taken). // to be 100% correct, we'd have to write an Any_container_type__element_type // class for each combination, but that is clearly infeasible. // // containers might still be uninitialized when we call get_container_info on // them. we need to check if they are IsValid and only then use their contents. // to that end, we derive a validator class from each container, // cast the container's address to it, and call its IsValid() method. // // checks performed include: is size() realistic; does begin() come before // end(), etc. we need to leverage all invariants because the values are // random in release mode. // // we sometimes need to access protected members of the STL containers. // granting access via friend is not possible since the system headers // must not be changed. that leaves us with two alternatives: // 1) write a 'shadow' class that has the same memory layout. this would // free us from the ugly Dinkumware naming conventions, but requires // more maintenance when the STL implementation changes. // 2) derive from the container. while not entirely bulletproof due to the // lack of virtual dtors, this is safe in practice because pointers are // neither returned to users nor freed. the only requirement is that // classes must not include virtual functions, because a vptr would // change the memory layout in unknown ways. // // it is rather difficult to abstract away implementation details of various // STL versions. we currently only really support Dinkumware due to // significant differences in the implementations of set, map and string. //---------------------------------------------------------------------------- // standard containers // base class (slightly simplifies code by providing default implementations // that can be used for most containers). // Container is the complete type of the STL container (we can't pass this // as a template because Dinkumware _Tree requires different parameters) template struct ContainerBase : public Container { bool IsValid(size_t UNUSED(el_size)) const { return true; } size_t NumElements(size_t UNUSED(el_size)) const { return this->size(); } static const u8* DereferenceAndAdvance(typename Container::iterator& it, size_t UNUSED(el_size)) { const u8* p = (const u8*)&*it; ++it; return p; } }; struct Any_deque : public ContainerBase > { #if STL_DINKUMWARE == 405 bool IsValid(size_t el_size) const { const size_t el_per_bucket = ElementsPerBucket(el_size); // initial element is beyond end of first bucket if(_Myoff >= el_per_bucket) return false; // more elements reported than fit in all buckets if(_Mysize > _Mapsize * el_per_bucket) return false; return true; } static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size) { struct Iterator : public iterator { Any_deque& Container() const { return *(Any_deque*)_Mycont; } size_t CurrentIndex() const { return _Myoff; } }; Iterator& it = *(Iterator*)&stl_it; Any_deque& container = it.Container(); const size_t currentIndex = it.CurrentIndex(); const u8* p = container.GetNthElement(currentIndex, el_size); ++it; return p; } private: static size_t ElementsPerBucket(size_t el_size) { return std::max(16u / el_size, (size_t)1u); // see _DEQUESIZ } const u8* GetNthElement(size_t i, size_t el_size) const { const size_t el_per_bucket = ElementsPerBucket(el_size); const size_t bucket_idx = i / el_per_bucket; ENSURE(bucket_idx < _Mapsize); const size_t idx_in_bucket = i - bucket_idx * el_per_bucket; ENSURE(idx_in_bucket < el_per_bucket); const u8** map = (const u8**)_Map; const u8* bucket = map[bucket_idx]; const u8* p = bucket + idx_in_bucket*el_size; return p; } #endif }; struct Any_list : public ContainerBase > { }; #if STL_DINKUMWARE == 405 template struct Any_tree : public std::_Tree<_Traits> { Any_tree() // (required because default ctor cannot be generated) { } bool IsValid(size_t UNUSED(el_size)) const { return true; } size_t NumElements(size_t UNUSED(el_size)) const { return size(); } static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size) { struct Iterator : public const_iterator { _Nodeptr Node() const { return _Ptr; } void SetNode(_Nodeptr node) { _Ptr = node; } }; Iterator& it = *(Iterator*)&stl_it; _Nodeptr node = it.Node(); const u8* p = (const u8*)&*it; // end() shouldn't be incremented, don't move if(_Isnil(node, el_size)) return p; // return smallest (leftmost) node of right subtree _Nodeptr _Pnode = _Right(node); if(!_Isnil(_Pnode, el_size)) { while(!_Isnil(_Left(_Pnode), el_size)) _Pnode = _Left(_Pnode); } // climb looking for right subtree else { while (!_Isnil(_Pnode = _Parent(node), el_size) && node == _Right(_Pnode)) node = _Pnode; // ==> parent while right subtree } it.SetNode(_Pnode); return p; }; private: // return reference to the given node's nil flag. // reimplemented because this member is stored after _Myval, so it's // dependent on el_size. static _Charref _Isnil(_Nodeptr _Pnode, size_t el_size) { const u8* p = (const u8*)&_Pnode->_Isnil; // correct for int specialization p += el_size - sizeof(value_type); // adjust for difference in el_size assert(*p <= 1); // bool value return (_Charref)*p; } }; struct Any_map : public Any_tree, std::allocator >, false> > { }; struct Any_multimap : public Any_map { }; struct Any_set: public Any_tree, std::allocator, false> > { }; struct Any_multiset: public Any_set { }; #endif struct Any_vector: public ContainerBase > { bool IsValid(size_t UNUSED(el_size)) const { // more elements reported than reserved if(size() > capacity()) return false; // front/back pointers incorrect if(&front() > &back()) return false; return true; } #if STL_DINKUMWARE == 405 size_t NumElements(size_t el_size) const { // vectors store front and back pointers and calculate their // element count as the difference between them. since we are // derived from a template specialization, the pointer arithmetic // is incorrect. we fix it by taking el_size into account. return ((u8*)_Mylast - (u8*)_Myfirst) * el_size; } static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size) { struct Iterator : public const_iterator { void Advance(size_t numBytes) { (u8*&)_Myptr += numBytes; } }; Iterator& it = *(Iterator*)&stl_it; const u8* p = (const u8*)&*it; it.Advance(el_size); return p; } #endif }; #if STL_DINKUMWARE == 405 struct Any_basic_string : public ContainerBase { bool IsValid(size_t el_size) const { // less than the small buffer reserved - impossible if(_Myres < (16/el_size)-1) return false; // more elements reported than reserved if(_Mysize > _Myres) return false; return true; } }; #endif // // standard container adapters // // debug_stl_get_container_info makes sure this was actually instantiated with // container = deque as we assume. struct Any_queue : public Any_deque { }; // debug_stl_get_container_info makes sure this was actually instantiated with // container = deque as we assume. struct Any_stack : public Any_deque { }; //----------------------------------------------------------------------------- // generic iterator - returns next element. dereferences and increments the // specific container iterator stored in it_mem. template const u8* stl_iterator(void* it_mem, size_t el_size) { typedef typename T::iterator iterator; iterator& stl_it = *(iterator*)it_mem; return T::DereferenceAndAdvance(stl_it, el_size); } // basic sanity checks that apply to all containers. template static bool IsContainerValid(const T& t, size_t el_count) { // note: don't test empty() because vector's implementation of it // depends on el_size. // size must be reasonable if(el_count > 0x1000000) return false; if(el_count != 0) { // valid pointer const u8* front = (const u8*)&*t.begin(); // (note: map doesn't have front) if(debug_IsPointerBogus(front)) return false; // note: don't test back() because that depends on el_size and // requires container-specific code. } return true; } // check if the container is IsValid and return # elements and an iterator; // this is instantiated once for each type of container. // we don't do this in the Any_* ctors because we need to return bool IsValid and // don't want to throw an exception (may confuse the debug code). template bool get_container_info(const T& t, size_t size, size_t el_size, size_t& el_count, DebugStlIterator& el_iterator, void* it_mem) { typedef typename T::iterator iterator; typedef typename T::const_iterator const_iterator; ENSURE(sizeof(T) == size); ENSURE(sizeof(iterator) < DEBUG_STL_MAX_ITERATOR_SIZE); el_count = t.NumElements(el_size); // bail if the container is uninitialized/invalid. if(!IsContainerValid(t, el_count)) return false; el_iterator = stl_iterator; // construct a copy of begin() at it_mem. placement new is necessary // because VC8's secure copy ctor apparently otherwise complains about // invalid values in the (uninitialized) destination memory. new(it_mem) const_iterator(t.begin()); return true; } // if indicates the object to be an STL container, // and given the size of its value_type (retrieved via debug information), // return number of elements and an iterator (any data it needs is stored in // it_mem, which must hold DEBUG_STL_MAX_ITERATOR_SIZE bytes). // returns 0 on success or an StlContainerError. Status debug_stl_get_container_info(const wchar_t* type_name, const u8* p, size_t size, size_t el_size, size_t* el_count, DebugStlIterator* el_iterator, void* it_mem) { #if MSC_VERSION UNUSED2(type_name); UNUSED2(p); UNUSED2(size); UNUSED2(el_size); UNUSED2(el_count); UNUSED2(el_iterator); UNUSED2(it_mem); return ERR::STL_CNT_UNSUPPORTED; // NOWARN #else bool handled = false, IsValid = false; #define CONTAINER(name, type_name_pattern)\ else if(match_wildcard(type_name, type_name_pattern))\ {\ handled = true;\ IsValid = get_container_info(*(Any_##name*)p, size, el_size, *el_count, *el_iterator, it_mem);\ } #define STD_CONTAINER(name) CONTAINER(name, L"std::" WIDEN(#name) L"<*>") // workaround for preprocessor limitation: what we're trying to do is // stringize the defined value of a macro. prepending and pasting L // apparently isn't possible because macro args aren't expanded before // being pasted; we therefore compare as chars[]. #define STRINGIZE2(id) # id #define STRINGIZE(id) STRINGIZE2(id) if(0) {} // kickoff // standard containers STD_CONTAINER(deque) STD_CONTAINER(list) STD_CONTAINER(vector) #if STL_DINKUMWARE == 405 STD_CONTAINER(map) STD_CONTAINER(multimap) STD_CONTAINER(set) STD_CONTAINER(multiset) STD_CONTAINER(basic_string) #endif // standard container adapters // (note: Any_queue etc. assumes the underlying container is a deque. // we make sure of that here and otherwise refuse to display it, because // doing so is lots of work for little gain.) CONTAINER(queue, L"std::queue<*,std::deque<*> >") CONTAINER(stack, L"std::stack<*,std::deque<*> >") // note: do not raise warnings - these can happen for new // STL classes or if the debuggee's memory is corrupted. if(!handled) return ERR::STL_CNT_UNKNOWN; // NOWARN if(!IsValid) return ERR::STL_CNT_INVALID; // NOWARN return INFO::OK; #endif } Index: ps/trunk/source/lib/external_libraries/dbghelp.cpp =================================================================== --- ps/trunk/source/lib/external_libraries/dbghelp.cpp (revision 19898) +++ ps/trunk/source/lib/external_libraries/dbghelp.cpp (revision 19899) @@ -1,57 +1,57 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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" #if OS_WIN #include "lib/sysdep/sysdep.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/external_libraries/dbghelp.h" // define extension function pointers extern "C" { #define FUNC(ret, name, params) ret (__stdcall *p##name) params; #include "lib/external_libraries/dbghelp_funcs.h" #undef FUNC } void dbghelp_ImportFunctions() { // for reasons unknown, LoadLibrary first checks the Dropbox shell // extension's directory (instead of "The directory from which the // application loaded.") and then the system directory, whose // dbghelp.dll is too old. we therefore specify the full path // to our executable directory, which contains a newer dbghelp.dll. const OsPath pathname = sys_ExecutablePathname().Parent()/"dbghelp.dll"; HMODULE hDbghelp = LoadLibraryW(OsString(pathname).c_str()); ENSURE(hDbghelp); #define FUNC(ret, name, params) p##name = (ret (__stdcall*) params)GetProcAddress(hDbghelp, #name); #include "lib/external_libraries/dbghelp_funcs.h" #undef FUNC // if this function is missing, the DLL is too old. ENSURE(pSymInitializeW); } #endif // OS_WIN Index: ps/trunk/source/lib/external_libraries/enet.h =================================================================== --- ps/trunk/source/lib/external_libraries/enet.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/enet.h (revision 19899) @@ -1,55 +1,55 @@ -/* Copyright (c) 2011 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 * "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. */ /* * bring in ENet header, with version check */ #ifndef INCLUDED_ENET #define INCLUDED_ENET #if OS_WIN // enet/win32.h wants to include winsock2.h which causes conflicts. // provide some required definitions from winsock.h, then pretend // we already included winsock.h typedef uintptr_t SOCKET; #define INVALID_SOCKET (SOCKET)(~0) struct fd_set; #define _WINSOCK2API_ // winsock2.h include guard #ifndef WIN32 # define WIN32 #endif #endif // OS_WIN #include #if defined(ENET_VERSION_MAJOR) && !(ENET_VERSION_MAJOR > 1 || ENET_VERSION_MINOR > 2) #error The game currently requires ENet 1.3.x. You are using an older version, which\ has an incompatible API and network protocol. Please switch to a newer version. #endif #endif // #ifndef INCLUDED_ENET Index: ps/trunk/source/lib/external_libraries/icu.h =================================================================== --- ps/trunk/source/lib/external_libraries/icu.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/icu.h (revision 19899) @@ -1,28 +1,28 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #ifndef INCLUDED_ICU #define INCLUDED_ICU #include #endif // #ifndef INCLUDED_ICU Index: ps/trunk/source/lib/external_libraries/opengl.h =================================================================== --- ps/trunk/source/lib/external_libraries/opengl.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/opengl.h (revision 19899) @@ -1,71 +1,71 @@ -/* Copyright (c) 2011 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 * "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. */ /* * bring in OpenGL header+library, with compatibility fixes */ #ifndef INCLUDED_OPENGL #define INCLUDED_OPENGL #include "lib/config2.h" // CONFIG2_GLES #if OS_WIN // wgl.h is a private header and should only be included from here. // if this isn't defined, it'll complain. #define WGL_HEADER_NEEDED #include "lib/sysdep/os/win/wgl.h" #endif #if CONFIG2_GLES # include #elif OS_MACOSX || OS_MAC # include #else # include #endif // if gl.h provides real prototypes for 1.2 / 1.3 functions, // exclude the corresponding function pointers in glext_funcs.h #ifdef GL_VERSION_1_2 #define REAL_GL_1_2 #endif #ifdef GL_VERSION_1_3 #define REAL_GL_1_3 #endif // this must come after GL/gl.h include, so we can't combine the // including GL/glext.h. #undef GL_GLEXT_PROTOTYPES #if CONFIG2_GLES # include #elif OS_MACOSX || OS_MAC # include #else # include # if OS_WIN # include # endif #endif #endif // #ifndef INCLUDED_OPENGL Index: ps/trunk/source/lib/external_libraries/glext_funcs.h =================================================================== --- ps/trunk/source/lib/external_libraries/glext_funcs.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/glext_funcs.h (revision 19899) @@ -1,411 +1,411 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * OpenGL extension function declarations (X macros). */ #include "lib/config2.h" // CONFIG2_GLES #if CONFIG2_GLES # include #elif OS_MACOSX # include #else # include #endif #if OS_WIN # include #endif /* FUNC is used for functions that are only extensions. FUNC2 is used for functions that have been promoted to core features. FUNC3 is used for functions that have been promoted to core features but have slightly changed semantics and need to be referred to by their core name instead of extension name. The FUNC2/FUNC3 calls include the version of OpenGL in which the extension was promoted, and the pre- and post-promotion names (e.g. "glBindBufferARB" vs "glBindBuffer"). If the GL driver is advertising a sufficiently high version, we load the promoted name; otherwise we use the *ARB name. (The spec says: "GL implementations of such later revisions should continue to export the name strings of promoted extensions in the EXTENSIONS string, and continue to support the ARB-affixed versions of functions and enumerants as a transition aid." but some drivers might be stupid/buggy and fail to do that, so we don't just use the ARB names unconditionally.) The names are made accessible to engine code only via the ARB name, to make it obvious that care must be taken (i.e. by being certain that the extension is actually supported). */ #if CONFIG2_GLES // no GLES extensions used yet // some functions that are extensions in GL are core functions in GLES, // so we should use them without the function pointer indirection #define pglActiveTextureARB glActiveTexture #define pglBlendColorEXT glBlendColor #define pglBlendEquationEXT glBlendEquation #define pglClientActiveTextureARB glClientActiveTexture #define pglCompressedTexImage2DARB glCompressedTexImage2D #define pglAttachObjectARB glAttachShader #define pglBindAttribLocationARB glBindAttribLocation #define pglCompileShaderARB glCompileShader #define pglCreateProgramObjectARB glCreateProgram #define pglCreateShaderObjectARB glCreateShader #define pglDeleteProgram glDeleteProgram #define pglDeleteShader glDeleteShader #define pglDisableVertexAttribArrayARB glDisableVertexAttribArray #define pglEnableVertexAttribArrayARB glEnableVertexAttribArray #define pglGetActiveUniformARB glGetActiveUniform #define pglGetProgramiv glGetProgramiv #define pglGetProgramInfoLog glGetProgramInfoLog #define pglGetShaderiv glGetShaderiv #define pglGetShaderInfoLog glGetShaderInfoLog #define pglGetUniformLocationARB glGetUniformLocation #define pglLinkProgramARB glLinkProgram #define pglShaderSourceARB glShaderSource #define pglUniform1fARB glUniform1f #define pglUniform2fARB glUniform2f #define pglUniform3fARB glUniform3f #define pglUniform4fARB glUniform4f #define pglUniform1iARB glUniform1i #define pglUniformMatrix4fvARB glUniformMatrix4fv #define pglUseProgramObjectARB glUseProgram #define pglVertexAttribPointerARB glVertexAttribPointer #define pglBindBufferARB glBindBuffer #define pglBufferDataARB glBufferData #define pglBufferSubDataARB glBufferSubData #define pglDeleteBuffersARB glDeleteBuffers #define pglGenBuffersARB glGenBuffers #define pglMapBufferARB glMapBuffer #define pglUnmapBufferARB glUnmapBuffer #define pglBindFramebufferEXT glBindFramebuffer #define pglCheckFramebufferStatusEXT glCheckFramebufferStatus #define pglDeleteFramebuffersEXT glDeleteFramebuffers #define pglFramebufferTexture2DEXT glFramebufferTexture2D #define pglGenFramebuffersEXT glGenFramebuffers #define GL_DEPTH_ATTACHMENT_EXT GL_DEPTH_ATTACHMENT #define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0 #define GL_FRAMEBUFFER_BINDING_EXT GL_FRAMEBUFFER_BINDING #define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE #define GL_FRAMEBUFFER_EXT GL_FRAMEBUFFER #define GL_CLAMP_TO_BORDER GL_CLAMP_TO_EDGE // TODO: should fix code that relies on GL_CLAMP_TO_BORDER typedef GLuint GLhandleARB; #else // were these defined as real functions in gl.h already? // GL_EXT_draw_range_elements / GL1.2: FUNC2(void, glDrawRangeElementsEXT, glDrawRangeElements, "1.2", (GLenum, GLuint, GLuint, GLsizei, GLenum, GLvoid*)) // GL_ARB_multitexture / GL1.3: FUNC2(void, glMultiTexCoord2fARB, glMultiTexCoord2f, "1.3", (int, float, float)) FUNC2(void, glMultiTexCoord3fARB, glMultiTexCoord3f, "1.3", (int, float, float, float)) FUNC2(void, glActiveTextureARB, glActiveTexture, "1.3", (int)) FUNC2(void, glClientActiveTextureARB, glClientActiveTexture, "1.3", (int)) // GL_EXT_blend_color / GL1.4 (optional in 1.2): FUNC2(void, glBlendColorEXT, glBlendColor, "1.4", (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)) // GL_EXT_blend_minmax / GL1.4 (optional in 1.2): FUNC2(void, glBlendEquationEXT, glBlendEquation, "1.4", (GLenum mode)) // GL_ARB_vertex_buffer_object / GL1.5: FUNC2(void, glBindBufferARB, glBindBuffer, "1.5", (int target, GLuint buffer)) FUNC2(void, glDeleteBuffersARB, glDeleteBuffers, "1.5", (GLsizei n, const GLuint* buffers)) FUNC2(void, glGenBuffersARB, glGenBuffers, "1.5", (GLsizei n, GLuint* buffers)) FUNC2(bool, glIsBufferARB, glIsBuffer, "1.5", (GLuint buffer)) FUNC2(void, glBufferDataARB, glBufferData, "1.5", (int target, GLsizeiptrARB size, const void* data, int usage)) FUNC2(void, glBufferSubDataARB, glBufferSubData, "1.5", (int target, GLintptrARB offset, GLsizeiptrARB size, const void* data)) FUNC2(void, glGetBufferSubDataARB, glGetBufferSubData, "1.5", (int target, GLintptrARB offset, GLsizeiptrARB size, void* data)) FUNC2(void*, glMapBufferARB, glMapBuffer, "1.5", (int target, int access)) FUNC2(bool, glUnmapBufferARB, glUnmapBuffer, "1.5", (int target)) FUNC2(void, glGetBufferParameterivARB, glGetBufferParameteriv, "1.5", (int target, int pname, int* params)) FUNC2(void, glGetBufferPointervARB, glGetBufferPointerv, "1.5", (int target, int pname, void** params)) // GL_ARB_texture_compression / GL1.3 FUNC2(void, glCompressedTexImage3DARB, glCompressedTexImage3D, "1.3", (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid*)) FUNC2(void, glCompressedTexImage2DARB, glCompressedTexImage2D, "1.3", (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid*)) FUNC2(void, glCompressedTexImage1DARB, glCompressedTexImage1D, "1.3", (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid*)) FUNC2(void, glCompressedTexSubImage3DARB, glCompressedTexSubImage3D, "1.3", (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid*)) FUNC2(void, glCompressedTexSubImage2DARB, glCompressedTexSubImage2D, "1.3", (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid*)) FUNC2(void, glCompressedTexSubImage1DARB, glCompressedTexSubImage1D, "1.3", (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid*)) FUNC2(void, glGetCompressedTexImageARB, glGetCompressedTexImage, "1.3", (GLenum, GLint, GLvoid*)) // GL_EXT_framebuffer_object FUNC(GLboolean, glIsRenderbufferEXT, (GLuint renderbuffer)) FUNC(void, glBindRenderbufferEXT, (GLenum target, GLuint renderbuffer)) FUNC(void, glDeleteRenderbuffersEXT, (GLsizei n, const GLuint *renderbuffers)) FUNC(void, glGenRenderbuffersEXT, (GLsizei n, GLuint *renderbuffers)) FUNC(void, glRenderbufferStorageEXT, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) FUNC(void, glGetRenderbufferParameterivEXT, (GLenum target, GLenum pname, GLint *params)) FUNC(GLboolean, glIsFramebufferEXT, (GLuint framebuffer)) FUNC(void, glBindFramebufferEXT, (GLenum target, GLuint framebuffer)) FUNC(void, glDeleteFramebuffersEXT, (GLsizei n, const GLuint *framebuffers)) FUNC(void, glGenFramebuffersEXT, (GLsizei n, GLuint *framebuffers)) FUNC(GLenum, glCheckFramebufferStatusEXT, (GLenum target)) FUNC(void, glFramebufferTexture1DEXT, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) FUNC(void, glFramebufferTexture2DEXT, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) FUNC(void, glFramebufferTexture3DEXT, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset)) FUNC(void, glFramebufferRenderbufferEXT, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) FUNC(void, glGetFramebufferAttachmentParameterivEXT, (GLenum target, GLenum attachment, GLenum pname, GLint *params)) FUNC(void, glGenerateMipmapEXT, (GLenum target)) FUNC(void, glBlitFramebufferEXT, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) FUNC(void, glDrawBuffers, (GLsizei n, const GLenum *bufs)) // GL_ARB_vertex_program, GL_ARB_fragment_program FUNC(void, glProgramStringARB, (GLenum target, GLenum format, GLsizei len, const GLvoid *string)) FUNC(void, glBindProgramARB, (GLenum target, GLuint program)) FUNC(void, glDeleteProgramsARB, (GLsizei n, const GLuint *programs)) FUNC(void, glGenProgramsARB, (GLsizei n, GLuint *programs)) FUNC(void, glProgramEnvParameter4dARB, (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) FUNC(void, glProgramEnvParameter4dvARB, (GLenum target, GLuint index, const GLdouble *params)) FUNC(void, glProgramEnvParameter4fARB, (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) FUNC(void, glProgramEnvParameter4fvARB, (GLenum target, GLuint index, const GLfloat *params)) FUNC(void, glProgramLocalParameter4dARB, (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) FUNC(void, glProgramLocalParameter4dvARB, (GLenum target, GLuint index, const GLdouble *params)) FUNC(void, glProgramLocalParameter4fARB, (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) FUNC(void, glProgramLocalParameter4fvARB, (GLenum target, GLuint index, const GLfloat *params)) FUNC(void, glGetProgramEnvParameterdvARB, (GLenum target, GLuint index, GLdouble *params)) FUNC(void, glGetProgramEnvParameterfvARB, (GLenum target, GLuint index, GLfloat *params)) FUNC(void, glGetProgramLocalParameterdvARB, (GLenum target, GLuint index, GLdouble *params)) FUNC(void, glGetProgramLocalParameterfvARB, (GLenum target, GLuint index, GLfloat *params)) FUNC(void, glGetProgramivARB, (GLenum target, GLenum pname, GLint *params)) FUNC(void, glGetProgramStringARB, (GLenum target, GLenum pname, GLvoid *string)) FUNC(GLboolean, glIsProgramARB, (GLuint program)) // GL_ARB_shader_objects // (NOTE: Many of these have "Object" in their ARB names, but "Program" or "Shader" in their core names. // When both Program and Shader versions exist, we use FUNC3 here and the engine must call the specific // core name instead of the generic ARB name.) FUNC3(void, glDeleteObjectARB, glDeleteShader, "2.0", (GLhandleARB obj)) FUNC3(void, glDeleteObjectARB, glDeleteProgram, "2.0", (GLhandleARB obj)) // FUNC2(GLhandleARB, glGetHandleARB, glGetHandle, "2.0", (GLenum pname)) // there is no analog to the ARB function in GL 2.0 (the functionality is probably moved into glGetIntegerv(GL_CURRENT_PROGRAM)) // so we can't represent it in this FUNC2 system, so just pretend it doesn't exist FUNC2(void, glDetachObjectARB, glDetachShader, "2.0", (GLhandleARB containerObj, GLhandleARB attachedObj)) FUNC2(GLhandleARB, glCreateShaderObjectARB, glCreateShader, "2.0", (GLenum shaderType)) FUNC2(void, glShaderSourceARB, glShaderSource, "2.0", (GLhandleARB shaderObj, GLsizei count, const char **string, const GLint *length)) FUNC2(void, glCompileShaderARB, glCompileShader, "2.0", (GLhandleARB shaderObj)) FUNC2(GLhandleARB, glCreateProgramObjectARB, glCreateProgram, "2.0", (void)) FUNC2(void, glAttachObjectARB, glAttachShader, "2.0", (GLhandleARB containerObj, GLhandleARB obj)) FUNC2(void, glLinkProgramARB, glLinkProgram, "2.0", (GLhandleARB programObj)) FUNC2(void, glUseProgramObjectARB, glUseProgram, "2.0", (GLhandleARB programObj)) FUNC2(void, glValidateProgramARB, glValidateProgram, "2.0", (GLhandleARB programObj)) FUNC2(void, glUniform1fARB, glUniform1f, "2.0", (GLint location, GLfloat v0)) FUNC2(void, glUniform2fARB, glUniform2f, "2.0", (GLint location, GLfloat v0, GLfloat v1)) FUNC2(void, glUniform3fARB, glUniform3f, "2.0", (GLint location, GLfloat v0, GLfloat v1, GLfloat v2)) FUNC2(void, glUniform4fARB, glUniform4f, "2.0", (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)) FUNC2(void, glUniform1iARB, glUniform1i, "2.0", (GLint location, GLint v0)) FUNC2(void, glUniform2iARB, glUniform2i, "2.0", (GLint location, GLint v0, GLint v1)) FUNC2(void, glUniform3iARB, glUniform3i, "2.0", (GLint location, GLint v0, GLint v1, GLint v2)) FUNC2(void, glUniform4iARB, glUniform4i, "2.0", (GLint location, GLint v0, GLint v1, GLint v2, GLint v3)) FUNC2(void, glUniform1fvARB, glUniform1fv, "2.0", (GLint location, GLsizei count, const GLfloat *value)) FUNC2(void, glUniform2fvARB, glUniform2fv, "2.0", (GLint location, GLsizei count, const GLfloat *value)) FUNC2(void, glUniform3fvARB, glUniform3fv, "2.0", (GLint location, GLsizei count, const GLfloat *value)) FUNC2(void, glUniform4fvARB, glUniform4fv, "2.0", (GLint location, GLsizei count, const GLfloat *value)) FUNC2(void, glUniform1ivARB, glUniform1iv, "2.0", (GLint location, GLsizei count, const GLint *value)) FUNC2(void, glUniform2ivARB, glUniform2iv, "2.0", (GLint location, GLsizei count, const GLint *value)) FUNC2(void, glUniform3ivARB, glUniform3iv, "2.0", (GLint location, GLsizei count, const GLint *value)) FUNC2(void, glUniform4ivARB, glUniform4iv, "2.0", (GLint location, GLsizei count, const GLint *value)) FUNC2(void, glUniformMatrix2fvARB, glUniformMatrix2fv, "2.0", (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)) FUNC2(void, glUniformMatrix3fvARB, glUniformMatrix3fv, "2.0", (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)) FUNC2(void, glUniformMatrix4fvARB, glUniformMatrix4fv, "2.0", (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)) FUNC3(void, glGetObjectParameterfvARB, glGetProgramfv, "2.0", (GLhandleARB obj, GLenum pname, GLfloat *params)) FUNC3(void, glGetObjectParameterfvARB, glGetShaderfv, "2.0", (GLhandleARB obj, GLenum pname, GLfloat *params)) FUNC3(void, glGetObjectParameterivARB, glGetProgramiv, "2.0", (GLhandleARB obj, GLenum pname, GLint *params)) FUNC3(void, glGetObjectParameterivARB, glGetShaderiv, "2.0", (GLhandleARB obj, GLenum pname, GLint *params)) FUNC3(void, glGetInfoLogARB, glGetProgramInfoLog, "2.0", (GLhandleARB obj, GLsizei maxLength, GLsizei *length, char *infoLog)) FUNC3(void, glGetInfoLogARB, glGetShaderInfoLog, "2.0", (GLhandleARB obj, GLsizei maxLength, GLsizei *length, char *infoLog)) FUNC2(void, glGetAttachedObjectsARB, glGetAttachedShaders, "2.0", (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj)) FUNC2(GLint, glGetUniformLocationARB, glGetUniformLocation, "2.0", (GLhandleARB programObj, const char *name)) FUNC2(void, glGetActiveUniformARB, glGetActiveUniform, "2.0", (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, char *name)) FUNC2(void, glGetUniformfvARB, glGetUniformfv, "2.0", (GLhandleARB programObj, GLint location, GLfloat *params)) FUNC2(void, glGetUniformivARB, glGetUniformiv, "2.0", (GLhandleARB programObj, GLint location, GLint *params)) FUNC2(void, glGetShaderSourceARB, glGetShaderSource, "2.0", (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source)) // GL_ARB_vertex_shader FUNC2(void, glVertexAttrib1fARB, glVertexAttrib1f, "2.0", (GLuint index, GLfloat v0)) FUNC2(void, glVertexAttrib1sARB, glVertexAttrib1s, "2.0", (GLuint index, GLshort v0)) FUNC2(void, glVertexAttrib1dARB, glVertexAttrib1d, "2.0", (GLuint index, GLdouble v0)) FUNC2(void, glVertexAttrib2fARB, glVertexAttrib2f, "2.0", (GLuint index, GLfloat v0, GLfloat v1)) FUNC2(void, glVertexAttrib2sARB, glVertexAttrib2s, "2.0", (GLuint index, GLshort v0, GLshort v1)) FUNC2(void, glVertexAttrib2dARB, glVertexAttrib2d, "2.0", (GLuint index, GLdouble v0, GLdouble v1)) FUNC2(void, glVertexAttrib3fARB, glVertexAttrib3f, "2.0", (GLuint index, GLfloat v0, GLfloat v1, GLfloat v2)) FUNC2(void, glVertexAttrib3sARB, glVertexAttrib3s, "2.0", (GLuint index, GLshort v0, GLshort v1, GLshort v2)) FUNC2(void, glVertexAttrib3dARB, glVertexAttrib3d, "2.0", (GLuint index, GLdouble v0, GLdouble v1, GLdouble v2)) FUNC2(void, glVertexAttrib4fARB, glVertexAttrib4f, "2.0", (GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)) FUNC2(void, glVertexAttrib4sARB, glVertexAttrib4s, "2.0", (GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3)) FUNC2(void, glVertexAttrib4dARB, glVertexAttrib4d, "2.0", (GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3)) FUNC2(void, glVertexAttrib4NubARB, glVertexAttrib4Nub, "2.0", (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w)) FUNC2(void, glVertexAttrib1fvARB, glVertexAttrib1fv, "2.0", (GLuint index, const GLfloat *v)) FUNC2(void, glVertexAttrib1svARB, glVertexAttrib1sv, "2.0", (GLuint index, const GLshort *v)) FUNC2(void, glVertexAttrib1dvARB, glVertexAttrib1dv, "2.0", (GLuint index, const GLdouble *v)) FUNC2(void, glVertexAttrib2fvARB, glVertexAttrib2fv, "2.0", (GLuint index, const GLfloat *v)) FUNC2(void, glVertexAttrib2svARB, glVertexAttrib2sv, "2.0", (GLuint index, const GLshort *v)) FUNC2(void, glVertexAttrib2dvARB, glVertexAttrib2dv, "2.0", (GLuint index, const GLdouble *v)) FUNC2(void, glVertexAttrib3fvARB, glVertexAttrib3fv, "2.0", (GLuint index, const GLfloat *v)) FUNC2(void, glVertexAttrib3svARB, glVertexAttrib3sv, "2.0", (GLuint index, const GLshort *v)) FUNC2(void, glVertexAttrib3dvARB, glVertexAttrib3dv, "2.0", (GLuint index, const GLdouble *v)) FUNC2(void, glVertexAttrib4fvARB, glVertexAttrib4fv, "2.0", (GLuint index, const GLfloat *v)) FUNC2(void, glVertexAttrib4svARB, glVertexAttrib4sv, "2.0", (GLuint index, const GLshort *v)) FUNC2(void, glVertexAttrib4dvARB, glVertexAttrib4dv, "2.0", (GLuint index, const GLdouble *v)) FUNC2(void, glVertexAttrib4ivARB, glVertexAttrib4iv, "2.0", (GLuint index, const GLint *v)) FUNC2(void, glVertexAttrib4bvARB, glVertexAttrib4bv, "2.0", (GLuint index, const GLbyte *v)) FUNC2(void, glVertexAttrib4ubvARB, glVertexAttrib4ubv, "2.0", (GLuint index, const GLubyte *v)) FUNC2(void, glVertexAttrib4usvARB, glVertexAttrib4usv, "2.0", (GLuint index, const GLushort *v)) FUNC2(void, glVertexAttrib4uivARB, glVertexAttrib4uiv, "2.0", (GLuint index, const GLuint *v)) FUNC2(void, glVertexAttrib4NbvARB, glVertexAttrib4Nbv, "2.0", (GLuint index, const GLbyte *v)) FUNC2(void, glVertexAttrib4NsvARB, glVertexAttrib4Nsv, "2.0", (GLuint index, const GLshort *v)) FUNC2(void, glVertexAttrib4NivARB, glVertexAttrib4Niv, "2.0", (GLuint index, const GLint *v)) FUNC2(void, glVertexAttrib4NubvARB, glVertexAttrib4Nubv, "2.0", (GLuint index, const GLubyte *v)) FUNC2(void, glVertexAttrib4NusvARB, glVertexAttrib4Nusv, "2.0", (GLuint index, const GLushort *v)) FUNC2(void, glVertexAttrib4NuivARB, glVertexAttrib4Nuiv, "2.0", (GLuint index, const GLuint *v)) FUNC2(void, glVertexAttribPointerARB, glVertexAttribPointer, "2.0", (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer)) FUNC2(void, glEnableVertexAttribArrayARB, glEnableVertexAttribArray, "2.0", (GLuint index)) FUNC2(void, glDisableVertexAttribArrayARB, glDisableVertexAttribArray, "2.0", (GLuint index)) FUNC2(void, glBindAttribLocationARB, glBindAttribLocation, "2.0", (GLhandleARB programObj, GLuint index, const char *name)) FUNC2(void, glGetActiveAttribARB, glGetActiveAttrib, "2.0", (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, int *size, GLenum *type, char *name)) FUNC2(GLint, glGetAttribLocationARB, glGetAttribLocation, "2.0", (GLhandleARB programObj, const char *name)) FUNC2(void, glGetVertexAttribdvARB, glGetVertexAttribdv, "2.0", (GLuint index, GLenum pname, GLdouble *params)) FUNC2(void, glGetVertexAttribfvARB, glGetVertexAttribfv, "2.0", (GLuint index, GLenum pname, GLfloat *params)) FUNC2(void, glGetVertexAttribivARB, glGetVertexAttribiv, "2.0", (GLuint index, GLenum pname, GLint *params)) FUNC2(void, glGetVertexAttribPointervARB, glGetVertexAttribPointerv, "2.0", (GLuint index, GLenum pname, void **pointer)) // GL_EXT_gpu_shader4 / GL3.0: FUNC2(void, glVertexAttribI1iEXT, glVertexAttribI1i, "3.0", (GLuint index, GLint x)) FUNC2(void, glVertexAttribI2iEXT, glVertexAttribI2i, "3.0", (GLuint index, GLint x, GLint y)) FUNC2(void, glVertexAttribI3iEXT, glVertexAttribI3i, "3.0", (GLuint index, GLint x, GLint y, GLint z)) FUNC2(void, glVertexAttribI4iEXT, glVertexAttribI4i, "3.0", (GLuint index, GLint x, GLint y, GLint z, GLint w)) FUNC2(void, glVertexAttribI1uiEXT, glVertexAttribI1ui, "3.0", (GLuint index, GLuint x)) FUNC2(void, glVertexAttribI2uiEXT, glVertexAttribI2ui, "3.0", (GLuint index, GLuint x, GLuint y)) FUNC2(void, glVertexAttribI3uiEXT, glVertexAttribI3ui, "3.0", (GLuint index, GLuint x, GLuint y, GLuint z)) FUNC2(void, glVertexAttribI4uiEXT, glVertexAttribI4ui, "3.0", (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w)) FUNC2(void, glVertexAttribI1ivEXT, glVertexAttribI1iv, "3.0", (GLuint index, const GLint *v)) FUNC2(void, glVertexAttribI2ivEXT, glVertexAttribI2iv, "3.0", (GLuint index, const GLint *v)) FUNC2(void, glVertexAttribI3ivEXT, glVertexAttribI3iv, "3.0", (GLuint index, const GLint *v)) FUNC2(void, glVertexAttribI4ivEXT, glVertexAttribI4iv, "3.0", (GLuint index, const GLint *v)) FUNC2(void, glVertexAttribI1uivEXT, glVertexAttribI1uiv, "3.0", (GLuint index, const GLuint *v)) FUNC2(void, glVertexAttribI2uivEXT, glVertexAttribI2uiv, "3.0", (GLuint index, const GLuint *v)) FUNC2(void, glVertexAttribI3uivEXT, glVertexAttribI3uiv, "3.0", (GLuint index, const GLuint *v)) FUNC2(void, glVertexAttribI4uivEXT, glVertexAttribI4uiv, "3.0", (GLuint index, const GLuint *v)) FUNC2(void, glVertexAttribI4bvEXT, glVertexAttribI4bv, "3.0", (GLuint index, const GLbyte *v)) FUNC2(void, glVertexAttribI4svEXT, glVertexAttribI4sv, "3.0", (GLuint index, const GLshort *v)) FUNC2(void, glVertexAttribI4ubvEXT, glVertexAttribI4ubv, "3.0", (GLuint index, const GLubyte *v)) FUNC2(void, glVertexAttribI4usvEXT, glVertexAttribI4usv, "3.0", (GLuint index, const GLushort *v)) FUNC2(void, glVertexAttribIPointerEXT, glVertexAttribIPointer, "3.0", (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer)) FUNC2(void, glGetVertexAttribIivEXT, glGetVertexAttribIiv, "3.0", (GLuint index, GLenum pname, GLint *params)) FUNC2(void, glGetVertexAttribIuivEXT, glGetVertexAttribIuiv, "3.0", (GLuint index, GLenum pname, GLuint *params)) FUNC2(void, glUniform1uiEXT, glUniform1ui, "3.0", (GLint location, GLuint v0)) FUNC2(void, glUniform2uiEXT, glUniform2ui, "3.0", (GLint location, GLuint v0, GLuint v1)) FUNC2(void, glUniform3uiEXT, glUniform3ui, "3.0", (GLint location, GLuint v0, GLuint v1, GLuint v2)) FUNC2(void, glUniform4uiEXT, glUniform4ui, "3.0", (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3)) FUNC2(void, glUniform1uivEXT, glUniform1uiv, "3.0", (GLint location, GLsizei count, const GLuint *value)) FUNC2(void, glUniform2uivEXT, glUniform2uiv, "3.0", (GLint location, GLsizei count, const GLuint *value)) FUNC2(void, glUniform3uivEXT, glUniform3uiv, "3.0", (GLint location, GLsizei count, const GLuint *value)) FUNC2(void, glUniform4uivEXT, glUniform4uiv, "3.0", (GLint location, GLsizei count, const GLuint *value)) FUNC2(void, glGetUniformuivEXT, glGetUniformuiv, "3.0", (GLuint program, GLint location, GLuint *params)) FUNC2(void, glBindFragDataLocationEXT, glBindFragDataLocation, "3.0", (GLuint program, GLuint colorNumber, const char *name)) FUNC2(GLint, glGetFragDataLocationEXT, glGetFragDataLocation, "3.0", (GLuint program, const char *name)) // GL_ARB_occlusion_query / GL1.5: FUNC2(void, glGenQueriesARB, glGenQueries, "1.5", (GLsizei n, GLuint *ids)) FUNC2(void, glDeleteQueriesARB, glDeleteQueries, "1.5", (GLsizei n, const GLuint *ids)) FUNC2(GLboolean, glIsQueryARB, glIsQuery, "1.5", (GLuint id)) FUNC2(void, glBeginQueryARB, glBeginQuery, "1.5", (GLenum target, GLuint id)) FUNC2(void, glEndQueryARB, glEndQuery, "1.5", (GLenum target)) FUNC2(void, glGetQueryivARB, glGetQueryiv, "1.5", (GLenum target, GLenum pname, GLint *params)) FUNC2(void, glGetQueryObjectivARB, glGetQueryObjectiv, "1.5", (GLuint id, GLenum pname, GLint *params)) FUNC2(void, glGetQueryObjectuivARB, glGetQueryObjectuiv, "1.5", (GLuint id, GLenum pname, GLuint *params)) // GL_ARB_sync / GL3.2: FUNC2(void, glGetInteger64v, glGetInteger64v, "3.2", (GLenum pname, GLint64 *params)) // GL_EXT_timer_query: FUNC(void, glGetQueryObjecti64vEXT, (GLuint id, GLenum pname, GLint64 *params)) FUNC(void, glGetQueryObjectui64vEXT, (GLuint id, GLenum pname, GLuint64 *params)) // GL_ARB_timer_query / GL3.3: FUNC2(void, glQueryCounter, glQueryCounter, "3.3", (GLuint id, GLenum target)) FUNC2(void, glGetQueryObjecti64v, glGetQueryObjecti64v, "3.3", (GLuint id, GLenum pname, GLint64 *params)) FUNC2(void, glGetQueryObjectui64v, glGetQueryObjectui64v, "3.3", (GLuint id, GLenum pname, GLuint64 *params)) // GL_ARB_map_buffer_range / GL3.0: FUNC2(void*, glMapBufferRange, glMapBufferRange, "3.0", (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access)) FUNC2(void, glFlushMappedBufferRange, glFlushMappedBufferRange, "3.0", (GLenum target, GLintptr offset, GLsizeiptr length)) // GL_GREMEDY_string_marker (from gDEBugger) FUNC(int, glStringMarkerGREMEDY, (GLsizei len, const GLvoid *string)) // GL_INTEL_performance_queries (undocumented, may be unstable, use at own risk; // see http://zaynar.co.uk/docs/gl-intel-performance-queries.html) FUNC(void, glGetFirstPerfQueryIdINTEL, (GLuint *queryId)) FUNC(void, glGetNextPerfQueryIdINTEL, (GLuint prevQueryId, GLuint *queryId)) FUNC(void, glGetPerfQueryInfoINTEL, (GLuint queryId, GLuint nameMaxLength, char *name, GLuint *counterBufferSize, GLuint *numCounters, GLuint *maxQueries, GLuint *)) FUNC(void, glGetPerfCounterInfoINTEL, (GLuint queryId, GLuint counterId, GLuint nameMaxLength, char *name, GLuint descMaxLength, char *desc, GLuint *offset, GLuint *size, GLuint *usage, GLuint *type, GLuint64 *)) FUNC(void, glCreatePerfQueryINTEL, (GLuint queryId, GLuint *id)) FUNC(void, glBeginPerfQueryINTEL, (GLuint id)) FUNC(void, glEndPerfQueryINTEL, (GLuint id)) FUNC(void, glDeletePerfQueryINTEL, (GLuint id)) FUNC(void, glGetPerfQueryDataINTEL, (GLuint id, GLenum requestType, GLuint maxLength, char *buffer, GLuint *length)) #endif // #if CONFIG_GLES2 #if OS_WIN // WGL_EXT_swap_control FUNC(int, wglSwapIntervalEXT, (int)) // WGL_ARB_pbuffer FUNC(HPBUFFERARB, wglCreatePbufferARB, (HDC, int, int, int, const int*)) FUNC(HDC, wglGetPbufferDCARB, (HPBUFFERARB)) FUNC(int, wglReleasePbufferDCARB, (HPBUFFERARB, HDC)) FUNC(int, wglDestroyPbufferARB, (HPBUFFERARB)) FUNC(int, wglQueryPbufferARB, (HPBUFFERARB, int, int*)) // GL_ARB_pixel_format FUNC(int, wglGetPixelFormatAttribivARB, (HDC, int, int, unsigned int, const int*, int*)) FUNC(int, wglGetPixelFormatAttribfvARB, (HDC, int, int, unsigned int, const int*, float*)) FUNC(int, wglChoosePixelFormatARB, (HDC, const int *, const float*, unsigned int, int*, unsigned int*)) #endif // OS_WIN // GLX_MESA_query_renderer FUNC(int /*Bool*/, glXQueryRendererIntegerMESA, (void /*Display*/ *dpy, int screen, int renderer, int attribute, unsigned int *value)) FUNC(int /*Bool*/, glXQueryCurrentRendererIntegerMESA, (int attribute, unsigned int *value)) FUNC(const char *, glXQueryRendererStringMESA, (void /*Display*/ *dpy, int screen, int renderer, int attribute)) FUNC(const char *, glXQueryCurrentRendererStringMESA, (int attribute)) Index: ps/trunk/source/lib/external_libraries/openal.h =================================================================== --- ps/trunk/source/lib/external_libraries/openal.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/openal.h (revision 19899) @@ -1,50 +1,50 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bring in OpenAL header+library, with compatibility fixes */ #ifndef INCLUDED_OPENAL #define INCLUDED_OPENAL #if OS_MACOSX # include # include #else # include # include #endif // ALC strings (e.g. device and extension names) are typed differently // between OpenAL specifications #ifdef AL_VERSION_1_1 typedef ALCchar* alcString; #else typedef ALCubyte* alcString; #endif #if MSC_VERSION # pragma comment(lib, "openal32.lib") #endif #endif // #ifndef INCLUDED_OPENAL Index: ps/trunk/source/lib/external_libraries/libsdl_fwd.h =================================================================== --- ps/trunk/source/lib/external_libraries/libsdl_fwd.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/libsdl_fwd.h (revision 19899) @@ -1,41 +1,41 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * forward declaration of SDL_Event */ #ifndef INCLUDED_SDL_FWD #define INCLUDED_SDL_FWD // 2006-08-26 SDL is dragged into 6 of our 7 static library components. // it must be specified in each of their "extern_libs" so that the // include path is set and can be found. // // obviously this is bad, so we work around the root cause. mostly only // SDL_Event is needed. unfortunately it cannot be forward-declared, // because it is a union (regrettable design mistake). // we fix this by wrapping it in a struct, which can safely be // forward-declared and used for SDL_Event_* parameters. struct SDL_Event_; #endif // #ifndef INCLUDED_SDL_FWD Index: ps/trunk/source/lib/external_libraries/libsdl.h =================================================================== --- ps/trunk/source/lib/external_libraries/libsdl.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/libsdl.h (revision 19899) @@ -1,56 +1,56 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * SDL header; uses emulator on Windows, otherwise libsdl. */ #ifndef INCLUDED_SDL #define INCLUDED_SDL #include "lib/external_libraries/libsdl_fwd.h" # include "SDL.h" # include "SDL_thread.h" #if !SDL_VERSION_ATLEAST(2,0,2) #error You are using an old libsdl release. At least libsdl2 >= 2.0.2 is required. #endif // if the compiler doesn't support inlining, this header will pull // in static bswap routines. doesn't matter - modern compilers // will strip them if unused, and this is more convenient than // another header that toggles between wsdl and SDL_endian.h. # include "SDL_endian.h" # if MSC_VERSION # pragma comment(lib, "SDL2") # pragma comment(lib, "SDL2main") # endif // complete definition of our forward-declared SDL_Event (see sdl_fwd.h) struct SDL_Event_ { SDL_Event ev; }; #endif // INCLUDED_SDL Index: ps/trunk/source/lib/external_libraries/suppress_boost_warnings.h =================================================================== --- ps/trunk/source/lib/external_libraries/suppress_boost_warnings.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/suppress_boost_warnings.h (revision 19899) @@ -1,48 +1,48 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // rationale: boost is only included from the PCH, but warnings are still // raised when templates are instantiated (e.g. vfs_lookup.cpp), // so include this header from such source files as well. #include "lib/sysdep/compiler.h" // ICC_VERSION #include "lib/sysdep/arch.h" // ARCH_IA32 #if MSC_VERSION # pragma warning(disable:4710) // function not inlined #endif #if ICC_VERSION # pragma warning(push) # pragma warning(disable:82) // storage class is not first # pragma warning(disable:193) // zero used for undefined preprocessing identifier # pragma warning(disable:304) // access control not specified # pragma warning(disable:367) // duplicate friend declaration # pragma warning(disable:444) // destructor for base class is not virtual # pragma warning(disable:522) // function redeclared inline after being called # pragma warning(disable:811) // exception specification for implicitly declared virtual function is incompatible with that of overridden function # pragma warning(disable:1879) // unimplemented pragma ignored # pragma warning(disable:2270) // the declaration of the copy assignment operator has been suppressed # pragma warning(disable:2273) // the declaration of the copy constructor has been suppressed # if ARCH_IA32 # pragma warning(disable:693) // calling convention specified here is ignored # endif #endif Index: ps/trunk/source/lib/external_libraries/zlib.h =================================================================== --- ps/trunk/source/lib/external_libraries/zlib.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/zlib.h (revision 19899) @@ -1,54 +1,54 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bring in ZLib header+library, with compatibility fixes */ #ifndef INCLUDED_ZLIB #define INCLUDED_ZLIB // zlib.h -> zconf.h includes , which causes conflicts. // define the include guard to prevent it from actually being included and // then manually define the few things that are actually needed. #define _WINDOWS_ // windows.h include guard #ifndef WINAPI # define WINAPI __stdcall # define WINAPIV __cdecl #endif #ifndef ZLIB_STATIC #define ZLIB_DLL #endif #include // automatically link against the required library #if MSC_VERSION # ifdef NDEBUG # pragma comment(lib, "zlib1.lib") # else # pragma comment(lib, "zlib1d.lib") # endif #endif #endif // #ifndef INCLUDED_ZLIB Index: ps/trunk/source/lib/file/archive/archive_zip.h =================================================================== --- ps/trunk/source/lib/file/archive/archive_zip.h (revision 19898) +++ ps/trunk/source/lib/file/archive/archive_zip.h (revision 19899) @@ -1,42 +1,42 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * archive backend for Zip files. */ #ifndef INCLUDED_ARCHIVE_ZIP #define INCLUDED_ARCHIVE_ZIP #include "lib/file/archive/archive.h" /** * @return 0 if opening the archive failed (e.g. because an external program is holding on to it) **/ LIB_API PIArchiveReader CreateArchiveReader_Zip(const OsPath& archivePathname); /** * @return 0 if opening the archive failed (e.g. because an external program is holding on to it) **/ LIB_API PIArchiveWriter CreateArchiveWriter_Zip(const OsPath& archivePathname, bool noDeflate); #endif // #ifndef INCLUDED_ARCHIVE_ZIP Index: ps/trunk/source/lib/file/archive/codec_zlib.h =================================================================== --- ps/trunk/source/lib/file/archive/codec_zlib.h (revision 19898) +++ ps/trunk/source/lib/file/archive/codec_zlib.h (revision 19899) @@ -1,32 +1,32 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_CODEC_ZLIB #define INCLUDED_CODEC_ZLIB #include "lib/file/archive/codec.h" extern PICodec CreateCodec_ZLibNone(); extern PICodec CreateCompressor_ZLibDeflate(); extern PICodec CreateDecompressor_ZLibDeflate(); #endif // INCLUDED_CODEC_ZLIB Index: ps/trunk/source/lib/file/archive/disabled_tests/test_zip.h =================================================================== --- ps/trunk/source/lib/file/archive/disabled_tests/test_zip.h (revision 19898) +++ ps/trunk/source/lib/file/archive/disabled_tests/test_zip.h (revision 19899) @@ -1,32 +1,32 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include #include "lib/res/file/archive/zip.h" class TestZip : public CxxTest::TestSuite { public: }; Index: ps/trunk/source/lib/file/common/file_loader.h =================================================================== --- ps/trunk/source/lib/file/common/file_loader.h (revision 19898) +++ ps/trunk/source/lib/file/common/file_loader.h (revision 19899) @@ -1,41 +1,41 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_FILE_LOADER #define INCLUDED_FILE_LOADER #include "lib/os_path.h" struct IFileLoader { virtual ~IFileLoader(); virtual size_t Precedence() const = 0; virtual wchar_t LocationCode() const = 0; virtual OsPath Path() const = 0; virtual Status Load(const OsPath& name, const shared_ptr& buf, size_t size) const = 0; }; typedef shared_ptr PIFileLoader; #endif // #ifndef INCLUDED_FILE_LOADER Index: ps/trunk/source/lib/file/common/real_directory.h =================================================================== --- ps/trunk/source/lib/file/common/real_directory.h (revision 19898) +++ ps/trunk/source/lib/file/common/real_directory.h (revision 19899) @@ -1,77 +1,77 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_REAL_DIRECTORY #define INCLUDED_REAL_DIRECTORY #include "lib/file/common/file_loader.h" #include "lib/sysdep/dir_watch.h" class RealDirectory : public IFileLoader { NONCOPYABLE(RealDirectory); public: RealDirectory(const OsPath& path, size_t priority, size_t flags); size_t Priority() const { return m_priority; } size_t Flags() const { return m_flags; } // IFileLoader virtual size_t Precedence() const; virtual wchar_t LocationCode() const; virtual OsPath Path() const { return m_path; } virtual Status Load(const OsPath& name, const shared_ptr& buf, size_t size) const; Status Store(const OsPath& name, const shared_ptr& fileContents, size_t size); void Watch(); private: // note: paths are relative to the root directory, so storing the // entire path instead of just the portion relative to the mount point // is not all too wasteful. const OsPath m_path; const size_t m_priority; const size_t m_flags; // note: watches are needed in each directory because some APIs // (e.g. FAM) cannot watch entire trees with one call. PDirWatch m_watch; }; typedef shared_ptr PRealDirectory; extern PRealDirectory CreateRealSubdirectory(const PRealDirectory& realDirectory, const OsPath& subdirectoryName); #endif // #ifndef INCLUDED_REAL_DIRECTORY Index: ps/trunk/source/lib/file/disabled_tests/test_file_cache.h =================================================================== --- ps/trunk/source/lib/file/disabled_tests/test_file_cache.h (revision 19898) +++ ps/trunk/source/lib/file/disabled_tests/test_file_cache.h (revision 19899) @@ -1,73 +1,73 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/res/file/file_cache.h" #include "lib/rand.h" class TestFileCache : public CxxTest::TestSuite { enum { TEST_ALLOC_TOTAL = 100*1000*1000 }; public: void test_cache_allocator() { // allocated address -> its size typedef std::map AllocMap; AllocMap allocations; // put allocator through its paces by allocating several times // its capacity (this ensures memory is reused) srand(1); size_t total_size_used = 0; while(total_size_used < TEST_ALLOC_TOTAL) { size_t size = rand(1, TEST_ALLOC_TOTAL/16); total_size_used += size; void* p; // until successful alloc: for(;;) { p = file_cache_allocator_alloc(size); if(p) break; // out of room - remove a previous allocation // .. choose one at random size_t chosen_idx = (size_t)rand(0, (size_t)allocations.size()); AllocMap::iterator it = allocations.begin(); for(; chosen_idx != 0; chosen_idx--) ++it; file_cache_allocator_free(it->first, it->second); allocations.erase(it); } // must not already have been allocated TS_ASSERT_EQUALS(allocations.find(p), allocations.end()); allocations[p] = size; } // reset to virginal state // note: even though everything has now been freed, this is // necessary since the freelists may be a bit scattered already. file_cache_allocator_reset(); } }; Index: ps/trunk/source/lib/file/file_system.cpp =================================================================== --- ps/trunk/source/lib/file/file_system.cpp (revision 19898) +++ ps/trunk/source/lib/file/file_system.cpp (revision 19899) @@ -1,193 +1,193 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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. */ /* * higher-level interface on top of sysdep/filesystem.h */ #include "precompiled.h" #include "lib/file/file_system.h" #include #include #include #include "lib/sysdep/filesystem.h" bool DirectoryExists(const OsPath& path) { WDIR* dir = wopendir(path); if(dir) { wclosedir(dir); return true; } return false; } bool FileExists(const OsPath& pathname) { struct stat s; const bool exists = wstat(pathname, &s) == 0; return exists; } u64 FileSize(const OsPath& pathname) { struct stat s; ENSURE(wstat(pathname, &s) == 0); return s.st_size; } Status GetFileInfo(const OsPath& pathname, CFileInfo* pPtrInfo) { errno = 0; struct stat s; memset(&s, 0, sizeof(s)); if(wstat(pathname, &s) != 0) WARN_RETURN(StatusFromErrno()); *pPtrInfo = CFileInfo(pathname.Filename(), s.st_size, s.st_mtime); return INFO::OK; } struct DirDeleter { void operator()(WDIR* osDir) const { const int ret = wclosedir(osDir); ENSURE(ret == 0); } }; Status GetDirectoryEntries(const OsPath& path, CFileInfos* files, DirectoryNames* subdirectoryNames) { // open directory errno = 0; WDIR* pDir = wopendir(path); if(!pDir) return StatusFromErrno(); // NOWARN shared_ptr osDir(pDir, DirDeleter()); for(;;) { errno = 0; struct wdirent* osEnt = wreaddir(osDir.get()); if(!osEnt) { // no error, just no more entries to return if(!errno) return INFO::OK; WARN_RETURN(StatusFromErrno()); } for(size_t i = 0; osEnt->d_name[i] != '\0'; i++) RETURN_STATUS_IF_ERR(Path::Validate(osEnt->d_name[i])); const OsPath name(osEnt->d_name); // get file information (mode, size, mtime) struct stat s; #if OS_WIN // .. return wdirent directly (much faster than calling stat). RETURN_STATUS_IF_ERR(wreaddir_stat_np(osDir.get(), &s)); #else // .. call regular stat(). errno = 0; const OsPath pathname = path / name; if(wstat(pathname, &s) != 0) WARN_RETURN(StatusFromErrno()); #endif if(files && S_ISREG(s.st_mode)) files->push_back(CFileInfo(name, s.st_size, s.st_mtime)); else if(subdirectoryNames && S_ISDIR(s.st_mode) && name != L"." && name != L"..") subdirectoryNames->push_back(name); } } Status CreateDirectories(const OsPath& path, mode_t mode, bool breakpoint) { if(path.empty()) return INFO::OK; struct stat s; if(wstat(path, &s) == 0) { if(!S_ISDIR(s.st_mode)) // encountered a file WARN_RETURN(ERR::FAIL); return INFO::OK; } // If we were passed a path ending with '/', strip the '/' now so that // we can consistently use Parent to find parent directory names if(path.IsDirectory()) return CreateDirectories(path.Parent(), mode, breakpoint); RETURN_STATUS_IF_ERR(CreateDirectories(path.Parent(), mode)); errno = 0; if(wmkdir(path, mode) != 0) { debug_printf("CreateDirectories: failed to mkdir %s (mode %d)\n", path.string8().c_str(), mode); if (breakpoint) WARN_RETURN(StatusFromErrno()); else return StatusFromErrno(); } return INFO::OK; } Status DeleteDirectory(const OsPath& path) { // note: we have to recursively empty the directory before it can // be deleted (required by Windows and POSIX rmdir()). CFileInfos files; DirectoryNames subdirectoryNames; RETURN_STATUS_IF_ERR(GetDirectoryEntries(path, &files, &subdirectoryNames)); // delete files for(size_t i = 0; i < files.size(); i++) { const OsPath pathname = path / files[i].Name(); errno = 0; if(wunlink(pathname) != 0) WARN_RETURN(StatusFromErrno()); } // recurse over subdirectoryNames for(size_t i = 0; i < subdirectoryNames.size(); i++) RETURN_STATUS_IF_ERR(DeleteDirectory(path / subdirectoryNames[i])); errno = 0; if(wrmdir(path) != 0) WARN_RETURN(StatusFromErrno()); return INFO::OK; } Index: ps/trunk/source/lib/external_libraries/powrprof.h =================================================================== --- ps/trunk/source/lib/external_libraries/powrprof.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/powrprof.h (revision 19899) @@ -1,160 +1,160 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bring in powrprof library. */ #ifndef INCLUDED_POWRPROF #define INCLUDED_POWRPROF #include "lib/sysdep/os/win/win.h" #include // the VC7 headers are missing some parts: // MinGW headers are already correct; only change on VC #if MSC_VERSION && MSC_VERSION < 1400 #ifndef NTSTATUS #define NTSTATUS long #endif #ifndef STATUS_SUCCESS #define STATUS_SUCCESS 0 #endif #if WINVER < 0x500 typedef enum { SystemPowerPolicyAc, SystemPowerPolicyDc, VerifySystemPolicyAc, VerifySystemPolicyDc, SystemPowerCapabilities, SystemBatteryState, SystemPowerStateHandler, ProcessorStateHandler, SystemPowerPolicyCurrent, AdministratorPowerPolicy, SystemReserveHiberFile, ProcessorInformation, SystemPowerInformation, ProcessorStateHandler2, LastWakeTime, // Compare with KeQueryInterruptTime() LastSleepTime, // Compare with KeQueryInterruptTime() SystemExecutionState, SystemPowerStateNotifyHandler, ProcessorPowerPolicyAc, ProcessorPowerPolicyDc, VerifyProcessorPowerPolicyAc, VerifyProcessorPowerPolicyDc, ProcessorPowerPolicyCurrent, SystemPowerStateLogging, SystemPowerLoggingEntry } POWER_INFORMATION_LEVEL; typedef struct { DWORD Granularity; DWORD Capacity; } BATTERY_REPORTING_SCALE, *PBATTERY_REPORTING_SCALE; typedef enum _SYSTEM_POWER_STATE { PowerSystemUnspecified = 0, PowerSystemWorking = 1, PowerSystemSleeping1 = 2, PowerSystemSleeping2 = 3, PowerSystemSleeping3 = 4, PowerSystemHibernate = 5, PowerSystemShutdown = 6, PowerSystemMaximum = 7 } SYSTEM_POWER_STATE, *PSYSTEM_POWER_STATE; typedef struct { // Misc supported system features BOOLEAN PowerButtonPresent; BOOLEAN SleepButtonPresent; BOOLEAN LidPresent; BOOLEAN SystemS1; BOOLEAN SystemS2; BOOLEAN SystemS3; BOOLEAN SystemS4; // hibernate BOOLEAN SystemS5; // off BOOLEAN HiberFilePresent; BOOLEAN FullWake; BOOLEAN VideoDimPresent; BOOLEAN ApmPresent; BOOLEAN UpsPresent; // Processors BOOLEAN ThermalControl; BOOLEAN ProcessorThrottle; BYTE ProcessorMinThrottle; BYTE ProcessorMaxThrottle; BYTE spare2[4]; // Disk BOOLEAN DiskSpinDown; BYTE spare3[8]; // System Battery BOOLEAN SystemBatteriesPresent; BOOLEAN BatteriesAreShortTerm; BATTERY_REPORTING_SCALE BatteryScale[3]; // Wake SYSTEM_POWER_STATE AcOnLineWake; SYSTEM_POWER_STATE SoftLidWake; SYSTEM_POWER_STATE RtcWake; SYSTEM_POWER_STATE MinDeviceWakeState; // note this may change on driver load SYSTEM_POWER_STATE DefaultLowLatencyWake; } SYSTEM_POWER_CAPABILITIES, *PSYSTEM_POWER_CAPABILITIES; #endif // WINVER < 0x500 typedef struct _SYSTEM_POWER_INFORMATION { ULONG MaxIdlenessAllowed; ULONG Idleness; ULONG TimeRemaining; UCHAR CoolingMode; } SYSTEM_POWER_INFORMATION, *PSYSTEM_POWER_INFORMATION; // SPI.CoolingMode #define PO_TZ_INVALID_MODE 0 // The system does not support CPU throttling, // or there is no thermal zone defined [..] #endif // #if MSC_VERSION // neither VC7.1 nor MinGW define this typedef struct _PROCESSOR_POWER_INFORMATION { ULONG Number; ULONG MaxMhz; ULONG CurrentMhz; ULONG MhzLimit; ULONG MaxIdleState; ULONG CurrentIdleState; } PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; #endif // #ifndef INCLUDED_POWRPROF Index: ps/trunk/source/lib/external_libraries/wxwidgets.h =================================================================== --- ps/trunk/source/lib/external_libraries/wxwidgets.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/wxwidgets.h (revision 19899) @@ -1,57 +1,57 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bring in wxWidgets headers, with compatibility fixes */ #ifndef INCLUDED_WXWIDGETS #define INCLUDED_WXWIDGETS // prevent wxWidgets from pulling in windows.h - it's mostly unnecessary // and interferes with posix_sock's declarations. #define _WINDOWS_ // include guard // manually define what is actually needed from windows.h: struct HINSTANCE__ { int unused; }; typedef struct HINSTANCE__* HINSTANCE; // definition as if STRICT were #defined #include "wx/wxprec.h" #include "wx/file.h" #include "wx/ffile.h" #include "wx/filename.h" #include "wx/mimetype.h" #include "wx/statline.h" #include "wx/debugrpt.h" #ifdef __WXMSW__ #include "wx/evtloop.h" // for SetCriticalWindow() #endif // __WXMSW__ // note: wxWidgets already does #pragma comment(lib) to add link targets. #endif // #ifndef INCLUDED_WXWIDGETS Index: ps/trunk/source/lib/file/archive/archive_zip.cpp =================================================================== --- ps/trunk/source/lib/file/archive/archive_zip.cpp (revision 19898) +++ ps/trunk/source/lib/file/archive/archive_zip.cpp (revision 19899) @@ -1,755 +1,755 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * archive backend for Zip files. */ #include "precompiled.h" #include "lib/file/archive/archive_zip.h" #include #include #include "lib/utf8.h" #include "lib/bits.h" #include "lib/byte_order.h" #include "lib/allocators/pool.h" #include "lib/sysdep/filesystem.h" #include "lib/file/archive/archive.h" #include "lib/file/archive/codec_zlib.h" #include "lib/file/archive/stream.h" #include "lib/file/file.h" #include "lib/file/io/io.h" //----------------------------------------------------------------------------- // timestamp conversion: DOS FAT <-> Unix time_t //----------------------------------------------------------------------------- static time_t time_t_from_FAT(u32 fat_timedate) { const u32 fat_time = bits(fat_timedate, 0, 15); const u32 fat_date = bits(fat_timedate, 16, 31); struct tm t; // struct tm format: t.tm_sec = bits(fat_time, 0,4) * 2; // [0,59] t.tm_min = bits(fat_time, 5,10); // [0,59] t.tm_hour = bits(fat_time, 11,15); // [0,23] t.tm_mday = bits(fat_date, 0,4); // [1,31] t.tm_mon = bits(fat_date, 5,8) - 1; // [0,11] t.tm_year = bits(fat_date, 9,15) + 80; // since 1900 t.tm_isdst = -1; // unknown - let libc determine // otherwise: totally bogus, and at the limit of 32-bit time_t ENSURE(t.tm_year < 138); time_t ret = mktime(&t); ENSURE(ret != (time_t)-1); // mktime shouldn't fail return ret; } static u32 FAT_from_time_t(time_t time) { // (values are adjusted for DST) struct tm* t = localtime(&time); const u16 fat_time = u16( (t->tm_sec/2) | // 5 (u16(t->tm_min) << 5) | // 6 (u16(t->tm_hour) << 11) // 5 ); const u16 fat_date = u16( (t->tm_mday) | // 5 (u16(t->tm_mon+1) << 5) | // 4 (u16(t->tm_year-80) << 9) // 7 ); u32 fat_timedate = u32_from_u16(fat_date, fat_time); return fat_timedate; } //----------------------------------------------------------------------------- // Zip archive definitions //----------------------------------------------------------------------------- static const u32 cdfh_magic = FOURCC_LE('P','K','\1','\2'); static const u32 lfh_magic = FOURCC_LE('P','K','\3','\4'); static const u32 ecdr_magic = FOURCC_LE('P','K','\5','\6'); enum ZipMethod { ZIP_METHOD_NONE = 0, ZIP_METHOD_DEFLATE = 8 }; #pragma pack(push, 1) class LFH { public: void Init(const CFileInfo& fileInfo, off_t csize, ZipMethod method, u32 checksum, const Path& pathname) { const std::string pathnameUTF8 = utf8_from_wstring(pathname.string()); const size_t pathnameSize = pathnameUTF8.length(); m_magic = lfh_magic; m_x1 = to_le16(0); m_flags = to_le16(0); m_method = to_le16(u16_from_larger(method)); m_fat_mtime = to_le32(FAT_from_time_t(fileInfo.MTime())); m_crc = to_le32(checksum); m_csize = to_le32(u32_from_larger(csize)); m_usize = to_le32(u32_from_larger(fileInfo.Size())); m_fn_len = to_le16(u16_from_larger(pathnameSize)); m_e_len = to_le16(0); memcpy((char*)this + sizeof(LFH), pathnameUTF8.c_str(), pathnameSize); } size_t Size() const { ENSURE(m_magic == lfh_magic); size_t size = sizeof(LFH); size += read_le16(&m_fn_len); size += read_le16(&m_e_len); // note: LFH doesn't have a comment field! return size; } private: u32 m_magic; u16 m_x1; // version needed u16 m_flags; u16 m_method; u32 m_fat_mtime; // last modified time (DOS FAT format) u32 m_crc; u32 m_csize; u32 m_usize; u16 m_fn_len; u16 m_e_len; }; cassert(sizeof(LFH) == 30); class CDFH { public: void Init(const CFileInfo& fileInfo, off_t ofs, off_t csize, ZipMethod method, u32 checksum, const Path& pathname, size_t slack) { const std::string pathnameUTF8 = utf8_from_wstring(pathname.string()); const size_t pathnameLength = pathnameUTF8.length(); m_magic = cdfh_magic; m_x1 = to_le32(0); m_flags = to_le16(0); m_method = to_le16(u16_from_larger(method)); m_fat_mtime = to_le32(FAT_from_time_t(fileInfo.MTime())); m_crc = to_le32(checksum); m_csize = to_le32(u32_from_larger(csize)); m_usize = to_le32(u32_from_larger(fileInfo.Size())); m_fn_len = to_le16(u16_from_larger(pathnameLength)); m_e_len = to_le16(0); m_c_len = to_le16(u16_from_larger((size_t)slack)); m_x2 = to_le32(0); m_x3 = to_le32(0); m_lfh_ofs = to_le32(u32_from_larger(ofs)); memcpy((char*)this + sizeof(CDFH), pathnameUTF8.c_str(), pathnameLength); } Path Pathname() const { const size_t length = (size_t)read_le16(&m_fn_len); const char* pathname = (const char*)this + sizeof(CDFH); // not 0-terminated! return Path(std::string(pathname, length)); } off_t HeaderOffset() const { return read_le32(&m_lfh_ofs); } off_t USize() const { return (off_t)read_le32(&m_usize); } off_t CSize() const { return (off_t)read_le32(&m_csize); } ZipMethod Method() const { return (ZipMethod)read_le16(&m_method); } u32 Checksum() const { return read_le32(&m_crc); } time_t MTime() const { const u32 fat_mtime = read_le32(&m_fat_mtime); return time_t_from_FAT(fat_mtime); } size_t Size() const { size_t size = sizeof(CDFH); size += read_le16(&m_fn_len); size += read_le16(&m_e_len); size += read_le16(&m_c_len); return size; } private: u32 m_magic; u32 m_x1; // versions u16 m_flags; u16 m_method; u32 m_fat_mtime; // last modified time (DOS FAT format) u32 m_crc; u32 m_csize; u32 m_usize; u16 m_fn_len; u16 m_e_len; u16 m_c_len; u32 m_x2; // spanning u32 m_x3; // attributes u32 m_lfh_ofs; }; cassert(sizeof(CDFH) == 46); class ECDR { public: void Init(size_t cd_numEntries, off_t cd_ofs, size_t cd_size) { m_magic = ecdr_magic; m_diskNum = to_le16(0); m_cd_diskNum = to_le16(0); m_cd_numEntriesOnDisk = to_le16(u16_from_larger(cd_numEntries)); m_cd_numEntries = m_cd_numEntriesOnDisk; m_cd_size = to_le32(u32_from_larger(cd_size)); m_cd_ofs = to_le32(u32_from_larger(cd_ofs)); m_comment_len = to_le16(0); } void Decompose(size_t& cd_numEntries, off_t& cd_ofs, size_t& cd_size) const { cd_numEntries = (size_t)read_le16(&m_cd_numEntries); cd_ofs = (off_t)read_le32(&m_cd_ofs); cd_size = (size_t)read_le32(&m_cd_size); } private: u32 m_magic; u16 m_diskNum; u16 m_cd_diskNum; u16 m_cd_numEntriesOnDisk; u16 m_cd_numEntries; u32 m_cd_size; u32 m_cd_ofs; u16 m_comment_len; }; cassert(sizeof(ECDR) == 22); #pragma pack(pop) //----------------------------------------------------------------------------- // ArchiveFile_Zip //----------------------------------------------------------------------------- class ArchiveFile_Zip : public IArchiveFile { public: ArchiveFile_Zip(const PFile& file, off_t ofs, off_t csize, u32 checksum, ZipMethod method) : m_file(file), m_ofs(ofs) , m_csize(csize), m_checksum(checksum), m_method((u16)method) , m_flags(NeedsFixup) { } virtual size_t Precedence() const { return 2u; } virtual wchar_t LocationCode() const { return 'A'; } virtual OsPath Path() const { return m_file->Pathname(); } virtual Status Load(const OsPath& UNUSED(name), const shared_ptr& buf, size_t size) const { AdjustOffset(); PICodec codec; switch(m_method) { case ZIP_METHOD_NONE: codec = CreateCodec_ZLibNone(); break; case ZIP_METHOD_DEFLATE: codec = CreateDecompressor_ZLibDeflate(); break; default: WARN_RETURN(ERR::ARCHIVE_UNKNOWN_METHOD); } Stream stream(codec); stream.SetOutputBuffer(buf.get(), size); io::Operation op(*m_file.get(), 0, m_csize, m_ofs); StreamFeeder streamFeeder(stream); RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder)); RETURN_STATUS_IF_ERR(stream.Finish()); #if CODEC_COMPUTE_CHECKSUM ENSURE(m_checksum == stream.Checksum()); #endif return INFO::OK; } private: enum Flags { // indicates m_ofs points to a "local file header" instead of // the file data. a fixup routine is called when reading the file; // it skips past the LFH and clears this flag. // this is somewhat of a hack, but vital to archive open performance. // without it, we'd have to scan through the entire archive file, // which can take *seconds*. // (we cannot use the information in CDFH, because its 'extra' field // has been observed to differ from that of the LFH) // since we read the LFH right before the rest of the file, the block // cache will absorb the IO cost. NeedsFixup = 1 }; struct LFH_Copier { LFH_Copier(u8* lfh_dst, size_t lfh_bytes_remaining) : lfh_dst(lfh_dst), lfh_bytes_remaining(lfh_bytes_remaining) { } // this code grabs an LFH struct from file block(s) that are // passed to the callback. usually, one call copies the whole thing, // but the LFH may straddle a block boundary. // // rationale: this allows using temp buffers for zip_fixup_lfh, // which avoids involving the file buffer manager and thus // avoids cluttering the trace and cache contents. Status operator()(const u8* block, size_t size) const { ENSURE(size <= lfh_bytes_remaining); memcpy(lfh_dst, block, size); lfh_dst += size; lfh_bytes_remaining -= size; return INFO::OK; } mutable u8* lfh_dst; mutable size_t lfh_bytes_remaining; }; /** * fix up m_ofs (adjust it to point to cdata instead of the LFH). * * note: we cannot use CDFH filename and extra field lengths to skip * past LFH since that may not mirror CDFH (has happened). * * this is called at file-open time instead of while mounting to * reduce seeks: since reading the file will typically follow, the * block cache entirely absorbs the IO cost. **/ void AdjustOffset() const { if(!(m_flags & NeedsFixup)) return; m_flags &= ~NeedsFixup; // performance note: this ends up reading one file block, which is // only in the block cache if the file starts in the same block as a // previously read file (i.e. both are small). LFH lfh; io::Operation op(*m_file.get(), 0, sizeof(LFH), m_ofs); if(io::Run(op, io::Parameters(), LFH_Copier((u8*)&lfh, sizeof(LFH))) == INFO::OK) m_ofs += (off_t)lfh.Size(); } PFile m_file; // all relevant LFH/CDFH fields not covered by CFileInfo mutable off_t m_ofs; off_t m_csize; u32 m_checksum; u16 m_method; mutable u16 m_flags; }; //----------------------------------------------------------------------------- // ArchiveReader_Zip //----------------------------------------------------------------------------- class ArchiveReader_Zip : public IArchiveReader { public: ArchiveReader_Zip(const OsPath& pathname) : m_file(new File(pathname, O_RDONLY)) { CFileInfo fileInfo; GetFileInfo(pathname, &fileInfo); m_fileSize = fileInfo.Size(); const size_t minFileSize = sizeof(LFH)+sizeof(CDFH)+sizeof(ECDR); ENSURE(m_fileSize >= off_t(minFileSize)); } virtual Status ReadEntries(ArchiveEntryCallback cb, uintptr_t cbData) { // locate and read Central Directory off_t cd_ofs = 0; size_t cd_numEntries = 0; size_t cd_size = 0; RETURN_STATUS_IF_ERR(LocateCentralDirectory(m_file, m_fileSize, cd_ofs, cd_numEntries, cd_size)); UniqueRange buf(io::Allocate(cd_size)); io::Operation op(*m_file.get(), buf.get(), cd_size, cd_ofs); RETURN_STATUS_IF_ERR(io::Run(op)); // iterate over Central Directory const u8* pos = (const u8*)buf.get(); for(size_t i = 0; i < cd_numEntries; i++) { // scan for next CDFH CDFH* cdfh = (CDFH*)FindRecord((const u8*)buf.get(), cd_size, pos, cdfh_magic, sizeof(CDFH)); if(!cdfh) WARN_RETURN(ERR::CORRUPTED); const Path relativePathname(cdfh->Pathname()); if(!relativePathname.IsDirectory()) { const OsPath name = relativePathname.Filename(); CFileInfo fileInfo(name, cdfh->USize(), cdfh->MTime()); shared_ptr archiveFile(new ArchiveFile_Zip(m_file, cdfh->HeaderOffset(), cdfh->CSize(), cdfh->Checksum(), cdfh->Method())); cb(relativePathname, fileInfo, archiveFile, cbData); } pos += cdfh->Size(); } return INFO::OK; } private: /** * Scan buffer for a Zip file record. * * @param buf * @param size * @param start position within buffer * @param magic signature of record * @param recordSize size of record (including signature) * @return pointer to record within buffer or 0 if not found. **/ static const u8* FindRecord(const u8* buf, size_t size, const u8* start, u32 magic, size_t recordSize) { // (don't use as the counter - otherwise we can't tell if // scanning within the buffer was necessary.) for(const u8* p = start; p <= buf+size-recordSize; p++) { // found it if(*(u32*)p == magic) { ENSURE(p == start); // otherwise, the archive is a bit broken return p; } } // passed EOF, didn't find it. // note: do not warn - this happens in the initial ECDR search at // EOF if the archive contains a comment field. return 0; } // search for ECDR in the last bytes of the file. // if found, fill with a copy of the (little-endian) ECDR and // return INFO::OK, otherwise IO error or ERR::CORRUPTED. static Status ScanForEcdr(const PFile& file, off_t fileSize, u8* buf, size_t maxScanSize, size_t& cd_numEntries, off_t& cd_ofs, size_t& cd_size) { // don't scan more than the entire file const size_t scanSize = std::min(maxScanSize, size_t(fileSize)); // read desired chunk of file into memory const off_t ofs = fileSize - off_t(scanSize); io::Operation op(*file.get(), buf, scanSize, ofs); RETURN_STATUS_IF_ERR(io::Run(op)); // look for ECDR in buffer const ECDR* ecdr = (const ECDR*)FindRecord(buf, scanSize, buf, ecdr_magic, sizeof(ECDR)); if(!ecdr) return INFO::CANNOT_HANDLE; ecdr->Decompose(cd_numEntries, cd_ofs, cd_size); return INFO::OK; } static Status LocateCentralDirectory(const PFile& file, off_t fileSize, off_t& cd_ofs, size_t& cd_numEntries, size_t& cd_size) { const size_t maxScanSize = 66000u; // see below UniqueRange buf(io::Allocate(maxScanSize)); // expected case: ECDR at EOF; no file comment Status ret = ScanForEcdr(file, fileSize, (u8*)buf.get(), sizeof(ECDR), cd_numEntries, cd_ofs, cd_size); if(ret == INFO::OK) return INFO::OK; // worst case: ECDR precedes 64 KiB of file comment ret = ScanForEcdr(file, fileSize, (u8*)buf.get(), maxScanSize, cd_numEntries, cd_ofs, cd_size); if(ret == INFO::OK) return INFO::OK; // both ECDR scans failed - this is not a valid Zip file. io::Operation op(*file.get(), buf.get(), sizeof(LFH)); RETURN_STATUS_IF_ERR(io::Run(op)); // the Zip file has an LFH but lacks an ECDR. this can happen if // the user hard-exits while an archive is being written. // notes: // - return ERR::CORRUPTED so VFS will not include this file. // - we could work around this by scanning all LFHs, but won't bother // because it'd be slow. // - do not warn - the corrupt archive will be deleted on next // successful archive builder run anyway. if(FindRecord((const u8*)buf.get(), sizeof(LFH), (const u8*)buf.get(), lfh_magic, sizeof(LFH))) return ERR::CORRUPTED; // NOWARN // totally bogus else WARN_RETURN(ERR::ARCHIVE_UNKNOWN_FORMAT); } PFile m_file; off_t m_fileSize; }; PIArchiveReader CreateArchiveReader_Zip(const OsPath& archivePathname) { try { return PIArchiveReader(new ArchiveReader_Zip(archivePathname)); } catch(Status) { return PIArchiveReader(); } } //----------------------------------------------------------------------------- // ArchiveWriter_Zip //----------------------------------------------------------------------------- class ArchiveWriter_Zip : public IArchiveWriter { public: ArchiveWriter_Zip(const OsPath& archivePathname, bool noDeflate) : m_file(new File(archivePathname, O_WRONLY)), m_fileSize(0) , m_numEntries(0), m_noDeflate(noDeflate) { THROW_STATUS_IF_ERR(pool_create(&m_cdfhPool, 10*MiB, 0)); } ~ArchiveWriter_Zip() { // append an ECDR to the CDFH list (this allows us to // write out both to the archive file in one burst) const size_t cd_size = m_cdfhPool.da.pos; ECDR* ecdr = (ECDR*)pool_alloc(&m_cdfhPool, sizeof(ECDR)); if(!ecdr) std::terminate(); const off_t cd_ofs = m_fileSize; ecdr->Init(m_numEntries, cd_ofs, cd_size); if(write(m_file->Descriptor(), m_cdfhPool.da.base, cd_size+sizeof(ECDR)) < 0) DEBUG_WARN_ERR(ERR::IO); // no way to return error code (void)pool_destroy(&m_cdfhPool); } Status AddFile(const OsPath& pathname, const OsPath& pathnameInArchive) { CFileInfo fileInfo; RETURN_STATUS_IF_ERR(GetFileInfo(pathname, &fileInfo)); PFile file(new File); RETURN_STATUS_IF_ERR(file->Open(pathname, O_RDONLY)); return AddFileOrMemory(fileInfo, pathnameInArchive, file, NULL); } Status AddMemory(const u8* data, size_t size, time_t mtime, const OsPath& pathnameInArchive) { CFileInfo fileInfo(pathnameInArchive, size, mtime); return AddFileOrMemory(fileInfo, pathnameInArchive, PFile(), data); } Status AddFileOrMemory(const CFileInfo& fileInfo, const OsPath& pathnameInArchive, const PFile& file, const u8* data) { ENSURE((file && !data) || (data && !file)); const off_t usize = fileInfo.Size(); // skip 0-length files. // rationale: zip.cpp needs to determine whether a CDFH entry is // a file or directory (the latter are written by some programs but // not needed - they'd only pollute the file table). // it looks like checking for usize=csize=0 is the safest way - // relying on file attributes (which are system-dependent!) is // even less safe. // we thus skip 0-length files to avoid confusing them with directories. if(!usize) return INFO::SKIPPED; const size_t pathnameLength = pathnameInArchive.string().length(); // choose method and the corresponding codec ZipMethod method; PICodec codec; if(m_noDeflate || IsFileTypeIncompressible(pathnameInArchive)) { method = ZIP_METHOD_NONE; codec = CreateCodec_ZLibNone(); } else { method = ZIP_METHOD_DEFLATE; codec = CreateCompressor_ZLibDeflate(); } // allocate memory const size_t csizeMax = codec->MaxOutputSize(size_t(usize)); UniqueRange buf(io::Allocate(sizeof(LFH) + pathnameLength + csizeMax)); // read and compress file contents size_t csize; u32 checksum; { u8* cdata = (u8*)buf.get() + sizeof(LFH) + pathnameLength; Stream stream(codec); stream.SetOutputBuffer(cdata, csizeMax); StreamFeeder streamFeeder(stream); if(file) { io::Operation op(*file.get(), 0, usize); RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder)); } else { RETURN_STATUS_IF_ERR(streamFeeder(data, usize)); } RETURN_STATUS_IF_ERR(stream.Finish()); csize = stream.OutSize(); checksum = stream.Checksum(); } // build LFH { LFH* lfh = (LFH*)buf.get(); lfh->Init(fileInfo, (off_t)csize, method, checksum, pathnameInArchive); } // append a CDFH to the central directory (in memory) const off_t ofs = m_fileSize; const size_t prev_pos = m_cdfhPool.da.pos; // (required to determine padding size) const size_t cdfhSize = sizeof(CDFH) + pathnameLength; CDFH* cdfh = (CDFH*)pool_alloc(&m_cdfhPool, cdfhSize); if(!cdfh) WARN_RETURN(ERR::NO_MEM); const size_t slack = m_cdfhPool.da.pos - prev_pos - cdfhSize; cdfh->Init(fileInfo, ofs, (off_t)csize, method, checksum, pathnameInArchive, slack); m_numEntries++; // write LFH, pathname and cdata to file const size_t packageSize = sizeof(LFH) + pathnameLength + csize; if(write(m_file->Descriptor(), buf.get(), packageSize) < 0) WARN_RETURN(ERR::IO); m_fileSize += (off_t)packageSize; return INFO::OK; } private: static bool IsFileTypeIncompressible(const OsPath& pathname) { const OsPath extension = pathname.Extension(); // file extensions that we don't want to compress static const wchar_t* incompressibleExtensions[] = { L".zip", L".rar", L".jpg", L".jpeg", L".png", L".ogg", L".mp3" }; for(size_t i = 0; i < ARRAY_SIZE(incompressibleExtensions); i++) { if(extension == incompressibleExtensions[i]) return true; } return false; } PFile m_file; off_t m_fileSize; Pool m_cdfhPool; size_t m_numEntries; bool m_noDeflate; }; PIArchiveWriter CreateArchiveWriter_Zip(const OsPath& archivePathname, bool noDeflate) { try { return PIArchiveWriter(new ArchiveWriter_Zip(archivePathname, noDeflate)); } catch(Status) { return PIArchiveWriter(); } } Index: ps/trunk/source/lib/file/archive/codec_zlib.cpp =================================================================== --- ps/trunk/source/lib/file/archive/codec_zlib.cpp (revision 19898) +++ ps/trunk/source/lib/file/archive/codec_zlib.cpp (revision 19899) @@ -1,306 +1,306 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/file/archive/codec_zlib.h" #include "lib/alignment.h" #include "lib/file/archive/codec.h" #include "lib/external_libraries/zlib.h" #include "lib/sysdep/cpu.h" class Codec_ZLib : public ICodec { public: u32 UpdateChecksum(u32 checksum, const u8* in, size_t inSize) const { #if CODEC_COMPUTE_CHECKSUM return (u32)crc32(checksum, in, (uInt)inSize); #else UNUSED2(checksum); UNUSED2(in); UNUSED2(inSize); return 0; #endif } protected: u32 InitializeChecksum() { #if CODEC_COMPUTE_CHECKSUM return crc32(0, 0, 0); #else return 0; #endif } }; //----------------------------------------------------------------------------- class Codec_ZLibNone : public Codec_ZLib { public: Codec_ZLibNone() { Reset(); } virtual ~Codec_ZLibNone() { } virtual size_t MaxOutputSize(size_t inSize) const { return inSize; } virtual Status Reset() { m_checksum = InitializeChecksum(); return INFO::OK; } virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced) { const size_t transferSize = std::min(inSize, outSize); memcpy(out, in, transferSize); inConsumed = outProduced = transferSize; m_checksum = UpdateChecksum(m_checksum, out, outProduced); return INFO::OK; } virtual Status Finish(u32& checksum, size_t& outProduced) { outProduced = 0; checksum = m_checksum; return INFO::OK; } private: u32 m_checksum; }; //----------------------------------------------------------------------------- class CodecZLibStream : public Codec_ZLib { protected: CodecZLibStream() { memset(&m_zs, 0, sizeof(m_zs)); m_checksum = InitializeChecksum(); } static Status LibError_from_zlib(int zlib_ret) { switch(zlib_ret) { case Z_OK: return INFO::OK; case Z_STREAM_END: WARN_RETURN(ERR::FAIL); case Z_MEM_ERROR: WARN_RETURN(ERR::NO_MEM); case Z_DATA_ERROR: WARN_RETURN(ERR::CORRUPTED); case Z_STREAM_ERROR: WARN_RETURN(ERR::INVALID_PARAM); default: WARN_RETURN(ERR::FAIL); } } static void WarnIfZLibError(int zlib_ret) { (void)LibError_from_zlib(zlib_ret); } typedef int ZEXPORT (*ZLibFunc)(z_streamp strm, int flush); Status CallStreamFunc(ZLibFunc func, int flush, const u8* in, const size_t inSize, u8* out, const size_t outSize, size_t& inConsumed, size_t& outProduced) { m_zs.next_in = (Byte*)in; m_zs.avail_in = (uInt)inSize; m_zs.next_out = (Byte*)out; m_zs.avail_out = (uInt)outSize; int ret = func(&m_zs, flush); // sanity check: if ZLib reports end of stream, all input data // must have been consumed. if(ret == Z_STREAM_END) { ENSURE(m_zs.avail_in == 0); ret = Z_OK; } ENSURE(inSize >= m_zs.avail_in && outSize >= m_zs.avail_out); inConsumed = inSize - m_zs.avail_in; outProduced = outSize - m_zs.avail_out; return LibError_from_zlib(ret); } mutable z_stream m_zs; // note: z_stream does contain an 'adler' checksum field, but that's // not updated in streams lacking a gzip header, so we'll have to // calculate a checksum ourselves. // adler32 is somewhat weaker than CRC32, but a more important argument // is that we should use the latter for compatibility with Zip archives. mutable u32 m_checksum; }; //----------------------------------------------------------------------------- class Compressor_ZLib : public CodecZLibStream { public: Compressor_ZLib() { // note: with Z_BEST_COMPRESSION, 78% percent of // archive builder CPU time is spent in ZLib, even though // that is interleaved with IO; everything else is negligible. // we prefer faster speed at the cost of 1.5% larger archives. const int level = Z_BEST_SPEED; const int windowBits = -MAX_WBITS; // max window size; omit ZLib header const int memLevel = 9; // max speed; total mem ~= 384KiB const int strategy = Z_DEFAULT_STRATEGY; // normal data - not RLE const int ret = deflateInit2(&m_zs, level, Z_DEFLATED, windowBits, memLevel, strategy); ENSURE(ret == Z_OK); } virtual ~Compressor_ZLib() { const int ret = deflateEnd(&m_zs); WarnIfZLibError(ret); } virtual size_t MaxOutputSize(size_t inSize) const { return (size_t)deflateBound(&m_zs, (uLong)inSize); } virtual Status Reset() { m_checksum = InitializeChecksum(); const int ret = deflateReset(&m_zs); return LibError_from_zlib(ret); } virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced) { m_checksum = UpdateChecksum(m_checksum, in, inSize); return CodecZLibStream::CallStreamFunc(deflate, 0, in, inSize, out, outSize, inConsumed, outProduced); } virtual Status Finish(u32& checksum, size_t& outProduced) { const uInt availOut = m_zs.avail_out; // notify zlib that no more data is forthcoming and have it flush output. // our output buffer has enough space due to use of deflateBound; // therefore, deflate must return Z_STREAM_END. const int ret = deflate(&m_zs, Z_FINISH); ENSURE(ret == Z_STREAM_END); outProduced = size_t(availOut - m_zs.avail_out); checksum = m_checksum; return INFO::OK; } }; //----------------------------------------------------------------------------- class Decompressor_ZLib : public CodecZLibStream { public: Decompressor_ZLib() { const int windowBits = -MAX_WBITS; // max window size; omit ZLib header const int ret = inflateInit2(&m_zs, windowBits); ENSURE(ret == Z_OK); } virtual ~Decompressor_ZLib() { const int ret = inflateEnd(&m_zs); WarnIfZLibError(ret); } virtual size_t MaxOutputSize(size_t inSize) const { // relying on an upper bound for the output is a really bad idea for // large files. archive formats store the uncompressed file sizes, // so callers should use that when allocating the output buffer. ENSURE(inSize < 1*MiB); return inSize*1032; // see http://www.zlib.org/zlib_tech.html } virtual Status Reset() { m_checksum = InitializeChecksum(); const int ret = inflateReset(&m_zs); return LibError_from_zlib(ret); } virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced) { const Status ret = CodecZLibStream::CallStreamFunc(inflate, Z_SYNC_FLUSH, in, inSize, out, outSize, inConsumed, outProduced); m_checksum = UpdateChecksum(m_checksum, out, outProduced); return ret; } virtual Status Finish(u32& checksum, size_t& outProduced) { // no action needed - decompression always flushes immediately. outProduced = 0; checksum = m_checksum; return INFO::OK; } }; //----------------------------------------------------------------------------- PICodec CreateCodec_ZLibNone() { return PICodec(new Codec_ZLibNone); } PICodec CreateCompressor_ZLibDeflate() { return PICodec(new Compressor_ZLib); } PICodec CreateDecompressor_ZLibDeflate() { return PICodec (new Decompressor_ZLib); } Index: ps/trunk/source/lib/file/archive/disabled_tests/test_fat_time.h =================================================================== --- ps/trunk/source/lib/file/archive/disabled_tests/test_fat_time.h (revision 19898) +++ ps/trunk/source/lib/file/archive/disabled_tests/test_fat_time.h (revision 19899) @@ -1,47 +1,47 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include #include "lib/res/file/archive/fat_time.h" class TestFatTime: public CxxTest::TestSuite { public: void test_fat_timedate_conversion() { // note: FAT time stores second/2, which means converting may // end up off by 1 second. time_t t, converted_t; t = time(0); converted_t = time_t_from_FAT(FAT_from_time_t(t)); TS_ASSERT_DELTA(t, converted_t, 2); t++; converted_t = time_t_from_FAT(FAT_from_time_t(t)); TS_ASSERT_DELTA(t, converted_t, 2); } }; Index: ps/trunk/source/lib/file/common/file_loader.cpp =================================================================== --- ps/trunk/source/lib/file/common/file_loader.cpp (revision 19898) +++ ps/trunk/source/lib/file/common/file_loader.cpp (revision 19899) @@ -1,28 +1,28 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/file/common/file_loader.h" /*virtual*/ IFileLoader::~IFileLoader() { } Index: ps/trunk/source/lib/file/common/real_directory.cpp =================================================================== --- ps/trunk/source/lib/file/common/real_directory.cpp (revision 19898) +++ ps/trunk/source/lib/file/common/real_directory.cpp (revision 19899) @@ -1,72 +1,72 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/file/common/real_directory.h" #include "lib/sysdep/filesystem.h" #include "lib/file/file.h" #include "lib/file/io/io.h" RealDirectory::RealDirectory(const OsPath& path, size_t priority, size_t flags) : m_path(path), m_priority(priority), m_flags(flags) { } /*virtual*/ size_t RealDirectory::Precedence() const { return 1u; } /*virtual*/ wchar_t RealDirectory::LocationCode() const { return 'F'; } /*virtual*/ Status RealDirectory::Load(const OsPath& name, const shared_ptr& buf, size_t size) const { return io::Load(m_path / name, buf.get(), size); } Status RealDirectory::Store(const OsPath& name, const shared_ptr& fileContents, size_t size) { return io::Store(m_path / name, fileContents.get(), size); } void RealDirectory::Watch() { if(!m_watch) (void)dir_watch_Add(m_path, m_watch); } PRealDirectory CreateRealSubdirectory(const PRealDirectory& realDirectory, const OsPath& subdirectoryName) { const OsPath path = realDirectory->Path() / subdirectoryName/""; return PRealDirectory(new RealDirectory(path, realDirectory->Priority(), realDirectory->Flags())); } Index: ps/trunk/source/lib/file/common/trace.h =================================================================== --- ps/trunk/source/lib/file/common/trace.h (revision 19898) +++ ps/trunk/source/lib/file/common/trace.h (revision 19899) @@ -1,128 +1,128 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * IO event recording */ // traces are useful for determining the optimal ordering of archived files // and can also serve as a repeatable IO benchmark. // note: since FileContents are smart pointers, the trace can't easily // be notified when they are released (relevant for cache simulation). // we have to assume that users process one file at a time -- as they // should. #ifndef INCLUDED_TRACE #define INCLUDED_TRACE #include "lib/os_path.h" // stores information about an IO event. class TraceEntry { public: enum EAction { Load = 'L', Store = 'S' }; TraceEntry(EAction action, const Path& pathname, size_t size); TraceEntry(const std::wstring& text); EAction Action() const { return m_action; } const Path& Pathname() const { return m_pathname; } size_t Size() const { return m_size; } std::wstring EncodeAsText() const; private: // note: keep an eye on the class size because all instances are kept // in memory (see ITrace) // time (as returned by timer_Time) after the operation completes. // rationale: when loading, the VFS doesn't know file size until // querying the cache or retrieving file information. float m_timestamp; EAction m_action; Path m_pathname; // size of file. // rationale: other applications using this trace format might not // have access to the VFS and its file information. size_t m_size; }; // note: to avoid interfering with measurements, this trace container // does not cause any IOs (except of course in Load/Store) struct ITrace { virtual ~ITrace(); virtual void NotifyLoad(const Path& pathname, size_t size) = 0; virtual void NotifyStore(const Path& pathname, size_t size) = 0; /** * store all entries into a file. * * @param pathname (native, absolute) * * note: the file format is text-based to allow human inspection and * because storing filename strings in a binary format would be a * bit awkward. **/ virtual Status Store(const OsPath& pathname) const = 0; /** * load entries from file. * * @param pathname (native, absolute) * * replaces any existing entries. **/ virtual Status Load(const OsPath& pathname) = 0; virtual const TraceEntry* Entries() const = 0; virtual size_t NumEntries() const = 0; }; typedef shared_ptr PITrace; extern PITrace CreateDummyTrace(size_t maxSize); extern PITrace CreateTrace(size_t maxSize); #endif // #ifndef INCLUDED_TRACE Index: ps/trunk/source/lib/file/file.h =================================================================== --- ps/trunk/source/lib/file/file.h (revision 19898) +++ ps/trunk/source/lib/file/file.h (revision 19899) @@ -1,101 +1,101 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * simple POSIX file wrapper. */ #ifndef INCLUDED_FILE #define INCLUDED_FILE #include "lib/os_path.h" #include "lib/sysdep/filesystem.h" // O_*, S_* namespace ERR { const Status FILE_ACCESS = -110300; const Status FILE_NOT_FOUND = -110301; } // @param oflag: either O_RDONLY or O_WRONLY (in which case O_CREAT and // O_TRUNC are added), plus O_DIRECT if aio is desired // @return file descriptor or a negative Status LIB_API Status FileOpen(const OsPath& pathname, int oflag); LIB_API void FileClose(int& fd); class File { public: File() : pathname(), fd(-1) { } File(const OsPath& pathname, int oflag) { THROW_STATUS_IF_ERR(Open(pathname, oflag)); } ~File() { Close(); } Status Open(const OsPath& pathname, int oflag) { Status ret = FileOpen(pathname, oflag); RETURN_STATUS_IF_ERR(ret); this->pathname = pathname; this->fd = (int)ret; this->oflag = oflag; return INFO::OK; } void Close() { FileClose(fd); } const OsPath& Pathname() const { return pathname; } int Descriptor() const { return fd; } int Flags() const { return oflag; } private: OsPath pathname; int fd; int oflag; }; typedef shared_ptr PFile; #endif // #ifndef INCLUDED_FILE Index: ps/trunk/source/lib/external_libraries/png.h =================================================================== --- ps/trunk/source/lib/external_libraries/png.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/png.h (revision 19899) @@ -1,44 +1,44 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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. */ /* * bring in LibPNG header+library, with compatibility fixes */ #ifndef INCLUDED_PNG #define INCLUDED_PNG // includes , which requires some fixes by our header. #include "lib/external_libraries/zlib.h" #include // automatically link against the required library #if MSC_VERSION # ifdef NDEBUG # pragma comment(lib, "libpng16.lib") # else # pragma comment(lib, "libpng16d.lib") # endif // NDEBUG #endif // MSC_VERSION #endif // #ifndef INCLUDED_PNG Index: ps/trunk/source/lib/external_libraries/vorbis.h =================================================================== --- ps/trunk/source/lib/external_libraries/vorbis.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/vorbis.h (revision 19899) @@ -1,40 +1,40 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bring in OGG Vorbis header+library */ #ifndef INCLUDED_VORBIS #define INCLUDED_VORBIS #include #if MSC_VERSION # ifdef NDEBUG # pragma comment(lib, "vorbisfile.lib") # else # pragma comment(lib, "vorbisfile_d.lib") # endif #endif #endif // #ifndef INCLUDED_VORBIS Index: ps/trunk/source/lib/file/archive/archive.h =================================================================== --- ps/trunk/source/lib/file/archive/archive.h (revision 19898) +++ ps/trunk/source/lib/file/archive/archive.h (revision 19899) @@ -1,109 +1,109 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * interface for reading from and creating archives. */ #ifndef INCLUDED_ARCHIVE #define INCLUDED_ARCHIVE #include "lib/file/file_system.h" // FileInfo #include "lib/file/common/file_loader.h" #include "lib/file/vfs/vfs_path.h" // rationale: this module doesn't build a directory tree of the entries // within an archive. that task is left to the VFS; here, we are only // concerned with enumerating all archive entries. namespace ERR { const Status ARCHIVE_UNKNOWN_FORMAT = -110400; const Status ARCHIVE_UNKNOWN_METHOD = -110401; } struct IArchiveFile : public IFileLoader { }; typedef shared_ptr PIArchiveFile; struct IArchiveReader { virtual ~IArchiveReader(); /** * called for each archive entry. * @param pathname full pathname of entry; only valid during the callback. **/ typedef void (*ArchiveEntryCallback)(const VfsPath& pathname, const CFileInfo& fileInfo, PIArchiveFile archiveFile, uintptr_t cbData); virtual Status ReadEntries(ArchiveEntryCallback cb, uintptr_t cbData) = 0; }; typedef shared_ptr PIArchiveReader; // note: when creating an archive, any existing file with the given pathname // will be overwritten. // rationale: don't support partial adding, i.e. updating archive with // only one file. this would require overwriting parts of the Zip archive, // which is annoying and slow. also, archives are usually built in // seek-optimal order, which would break if we start inserting files. // while testing, loose files can be used, so there's no loss. struct IArchiveWriter { /** * write out the archive to disk; only hereafter is it valid. **/ virtual ~IArchiveWriter(); /** * add a file to the archive. * * rationale: passing in a filename instead of the compressed file * contents makes for better encapsulation because callers don't need * to know about the codec. one disadvantage is that loading the file * contents can no longer take advantage of the VFS cache nor previously * archived versions. however, the archive builder usually adds files * precisely because they aren't in archives, and the cache would * thrash anyway, so this is deemed acceptable. * * @param pathname the actual file to add * @param pathnameInArchive the name to store in the archive **/ virtual Status AddFile(const OsPath& pathname, const Path& pathnameInArchive) = 0; /** * add a file to the archive, when it is already in memory and not on disk. * * @param data the uncompressed file contents to add * @param size the length of data * @param mtime the last-modified-time to be stored in the archive * @param pathnameInArchive the name to store in the archive **/ virtual Status AddMemory(const u8* data, size_t size, time_t mtime, const OsPath& pathnameInArchive) = 0; }; typedef shared_ptr PIArchiveWriter; #endif // #ifndef INCLUDED_ARCHIVE Index: ps/trunk/source/lib/file/archive/codec.h =================================================================== --- ps/trunk/source/lib/file/archive/codec.h (revision 19898) +++ ps/trunk/source/lib/file/archive/codec.h (revision 19899) @@ -1,94 +1,94 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 layer allows for other compression methods/libraries * besides ZLib. it also simplifies the interface for user code and * does error checking, etc. */ #ifndef INCLUDED_CODEC #define INCLUDED_CODEC #define CODEC_COMPUTE_CHECKSUM 1 struct ICodec { public: /** * note: the implementation should not check whether any data remains - * codecs are sometimes destroyed without completing a transfer. **/ virtual ~ICodec(); /** * @return an upper bound on the output size for the given amount of input. * this is used when allocating a single buffer for the whole operation. **/ virtual size_t MaxOutputSize(size_t inSize) const = 0; /** * clear all previous state and prepare for reuse. * * this is as if the object were destroyed and re-created, but more * efficient since it avoids reallocating a considerable amount of * memory (about 200KB for LZ). **/ virtual Status Reset() = 0; /** * process (i.e. compress or decompress) data. * * @param in * @param inSize * @param out * @param outSize Bytes remaining in the output buffer; shall not be zero. * @param inConsumed,outProduced How many bytes in the input and * output buffers were used. either or both of these can be zero if * the input size is small or there's not enough output space. **/ virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced) = 0; /** * Flush buffers and make sure all output has been produced. * * @param checksum Checksum over all input data. * @param outProduced * @return error status for the entire operation. **/ virtual Status Finish(u32& checksum, size_t& outProduced) = 0; /** * update a checksum to reflect the contents of a buffer. * * @param checksum the initial value (must be 0 on first call) * @param in * @param inSize * @return the new checksum. note: after all data has been seen, this is * identical to the what Finish would return. **/ virtual u32 UpdateChecksum(u32 checksum, const u8* in, size_t inSize) const = 0; }; typedef shared_ptr PICodec; #endif // #ifndef INCLUDED_CODEC Index: ps/trunk/source/lib/file/archive/disabled_tests/test_compression.h =================================================================== --- ps/trunk/source/lib/file/archive/disabled_tests/test_compression.h (revision 19898) +++ ps/trunk/source/lib/file/archive/disabled_tests/test_compression.h (revision 19899) @@ -1,76 +1,76 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/self_test.h" #include "lib/res/file/archive/compression.h" class TestCompression : public CxxTest::TestSuite { public: void test_compress_decompress_compare() { // generate random input data // (limit values to 0..7 so that the data will actually be compressible) const size_t data_size = 10000; u8 data[data_size]; for(size_t i = 0; i < data_size; i++) data[i] = rand() & 0x07; u8* cdata; size_t csize; u8 udata[data_size]; // compress uintptr_t c = comp_alloc(CT_COMPRESSION, CM_DEFLATE); { TS_ASSERT(c != 0); const size_t csizeBound = comp_max_output_size(c, data_size); TS_ASSERT_OK(comp_alloc_output(c, csizeBound)); const ssize_t cdata_produced = comp_feed(c, data, data_size); TS_ASSERT(cdata_produced >= 0); u32 checksum; TS_ASSERT_OK(comp_finish(c, &cdata, &csize, &checksum)); TS_ASSERT(cdata_produced <= (ssize_t)csize); // can't have produced more than total } // decompress uintptr_t d = comp_alloc(CT_DECOMPRESSION, CM_DEFLATE); { TS_ASSERT(d != 0); comp_set_output(d, udata, data_size); const ssize_t udata_produced = comp_feed(d, cdata, csize); TS_ASSERT(udata_produced >= 0); u8* udata_final; size_t usize_final; u32 checksum; TS_ASSERT_OK(comp_finish(d, &udata_final, &usize_final, &checksum)); TS_ASSERT(udata_produced <= (ssize_t)usize_final); // can't have produced more than total TS_ASSERT_EQUALS(udata_final, udata); // output buffer address is same TS_ASSERT_EQUALS(usize_final, data_size); // correct amount of output } comp_free(c); comp_free(d); // verify data survived intact TS_ASSERT_SAME_DATA(data, udata, data_size); } }; Index: ps/trunk/source/lib/file/archive/stream.h =================================================================== --- ps/trunk/source/lib/file/archive/stream.h (revision 19898) +++ ps/trunk/source/lib/file/archive/stream.h (revision 19899) @@ -1,133 +1,133 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * output buffer and 'stream' layered on top of a compression codec */ #ifndef INCLUDED_STREAM #define INCLUDED_STREAM #include "lib/file/archive/codec.h" // note: this is similar in function to std::vector, but we don't need // iterators etc. and would prefer to avoid initializing each byte. class OutputBufferManager { public: OutputBufferManager(); void Reset(); void SetBuffer(u8* buffer, size_t size); /** * allocate a new output buffer. * * @param size [bytes] to allocate. * * notes: * - if a buffer had previously been allocated and is large enough, * it is reused (this reduces the number of allocations). * - this class manages the lifetime of the buffer. **/ void AllocateBuffer(size_t size); u8* Buffer() const { return m_buffer; } size_t Size() const { return m_size; } private: bool IsAllowableBuffer(u8* buffer, size_t size); u8* m_buffer; size_t m_size; shared_ptr m_mem; // size of m_mem. allows reusing previously allocated buffers // (user-specified buffers can't be reused because we have no control // over their lifetime) size_t m_capacity; }; class Stream { public: Stream(const PICodec& codec); void SetOutputBuffer(u8* out, size_t outSize); void AllocateOutputBuffer(size_t outSizeMax); /** * 'feed' the codec with a data block. **/ Status Feed(const u8* in, size_t inSize); Status Finish(); size_t OutSize() const { return m_outProduced; } u32 Checksum() const { return m_checksum; } private: PICodec m_codec; OutputBufferManager m_outputBufferManager; size_t m_inConsumed; size_t m_outProduced; u32 m_checksum; }; // avoids the need for std::bind (not supported on all compilers) and boost::bind (can't be // used at work) struct StreamFeeder { NONCOPYABLE(StreamFeeder); public: StreamFeeder(Stream& stream) : stream(stream) { } Status operator()(const u8* data, size_t size) const { return stream.Feed(data, size); } private: Stream& stream; }; #endif // #ifndef INCLUDED_STREAM Index: ps/trunk/source/lib/file/common/file_stats.h =================================================================== --- ps/trunk/source/lib/file/common/file_stats.h (revision 19898) +++ ps/trunk/source/lib/file/common/file_stats.h (revision 19899) @@ -1,118 +1,118 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * gathers statistics from all file modules. */ #ifndef INCLUDED_FILE_STATS #define INCLUDED_FILE_STATS #include "lib/posix/posix_aio.h" // LIO_READ, LIO_WRITE #define FILE_STATS_ENABLED 0 enum FileIOImplentation { FI_LOWIO, FI_AIO, FI_BCACHE, FI_MAX_IDX }; enum CacheRet { CR_HIT, CR_MISS }; #if FILE_STATS_ENABLED // vfs extern void stats_vfs_file_add(size_t file_size); extern void stats_vfs_file_remove(size_t file_size); extern void stats_vfs_init_start(); extern void stats_vfs_init_finish(); // file // currently not called because string_pool is now in lib/allocators extern void stats_unique_name(size_t name_len); extern void stats_open(); extern void stats_close(); // file_buf extern void stats_buf_alloc(size_t size, size_t alignedSize); extern void stats_buf_free(); extern void stats_buf_ref(); // file_io extern void stats_io_user_request(size_t user_size); // this is used to measure effective throughput for the two // synchronous IO variants. // note: improved measurements of the actual aio throughput by instrumenting // issue/wait doesn't work because IOManager's decompression may cause us to // miss the exact end of IO, thus throwing off measurements. class ScopedIoMonitor { public: ScopedIoMonitor(); ~ScopedIoMonitor(); void NotifyOfSuccess(FileIOImplentation fi, int opcode, off_t size); private: double m_startTime; }; extern void stats_cb_start(); extern void stats_cb_finish(); // file_cache extern void stats_cache(CacheRet cr, size_t size); extern void stats_block_cache(CacheRet cr); // archive builder extern void stats_ab_connection(bool already_exists); extern void file_stats_dump(); #else #define stats_vfs_file_add(file_size) #define stats_vfs_file_remove(file_size) #define stats_vfs_init_start() #define stats_vfs_init_finish() #define stats_unique_name(name_len) #define stats_open() #define stats_close() #define stats_buf_alloc(size, alignedSize) #define stats_buf_free() #define stats_buf_ref() #define stats_io_user_request(user_size) class ScopedIoMonitor { public: ScopedIoMonitor() {} ~ScopedIoMonitor() {} void NotifyOfSuccess(FileIOImplentation UNUSED(fi), int UNUSED(opcode), off_t UNUSED(size)) {} }; #define stats_cb_start() #define stats_cb_finish() #define stats_cache(cr, size) #define stats_block_cache(cr) #define stats_ab_connection(already_exists) #define file_stats_dump() #endif #endif // #ifndef INCLUDED_FILE_STATS Index: ps/trunk/source/lib/file/common/trace.cpp =================================================================== --- ps/trunk/source/lib/file/common/trace.cpp (revision 19898) +++ ps/trunk/source/lib/file/common/trace.cpp (revision 19899) @@ -1,235 +1,235 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * IO event recording */ #include "precompiled.h" #include "lib/file/common/trace.h" #include #include #include "lib/allocators/pool.h" #include "lib/timer.h" // timer_Time #include "lib/sysdep/sysdep.h" // sys_OpenFile /*virtual*/ ITrace::~ITrace() { } //----------------------------------------------------------------------------- TraceEntry::TraceEntry(EAction action, const Path& pathname, size_t size) : m_timestamp((float)timer_Time()) , m_action(action) , m_pathname(pathname) , m_size(size) { } TraceEntry::TraceEntry(const std::wstring& text) { // swscanf is far too awkward to get working cross-platform, // so use iostreams here instead wchar_t dummy; wchar_t action; std::wstringstream stream(text); stream >> m_timestamp; stream >> dummy; ENSURE(dummy == ':'); stream >> action; ENSURE(action == 'L' || action == 'S'); m_action = (EAction)action; stream >> dummy; ENSURE(dummy == '"'); Path::String pathname; std::getline(stream, pathname, L'"'); m_pathname = Path(pathname); stream >> m_size; ENSURE(stream.get() == '\n'); // NOTE: Don't use good() here - it fails due to a bug in older libc++ versions ENSURE(!stream.bad() && !stream.fail()); ENSURE(stream.get() == WEOF); } std::wstring TraceEntry::EncodeAsText() const { const wchar_t action = (wchar_t)m_action; wchar_t buf[1000]; swprintf_s(buf, ARRAY_SIZE(buf), L"%#010f: %c \"%ls\" %lu\n", m_timestamp, action, m_pathname.string().c_str(), (unsigned long)m_size); return buf; } //----------------------------------------------------------------------------- class Trace_Dummy : public ITrace { public: Trace_Dummy(size_t UNUSED(maxSize)) { } virtual void NotifyLoad(const Path& UNUSED(pathname), size_t UNUSED(size)) { } virtual void NotifyStore(const Path& UNUSED(pathname), size_t UNUSED(size)) { } virtual Status Load(const OsPath& UNUSED(pathname)) { return INFO::OK; } virtual Status Store(const OsPath& UNUSED(pathname)) const { return INFO::OK; } virtual const TraceEntry* Entries() const { return 0; } virtual size_t NumEntries() const { return 0; } }; //----------------------------------------------------------------------------- class Trace : public ITrace { public: Trace(size_t maxSize) { (void)pool_create(&m_pool, maxSize, sizeof(TraceEntry)); } virtual ~Trace() { for(size_t i = 0; i < NumEntries(); i++) { TraceEntry* entry = (TraceEntry*)(uintptr_t(m_pool.da.base) + i*m_pool.el_size); entry->~TraceEntry(); } (void)pool_destroy(&m_pool); } virtual void NotifyLoad(const Path& pathname, size_t size) { new(Allocate()) TraceEntry(TraceEntry::Load, pathname, size); } virtual void NotifyStore(const Path& pathname, size_t size) { new(Allocate()) TraceEntry(TraceEntry::Store, pathname, size); } virtual Status Load(const OsPath& pathname) { pool_free_all(&m_pool); errno = 0; FILE* file = sys_OpenFile(pathname, "rt"); if(!file) WARN_RETURN(StatusFromErrno()); for(;;) { wchar_t text[500]; if(!fgetws(text, ARRAY_SIZE(text)-1, file)) break; new(Allocate()) TraceEntry(text); } fclose(file); return INFO::OK; } virtual Status Store(const OsPath& pathname) const { errno = 0; FILE* file = sys_OpenFile(pathname, "at"); if(!file) WARN_RETURN(StatusFromErrno()); for(size_t i = 0; i < NumEntries(); i++) { std::wstring text = Entries()[i].EncodeAsText(); fputws(text.c_str(), file); } (void)fclose(file); return INFO::OK; } virtual const TraceEntry* Entries() const { return (const TraceEntry*)m_pool.da.base; } virtual size_t NumEntries() const { return m_pool.da.pos / m_pool.el_size; } private: void* Allocate() { void* p = pool_alloc(&m_pool, 0); ENSURE(p); return p; } Pool m_pool; }; PITrace CreateDummyTrace(size_t maxSize) { return PITrace(new Trace_Dummy(maxSize)); } PITrace CreateTrace(size_t maxSize) { return PITrace(new Trace(maxSize)); } Index: ps/trunk/source/lib/file/file.cpp =================================================================== --- ps/trunk/source/lib/file/file.cpp (revision 19898) +++ ps/trunk/source/lib/file/file.cpp (revision 19899) @@ -1,63 +1,63 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * simple POSIX file wrapper. */ #include "precompiled.h" #include "lib/file/file.h" #include "lib/file/common/file_stats.h" static const StatusDefinition fileStatusDefinitions[] = { { ERR::FILE_ACCESS, L"Insufficient access rights to open file", EACCES }, { ERR::FILE_NOT_FOUND, L"No such file or directory", ENOENT } }; STATUS_ADD_DEFINITIONS(fileStatusDefinitions); Status FileOpen(const OsPath& pathname, int oflag) { ENSURE((oflag & ~(O_RDONLY|O_WRONLY|O_DIRECT)) == 0); if(oflag & O_WRONLY) oflag |= O_CREAT|O_TRUNC; // prevent exploits by disallowing writes to our files by other users. // note that the system-wide installed cache is read-only. const mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; // 0644 const int fd = wopen(pathname, oflag, mode); if(fd < 0) return StatusFromErrno(); // NOWARN stats_open(); return (Status)fd; } void FileClose(int& fd) { if(fd >= 0) { wclose(fd); fd = -1; } } Index: ps/trunk/source/lib/external_libraries/openmp.h =================================================================== --- ps/trunk/source/lib/external_libraries/openmp.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/openmp.h (revision 19899) @@ -1,63 +1,63 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_EXTERNAL_LIBRARIES_OPENMP #define INCLUDED_EXTERNAL_LIBRARIES_OPENMP // allows removing all OpenMP-related code via #define ENABLE_OPENMP 0 // before including this header. (useful during debugging, because the // VC debugger isn't able to display OpenMP private variables) #ifdef ENABLE_OPENMP # if ENABLE_OPENMP && !defined(_OPENMP) # error "either enable OpenMP in the compiler settings or don't set ENABLE_OPENMP to 1" # endif #else // no user preference; default to compiler setting # ifdef _OPENMP # define ENABLE_OPENMP 1 # else # define ENABLE_OPENMP 0 # endif #endif #if ENABLE_OPENMP # include #else # define omp_get_num_threads() 1 # define omp_get_thread_num() 0 # define omp_in_parallel() 0 #endif // wrapper macro that evaluates to nothing if !ENABLE_OPENMP // (much more convenient than individual #if ENABLE_OPENMP) #if ENABLE_OPENMP # if MSC_VERSION # define OMP(args) __pragma(omp args) # elif GCC_VERSION # define OMP _Pragma("omp " #args) # else # error "port" # endif #else # define OMP(args) #endif #endif // #ifndef INCLUDED_EXTERNAL_LIBRARIES_OPENMP Index: ps/trunk/source/lib/external_libraries/tinygettext.h =================================================================== --- ps/trunk/source/lib/external_libraries/tinygettext.h (revision 19898) +++ ps/trunk/source/lib/external_libraries/tinygettext.h (revision 19899) @@ -1,44 +1,44 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * Bring in the TinyGettext header file. */ #ifndef INCLUDED_TINYGETTEXT #define INCLUDED_TINYGETTEXT #if MSC_VERSION # pragma warning(push) # pragma warning(disable:4251) // "class X needs to have dll-interface to be used by clients of class Y" # pragma warning(disable:4800) // "forcing value to bool 'true' or 'false' (performance warning)" #endif #include #include #include #if MSC_VERSION # pragma warning(pop) #endif #endif // INCLUDED_TINYGETTEXT Index: ps/trunk/source/lib/file/archive/archive.cpp =================================================================== --- ps/trunk/source/lib/file/archive/archive.cpp (revision 19898) +++ ps/trunk/source/lib/file/archive/archive.cpp (revision 19899) @@ -1,42 +1,42 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * interface for reading from and creating archives. */ #include "precompiled.h" #include "lib/file/archive/archive.h" static const StatusDefinition archiveStatusDefinitions[] = { { ERR::ARCHIVE_UNKNOWN_FORMAT, L"Unknown archive format" }, { ERR::ARCHIVE_UNKNOWN_METHOD, L"Unknown compression method" } }; STATUS_ADD_DEFINITIONS(archiveStatusDefinitions); IArchiveReader::~IArchiveReader() { } IArchiveWriter::~IArchiveWriter() { } Index: ps/trunk/source/lib/file/archive/codec.cpp =================================================================== --- ps/trunk/source/lib/file/archive/codec.cpp (revision 19898) +++ ps/trunk/source/lib/file/archive/codec.cpp (revision 19899) @@ -1,28 +1,28 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/file/archive/codec.h" ICodec::~ICodec() { } Index: ps/trunk/source/lib/file/archive/disabled_tests/test_codec_zlib.h =================================================================== --- ps/trunk/source/lib/file/archive/disabled_tests/test_codec_zlib.h (revision 19898) +++ ps/trunk/source/lib/file/archive/disabled_tests/test_codec_zlib.h (revision 19899) @@ -1,81 +1,81 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/self_test.h" #include "lib/res/file/archive/codec_zlib.h" class TestCodecZLib : public CxxTest::TestSuite { public: void test_compress_decompress_compare() { size_t inConsumed, outProduced; u32 checksum; // generate random input udata // (limit values to 0..7 so that the udata will actually be compressible) const size_t usize = 10000; u8 udata[usize]; for(size_t i = 0; i < usize; i++) udata[i] = rand() & 0x07; // compress u8* cdata; size_t csize; { boost::shared_ptr compressor_zlib = CreateCompressor_ZLib(); ICodec* c = compressor_zlib.get(); const size_t csizeMax = c->MaxOutputSize(usize); cdata = new u8[csizeMax]; TS_ASSERT_OK(c->Process(udata, usize, cdata, csizeMax, inConsumed, outProduced)); TS_ASSERT_EQUALS(inConsumed, usize); TS_ASSERT_LESS_THAN_EQUALS(outProduced, csizeMax); u8* cdata2; TS_ASSERT_OK(c->Finish(cdata2, csize, checksum)); TS_ASSERT_EQUALS(cdata, cdata2); TS_ASSERT_EQUALS(csize, outProduced); } // make sure the data changed during compression TS_ASSERT(csize != usize || memcmp(udata, cdata, std::min(usize, csize)) != 0); // decompress u8 ddata[usize]; { boost::shared_ptr decompressor_zlib = CreateDecompressor_ZLib(); ICodec* d = decompressor_zlib.get(); TS_ASSERT_OK(decompressor_zlib->Process(cdata, csize, ddata, usize, inConsumed, outProduced)); TS_ASSERT_EQUALS(inConsumed, csize); // ZLib always outputs as much data as possible TS_ASSERT_EQUALS(outProduced, usize); // .. so these figures are correct before Finish() u8* ddata2; size_t dsize; TS_ASSERT_OK(d->Finish(&ddata2, &dsize, &checksum)); TS_ASSERT_EQUALS(ddata, ddata2); TS_ASSERT_EQUALS(dsize, outProduced); } // verify udata survived intact TS_ASSERT_SAME_DATA(udata, ddata, usize); delete[] cdata; } }; Index: ps/trunk/source/lib/file/archive/stream.cpp =================================================================== --- ps/trunk/source/lib/file/archive/stream.cpp (revision 19898) +++ ps/trunk/source/lib/file/archive/stream.cpp (revision 19899) @@ -1,138 +1,138 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/file/archive/stream.h" #include "lib/allocators/page_aligned.h" #include "lib/allocators/shared_ptr.h" #include "lib/file/archive/codec.h" //#include "lib/timer.h" //TIMER_ADD_CLIENT(tc_stream); OutputBufferManager::OutputBufferManager() { Reset(); } void OutputBufferManager::Reset() { m_buffer = 0; m_size = 0; m_capacity = 0; } void OutputBufferManager::SetBuffer(u8* buffer, size_t size) { ENSURE(IsAllowableBuffer(buffer, size)); m_buffer = buffer; m_size = size; } void OutputBufferManager::AllocateBuffer(size_t size) { // notes: // - this implementation allows reusing previous buffers if they // are big enough, which reduces the number of allocations. // - no further attempts to reduce allocations (e.g. by doubling // the current size) are made; this strategy is enough. // - Pool etc. cannot be used because files may be huge (larger // than the address space of 32-bit systems). // no buffer or the previous one wasn't big enough: reallocate if(!m_mem || m_capacity < size) { AllocateAligned(m_mem, size); m_capacity = size; } SetBuffer(m_mem.get(), size); } bool OutputBufferManager::IsAllowableBuffer(u8* buffer, size_t size) { // none yet established if(m_buffer == 0 && m_size == 0) return true; // same as last time (happens with temp buffers) if(m_buffer == buffer && m_size == size) return true; // located after the last buffer (note: not necessarily after // the entire buffer; a lack of input can cause the output buffer // to only partially be used before the next call.) if((unsigned)(buffer - m_buffer) <= m_size) return true; return false; } //----------------------------------------------------------------------------- Stream::Stream(const PICodec& codec) : m_codec(codec) , m_inConsumed(0), m_outProduced(0) { } void Stream::AllocateOutputBuffer(size_t outSizeMax) { m_outputBufferManager.AllocateBuffer(outSizeMax); } void Stream::SetOutputBuffer(u8* out, size_t outSize) { m_outputBufferManager.SetBuffer(out, outSize); } Status Stream::Feed(const u8* in, size_t inSize) { if(m_outProduced == m_outputBufferManager.Size()) // output buffer full; must not call Process return INFO::ALL_COMPLETE; size_t inConsumed, outProduced; u8* const out = m_outputBufferManager.Buffer() + m_outProduced; const size_t outSize = m_outputBufferManager.Size() - m_outProduced; RETURN_STATUS_IF_ERR(m_codec->Process(in, inSize, out, outSize, inConsumed, outProduced)); m_inConsumed += inConsumed; m_outProduced += outProduced; return INFO::OK; } Status Stream::Finish() { size_t outProduced; RETURN_STATUS_IF_ERR(m_codec->Finish(m_checksum, outProduced)); m_outProduced += outProduced; return INFO::OK; } Index: ps/trunk/source/lib/file/common/file_stats.cpp =================================================================== --- ps/trunk/source/lib/file/common/file_stats.cpp (revision 19898) +++ ps/trunk/source/lib/file/common/file_stats.cpp (revision 19899) @@ -1,337 +1,337 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * gathers statistics from all file modules. */ #include "precompiled.h" #include "lib/file/common/file_stats.h" #include #include "lib/timer.h" #if FILE_STATS_ENABLED // vfs static size_t vfs_files; static double vfs_size_total; static double vfs_init_elapsed_time; // file static size_t unique_names; static size_t unique_name_len_total; static size_t open_files_cur, open_files_max; // total = opened_files.size() // file_buf static size_t extant_bufs_cur, extant_bufs_max, extant_bufs_total; static double buf_size_total, buf_aligned_size_total; // file_io static size_t user_ios; static double user_io_size_total; static double io_actual_size_total[FI_MAX_IDX][2]; static double io_elapsed_time[FI_MAX_IDX][2]; static double io_process_time_total; static size_t io_seeks; // file_cache static size_t cache_count[2]; static double cache_size_total[2]; static size_t conflict_misses; //static double conflict_miss_size_total; // JW: currently not used nor computed static size_t block_cache_count[2]; // archive builder static size_t ab_connection_attempts; // total number of trace entries static size_t ab_repeated_connections; // how many of these were not unique // convenience functions for measuring elapsed time in an interval. // by exposing start/finish calls, we avoid callers from querying // timestamps when stats are disabled. static double start_time; static void timer_start(double* start_time_storage = &start_time) { // make sure no measurement is currently active // (since start_time is shared static storage) ENSURE(*start_time_storage == 0.0); *start_time_storage = timer_Time(); } static double timer_reset(double* start_time_storage = &start_time) { double elapsed = timer_Time() - *start_time_storage; *start_time_storage = 0.0; return elapsed; } //----------------------------------------------------------------------------- // // vfs // void stats_vfs_file_add(size_t file_size) { vfs_files++; vfs_size_total += file_size; } void stats_vfs_file_remove(size_t file_size) { vfs_files--; vfs_size_total -= file_size; } // stats_vfs_init_* are currently unused void stats_vfs_init_start() { timer_start(); } void stats_vfs_init_finish() { vfs_init_elapsed_time += timer_reset(); } // // file // void stats_unique_name(size_t name_len) { unique_names++; unique_name_len_total += name_len; } void stats_open() { open_files_cur++; open_files_max = std::max(open_files_max, open_files_cur); // could also use a set to determine unique files that have been opened } void stats_close() { ENSURE(open_files_cur > 0); open_files_cur--; } // // file_buf // void stats_buf_alloc(size_t size, size_t alignedSize) { extant_bufs_cur++; extant_bufs_max = std::max(extant_bufs_max, extant_bufs_cur); extant_bufs_total++; buf_size_total += size; buf_aligned_size_total += alignedSize; } void stats_buf_free() { ENSURE(extant_bufs_cur > 0); extant_bufs_cur--; } void stats_buf_ref() { extant_bufs_cur++; } // // file_io // void stats_io_user_request(size_t user_size) { user_ios++; user_io_size_total += user_size; } ScopedIoMonitor::ScopedIoMonitor() { m_startTime = 0.0; timer_start(&m_startTime); } ScopedIoMonitor::~ScopedIoMonitor() { // note: we can only bill IOs that have succeeded :S timer_reset(&m_startTime); } void ScopedIoMonitor::NotifyOfSuccess(FileIOImplentation fi, int opcode, off_t size) { ENSURE(fi < FI_MAX_IDX); ENSURE(opcode == LIO_READ || opcode == LIO_WRITE); io_actual_size_total[fi][opcode == LIO_WRITE] += size; io_elapsed_time[fi][opcode == LIO_WRITE] += timer_reset(&m_startTime); } void stats_cb_start() { timer_start(); } void stats_cb_finish() { io_process_time_total += timer_reset(); } // // file_cache // void stats_cache(CacheRet cr, size_t size) { ENSURE(cr == CR_HIT || cr == CR_MISS); #if 0 if(cr == CR_MISS) { PairIB ret = ever_cached_files.insert(atom_fn); if(!ret.second) // was already cached once { conflict_miss_size_total += size; conflict_misses++; } } #endif cache_count[cr]++; cache_size_total[cr] += size; } void stats_block_cache(CacheRet cr) { ENSURE(cr == CR_HIT || cr == CR_MISS); block_cache_count[cr]++; } // // archive builder // void stats_ab_connection(bool already_exists) { ab_connection_attempts++; if(already_exists) ab_repeated_connections++; } //----------------------------------------------------------------------------- template int percent(T num, T divisor) { if(!divisor) return 0; return (int)(100*num / divisor); } void file_stats_dump() { if(!debug_filter_allows("FILE_STATS|")) return; const double KB = 1e3; const double MB = 1e6; const double ms = 1e-3; debug_printf("--------------------------------------------------------------------------------\n"); debug_printf("File statistics:\n"); // note: we split the reports into several debug_printfs for clarity; // this is necessary anyway due to fixed-size buffer. debug_printf( L"\nvfs:\n" L"Total files: %lu (%g MB)\n" L"Init/mount time: %g ms\n", (unsigned long)vfs_files, vfs_size_total/MB, vfs_init_elapsed_time/ms ); debug_printf( L"\nfile:\n" L"Total names: %lu (%lu KB)\n" L"Max. concurrent: %lu; leaked: %lu.\n", (unsigned long)unique_names, (unsigned long)(unique_name_len_total/1000), (unsigned long)open_files_max, (unsigned long)open_files_cur ); debug_printf( L"\nfile_buf:\n" L"Total buffers used: %lu (%g MB)\n" L"Max concurrent: %lu; leaked: %lu\n" L"Internal fragmentation: %d%%\n", (unsigned long)extant_bufs_total, buf_size_total/MB, (unsigned long)extant_bufs_max, (unsigned long)extant_bufs_cur, percent(buf_aligned_size_total-buf_size_total, buf_size_total) ); debug_printf( L"\nfile_io:\n" L"Total user load requests: %lu (%g MB)\n" L"IO thoughput [MB/s; 0=never happened]:\n" L" lowio: R=%.3g, W=%.3g\n" L" aio: R=%.3g, W=%.3g\n" L"Average size = %g KB; seeks: %lu; total callback time: %g ms\n" L"Total data actually read from disk = %g MB\n", (unsigned long)user_ios, user_io_size_total/MB, #define THROUGHPUT(impl, opcode) (io_elapsed_time[impl][opcode == LIO_WRITE] == 0.0)? 0.0 : (io_actual_size_total[impl][opcode == LIO_WRITE] / io_elapsed_time[impl][opcode == LIO_WRITE] / MB) THROUGHPUT(FI_LOWIO, LIO_READ), THROUGHPUT(FI_LOWIO, LIO_WRITE), THROUGHPUT(FI_AIO , LIO_READ), THROUGHPUT(FI_AIO , LIO_WRITE), user_io_size_total/user_ios/KB, (unsigned long)io_seeks, io_process_time_total/ms, (io_actual_size_total[FI_LOWIO][0]+io_actual_size_total[FI_AIO][0])/MB ); debug_printf( L"\nfile_cache:\n" L"Hits: %lu (%g MB); misses %lu (%g MB); ratio: %u%%\n" L"Percent of requested bytes satisfied by cache: %u%%; non-compulsory misses: %lu (%u%% of misses)\n" L"Block hits: %lu; misses: %lu; ratio: %u%%\n", (unsigned long)cache_count[CR_HIT], cache_size_total[CR_HIT]/MB, (unsigned long)cache_count[CR_MISS], cache_size_total[CR_MISS]/MB, percent(cache_count[CR_HIT], cache_count[CR_HIT]+cache_count[CR_MISS]), percent(cache_size_total[CR_HIT], cache_size_total[CR_HIT]+cache_size_total[CR_MISS]), (unsigned long)conflict_misses, percent(conflict_misses, cache_count[CR_MISS]), (unsigned long)block_cache_count[CR_HIT], (unsigned long)block_cache_count[CR_MISS], percent(block_cache_count[CR_HIT], block_cache_count[CR_HIT]+block_cache_count[CR_MISS]) ); debug_printf( L"\nvfs_optimizer:\n" L"Total trace entries: %lu; repeated connections: %lu; unique files: %lu\n", (unsigned long)ab_connection_attempts, (unsigned long)ab_repeated_connections, (unsigned long)(ab_connection_attempts-ab_repeated_connections) ); } #endif // FILE_STATS_ENABLED Index: ps/trunk/source/lib/file/common/tests/test_trace.h =================================================================== --- ps/trunk/source/lib/file/common/tests/test_trace.h (revision 19898) +++ ps/trunk/source/lib/file/common/tests/test_trace.h (revision 19899) @@ -1,71 +1,71 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/file/common/trace.h" class TestTraceEntry : public CxxTest::TestSuite { public: void test_entry() { std::wstring buf1; std::wstring buf2; TraceEntry t1(TraceEntry::Load, L"example.txt", 1234); TS_ASSERT_EQUALS(t1.Action(), TraceEntry::Load); TS_ASSERT_PATH_EQUALS(t1.Pathname(), OsPath(L"example.txt")); TS_ASSERT_EQUALS(t1.Size(), (size_t)1234); buf1 = t1.EncodeAsText(); // The part before the ':' is an unpredictable timestamp, // so just test the string after that point TS_ASSERT_WSTR_EQUALS(wcschr(buf1.c_str(), ':'), L": L \"example.txt\" 1234\n"); TraceEntry t2(TraceEntry::Store, L"example two.txt", 16777216); TS_ASSERT_EQUALS(t2.Action(), TraceEntry::Store); TS_ASSERT_PATH_EQUALS(t2.Pathname(), OsPath(L"example two.txt")); TS_ASSERT_EQUALS(t2.Size(), (size_t)16777216); buf2 = t2.EncodeAsText(); TS_ASSERT_WSTR_EQUALS(wcschr(buf2.c_str(), ':'), L": S \"example two.txt\" 16777216\n"); TraceEntry t3(buf1); TS_ASSERT_EQUALS(t3.Action(), TraceEntry::Load); TS_ASSERT_PATH_EQUALS(t3.Pathname(), OsPath(L"example.txt")); TS_ASSERT_EQUALS(t3.Size(), (size_t)1234); TraceEntry t4(buf2); TS_ASSERT_EQUALS(t4.Action(), TraceEntry::Store); TS_ASSERT_PATH_EQUALS(t4.Pathname(), OsPath(L"example two.txt")); TS_ASSERT_EQUALS(t4.Size(), (size_t)16777216); } void test_maxpath() { OsPath path1(std::wstring(PATH_MAX, L'x')); std::wstring buf1 = L"0: L \"" + path1.string() + L"\" 0\n"; TraceEntry t1(buf1); TS_ASSERT_PATH_EQUALS(t1.Pathname(), path1); } }; Index: ps/trunk/source/lib/file/disabled_tests/test_path.h =================================================================== --- ps/trunk/source/lib/file/disabled_tests/test_path.h (revision 19898) +++ ps/trunk/source/lib/file/disabled_tests/test_path.h (revision 19899) @@ -1,81 +1,81 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/self_test.h" #include "lib/res/file/path.h" class TestPath : public CxxTest::TestSuite { public: void test_conversion() { char N_path[PATH_MAX] = {0}; TS_ASSERT_OK(file_make_native_path("a/b/c", N_path)); #if OS_WIN TS_ASSERT_STR_EQUALS(N_path, "a\\b\\c"); #else TS_ASSERT_STR_EQUALS(N_path, "a/b/c"); #endif char P_path[PATH_MAX] = {0}; TS_ASSERT_OK(file_make_portable_path("a\\b\\c", P_path)); #if OS_WIN TS_ASSERT_STR_EQUALS(P_path, "a/b/c"); #else // sounds strange, but correct: on non-Windows, \\ didn't // get recognized as separators and weren't converted. TS_ASSERT_STR_EQUALS(P_path, "a\\b\\c"); #endif } // file_make_full_*_path is left untested (hard to do so) void test_pool() { // .. return same address for same string? const char* atom1 = path_Pool->UniqueCopy("a/bc/def"); const char* atom2 = path_Pool->UniqueCopy("a/bc/def"); TS_ASSERT_EQUALS(atom1, atom2); // .. early out (already in pool) check works? const char* atom3 = path_Pool->UniqueCopy(atom1); TS_ASSERT_EQUALS(atom3, atom1); // is it reported as in pool? TS_ASSERT(path_Pool()->Contains(atom1)); // path_Pool()->RandomString // see if the atom added above eventually comes out when a // random one is returned from the pool. int tries_left; for(tries_left = 1000; tries_left != 0; tries_left--) { const char* random_name = path_Pool->RandomString(); if(random_name == atom1) break; } TS_ASSERT(tries_left != 0); } }; Index: ps/trunk/source/lib/file/io/write_buffer.cpp =================================================================== --- ps/trunk/source/lib/file/io/write_buffer.cpp (revision 19898) +++ ps/trunk/source/lib/file/io/write_buffer.cpp (revision 19899) @@ -1,147 +1,147 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/file/io/write_buffer.h" #include "lib/bits.h" // IsAligned #include "lib/sysdep/cpu.h" #include "lib/allocators/shared_ptr.h" #include "lib/file/io/io.h" static const size_t BLOCK_SIZE = 512*KiB; WriteBuffer::WriteBuffer() : m_capacity(pageSize), m_data((u8*)rtl_AllocateAligned(m_capacity, maxSectorSize), AlignedDeleter()), m_size(0) { } void WriteBuffer::EnsureSufficientCapacity(size_t size) { if(m_size + size > m_capacity) { m_capacity = round_up_to_pow2(m_size + size); shared_ptr newData; AllocateAligned(newData, m_capacity, maxSectorSize); memcpy(newData.get(), m_data.get(), m_size); m_data = newData; } } void WriteBuffer::Append(const void* data, size_t size) { EnsureSufficientCapacity(size); memcpy(m_data.get() + m_size, data, size); m_size += size; } void WriteBuffer::Reserve(size_t size) { EnsureSufficientCapacity(size); memset(m_data.get() + m_size, 0, size); m_size += size; } void WriteBuffer::Overwrite(const void* data, size_t size, size_t offset) { ENSURE(offset+size < m_size); memcpy(m_data.get()+offset, data, size); } //----------------------------------------------------------------------------- // UnalignedWriter //----------------------------------------------------------------------------- UnalignedWriter::UnalignedWriter(const PFile& file, off_t ofs) : m_file(file), m_alignedBuf((u8*)rtl_AllocateAligned(BLOCK_SIZE, maxSectorSize), AlignedDeleter()) { m_alignedOfs = round_down(ofs, (off_t)BLOCK_SIZE); const size_t misalignment = (size_t)(ofs - m_alignedOfs); if(misalignment) { io::Operation op(*m_file.get(), m_alignedBuf.get(), BLOCK_SIZE, m_alignedOfs); THROW_STATUS_IF_ERR(io::Run(op)); } m_bytesUsed = misalignment; } UnalignedWriter::~UnalignedWriter() { Flush(); } Status UnalignedWriter::Append(const u8* data, size_t size) const { while(size != 0) { // optimization: write directly from the input buffer, if possible const size_t alignedSize = (size / BLOCK_SIZE) * BLOCK_SIZE; if(m_bytesUsed == 0 && IsAligned(data, maxSectorSize) && alignedSize != 0) { io::Operation op(*m_file.get(), (void*)data, alignedSize, m_alignedOfs); RETURN_STATUS_IF_ERR(io::Run(op)); m_alignedOfs += (off_t)alignedSize; data += alignedSize; size -= alignedSize; } const size_t chunkSize = std::min(size, BLOCK_SIZE-m_bytesUsed); memcpy(m_alignedBuf.get()+m_bytesUsed, data, chunkSize); m_bytesUsed += chunkSize; data += chunkSize; size -= chunkSize; if(m_bytesUsed == BLOCK_SIZE) RETURN_STATUS_IF_ERR(WriteBlock()); } return INFO::OK; } void UnalignedWriter::Flush() const { if(m_bytesUsed) { memset(m_alignedBuf.get()+m_bytesUsed, 0, BLOCK_SIZE-m_bytesUsed); (void)WriteBlock(); } } Status UnalignedWriter::WriteBlock() const { io::Operation op(*m_file.get(), m_alignedBuf.get(), BLOCK_SIZE, m_alignedOfs); RETURN_STATUS_IF_ERR(io::Run(op)); m_alignedOfs += BLOCK_SIZE; m_bytesUsed = 0; return INFO::OK; } Index: ps/trunk/source/lib/file/io/io.h =================================================================== --- ps/trunk/source/lib/file/io/io.h (revision 19898) +++ ps/trunk/source/lib/file/io/io.h (revision 19899) @@ -1,361 +1,361 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * provide asynchronous and synchronous I/O with hooks to allow * overlapped processing or progress reporting. */ #ifndef INCLUDED_IO #define INCLUDED_IO #include "lib/config2.h" #include "lib/alignment.h" #include "lib/bits.h" #include "lib/timer.h" #include "lib/file/file.h" #include "lib/sysdep/filesystem.h" // wtruncate #include "lib/posix/posix_aio.h" // LIO_READ, LIO_WRITE #include "lib/allocators/unique_range.h" namespace ERR { const Status IO = -110301; } namespace io { // @return memory suitable for use as an I/O buffer (address is a // multiple of alignment, size is rounded up to a multiple of alignment) // @param alignment is automatically increased if smaller than the // UniqueRange requirement. // // use this instead of the file cache for write buffers that are // never reused (avoids displacing other items). static inline UniqueRange Allocate(size_t size, size_t alignment = maxSectorSize) { return AllocateAligned(size, alignment); } #pragma pack(push, 1) // required information for any I/O (this is basically the same as aiocb, // but also applies to synchronous I/O and has shorter/nicer names.) struct Operation { // @param buf can be 0, in which case temporary block buffers are allocated. // otherwise, it must be aligned and padded to the I/O alignment, e.g. via // io::Allocate. Operation(const File& file, void* buf, off_t size, off_t offset = 0) : fd(file.Descriptor()), opcode((file.Flags() & O_WRONLY)? LIO_WRITE : LIO_READ) , offset(offset), size(size), buf((void*)buf) { } void Validate() const { ENSURE(fd >= 0); ENSURE(opcode == LIO_READ || opcode == LIO_WRITE); ENSURE(offset >= 0); ENSURE(size >= 0); // buf can legitimately be 0 (see above) } int fd; int opcode; off_t offset; off_t size; void* buf; }; // optional information how an Operation is to be carried out struct Parameters { // default to single blocking I/Os Parameters() : alignment(1) // no alignment requirements , blockSize(0) // do not split into blocks , queueDepth(1) // disable aio { } // parameters for asynchronous I/O that maximize throughput on current drives struct OverlappedTag {}; Parameters(OverlappedTag) : alignment(maxSectorSize), blockSize(128*KiB), queueDepth(32) { } Parameters(size_t blockSize, size_t queueDepth, off_t alignment = maxSectorSize) : alignment(alignment), blockSize(blockSize), queueDepth(queueDepth) { } void Validate(const Operation& op) const { ENSURE(is_pow2(alignment)); ENSURE(alignment > 0); if(blockSize != 0) { ENSURE(is_pow2(blockSize)); ENSURE(pageSize <= blockSize); // (don't bother checking an upper bound) } ENSURE(1 <= queueDepth && queueDepth <= maxQueueDepth); ENSURE(IsAligned(op.offset, alignment)); // op.size doesn't need to be aligned ENSURE(IsAligned(op.buf, alignment)); } // (ATTO only allows 10, which improves upon 8) static const size_t maxQueueDepth = 32; off_t alignment; size_t blockSize; // 0 for one big "block" size_t queueDepth; }; #define IO_OVERLAPPED io::Parameters(io::Parameters::OverlappedTag()) struct DefaultCompletedHook { /** * called after a block I/O has completed. * @return Status (see RETURN_STATUS_FROM_CALLBACK). * * allows progress notification and processing data while waiting for * previous I/Os to complete. **/ Status operator()(const u8* UNUSED(block), size_t UNUSED(blockSize)) const { return INFO::OK; } }; struct DefaultIssueHook { /** * called before a block I/O is issued. * @return Status (see RETURN_STATUS_FROM_CALLBACK). * * allows generating the data to write while waiting for * previous I/Os to complete. **/ Status operator()(aiocb& UNUSED(cb)) const { return INFO::OK; } }; // ring buffer of partially initialized aiocb that can be passed // directly to aio_write etc. after setting offset and buffer. class ControlBlockRingBuffer { public: ControlBlockRingBuffer(const Operation& op, const Parameters& p) : controlBlocks() // zero-initialize { const size_t blockSize = p.blockSize? p.blockSize : (size_t)op.size; const bool temporaryBuffersRequested = (op.buf == 0); if(temporaryBuffersRequested) buffers = io::Allocate(blockSize * p.queueDepth, p.alignment); for(size_t i = 0; i < ARRAY_SIZE(controlBlocks); i++) { aiocb& cb = operator[](i); cb.aio_fildes = op.fd; cb.aio_nbytes = blockSize; cb.aio_lio_opcode = op.opcode; if(temporaryBuffersRequested) cb.aio_buf = (volatile void*)(uintptr_t(buffers.get()) + i * blockSize); } } INLINE aiocb& operator[](size_t counter) { return controlBlocks[counter % ARRAY_SIZE(controlBlocks)]; } private: UniqueRange buffers; aiocb controlBlocks[Parameters::maxQueueDepth]; }; #pragma pack(pop) LIB_API Status Issue(aiocb& cb, size_t queueDepth); LIB_API Status WaitUntilComplete(aiocb& cb, size_t queueDepth); //----------------------------------------------------------------------------- // Run #ifndef ENABLE_IO_STATS #define ENABLE_IO_STATS 0 #endif // (hooks must be passed by const reference to allow passing rvalues. // functors with non-const member data can mark them as mutable.) template static inline Status Run(const Operation& op, const Parameters& p = Parameters(), const CompletedHook& completedHook = CompletedHook(), const IssueHook& issueHook = IssueHook()) { op.Validate(); p.Validate(op); ControlBlockRingBuffer controlBlockRingBuffer(op, p); #if ENABLE_IO_STATS const double t0 = timer_Time(); COMPILER_FENCE; #endif const off_t numBlocks = p.blockSize? (off_t)DivideRoundUp((u64)op.size, (u64)p.blockSize) : 1; for(off_t blocksIssued = 0, blocksCompleted = 0; blocksCompleted < numBlocks; blocksCompleted++) { for(; blocksIssued != numBlocks && blocksIssued < blocksCompleted + (off_t)p.queueDepth; blocksIssued++) { aiocb& cb = controlBlockRingBuffer[blocksIssued]; cb.aio_offset = op.offset + blocksIssued * p.blockSize; if(op.buf) cb.aio_buf = (volatile void*)(uintptr_t(op.buf) + blocksIssued * p.blockSize); if(blocksIssued == numBlocks-1) cb.aio_nbytes = round_up(size_t(op.size - blocksIssued * p.blockSize), size_t(p.alignment)); RETURN_STATUS_FROM_CALLBACK(issueHook(cb)); RETURN_STATUS_IF_ERR(Issue(cb, p.queueDepth)); } aiocb& cb = controlBlockRingBuffer[blocksCompleted]; RETURN_STATUS_IF_ERR(WaitUntilComplete(cb, p.queueDepth)); RETURN_STATUS_FROM_CALLBACK(completedHook((u8*)cb.aio_buf, cb.aio_nbytes)); } #if ENABLE_IO_STATS COMPILER_FENCE; const double t1 = timer_Time(); const off_t totalSize = p.blockSize? numBlocks*p.blockSize : op.size; debug_printf("IO: %.2f MB/s (%.2f)\n", totalSize/(t1-t0)/1e6, (t1-t0)*1e3); #endif return INFO::OK; } // (overloads allow omitting parameters without requiring a template argument list) template static inline Status Run(const Operation& op, const Parameters& p = Parameters(), const CompletedHook& completedHook = CompletedHook()) { return Run(op, p, completedHook, DefaultIssueHook()); } static inline Status Run(const Operation& op, const Parameters& p = Parameters()) { return Run(op, p, DefaultCompletedHook(), DefaultIssueHook()); } //----------------------------------------------------------------------------- // Store // efficient writing requires preallocation; the resulting file is // padded to the sector size and needs to be truncated afterwards. // this function takes care of both. template static inline Status Store(const OsPath& pathname, const void* data, size_t size, const Parameters& p = Parameters(), const CompletedHook& completedHook = CompletedHook(), const IssueHook& issueHook = IssueHook()) { File file; int oflag = O_WRONLY; if(p.queueDepth != 1) oflag |= O_DIRECT; RETURN_STATUS_IF_ERR(file.Open(pathname, oflag)); io::Operation op(file, (void*)data, size); #if OS_WIN (void)waio_Preallocate(op.fd, (off_t)size); #endif RETURN_STATUS_IF_ERR(io::Run(op, p, completedHook, issueHook)); file.Close(); // (required by wtruncate) RETURN_STATUS_IF_ERR(wtruncate(pathname, size)); return INFO::OK; } template static inline Status Store(const OsPath& pathname, const void* data, size_t size, const Parameters& p = Parameters(), const CompletedHook& completedHook = CompletedHook()) { return Store(pathname, data, size, p, completedHook, DefaultIssueHook()); } static inline Status Store(const OsPath& pathname, const void* data, size_t size, const Parameters& p = Parameters()) { return Store(pathname, data, size, p, DefaultCompletedHook(), DefaultIssueHook()); } //----------------------------------------------------------------------------- // Load // convenience function provided for symmetry with Store. template static inline Status Load(const OsPath& pathname, void* buf, size_t size, const Parameters& p = Parameters(), const CompletedHook& completedHook = CompletedHook(), const IssueHook& issueHook = IssueHook()) { File file; int oflag = O_RDONLY; if(p.queueDepth != 1) oflag |= O_DIRECT; RETURN_STATUS_IF_ERR(file.Open(pathname, oflag)); io::Operation op(file, buf, size); return io::Run(op, p, completedHook, issueHook); } template static inline Status Load(const OsPath& pathname, void* buf, size_t size, const Parameters& p = Parameters(), const CompletedHook& completedHook = CompletedHook()) { return Load(pathname, buf, size, p, completedHook, DefaultIssueHook()); } static inline Status Load(const OsPath& pathname, void* buf, size_t size, const Parameters& p = Parameters()) { return Load(pathname, buf, size, p, DefaultCompletedHook(), DefaultIssueHook()); } } // namespace io #endif // #ifndef INCLUDED_IO Index: ps/trunk/source/lib/file/io/io.cpp =================================================================== --- ps/trunk/source/lib/file/io/io.cpp (revision 19898) +++ ps/trunk/source/lib/file/io/io.cpp (revision 19899) @@ -1,102 +1,102 @@ -/* Copyright (c) 2011 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 * "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 "lib/file/io/io.h" #include "lib/sysdep/rtl.h" static const StatusDefinition ioStatusDefinitions[] = { { ERR::IO, L"Error during IO", EIO } }; STATUS_ADD_DEFINITIONS(ioStatusDefinitions); namespace io { // this is just a thin wrapper on top of lowio and POSIX aio. // note that the Windows aio implementation requires buffers, sizes and // offsets to be sector-aligned. Status Issue(aiocb& cb, size_t queueDepth) { #if CONFIG2_FILE_ENABLE_AIO if(queueDepth > 1) { const int ret = (cb.aio_lio_opcode == LIO_WRITE)? aio_write(&cb): aio_read(&cb); if(ret != 0) WARN_RETURN(StatusFromErrno()); } else #else UNUSED2(queueDepth); #endif { ENSURE(lseek(cb.aio_fildes, cb.aio_offset, SEEK_SET) == cb.aio_offset); void* buf = (void*)cb.aio_buf; // cast from volatile void* const ssize_t bytesTransferred = (cb.aio_lio_opcode == LIO_WRITE)? write(cb.aio_fildes, buf, cb.aio_nbytes) : read(cb.aio_fildes, buf, cb.aio_nbytes); if(bytesTransferred < 0) WARN_RETURN(StatusFromErrno()); cb.aio_nbytes = (size_t)bytesTransferred; } return INFO::OK; } Status WaitUntilComplete(aiocb& cb, size_t queueDepth) { #if CONFIG2_FILE_ENABLE_AIO if(queueDepth > 1) { aiocb* const cbs = &cb; timespec* const timeout = 0; // infinite SUSPEND_AGAIN: errno = 0; const int ret = aio_suspend(&cbs, 1, timeout); if(ret != 0) { if(errno == EINTR) // interrupted by signal goto SUSPEND_AGAIN; WARN_RETURN(StatusFromErrno()); } const int err = aio_error(&cb); ENSURE(err != EINPROGRESS); // else aio_return is undefined ssize_t bytesTransferred = aio_return(&cb); if(bytesTransferred == -1) // transfer failed { errno = err; WARN_RETURN(StatusFromErrno()); } cb.aio_nbytes = (size_t)bytesTransferred; } #else UNUSED2(cb); UNUSED2(queueDepth); #endif return INFO::OK; } } // namespace io Index: ps/trunk/source/lib/file/file_system.h =================================================================== --- ps/trunk/source/lib/file/file_system.h (revision 19898) +++ ps/trunk/source/lib/file/file_system.h (revision 19899) @@ -1,88 +1,88 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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. */ /* * higher-level interface on top of sysdep/filesystem.h */ #ifndef INCLUDED_FILE_SYSTEM #define INCLUDED_FILE_SYSTEM #include "lib/os_path.h" #include "lib/posix/posix_filesystem.h" // mode_t LIB_API bool DirectoryExists(const OsPath& path); LIB_API bool FileExists(const OsPath& pathname); LIB_API u64 FileSize(const OsPath& pathname); // (bundling size and mtime avoids a second expensive call to stat()) class CFileInfo { public: CFileInfo() { } CFileInfo(const OsPath& name, off_t size, time_t mtime) : name(name), size(size), mtime(mtime) { } const OsPath& Name() const { return name; } off_t Size() const { return size; } time_t MTime() const { return mtime; } private: OsPath name; off_t size; time_t mtime; }; LIB_API Status GetFileInfo(const OsPath& pathname, CFileInfo* fileInfo); typedef std::vector CFileInfos; typedef std::vector DirectoryNames; LIB_API Status GetDirectoryEntries(const OsPath& path, CFileInfos* files, DirectoryNames* subdirectoryNames); // same as boost::filesystem::create_directories, except that mkdir is invoked with // instead of 0755. // If the breakpoint is enabled, debug_break will be called if the directory didn't exist and couldn't be created. LIB_API Status CreateDirectories(const OsPath& path, mode_t mode, bool breakpoint = true); LIB_API Status DeleteDirectory(const OsPath& dirPath); #endif // #ifndef INCLUDED_FILE_SYSTEM Index: ps/trunk/source/lib/file/io/write_buffer.h =================================================================== --- ps/trunk/source/lib/file/io/write_buffer.h (revision 19898) +++ ps/trunk/source/lib/file/io/write_buffer.h (revision 19899) @@ -1,86 +1,86 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_WRITE_BUFFER #define INCLUDED_WRITE_BUFFER #include "lib/file/file.h" class WriteBuffer { public: WriteBuffer(); void Append(const void* data, size_t size); void Reserve(size_t size); void Overwrite(const void* data, size_t size, size_t offset); shared_ptr Data() const { return m_data; } size_t Size() const { return m_size; } private: void EnsureSufficientCapacity(size_t size); size_t m_capacity; // must come first (init order) shared_ptr m_data; size_t m_size; }; class UnalignedWriter { NONCOPYABLE(UnalignedWriter); public: UnalignedWriter(const PFile& file, off_t ofs); ~UnalignedWriter(); /** * add data to the align buffer, writing it out to disk if full. **/ Status Append(const u8* data, size_t size) const; /** * zero-initialize any remaining space in the align buffer and write * it to the file. this is called by the destructor. **/ void Flush() const; private: Status WriteBlock() const; PFile m_file; shared_ptr m_alignedBuf; mutable off_t m_alignedOfs; mutable size_t m_bytesUsed; }; typedef shared_ptr PUnalignedWriter; #endif // #ifndef INCLUDED_WRITE_BUFFER Index: ps/trunk/source/lib/file/vfs/tests/test_vfs_tree.h =================================================================== --- ps/trunk/source/lib/file/vfs/tests/test_vfs_tree.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/tests/test_vfs_tree.h (revision 19899) @@ -1,191 +1,191 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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 "lib/self_test.h" #include "lib/file/common/file_loader.h" #include "lib/file/vfs/vfs.h" #include "lib/file/vfs/vfs_lookup.h" #include "lib/file/vfs/vfs_tree.h" class MockLoader : public IFileLoader { private: size_t m_Precedence; public: MockLoader(size_t precedence) : m_Precedence(precedence) { } size_t Precedence() const { return m_Precedence; } wchar_t LocationCode() const { return L'\0'; } OsPath Path() const { return L"";} Status Load(const OsPath& UNUSED(name), const shared_ptr& UNUSED(buf), size_t UNUSED(size)) const {return INFO::OK; } }; class TestVfsTree : public CxxTest::TestSuite { /** * We create a few different "mods" here to test proper .DELETED * behavior. * * To check which file is used we use the priority. * * 1 * +--a * +--b/ * | +--a * | \--b/ * | \--a * \--c/ * +--a * \--b * * 2 * +--a.DELETED * +--b/ * | +--a * | \--b.DELETED * +--c.DELETED * \--c/ * +--a * \--b * * 3 * +--a * \--b/ * \--b/ * \--a */ void mount_mod(size_t mod, VfsDirectory& dir) { size_t priority = mod; PIFileLoader loader(new MockLoader(1)); switch(mod) { case 1: { dir.AddFile(VfsFile("a", 0, 0, priority, loader)); VfsDirectory* b = dir.AddSubdirectory("b"); b->AddFile(VfsFile("a", 0, 0, priority, loader)); VfsDirectory* b_b = b->AddSubdirectory("b"); b_b->AddFile(VfsFile("a", 0, 0, priority, loader)); VfsDirectory* c = dir.AddSubdirectory("c"); c->AddFile(VfsFile("a", 0, 0, priority, loader)); c->AddFile(VfsFile("b", 0, 0, priority, loader)); break; } case 2: { dir.DeleteSubtree(VfsFile("a.DELETED", 0, 0, priority, loader)); VfsDirectory* b = dir.AddSubdirectory("b"); b->AddFile(VfsFile("a", 0, 0, priority, loader)); b->DeleteSubtree(VfsFile("b.DELETED", 0, 0, priority, loader)); dir.DeleteSubtree(VfsFile("c.DELETED", 0, 0, priority, loader)); VfsDirectory* c = dir.AddSubdirectory("c"); c->AddFile(VfsFile("a", 0, 0, priority, loader)); c->AddFile(VfsFile("b", 0, 0, priority, loader)); break; } case 3: { dir.AddFile(VfsFile("a", 0, 0, priority, loader)); VfsDirectory* b = dir.AddSubdirectory("b"); VfsDirectory* b_b = b->AddSubdirectory("b"); b_b->AddFile(VfsFile("a", 0, 0, priority, loader)); break; } NODEFAULT; } } void check_priority(VfsDirectory& root, const VfsPath& path, size_t priority) { VfsDirectory* dir; VfsFile* file; TS_ASSERT_OK(vfs_Lookup(path, &root, dir, &file, VFS_LOOKUP_SKIP_POPULATE)); TS_ASSERT_EQUALS(file->Priority(), priority); } void file_does_not_exists(VfsDirectory& root, const VfsPath& path) { VfsDirectory* dir; VfsFile* file; TS_ASSERT_EQUALS(vfs_Lookup(path, &root, dir, &file, VFS_LOOKUP_SKIP_POPULATE), ERR::VFS_FILE_NOT_FOUND); } void directory_exists(VfsDirectory& root, const VfsPath& path, Status error = INFO::OK) { VfsDirectory* dir; TS_ASSERT_EQUALS(vfs_Lookup(path, &root, dir, nullptr, VFS_LOOKUP_SKIP_POPULATE), error); } public: void test_replacement() { VfsDirectory dir; PIFileLoader loader(new MockLoader(1)); VfsFile file0("a", 0, 0, 0, loader); VfsFile file1("a", 0, 20, 0, loader); VfsFile file2("a", 0, 10, 1, loader); // Modification time TS_ASSERT_EQUALS(dir.AddFile(file0)->MTime(), file0.MTime()); TS_ASSERT_EQUALS(dir.AddFile(file1)->MTime(), file1.MTime()); TS_ASSERT_EQUALS(dir.AddFile(file0)->MTime(), file1.MTime()); // Priority TS_ASSERT_EQUALS(dir.AddFile(file2)->MTime(), file2.MTime()); TS_ASSERT_EQUALS(dir.AddFile(file1)->MTime(), file2.MTime()); } void test_deleted() { VfsDirectory dir; mount_mod(1, dir); mount_mod(2, dir); file_does_not_exists(dir, "a"); check_priority(dir, "b/a", 2); directory_exists(dir, "b/b/", ERR::VFS_DIR_NOT_FOUND); directory_exists(dir, "c/"); check_priority(dir, "c/a", 2); check_priority(dir, "c/b", 2); dir.Clear(); mount_mod(1, dir); mount_mod(2, dir); mount_mod(3, dir); check_priority(dir, "a", 3); check_priority(dir, "b/b/a", 3); dir.Clear(); mount_mod(1, dir); mount_mod(3, dir); mount_mod(2, dir); check_priority(dir, "a", 3); check_priority(dir, "b/b/a", 3); dir.Clear(); } }; Index: ps/trunk/source/lib/file/vfs/vfs_lookup.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_lookup.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_lookup.h (revision 19899) @@ -1,75 +1,75 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * look up directories/files by traversing path components. */ #ifndef INCLUDED_VFS_LOOKUP #define INCLUDED_VFS_LOOKUP #include "lib/file/vfs/vfs_path.h" class VfsFile; class VfsDirectory; // note: VfsDirectory pointers are non-const because they may be // populated during the lookup. enum VfsLookupFlags { // add (if they do not already exist) subdirectory components // encountered in the path[name]. VFS_LOOKUP_ADD = 1, // if VFS directories encountered are not already associated // with a real directory, do so (creating the directories // if they do not already exist). VFS_LOOKUP_CREATE = 2, // don't populate the directories encountered. this makes sense // when adding files from an archive, which would otherwise // cause nearly every directory to be populated. VFS_LOOKUP_SKIP_POPULATE = 4, // even create directories if they are already present, this is // useful to write new files to the directory that was attached // last, if the directory wasn't mounted with VFS_MOUNT_REPLACEABLE VFS_LOOKUP_CREATE_ALWAYS = 8 }; /** * Resolve a pathname. * * @param pathname * @param startDirectory VfsStartDirectory. * @param directory is set to the last directory component that is encountered. * @param pfile File is set to 0 if there is no name component, otherwise the * corresponding file. * @param flags @see VfsLookupFlags. * @return Status (INFO::OK if all components in pathname exist). * * to allow noiseless file-existence queries, this does not raise warnings. **/ extern Status vfs_Lookup(const VfsPath& pathname, VfsDirectory* startDirectory, VfsDirectory*& directory, VfsFile** pfile, size_t flags = 0); #endif // #ifndef INCLUDED_VFS_LOOKUP Index: ps/trunk/source/lib/file/vfs/vfs_populate.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_populate.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_populate.h (revision 19899) @@ -1,57 +1,57 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * populate VFS directories with files */ #ifndef INCLUDED_VFS_POPULATE #define INCLUDED_VFS_POPULATE #include "lib/file/common/real_directory.h" class VfsDirectory; /** * attach a real directory to a VFS directory. * * when the VFS directory is accessed, it will first be populated from * the real directory. (this delays the impact of mounting a large * directory, distributing the cost from startup to the first accesses * to each subdirectory.) * * note: the most recently attached real directory will be used when * creating files in the VFS directory. **/ extern Status vfs_Attach(VfsDirectory* directory, const PRealDirectory& realDirectory); /** * populate the directory from the attached real directory. * * adds each real file and subdirectory entry to the VFS directory. * the full contents of any archives in the real directory are also added. * * has no effect if no directory has been attached since the last populate. **/ extern Status vfs_Populate(VfsDirectory* directory); #endif // #ifndef INCLUDED_VFS_POPULATE Index: ps/trunk/source/lib/file/vfs/vfs_util.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_util.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_util.h (revision 19899) @@ -1,102 +1,102 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * helper functions for directory access */ #ifndef INCLUDED_VFS_UTIL #define INCLUDED_VFS_UTIL #include "lib/os_path.h" #include "lib/file/vfs/vfs.h" namespace vfs { extern Status GetPathnames(const PIVFS& fs, const VfsPath& path, const wchar_t* filter, VfsPaths& pathnames); /** * called for files in a directory. * * @param pathname full pathname (since CFileInfo only gives the name). * @param fileInfo file information * @param cbData user-specified context * @return INFO::OK on success; any other value will immediately * be returned to the caller (no more calls will be forthcoming). * * CAVEAT: pathname and fileInfo are only valid until the function * returns! **/ typedef Status (*FileCallback)(const VfsPath& pathname, const CFileInfo& fileInfo, const uintptr_t cbData); /** * called for directories in a directory. * * @param pathname full pathname * @param cbData user-specified context * @return INFO::OK on success; any other value will immediately * be returned to the caller (no more calls will be forthcoming). * * CAVEAT: pathname only valid until the function returns! **/ typedef Status (*DirCallback)(const VfsPath& pathname, const uintptr_t cbData); enum DirFlags { DIR_RECURSIVE = 1 }; /** * call back for each file in a directory tree, and optionally each directory. * * @param fs * @param path * @param cb @ref FileCallback * @param cbData * @param pattern that file names must match. '*' and '&' wildcards * are allowed. 0 matches everything. * @param flags @ref DirFlags * @param dircb @ref DirCallback * @param dircbData * @return Status **/ extern Status ForEachFile(const PIVFS& fs, const VfsPath& path, FileCallback cb, uintptr_t cbData, const wchar_t* pattern = 0, size_t flags = 0, DirCallback dircb = NULL, uintptr_t dircbData = 0); /** * Determine the next available pathname with a given format. * This is useful when creating new files without overwriting the previous * ones (screenshots are a good example). * * @param fs * @param pathnameFormat Format string for the pathname; must contain one * format specifier for an integer. * Example: "screenshots/screenshot%04d.png" * @param nextNumber in: the first number to try; out: the next number. * If 0, numbers corresponding to existing files are skipped. * @param nextPathname receives the output. **/ extern void NextNumberedFilename(const PIVFS& fs, const VfsPath& pathnameFormat, size_t& nextNumber, VfsPath& nextPathname); } // namespace vfs #endif // #ifndef INCLUDED_VFS_UTIL Index: ps/trunk/source/lib/frequency_filter.h =================================================================== --- ps/trunk/source/lib/frequency_filter.h (revision 19898) +++ ps/trunk/source/lib/frequency_filter.h (revision 19899) @@ -1,45 +1,45 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_FREQUENCY_FILTER #define INCLUDED_FREQUENCY_FILTER // calculate frequency of events (tuned for 100 Hz) struct IFrequencyFilter { virtual ~IFrequencyFilter() {} virtual void Update(double value) = 0; // smoothed but rapidly tracked frequency virtual double SmoothedFrequency() const = 0; // stable, non-fluctuating value for user display virtual int StableFrequency() const = 0; }; typedef shared_ptr PIFrequencyFilter; // expectedFrequency is a guess that hopefully speeds up convergence LIB_API PIFrequencyFilter CreateFrequencyFilter(double resolution, double expectedFrequency); #endif // #ifndef INCLUDED_FREQUENCY_FILTER Index: ps/trunk/source/lib/module_init.cpp =================================================================== --- ps/trunk/source/lib/module_init.cpp (revision 19898) +++ ps/trunk/source/lib/module_init.cpp (revision 19899) @@ -1,90 +1,90 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * helpers for module initialization/shutdown. */ #include "precompiled.h" #include "lib/module_init.h" #include "lib/sysdep/cpu.h" // cpu_CAS // not yet initialized, or already shutdown static const ModuleInitState UNINITIALIZED = 0; // value documented in header // running user callback - concurrent ModuleInit callers must spin static const ModuleInitState BUSY = ERR::AGAIN; // never returned // init succeeded; allow shutdown static const ModuleInitState INITIALIZED = INFO::SKIPPED; Status ModuleInit(volatile ModuleInitState* initState, Status (*init)()) { for(;;) { if(cpu_CAS(initState, UNINITIALIZED, BUSY)) { Status ret = init(); *initState = (ret == INFO::OK)? INITIALIZED : ret; COMPILER_FENCE; return ret; } const ModuleInitState latchedInitState = *initState; if(latchedInitState == UNINITIALIZED || latchedInitState == BUSY) { cpu_Pause(); continue; } ENSURE(latchedInitState == INITIALIZED || latchedInitState < 0); return (Status)latchedInitState; } } Status ModuleShutdown(volatile ModuleInitState* initState, void (*shutdown)()) { for(;;) { if(cpu_CAS(initState, INITIALIZED, BUSY)) { shutdown(); *initState = UNINITIALIZED; COMPILER_FENCE; return INFO::OK; } const ModuleInitState latchedInitState = *initState; if(latchedInitState == INITIALIZED || latchedInitState == BUSY) { cpu_Pause(); continue; } if(latchedInitState == UNINITIALIZED) return INFO::SKIPPED; ENSURE(latchedInitState < 0); return (Status)latchedInitState; } } Index: ps/trunk/source/lib/path.h =================================================================== --- ps/trunk/source/lib/path.h (revision 19898) +++ ps/trunk/source/lib/path.h (revision 19899) @@ -1,321 +1,321 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * Path string class, similar to boost::filesystem::basic_path. */ // notes: // - this module is independent of lib/file so that it can be used from // other code without pulling in the entire file manager. // - there is no restriction on buffer lengths except the underlying OS. // input buffers must not exceed PATH_MAX chars, while outputs // must hold at least that much. // - unless otherwise mentioned, all functions are intended to work with // native and VFS paths. // when reading, both '/' and SYS_DIR_SEP are accepted; '/' is written. #ifndef INCLUDED_PATH #define INCLUDED_PATH #if CONFIG_ENABLE_BOOST # include "boost/functional/hash.hpp" #endif #include "lib/utf8.h" #include namespace ERR { const Status PATH_CHARACTER_ILLEGAL = -100300; const Status PATH_CHARACTER_UNSAFE = -100301; const Status PATH_NOT_FOUND = -100302; const Status PATH_MIXED_SEPARATORS = -100303; } /** * is s2 a subpath of s1, or vice versa? (equal counts as subpath) * * @param s1, s2 comparand strings * @return bool **/ LIB_API bool path_is_subpath(const wchar_t* s1, const wchar_t* s2); /** * Get the path component of a path. * Skips over all characters up to the last dir separator, if any. * * @param path Input path. * @return pointer to path component within \. **/ LIB_API const wchar_t* path_name_only(const wchar_t* path); // NB: there is a need for 'generic' paths (e.g. for Trace entry / archive pathnames). // converting between specialized variants via c_str would be inefficient, and the // Os/VfsPath typedefs are hopefully sufficient to avoid errors. class Path { public: typedef std::wstring String; Path() { DetectSeparator(); } Path(const char* p) : path((const unsigned char*)p, (const unsigned char*)p+strlen(p)) // interpret bytes as unsigned; makes no difference for ASCII, // and ensures OsPath on Unix will only contain values 0 <= c < 0x100 { DetectSeparator(); } Path(const wchar_t* p) : path(p, p+wcslen(p)) { DetectSeparator(); } Path(const std::string& s) : path((const unsigned char*)s.c_str(), (const unsigned char*)s.c_str()+s.length()) { DetectSeparator(); } Path(const std::wstring& s) : path(s) { DetectSeparator(); } Path& operator=(const Path& rhs) { path = rhs.path; DetectSeparator(); // (warns if separators differ) return *this; } bool empty() const { return path.empty(); } const String& string() const { return path; } /** * Return a UTF-8 version of the path, in a human-readable but potentially * lossy form. It is *not* safe to take this string and construct a new * Path object from it (it may fail for some non-ASCII paths) - it should * only be used for displaying paths to users. */ std::string string8() const { Status err; #if !OS_WIN // On Unix, assume paths consisting of 8-bit charactes saved in this wide string. std::string spath(path.begin(), path.end()); // Return it if it's valid UTF-8 wstring_from_utf8(spath, &err); if(err == INFO::OK) return spath; // Otherwise assume ISO-8859-1 and let utf8_from_wstring treat each character as a Unicode code point. #endif // On Windows, paths are UTF-16 strings. We don't support non-BMP characters so we can assume it's simply a wstring. return utf8_from_wstring(path, &err); } bool operator<(const Path& rhs) const { return path < rhs.path; } bool operator==(const Path& rhs) const { return path == rhs.path; } bool operator!=(const Path& rhs) const { return !operator==(rhs); } bool IsDirectory() const { if(empty()) // (ensure length()-1 is safe) return true; // (the VFS root directory is represented as an empty string) return path[path.length()-1] == separator; } Path Parent() const { const size_t idxSlash = path.find_last_of(separator); if(idxSlash == String::npos) return L""; return path.substr(0, idxSlash); } Path Filename() const { const size_t idxSlash = path.find_last_of(separator); if(idxSlash == String::npos) return path; return path.substr(idxSlash+1); } Path Basename() const { const Path filename = Filename(); const size_t idxDot = filename.string().find_last_of('.'); if(idxDot == String::npos) return filename; return filename.string().substr(0, idxDot); } // (Path return type allows callers to use our operator==) Path Extension() const { const Path filename = Filename(); const size_t idxDot = filename.string().find_last_of('.'); if(idxDot == String::npos) return Path(); return filename.string().substr(idxDot); } Path ChangeExtension(Path extension) const { return Parent() / Path(Basename().string() + extension.string()); } Path operator/(Path rhs) const { Path ret = *this; if(ret.path.empty()) // (empty paths assume '/') ret.separator = rhs.separator; if(!ret.IsDirectory()) ret.path += ret.separator; if(rhs.path.find((ret.separator == '/')? '\\' : '/') != String::npos) { PrintToDebugOutput(); rhs.PrintToDebugOutput(); DEBUG_WARN_ERR(ERR::PATH_MIXED_SEPARATORS); } ret.path += rhs.path; return ret; } /** * Return the path before the common part of both paths * @param other Indicates the start of the path which should be removed * @note other should be a VfsPath, while this should be an OsPath */ Path BeforeCommon(Path other) const { Path ret = *this; if(ret.empty() || other.empty()) return L""; // Convert the separator to allow for string comparison if(other.separator != ret.separator) replace(other.path.begin(), other.path.end(), other.separator, ret.separator); const size_t idx = ret.path.rfind(other.path); if(idx == String::npos) return L""; return path.substr(0, idx); } static Status Validate(String::value_type c); private: void PrintToDebugOutput() const { debug_printf("Path %s, separator %c\n", string8().c_str(), (char)separator); } void DetectSeparator() { const size_t idxBackslash = path.find('\\'); if(path.find('/') != String::npos && idxBackslash != String::npos) { PrintToDebugOutput(); DEBUG_WARN_ERR(ERR::PATH_MIXED_SEPARATORS); } // (default to '/' for empty strings) separator = (idxBackslash == String::npos)? '/' : '\\'; } String path; // note: ideally, path strings would only contain '/' or even SYS_DIR_SEP. // however, Windows-specific code (e.g. the sound driver detection) // uses these routines with '\\' strings. the boost::filesystem approach of // converting them all to '/' and then back via external_file_string is // annoying and inefficient. we allow either type of separators, // appending whichever was first encountered. when modifying the path, // we ensure the same separator is used. wchar_t separator; }; static inline std::wostream& operator<<(std::wostream& s, const Path& path) { s << path.string(); return s; } static inline std::wistream& operator>>(std::wistream& s, Path& path) { Path::String string; s >> string; path = Path(string); return s; } #if CONFIG_ENABLE_BOOST namespace boost { template<> struct hash : std::unary_function { std::size_t operator()(const Path& path) const { return hash_value(path.string()); } }; } #endif // #if CONFIG_ENABLE_BOOST #endif // #ifndef INCLUDED_PATH Index: ps/trunk/source/lib/posix/posix.cpp =================================================================== --- ps/trunk/source/lib/posix/posix.cpp (revision 19898) +++ ps/trunk/source/lib/posix/posix.cpp (revision 19899) @@ -1,58 +1,58 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/posix/posix.h" #if EMULATE_WCSDUP wchar_t* wcsdup(const wchar_t* str) { const size_t num_chars = wcslen(str); wchar_t* dst = (wchar_t*)malloc((num_chars+1)*sizeof(wchar_t)); // note: wcsdup is required to use malloc if(!dst) return 0; wcscpy_s(dst, num_chars+1, str); return dst; } #endif #if EMULATE_WCSCASECMP int wcscasecmp (const wchar_t* s1, const wchar_t* s2) { wint_t a1, a2; if (s1 == s2) return 0; do { a1 = towlower(*s1++); a2 = towlower(*s2++); if (a1 == L'\0') break; } while (a1 == a2); return a1 - a2; } #endif Index: ps/trunk/source/lib/posix/posix_errno.h =================================================================== --- ps/trunk/source/lib/posix/posix_errno.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_errno.h (revision 19899) @@ -1,27 +1,27 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #if OS_WIN # include "lib/sysdep/os/win/wposix/werrno.h" #else # include #endif Index: ps/trunk/source/lib/file/vfs/file_cache.h =================================================================== --- ps/trunk/source/lib/file/vfs/file_cache.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/file_cache.h (revision 19899) @@ -1,108 +1,108 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * cache of file contents (supports zero-copy IO) */ #ifndef INCLUDED_FILE_CACHE #define INCLUDED_FILE_CACHE #include "lib/file/vfs/vfs_path.h" /** * cache of file contents with support for zero-copy IO. * this works by reserving a region of the cache, using it as the IO buffer, * and returning the memory directly to users. optional write-protection * via MMU ensures that the shared contents aren't inadvertently changed. * * (unique copies of) VFS pathnames are used as lookup key and owner tag. * * to ensure efficient operation and prevent fragmentation, only one * reference should be active at a time. in other words, read a file, * process it, and only then start reading the next file. * * rationale: this is rather similar to BlockCache; however, the differences * (Reserve's size parameter, eviction policies) are enough to warrant * separate implementations. **/ class FileCache { public: /** * @param size maximum amount [bytes] of memory to use for the cache. * (managed as a virtual memory region that's committed on-demand) **/ FileCache(size_t size); /** * Reserve a chunk of the cache's memory region. * * @param size required number of bytes (more may be allocated due to * alignment and/or internal fragmentation) * @return memory suitably aligned for IO; never fails. * * it is expected that this data will be Add()-ed once its IO completes. **/ shared_ptr Reserve(size_t size); /** * Add a file's contents to the cache. * * The cache will be able to satisfy subsequent Retrieve() calls by * returning this data; if CONFIG2_CACHE_READ_ONLY, the buffer is made * read-only. If need be and no references are currently attached to it, * the memory can also be commandeered by Reserve(). * * @param data * @param size * @param pathname key that will be used to Retrieve file contents. * @param cost is the expected cost of retrieving the file again and * influences how/when it is evicted from the cache. **/ void Add(const VfsPath& pathname, const shared_ptr& data, size_t size, size_t cost = 1); /** * Remove a file's contents from the cache (if it exists). * * this ensures subsequent reads of the files see the current, presumably * recently changed, contents of the file. * * this would typically be called in response to a notification that a * file has changed. **/ void Remove(const VfsPath& pathname); /** * Attempt to retrieve a file's contents from the file cache. * * @return whether the contents were successfully retrieved; if so, * data references the read-only file contents. **/ bool Retrieve(const VfsPath& pathname, shared_ptr& data, size_t& size); private: class Impl; shared_ptr impl; }; #endif // #ifndef INCLUDED_FILE_CACHE Index: ps/trunk/source/lib/file/vfs/vfs_lookup.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_lookup.cpp (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_lookup.cpp (revision 19899) @@ -1,149 +1,149 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * look up directories/files by traversing path components. */ #include "precompiled.h" #include "lib/file/vfs/vfs_lookup.h" #include "lib/external_libraries/suppress_boost_warnings.h" #include "lib/sysdep/filesystem.h" #include "lib/file/file.h" #include "lib/file/vfs/vfs.h" // error codes #include "lib/file/vfs/vfs_tree.h" #include "lib/file/vfs/vfs_populate.h" #include "lib/timer.h" static Status CreateDirectory(const OsPath& path) { { const mode_t mode = S_IRWXU; // 0700 as prescribed by XDG basedir const int ret = wmkdir(path, mode); if(ret == 0) // success return INFO::OK; } // failed because the directory already exists. this can happen // when the first vfs_Lookup has addMissingDirectories && // !createMissingDirectories, and the directory is subsequently // created. return 'success' to attach the existing directory.. if(errno == EEXIST) { // but first ensure it's really a directory (otherwise, a // file is "in the way" and needs to be deleted) struct stat s; const int ret = wstat(path, &s); ENSURE(ret == 0); // (wmkdir said it existed) ENSURE(S_ISDIR(s.st_mode)); return INFO::OK; } if (errno == EACCES) return ERR::FILE_ACCESS; // unexpected failure debug_printf("wmkdir failed with errno=%d\n", errno); DEBUG_WARN_ERR(ERR::LOGIC); WARN_RETURN(StatusFromErrno()); } Status vfs_Lookup(const VfsPath& pathname, VfsDirectory* startDirectory, VfsDirectory*& directory, VfsFile** pfile, size_t flags) { // extract and validate flags (ensure no unknown bits are set) const bool addMissingDirectories = (flags & VFS_LOOKUP_ADD) != 0; const bool createMissingDirectories = (flags & VFS_LOOKUP_CREATE) != 0; const bool skipPopulate = (flags & VFS_LOOKUP_SKIP_POPULATE) != 0; const bool createAlways = (flags & VFS_LOOKUP_CREATE_ALWAYS) != 0; ENSURE((flags & ~(VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_SKIP_POPULATE|VFS_LOOKUP_CREATE_ALWAYS)) == 0); directory = startDirectory; if(pfile) *pfile = 0; if(!skipPopulate) RETURN_STATUS_IF_ERR(vfs_Populate(directory)); // early-out for pathname == "" when mounting into VFS root if(pathname.empty()) // (prevent iterator error in loop end condition) { if(pfile) // preserve a guarantee that if pfile then we either return an error or set *pfile return ERR::VFS_FILE_NOT_FOUND; else return INFO::OK; } // for each directory component: size_t pos = 0; // (needed outside of loop) for(;;) { const size_t nextSlash = pathname.string().find_first_of('/', pos); if(nextSlash == VfsPath::String::npos) break; const VfsPath subdirectoryName = pathname.string().substr(pos, nextSlash-pos); pos = nextSlash+1; VfsDirectory* subdirectory = directory->GetSubdirectory(subdirectoryName); if(!subdirectory) { if(addMissingDirectories) subdirectory = directory->AddSubdirectory(subdirectoryName); else return ERR::VFS_DIR_NOT_FOUND; // NOWARN } if(createMissingDirectories && (!subdirectory->AssociatedDirectory() || (createAlways && (subdirectory->AssociatedDirectory()->Flags() & VFS_MOUNT_REPLACEABLE) != 0))) { OsPath currentPath; if(directory->AssociatedDirectory()) // (is NULL when mounting into root) currentPath = directory->AssociatedDirectory()->Path(); currentPath = currentPath / subdirectoryName; RETURN_STATUS_IF_ERR(CreateDirectory(currentPath)); PRealDirectory realDirectory(new RealDirectory(currentPath, 0, 0)); RETURN_STATUS_IF_ERR(vfs_Attach(subdirectory, realDirectory)); } if(!skipPopulate) RETURN_STATUS_IF_ERR(vfs_Populate(subdirectory)); directory = subdirectory; } if(pfile) { ENSURE(!pathname.IsDirectory()); const VfsPath filename = pathname.string().substr(pos); *pfile = directory->GetFile(filename); if(!*pfile) return ERR::VFS_FILE_NOT_FOUND; // NOWARN } return INFO::OK; } Index: ps/trunk/source/lib/file/vfs/vfs_populate.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_populate.cpp (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_populate.cpp (revision 19899) @@ -1,175 +1,175 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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. */ /* * populate VFS directories with files */ #include "precompiled.h" #include "lib/file/vfs/vfs_populate.h" #include "lib/file/archive/archive_zip.h" #include "lib/file/vfs/vfs_tree.h" #include "lib/file/vfs/vfs_lookup.h" #include "lib/file/vfs/vfs.h" // error codes struct CompareFileInfoByName { bool operator()(const CFileInfo& a, const CFileInfo& b) { return a.Name() < b.Name(); } }; // helper class that allows breaking up the logic into sub-functions without // always having to pass directory/realDirectory as parameters. class PopulateHelper { NONCOPYABLE(PopulateHelper); public: PopulateHelper(VfsDirectory* directory, const PRealDirectory& realDirectory) : m_directory(directory), m_realDirectory(realDirectory) { } Status AddEntries() const { CFileInfos files; files.reserve(500); DirectoryNames subdirectoryNames; subdirectoryNames.reserve(50); RETURN_STATUS_IF_ERR(GetDirectoryEntries(m_realDirectory->Path(), &files, &subdirectoryNames)); // Since .DELETED files only remove files in lower priority mods // loose files and archive files have no conflicts so we do not need // to sort them. // We add directories after they might have been removed by .DELETED // files (as they did not contain any files at that point). The order // of GetDirectoryEntries is undefined, but that does not really matter (TODO really?) // so we do not need to sort its output. RETURN_STATUS_IF_ERR(AddFiles(files)); AddSubdirectories(subdirectoryNames); return INFO::OK; } private: void AddFile(const CFileInfo& fileInfo) const { const VfsPath name = fileInfo.Name(); const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), m_realDirectory->Priority(), m_realDirectory); if(name.Extension() == L".DELETED") { m_directory->DeleteSubtree(file); if(!(m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED)) return; } m_directory->AddFile(file); } static void AddArchiveFile(const VfsPath& pathname, const CFileInfo& fileInfo, PIArchiveFile archiveFile, uintptr_t cbData) { PopulateHelper* this_ = (PopulateHelper*)cbData; // (we have to create missing subdirectoryNames because archivers // don't always place directory entries before their files) const size_t flags = VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE; VfsDirectory* directory; WARN_IF_ERR(vfs_Lookup(pathname, this_->m_directory, directory, 0, flags)); const VfsPath name = fileInfo.Name(); const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), this_->m_realDirectory->Priority(), archiveFile); if(name.Extension() == L".DELETED") { directory->DeleteSubtree(file); if(!(this_->m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED)) return; } directory->AddFile(file); } Status AddFiles(const CFileInfos& files) const { const OsPath path(m_realDirectory->Path()); for(size_t i = 0; i < files.size(); i++) { const OsPath pathname = path / files[i].Name(); if(pathname.Extension() == L".zip") { PIArchiveReader archiveReader = CreateArchiveReader_Zip(pathname); // archiveReader == nullptr if file could not be opened (e.g. because // archive is currently open in another program) if(archiveReader) RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(AddArchiveFile, (uintptr_t)this)); } else // regular (non-archive) file AddFile(files[i]); } return INFO::OK; } void AddSubdirectories(const DirectoryNames& subdirectoryNames) const { for(size_t i = 0; i < subdirectoryNames.size(); i++) { // skip version control directories - this avoids cluttering the // VFS with hundreds of irrelevant files. if(subdirectoryNames[i] == L".svn" || subdirectoryNames[i] == L".git") continue; VfsDirectory* subdirectory = m_directory->AddSubdirectory(subdirectoryNames[i]); PRealDirectory realDirectory = CreateRealSubdirectory(m_realDirectory, subdirectoryNames[i]); vfs_Attach(subdirectory, realDirectory); } } VfsDirectory* const m_directory; PRealDirectory m_realDirectory; }; Status vfs_Populate(VfsDirectory* directory) { if(!directory->ShouldPopulate()) return INFO::OK; const PRealDirectory& realDirectory = directory->AssociatedDirectory(); if(realDirectory->Flags() & VFS_MOUNT_WATCH) realDirectory->Watch(); PopulateHelper helper(directory, realDirectory); RETURN_STATUS_IF_ERR(helper.AddEntries()); return INFO::OK; } Status vfs_Attach(VfsDirectory* directory, const PRealDirectory& realDirectory) { RETURN_STATUS_IF_ERR(vfs_Populate(directory)); directory->SetAssociatedDirectory(realDirectory); return INFO::OK; } Index: ps/trunk/source/lib/file/vfs/vfs_util.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_util.cpp (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_util.cpp (revision 19899) @@ -1,139 +1,139 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * helper functions for directory access */ #include "precompiled.h" #include "lib/file/vfs/vfs_util.h" #include #include #include #include "lib/regex.h" #include "lib/sysdep/filesystem.h" namespace vfs { Status GetPathnames(const PIVFS& fs, const VfsPath& path, const wchar_t* filter, VfsPaths& pathnames) { std::vector files; RETURN_STATUS_IF_ERR(fs->GetDirectoryEntries(path, &files, 0)); pathnames.clear(); pathnames.reserve(files.size()); for(size_t i = 0; i < files.size(); i++) { if(match_wildcard(files[i].Name().string().c_str(), filter)) pathnames.push_back(path / files[i].Name()); } return INFO::OK; } Status ForEachFile(const PIVFS& fs, const VfsPath& startPath, FileCallback cb, uintptr_t cbData, const wchar_t* pattern, size_t flags, DirCallback dircb, uintptr_t dircbData) { // (declare here to avoid reallocations) CFileInfos files; DirectoryNames subdirectoryNames; // (a FIFO queue is more efficient than recursion because it uses less // stack space and avoids seeks due to breadth-first traversal.) std::queue pendingDirectories; pendingDirectories.push(startPath/""); while(!pendingDirectories.empty()) { const VfsPath& path = pendingDirectories.front(); RETURN_STATUS_IF_ERR(fs->GetDirectoryEntries(path, &files, &subdirectoryNames)); if(dircb) RETURN_STATUS_IF_ERR(dircb(path, dircbData)); for(size_t i = 0; i < files.size(); i++) { const CFileInfo fileInfo = files[i]; if(!match_wildcard(fileInfo.Name().string().c_str(), pattern)) continue; const VfsPath pathname(path / fileInfo.Name()); // (CFileInfo only stores the name) RETURN_STATUS_IF_ERR(cb(pathname, fileInfo, cbData)); } if(!(flags & DIR_RECURSIVE)) break; for(size_t i = 0; i < subdirectoryNames.size(); i++) pendingDirectories.push(path / subdirectoryNames[i]/""); pendingDirectories.pop(); } return INFO::OK; } void NextNumberedFilename(const PIVFS& fs, const VfsPath& pathnameFormat, size_t& nextNumber, VfsPath& nextPathname) { // (first call only:) scan directory and set nextNumber according to // highest matching filename found. this avoids filling "holes" in // the number series due to deleted files, which could be confusing. // example: add 1st and 2nd; [exit] delete 1st; [restart] // add 3rd -> without this measure it would get number 1, not 3. if(nextNumber == 0) { const VfsPath nameFormat = pathnameFormat.Filename(); const VfsPath path = pathnameFormat.Parent()/""; size_t maxNumber = 0; CFileInfos files; fs->GetDirectoryEntries(path, &files, 0); for(size_t i = 0; i < files.size(); i++) { int number; if(swscanf_s(files[i].Name().string().c_str(), nameFormat.string().c_str(), &number) == 1) maxNumber = std::max(size_t(number), maxNumber); } nextNumber = maxNumber+1; } // now increment number until that file doesn't yet exist. // this is fairly slow, but typically only happens once due // to scan loop above. (we still need to provide for looping since // someone may have added files in the meantime) // we don't bother with binary search - this isn't a bottleneck. do { wchar_t pathnameBuf[PATH_MAX]; swprintf_s(pathnameBuf, ARRAY_SIZE(pathnameBuf), pathnameFormat.string().c_str(), nextNumber++); nextPathname = pathnameBuf; } while(fs->GetFileInfo(nextPathname, 0) == INFO::OK); } } // namespace vfs Index: ps/trunk/source/lib/frequency_filter.cpp =================================================================== --- ps/trunk/source/lib/frequency_filter.cpp (revision 19898) +++ ps/trunk/source/lib/frequency_filter.cpp (revision 19899) @@ -1,253 +1,253 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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 "lib/frequency_filter.h" static const double errorTolerance = 0.25; static const double sensitivity = 0.10; static const double sampleTime = 2.0; // seconds /** * variable-width window for frequency determination **/ class FrequencyEstimator { NONCOPYABLE(FrequencyEstimator); public: FrequencyEstimator(double resolution) : m_minDeltaTime(4.0 * resolution) // chosen to reduce error but still yield rapid updates. , m_lastTime(0) // will be set on first call , m_numEvents(0) { ENSURE(resolution > 0.0); } bool operator()(double time, double& frequency) { m_numEvents++; if(m_lastTime == 0.0) m_lastTime = time; // count # events until deltaTime is large enough // (reduces quantization errors if resolution is low) const double deltaTime = time - m_lastTime; if(deltaTime <= m_minDeltaTime) return false; frequency = m_numEvents / deltaTime; m_numEvents = 0; m_lastTime = time; return true; // success } private: const double m_minDeltaTime; double m_lastTime; int m_numEvents; }; /** * variable-gain IIR filter **/ class IirFilter { public: IirFilter(double sensitivity, double initialValue) : m_sensitivity(sensitivity), m_prev(initialValue) { } // bias = 0: no change. > 0: increase (n-th root). < 0: decrease (^n) double operator()(double x, int bias) { // sensitivity to changes ([0,1]). const double gain = pow(m_sensitivity, ComputeExponent(bias)); return m_prev = x*gain + m_prev*(1.0-gain); } private: static double ComputeExponent(int bias) { if(bias > 0) return 1.0 / bias; // n-th root else if(bias == 0) return 1.0; // no change else return -bias; // power-of-n } double m_sensitivity; double m_prev; }; /** * regulate IIR gain for rapid but smooth tracking of a function. * this is similar in principle to a PID controller but is tuned for * the special case of FPS values to simplify stabilizing the filter. **/ class Controller { public: Controller(double initialValue) : m_timesOnSameSide(0) { std::fill(m_history, m_history+m_historySize, initialValue); } // bias := exponential change to gain, (-inf, inf) int ComputeBias(double smoothedValue, double value) { if(WasOnSameSide(value)) // (must be checked before updating history) m_timesOnSameSide++; else m_timesOnSameSide = 0; // update history std::copy(m_history, m_history+m_historySize, m_history+1); m_history[m_historySize-1] = value; // dampen jitter if(Change(smoothedValue, value) < 0.04) return -1; // dampen spikes/bounces. if(WasSpike()) return -2; // if the past few samples have been consistently above/below // average, the function is changing and we need to catch up. // (similar to I in a PID) if(m_timesOnSameSide >= 3) return std::min(m_timesOnSameSide, 4); // suppress large jumps. if(Change(m_history[m_historySize-1], value) > 0.30) return -4; // gain -> 0 return 0; } private: bool WasOnSameSide(double value) const { int sum = 0; for(size_t i = 0; i < m_historySize; i++) { const int vote = (value >= m_history[i])? 1 : -1; sum += vote; } return abs(sum) == (int)m_historySize; } static double Change(double from, double to) { return fabs(from - to) / from; } // /\ or \/ in last three history entries bool WasSpike() const { cassert(m_historySize >= 3); const double h2 = m_history[m_historySize-3], h1 = m_history[m_historySize-2], h0 = m_history[m_historySize-1]; if(((h2-h1) * (h1-h0)) > 0) // no sign change return false; if(Change(h2, h0) > 0.05) // overall change from oldest to newest value return false; if(Change(h1, h0) < 0.10) // no intervening spike return false; return true; } static const size_t m_historySize = 3; double m_history[m_historySize]; int m_timesOnSameSide; }; class FrequencyFilter : public IFrequencyFilter { NONCOPYABLE(FrequencyFilter); public: FrequencyFilter(double resolution, double expectedFrequency) : m_frequencyEstimator(resolution), m_controller(expectedFrequency), m_iirFilter(sensitivity, expectedFrequency) , m_stableFrequency((int)expectedFrequency), m_smoothedFrequency(expectedFrequency), m_averagedFrequency(expectedFrequency) , m_numberOfSamples((int)(sampleTime * expectedFrequency) + 1) { } virtual void Update(double time) { double frequency; if(!m_frequencyEstimator(time, frequency)) return; const int bias = m_controller.ComputeBias(m_smoothedFrequency, frequency); m_smoothedFrequency = m_iirFilter(frequency, bias); // Keep a moving average of the frequency over the last two seconds // If there is a spike of more than 25% (e.g. loading screen => game) // then reset the moving average and reset the number of samples needed const double difference = fabs(m_smoothedFrequency - m_averagedFrequency); if (difference > errorTolerance * m_averagedFrequency) { m_averagedFrequency = m_smoothedFrequency; m_numberOfSamples = (int)(m_averagedFrequency * sampleTime) + 1; } else m_averagedFrequency = ((double)(m_numberOfSamples - 1) * m_averagedFrequency + m_smoothedFrequency) / (double)m_numberOfSamples; // allow the smoothed FPS to free-run until it is no longer near the // previous stable FPS value. round up because values are more often // too low than too high. m_stableFrequency = (int)(m_averagedFrequency + 0.99); } virtual double SmoothedFrequency() const { return m_smoothedFrequency; } virtual int StableFrequency() const { return m_stableFrequency; } private: FrequencyEstimator m_frequencyEstimator; Controller m_controller; IirFilter m_iirFilter; int m_stableFrequency; double m_smoothedFrequency; double m_averagedFrequency; int m_numberOfSamples; }; PIFrequencyFilter CreateFrequencyFilter(double resolution, double expectedFrequency) { return PIFrequencyFilter(new FrequencyFilter(resolution, expectedFrequency)); } Index: ps/trunk/source/lib/lib_api.h =================================================================== --- ps/trunk/source/lib/lib_api.h (revision 19898) +++ ps/trunk/source/lib/lib_api.h (revision 19899) @@ -1,56 +1,56 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_LIB_API #define INCLUDED_LIB_API #include "lib/sysdep/compiler.h" // note: EXTERN_C cannot be used because shared_ptr is often returned // by value, which requires C++ linkage. #ifdef LIB_STATIC_LINK # define LIB_API #else # if MSC_VERSION # ifdef LIB_BUILD # define LIB_API __declspec(dllexport) # else # define LIB_API __declspec(dllimport) # ifdef NDEBUG # pragma comment(lib, "lowlevel.lib") # else # pragma comment(lib, "lowlevel_d.lib") # endif # endif # elif GCC_VERSION # ifdef LIB_BUILD # define LIB_API __attribute__ ((visibility("default"))) # else # define LIB_API # endif # else # error "Don't know how to define LIB_API for this compiler" # endif #endif #endif // #ifndef INCLUDED_LIB_API Index: ps/trunk/source/lib/path.cpp =================================================================== --- ps/trunk/source/lib/path.cpp (revision 19898) +++ ps/trunk/source/lib/path.cpp (revision 19899) @@ -1,126 +1,126 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * helper functions for path strings. */ #include "precompiled.h" #include "lib/path.h" #include #include static const StatusDefinition pathStatusDefinitions[] = { { ERR::PATH_CHARACTER_ILLEGAL, L"illegal path character" }, { ERR::PATH_CHARACTER_UNSAFE, L"unsafe path character" }, { ERR::PATH_NOT_FOUND, L"path not found" }, { ERR::PATH_MIXED_SEPARATORS, L"path contains both slash and backslash separators" } }; STATUS_ADD_DEFINITIONS(pathStatusDefinitions); static bool path_is_dir_sep(wchar_t c) { if(c == '/' || c == '\\') return true; return false; } // is s2 a subpath of s1, or vice versa? // (equal counts as subpath) bool path_is_subpath(const wchar_t* s1, const wchar_t* s2) { // make sure s1 is the shorter string if(wcslen(s1) > wcslen(s2)) std::swap(s1, s2); wchar_t c1 = 0, last_c1, c2; for(;;) { last_c1 = c1; c1 = *s1++, c2 = *s2++; // end of s1 reached: if(c1 == '\0') { // s1 matched s2 up until: if((c2 == '\0') || // its end (i.e. they're equal length) OR path_is_dir_sep(c2) || // start of next component OR path_is_dir_sep(last_c1)) // ", but both have a trailing slash // => is subpath return true; } // mismatch => is not subpath if(c1 != c2) return false; } } //----------------------------------------------------------------------------- // return pointer to the name component within path (i.e. skips over all // characters up to the last dir separator, if any). const wchar_t* path_name_only(const wchar_t* path) { const wchar_t* slash1 = wcsrchr(path, '/'); const wchar_t* slash2 = wcsrchr(path, '\\'); // neither present, it's a filename only if(!slash1 && !slash2) return path; // return name, i.e. component after the last slash const wchar_t* name = std::max(slash1, slash2)+1; return name; } /*static*/ Status Path::Validate(String::value_type c) { if(c < 32) return ERR::PATH_CHARACTER_UNSAFE; #if !OS_WIN if(c >= UCHAR_MAX) return ERR::PATH_CHARACTER_UNSAFE; #endif switch(c) { case '\\': case '/': case ':': case '"': case '?': case '*': case '<': case '>': case '|': case '^': return ERR::PATH_CHARACTER_ILLEGAL; default: return INFO::OK; } } Index: ps/trunk/source/lib/pch/pch_warnings.h =================================================================== --- ps/trunk/source/lib/pch/pch_warnings.h (revision 19898) +++ ps/trunk/source/lib/pch/pch_warnings.h (revision 19899) @@ -1,77 +1,77 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_PCH_WARNINGS #define INCLUDED_PCH_WARNINGS #include "lib/sysdep/compiler.h" // MSC_VERSION #if MSC_VERSION // .. unimportant but not entirely senseless W4 # pragma warning(disable:4201) // nameless struct (Matrix3D) # pragma warning(disable:4244) // conversion from uintN to uint8 // .. always disabled W4 # pragma warning(disable:4103) // alignment changed after including header (boost has #pragma pack/pop in separate headers) # pragma warning(disable:4127) // conditional expression is constant; rationale: see STMT in lib.h. # pragma warning(disable:4324) // structure was padded due to __declspec(align()) # pragma warning(disable:4351) // yes, default init of array entries is desired # pragma warning(disable:4355) // 'this' used in base member initializer list # pragma warning(disable:4512) // assignment operator could not be generated # pragma warning(disable:4718) // recursive call has no side effects, deleting # pragma warning(disable:4786) // identifier truncated to 255 chars # pragma warning(disable:4996) // function is deprecated # pragma warning(disable:6011) // dereferencing NULL pointer # pragma warning(disable:6246) // local declaration hides declaration of the same name in outer scope # pragma warning(disable:6326) // potential comparison of a constant with another constant # pragma warning(disable:6334) // sizeof operator applied to an expression with an operator might yield unexpected results // .. Intel-specific # if ICC_VERSION # pragma warning(disable:383) // value copied to temporary, reference to temporary used # pragma warning(disable:981) // operands are evaluated in unspecified order # pragma warning(disable:1418) // external function definition with no prior declaration (raised for all non-static function templates) # pragma warning(disable:1572) // floating-point equality and inequality comparisons are unreliable # pragma warning(disable:1684) // conversion from pointer to same-sized integral type # pragma warning(disable:1786) // function is deprecated (disabling 4996 isn't sufficient) # pragma warning(disable:2415) // variable of static storage duration was declared but never referenced (raised by Boost) # endif // .. disabled by default in W4, ENABLE them # pragma warning(default:4062) // enumerator is not handled # pragma warning(default:4254) // [bit field] conversion, possible loss of data # pragma warning(default:4265) // class has virtual functions, but destructor is not virtual # pragma warning(default:4296) // [unsigned comparison vs. 0 =>] expression is always false # pragma warning(default:4545 4546 4547 4549) // ill-formed comma expressions; exclude 4548 since _SECURE_SCL triggers it frequently # pragma warning(default:4557) // __assume contains effect //# pragma warning(default:4710) // function not inlined (often happens in STL) # pragma warning(default:4836) // local types or unnamed types cannot be used as template arguments # pragma warning(default:4905) // wide string literal cast to LPSTR # pragma warning(default:4906) // string literal cast to LPWSTR # pragma warning(default:4928) // illegal copy-initialization; more than one user-defined conversion has been implicitly applied # pragma warning(default:4946) // reinterpret_cast used between related classes // .. disabled by default in W4, leave them that way //# pragma warning(default:4191) // unsafe conversion (false alarms for function pointers) //# pragma warning(default:4263) // member function does not override any base class virtual member function (happens in GUI) //# pragma warning(default:4555) // expression has no effect (triggered by STL unused) //# pragma warning(default:4619) // #pragma warning: there is no [such] warning number (false alarms in STL) //# pragma warning(default:4668) // not defined as a preprocessor macro, replacing with '0' (frequent in Windows) #endif #endif // #ifndef INCLUDED_PCH_WARNINGS Index: ps/trunk/source/lib/posix/posix_dlfcn.h =================================================================== --- ps/trunk/source/lib/posix/posix_dlfcn.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_dlfcn.h (revision 19899) @@ -1,29 +1,29 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #if OS_WIN # include "lib/sysdep/os/win/wposix/wdlfcn.h" #else # include #endif #include "lib/posix/posix_errno.h" // for user convenience Index: ps/trunk/source/lib/posix/posix_pthread.h =================================================================== --- ps/trunk/source/lib/posix/posix_pthread.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_pthread.h (revision 19899) @@ -1,30 +1,30 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #if OS_WIN # include "lib/sysdep/os/win/wposix/wpthread.h" #else # include # include #endif #include "lib/posix/posix_errno.h" // for user convenience Index: ps/trunk/source/lib/file/vfs/file_cache.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/file_cache.cpp (revision 19898) +++ ps/trunk/source/lib/file/vfs/file_cache.cpp (revision 19899) @@ -1,253 +1,253 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * cache of file contents (supports zero-copy IO) */ #include "precompiled.h" #include "lib/file/vfs/file_cache.h" #include "lib/external_libraries/suppress_boost_warnings.h" #include "lib/file/common/file_stats.h" #include "lib/adts/cache_adt.h" #include "lib/bits.h" // round_up #include "lib/allocators/allocator_checker.h" #include "lib/allocators/shared_ptr.h" #include "lib/allocators/headerless.h" #include "lib/sysdep/os_cpu.h" // os_cpu_PageSize #include "lib/posix/posix_mman.h" // mprotect //----------------------------------------------------------------------------- // allocator /* the biggest worry of a file cache is external fragmentation. there are two basic ways to combat this: 1) 'defragment' periodically - move blocks around to increase size of available 'holes'. 2) prevent fragmentation from occurring at all via deliberate alloc/free policy. file contents are returned directly to the user (zero-copy IO), so only currently unreferenced blocks can be moved. it is believed that this would severely hamper defragmentation; we therefore go with the latter approach. the basic insight is: fragmentation occurs when a block is freed whose neighbors are not free (thus preventing coalescing). this can be prevented by allocating objects of similar lifetimes together. typical workloads (uniform access frequency) already show such behavior: the Landlord cache manager evicts files in an LRU manner, which matches the allocation policy. references: "The Memory Fragmentation Problem - Solved?" (Johnstone and Wilson) "Dynamic Storage Allocation - A Survey and Critical Review" (Johnstone and Wilson) */ // shared_ptrs must own a reference to their allocator to ensure it's extant when // they are freed. it is stored in the shared_ptr deleter. class Allocator; typedef shared_ptr PAllocator; class FileCacheDeleter { public: FileCacheDeleter(size_t size, const PAllocator& allocator) : m_size(size), m_allocator(allocator) { } // (this uses Allocator and must come after its definition) void operator()(u8* mem) const; private: size_t m_size; PAllocator m_allocator; }; // adds statistics and AllocatorChecker to a HeaderlessAllocator class Allocator { public: Allocator(size_t maxSize) : m_allocator(maxSize) { } shared_ptr Allocate(size_t size, const PAllocator& pthis) { const size_t alignedSize = Align(size); u8* mem = (u8*)m_allocator.Allocate(alignedSize); if(!mem) return DummySharedPtr(0); // (prevent FileCacheDeleter from seeing a null pointer) #ifndef NDEBUG m_checker.OnAllocate(mem, alignedSize); #endif stats_buf_alloc(size, alignedSize); return shared_ptr(mem, FileCacheDeleter(size, pthis)); } void Deallocate(u8* mem, size_t size) { const size_t alignedSize = Align(size); // (re)allow writes in case the buffer was made read-only. it would // be nice to unmap the buffer, but this is not possible because // HeaderlessAllocator needs to affix boundary tags. (void)mprotect(mem, size, PROT_READ|PROT_WRITE); #ifndef NDEBUG m_checker.OnDeallocate(mem, alignedSize); #endif m_allocator.Deallocate(mem, alignedSize); stats_buf_free(); } private: HeaderlessAllocator m_allocator; #ifndef NDEBUG AllocatorChecker m_checker; #endif }; void FileCacheDeleter::operator()(u8* mem) const { m_allocator->Deallocate(mem, m_size); } //----------------------------------------------------------------------------- // FileCache::Impl //----------------------------------------------------------------------------- // since users are strongly encouraged to only load/process one file at a // time, there won't be many active references to cache entries. we could // take advantage of this with a separate extant list, but the cache's // hash map should be fast enough and this way is less work than maintaining // (possibly disjunct) cached and extant lists. class FileCache::Impl { public: Impl(size_t maxSize) : m_allocator(new Allocator(maxSize)) { } shared_ptr Reserve(size_t size) { // (should never happen because the VFS ensures size != 0.) ENSURE(size != 0); // (300 iterations have been observed when reserving several MB // of space in a full cache) for(;;) { { shared_ptr data = m_allocator->Allocate(size, m_allocator); if(data) return data; } // remove least valuable entry from cache (if users are holding // references, the contents won't actually be deallocated) { shared_ptr discardedData; size_t discardedSize; bool removed = m_cache.remove_least_valuable(&discardedData, &discardedSize); // the cache is empty, and allocation still failed. // apparently the cache is full of data that's still // referenced, so we can't reserve any more space. if(!removed) return shared_ptr(); } } } void Add(const VfsPath& pathname, const shared_ptr& data, size_t size, size_t cost) { // zero-copy cache => all users share the contents => must not // allow changes. this will be reverted when deallocating. (void)mprotect((void*)data.get(), size, PROT_READ); m_cache.add(pathname, data, size, cost); } bool Retrieve(const VfsPath& pathname, shared_ptr& data, size_t& size) { // (note: don't call stats_cache because we don't know the file size // in case of a cache miss; doing so is left to the caller.) stats_buf_ref(); return m_cache.retrieve(pathname, data, &size); } void Remove(const VfsPath& pathname) { m_cache.remove(pathname); // note: we could check if someone is still holding a reference // to the contents, but that currently doesn't matter. } private: typedef Cache > CacheType; CacheType m_cache; PAllocator m_allocator; }; //----------------------------------------------------------------------------- FileCache::FileCache(size_t size) : impl(new Impl(size)) { } shared_ptr FileCache::Reserve(size_t size) { return impl->Reserve(size); } void FileCache::Add(const VfsPath& pathname, const shared_ptr& data, size_t size, size_t cost) { impl->Add(pathname, data, size, cost); } void FileCache::Remove(const VfsPath& pathname) { impl->Remove(pathname); } bool FileCache::Retrieve(const VfsPath& pathname, shared_ptr& data, size_t& size) { return impl->Retrieve(pathname, data, size); } Index: ps/trunk/source/lib/file/vfs/vfs.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs.h (revision 19899) @@ -1,240 +1,240 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * Virtual File System API - allows transparent access to files in * archives, modding via multiple mount points and hotloading. */ #ifndef INCLUDED_VFS #define INCLUDED_VFS #include "lib/file/file_system.h" // CFileInfo #include "lib/file/vfs/vfs_path.h" namespace ERR { const Status VFS_DIR_NOT_FOUND = -110100; const Status VFS_FILE_NOT_FOUND = -110101; const Status VFS_ALREADY_MOUNTED = -110102; } // (recursive mounting and mounting archives are no longer optional since they don't hurt) enum VfsMountFlags { /** * all real directories mounted during this operation will be watched * for changes. this flag is provided to avoid watches in output-only * directories, e.g. screenshots/ (only causes unnecessary overhead). **/ VFS_MOUNT_WATCH = 1, /** * anything mounted from here should be included when building archives. **/ VFS_MOUNT_ARCHIVABLE = 2, /** * return ERR::VFS_DIR_NOT_FOUND if the given real path doesn't exist. * (the default behavior is to create all real directories in the path) **/ VFS_MOUNT_MUST_EXIST = 4, /** * keep the files named "*.DELETED" visible in the VFS directories. * the standard behavior of hiding the file with the same name minus the * ".DELETED" suffix will still apply. * (the default behavior is to hide both the suffixed and unsuffixed files) **/ VFS_MOUNT_KEEP_DELETED = 8, /** * mark a directory replaceable, so that when writing a file to this path * new real directories will be created instead of reusing already existing * ones mounted at a subpath of the VFS path. * (the default behaviour is to write to the real directory associated * with the VFS directory that was last mounted to this path (or subpath)) **/ VFS_MOUNT_REPLACEABLE = 16 }; // (member functions are thread-safe after the instance has been // constructed - each acquires a pthread mutex.) struct IVFS { virtual ~IVFS() {} /** * mount a directory into the VFS. * * @param mountPoint (will be created if it does not already exist) * @param path real directory path * @param flags * @param priority * @return Status. * * if files are encountered that already exist in the VFS (sub)directories, * the most recent / highest priority/precedence version is preferred. * * if files with archive extensions are seen, their contents are added * as well. **/ virtual Status Mount(const VfsPath& mountPoint, const OsPath& path, size_t flags = 0, size_t priority = 0) = 0; /** * Retrieve information about a file (similar to POSIX stat). * * @param pathname * @param pfileInfo receives information about the file. Passing NULL * suppresses warnings if the file doesn't exist. * * @return Status. **/ virtual Status GetFileInfo(const VfsPath& pathname, CFileInfo* pfileInfo) const = 0; /** * Retrieve mount priority for a file. * * @param pathname * @param ppriority receives priority value, if the file can be found. * * @return Status. **/ virtual Status GetFilePriority(const VfsPath& pathname, size_t* ppriority) const = 0; /** * Retrieve lists of all files and subdirectories in a directory. * * @return Status. * * Rationale: * - this interface avoids having to lock the directory while an * iterator is extant. * - we cannot efficiently provide routines for returning files and * subdirectories separately due to the underlying POSIX interface. **/ virtual Status GetDirectoryEntries(const VfsPath& path, CFileInfos* fileInfos, DirectoryNames* subdirectoryNames) const = 0; /** * Create a file with the given contents. * @param pathname * @param fileContents * @param size [bytes] of the contents, will match that of the file. * @return Status. * * rationale: disallowing partial writes simplifies file cache coherency * (we need only invalidate cached data when closing a newly written file). **/ virtual Status CreateFile(const VfsPath& pathname, const shared_ptr& fileContents, size_t size) = 0; /** * Replace a file with the given contents. * * @see CreateFile * * Used to replace a file if it is already present (even if the file is not * in the attached vfs directory). Calls CreateFile if the file doesn't yet * exist. **/ virtual Status ReplaceFile(const VfsPath& pathname, const shared_ptr& fileContents, size_t size) = 0; /** * Read an entire file into memory. * * @param pathname * @param fileContents receives a smart pointer to the contents. * CAVEAT: this will be taken from the file cache if the VFS was * created with cacheSize != 0 and size < cacheSize. There is no * provision for Copy-on-Write, which means that such buffers * must not be modified (this is enforced via mprotect). * @param size receives the size [bytes] of the file contents. * @return Status. **/ virtual Status LoadFile(const VfsPath& pathname, shared_ptr& fileContents, size_t& size) = 0; /** * @return a string representation of all files and directories. **/ virtual std::wstring TextRepresentation() const = 0; /** * retrieve the real (POSIX) pathname underlying a VFS file. * * this is useful for passing paths to external libraries. **/ virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname) = 0; /** * retrieve the real (POSIX) pathname underlying a VFS directory. * * this is useful for passing paths to external libraries. **/ virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname) = 0; /** * retrieve the VFS pathname that corresponds to a real file. * * this is useful for reacting to file change notifications. * * the current implementation requires time proportional to the * number of directories; this could be accelerated by only checking * directories below a mount point with a matching real path. **/ virtual Status GetVirtualPath(const OsPath& realPathname, VfsPath& pathname) = 0; /** * remove file from the virtual directory listing and evict its * data from the cache. **/ virtual Status RemoveFile(const VfsPath& pathname) = 0; /** * request the directory be re-populated when it is next accessed. * useful for synchronizing with the underlying filesystem after * files have been created or their metadata changed. **/ virtual Status RepopulateDirectory(const VfsPath& path) = 0; /** * empty the contents of the filesystem. * this is typically only necessary when changing the set of * mounted directories, e.g. when switching mods. * NB: open files are not affected. **/ virtual void Clear() = 0; }; typedef shared_ptr PIVFS; /** * create an instance of a Virtual File System. * * @param cacheSize size [bytes] of memory to reserve for a file cache, * or zero to disable it. if small enough to fit, file contents are * stored here until no references remain and they are evicted. * * note: there is no limitation to a single instance, it may make sense * to create and destroy VFS instances during each unit test. **/ LIB_API PIVFS CreateVfs(size_t cacheSize); #endif // #ifndef INCLUDED_VFS Index: ps/trunk/source/lib/file/vfs/vfs_path.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_path.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_path.h (revision 19899) @@ -1,44 +1,44 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_VFS_PATH #define INCLUDED_VFS_PATH #include "lib/path.h" /** * VFS path of the form "(dir/)*file?" * * in other words: the root directory is "" and paths are separated by '/'. * a trailing slash is allowed for directory names. * rationale: it is important to avoid a leading slash because that might be * confused with an absolute POSIX path. * * there is no restriction on path length; when dimensioning character * arrays, prefer PATH_MAX. **/ typedef Path VfsPath; typedef std::vector VfsPaths; #endif // #ifndef INCLUDED_VFS_PATH Index: ps/trunk/source/lib/file/vfs/vfs_tree.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_tree.h (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_tree.h (revision 19899) @@ -1,194 +1,194 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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. */ /* * 'tree' of VFS directories and files */ #ifndef INCLUDED_VFS_TREE #define INCLUDED_VFS_TREE #include #include "lib/file/file_system.h" // CFileInfo #include "lib/file/common/file_loader.h" // PIFileLoader #include "lib/file/common/real_directory.h" // PRealDirectory #include "lib/file/vfs/vfs_path.h" class VfsFile { public: VfsFile(const VfsPath& name, size_t size, time_t mtime, size_t priority, const PIFileLoader& provider); const VfsPath& Name() const { return m_name; } size_t Size() const { return m_size; } time_t MTime() const { return m_mtime; } size_t Priority() const { return m_priority; } const PIFileLoader& Loader() const { return m_loader; } private: VfsPath m_name; size_t m_size; time_t m_mtime; size_t m_priority; PIFileLoader m_loader; }; class VfsDirectory { /** * remove all files with a lower priority than @p file * and do the same for all subdirectories recursively. * @return true if the directory is empty afterwards **/ bool DeleteTree(const VfsFile& file); public: typedef std::map VfsFiles; typedef std::map VfsSubdirectories; VfsDirectory(); /** * remove the given file or subdirectory according to the priority of the * passed .DELETED file. * CAUTION: Invalidates all previously returned pointers of the file or * subdirectory (and contents) if those have lower priority * than @p file. **/ void DeleteSubtree(const VfsFile& file); /** * @return address of existing or newly inserted file. **/ VfsFile* AddFile(const VfsFile& file); /** * @return address of existing or newly inserted subdirectory. **/ VfsDirectory* AddSubdirectory(const VfsPath& name); /** * remove the given file from the virtual directory (no effect on * the physical file). no effect if the file does not exist. **/ void RemoveFile(const VfsPath& name); /** * @return file with the given name. * (note: non-const to allow changes to the file) **/ VfsFile* GetFile(const VfsPath& name); /** * @return subdirectory with the given name. * (note: non-const to allow changes to the subdirectory) **/ VfsDirectory* GetSubdirectory(const VfsPath& name); // note: exposing only iterators wouldn't enable callers to reserve space. const VfsFiles& Files() const { return m_files; } const VfsSubdirectories& Subdirectories() const { return m_subdirectories; } /** * side effect: the next ShouldPopulate() will return true. **/ void SetAssociatedDirectory(const PRealDirectory& realDirectory); const PRealDirectory& AssociatedDirectory() const { return m_realDirectory; } /** * @return whether this directory should be populated from its * AssociatedDirectory(). note that calling this is a promise to * do so if true is returned -- the flag is reset immediately. **/ bool ShouldPopulate(); /** * ensure the next ShouldPopulate returns true. **/ void RequestRepopulate(); /** * empty file and subdirectory lists (e.g. when rebuilding VFS). * CAUTION: this invalidates all previously returned pointers. **/ void Clear(); private: VfsFiles m_files; VfsSubdirectories m_subdirectories; PRealDirectory m_realDirectory; volatile intptr_t m_shouldPopulate; // (cpu_CAS can't be used on bool) }; /** * @return a string containing file attributes (location, size, timestamp) and name. **/ extern std::wstring FileDescription(const VfsFile& file); /** * @return a string holding each files' description (one per line). **/ extern std::wstring FileDescriptions(const VfsDirectory& directory, size_t indentLevel); /** * append each directory's files' description to the given string. **/ void DirectoryDescriptionR(std::wstring& descriptions, const VfsDirectory& directory, size_t indentLevel); #endif // #ifndef INCLUDED_VFS_TREE Index: ps/trunk/source/lib/fnv_hash.h =================================================================== --- ps/trunk/source/lib/fnv_hash.h (revision 19898) +++ ps/trunk/source/lib/fnv_hash.h (revision 19899) @@ -1,55 +1,55 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Fowler/Noll/Vo string hash */ #ifndef INCLUDED_FNV_HASH #define INCLUDED_FNV_HASH /** * rationale: this algorithm was chosen because it delivers 'good' results * for string data and is relatively simple. other good alternatives exist; * see Ozan Yigit's hash roundup. **/ /** * calculate FNV1-A hash. * * @param buf input buffer. * @param len if 0 (default), treat buf as a C-string; otherwise, * indicates how many bytes of buffer to hash. * @return hash result. note: results are distinct for buffers containing * differing amounts of zero bytes because the hash value is seeded. **/ extern u32 fnv_hash(const void* buf, size_t len = 0); /// 64-bit version of fnv_hash. extern u64 fnv_hash64(const void* buf, size_t len = 0); /** * special version of fnv_hash for strings: first converts to lowercase * (useful for comparing mixed-case filenames) **/ extern u32 fnv_lc_hash(const char* str, size_t len = 0); #endif // #ifndef INCLUDED_FNV_HASH Index: ps/trunk/source/lib/lib.h =================================================================== --- ps/trunk/source/lib/lib.h (revision 19898) +++ ps/trunk/source/lib/lib.h (revision 19899) @@ -1,168 +1,168 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * various utility functions. */ /** low-level aka "lib" ------------------- this codebase was grown from modules shared between several projects, i.e. my personal library; hence the name "lib". it has been expanded to fit the needs of 0ad - in particular, resource loading. owing to the dual-use situation, the 0ad coding conventions are not met; also, major changes are ill-advised because they may break other projects. design goals ------------ - fast and low-overhead, including startup time - portable: must run on Win32, Mac OS X and Linux - reusable across projects, i.e. no dependency on a central 'manager' that ties modules together. scope ----- - POSIX definitions - resource management - debugging tools (including memory tracker) - low-level helper functions, e.g. ADTs, endian conversion and timing - platform-dependent system/feature detection **/ #ifndef INCLUDED_LIB #define INCLUDED_LIB #include // fabsf #include // numeric_limits #include // out_of_range #include // min, max template T Clamp(T val, T min, T max) { ASSERT(min <= max); return std::max(min, std::min(val, max)); } template T DivideRoundUp(T dividend, T divisor) { ASSERT(divisor != 0); return (dividend + divisor-1) / divisor; } /** * are the given floats nearly "equal"? * * @return whether the numbers are within "epsilon" of each other. * * notes: * - the epsilon magic number varies with the magnitude of the inputs. * we use a sane default, but don't use this routine for very * large/small comparands. * - floating-point numbers don't magically lose precision. addition, * subtraction and multiplication results are precise up to the mantissa's * least-significant bit. only division, sqrt, sin/cos and other * transcendental operations introduce error. **/ inline bool feq(double d1, double d2, double epsilon = 0.00001) { return fabs(d1 - d2) < epsilon; } inline bool feqf(float f1, float f2, float epsilon = 0.001f) { return fabsf(f1 - f2) < epsilon; } inline bool IsSimilarMagnitude(double d1, double d2, const double relativeErrorTolerance = 0.05) { const double relativeError = fabs(d1/d2 - 1.0); if(relativeError > relativeErrorTolerance) return false; return true; } //----------------------------------------------------------------------------- // type conversion // note: these avoid a common mistake in using >> (ANSI requires // shift count be less than the bit width of the type). extern u32 u64_hi(u64 x); /// return upper 32-bits extern u32 u64_lo(u64 x); /// return lower 32-bits extern u16 u32_hi(u32 x); /// return upper 16-bits extern u16 u32_lo(u32 x); /// return lower 16-bits extern u64 u64_from_u32(u32 hi, u32 lo); /// assemble u64 from u32 extern u32 u32_from_u16(u16 hi, u16 lo); /// assemble u32 from u16 // safe downcasters: cast from any integral type to u32 or u16; // issues warning if larger than would fit in the target type. // // these are generally useful but included here (instead of e.g. lib.h) for // several reasons: // - including implementation in lib.h doesn't work because the definition // of ENSURE in turn requires lib.h's STMT. // - separate compilation of templates via export isn't supported by // most compilers. template u8 u8_from_larger(T x) { const u8 max = std::numeric_limits::max(); if((u64)x > (u64)max) throw std::out_of_range("u8_from_larger"); return (u8)(x & max); } template u16 u16_from_larger(T x) { const u16 max = std::numeric_limits::max(); if((u64)x > (u64)max) throw std::out_of_range("u16_from_larger"); return (u16)(x & max); } template u32 u32_from_larger(T x) { const u32 max = std::numeric_limits::max(); if((u64)x > (u64)max) throw std::out_of_range("u32_from_larger"); return (u32)(x & max); } /// convert double to u8; verifies number is in range. extern u8 u8_from_double(double in); /// convert double to u16; verifies number is in range. extern u16 u16_from_double(double in); #endif // #ifndef INCLUDED_LIB Index: ps/trunk/source/lib/os_path.h =================================================================== --- ps/trunk/source/lib/os_path.h (revision 19898) +++ ps/trunk/source/lib/os_path.h (revision 19899) @@ -1,56 +1,56 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_OS_PATH #define INCLUDED_OS_PATH #include "lib/path.h" // rationale: // users are responsible for ensuring the path doesn't contain any forbidden // characters (including any code points < 0x00 or >= 0x100 on anything but Windows) typedef Path OsPath; #if OS_WIN static inline const Path::String& OsString(const OsPath& path) { return path.string(); } #else static inline std::string OsString(const OsPath& path) { const Path::String& wstring = path.string(); std::string string(wstring.length(), '\0'); for(size_t i = 0; i < wstring.length(); i++) { ENSURE((unsigned)wstring[i] <= (unsigned)UCHAR_MAX); string[i] = (char)wstring[i]; } return string; } #endif #endif // #ifndef INCLUDED_OS_PATH Index: ps/trunk/source/lib/pch/pch_stdlib.h =================================================================== --- ps/trunk/source/lib/pch/pch_stdlib.h (revision 19898) +++ ps/trunk/source/lib/pch/pch_stdlib.h (revision 19899) @@ -1,93 +1,93 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_PCH_STDLIB #define INCLUDED_PCH_STDLIB #if !MINIMAL_PCH // all new-form C library headers #include #include #include #include //#include // defines e.g. "and" to "&". unnecessary and causes trouble with asm. #include #include #include //#include // incompatible with libpng on Debian/Ubuntu #include #include #include #include #include #include #include #include #include #endif // !MINIMAL_PCH #if MINIMAL_PCH < 2 // common C++98 STL headers #include #include #endif #if MINIMAL_PCH < 3 // all other C++98 STL headers #include #include #include #include #include #include #include #include #include #include #include #endif #if !MINIMAL_PCH // all other C++98 headers #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif // !MINIMAL_PCH #endif // #ifndef INCLUDED_PCH_STDLIB Index: ps/trunk/source/lib/posix/posix_aio.h =================================================================== --- ps/trunk/source/lib/posix/posix_aio.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_aio.h (revision 19899) @@ -1,68 +1,68 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ #ifndef INCLUDED_POSIX_AIO #define INCLUDED_POSIX_AIO // despite the comment in wposix.h about not using Windows headers for // POSIX declarations, this one is harmless (no incompatible declarations) // and can safely be used on Windows as well. #include #if OS_WIN # include "lib/sysdep/os/win/wposix/waio.h" #elif OS_ANDROID || OS_OPENBSD // Android doesn't provide aio.h. We don't actually use aio on Linuxes (see // CONFIG2_FILE_ENABLE_AIO) but we use its symbols and structs, so define // them here # if OS_OPENBSD // OpenBSD 5.1 (latest version at time of writing) has no struct sigevent defined, // so we do this here. struct sigevent { int sigev_notify; int sigev_signo; union sigval sigev_value; void (*sigev_notify_function)(union sigval); pthread_attr_t *sigev_notify_attributes; }; # endif # define LIO_READ 0 # define LIO_WRITE 1 # define LIO_NOP 2 struct aiocb { int aio_fildes; off_t aio_offset; volatile void* aio_buf; size_t aio_nbytes; int aio_reqprio; struct sigevent aio_sigevent; int aio_lio_opcode; }; #else # include #endif #include "lib/posix/posix_errno.h" // for user convenience #endif // #ifndef INCLUDED_POSIX_AIO Index: ps/trunk/source/lib/posix/posix_mman.h =================================================================== --- ps/trunk/source/lib/posix/posix_mman.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_mman.h (revision 19899) @@ -1,34 +1,34 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ #ifndef INCLUDED_POSIX_MMAN #define INCLUDED_POSIX_MMAN #if OS_WIN # include "lib/sysdep/os/win/wposix/wmman.h" #else # include #endif #include "lib/posix/posix_errno.h" // for user convenience #endif // #ifndef INCLUDED_POSIX_MMAN Index: ps/trunk/source/lib/file/vfs/vfs.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs.cpp (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs.cpp (revision 19899) @@ -1,326 +1,326 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "lib/file/vfs/vfs.h" #include "lib/allocators/shared_ptr.h" #include "lib/posix/posix_pthread.h" #include "lib/file/file_system.h" #include "lib/file/common/file_stats.h" #include "lib/file/common/trace.h" #include "lib/file/archive/archive.h" #include "lib/file/io/io.h" #include "lib/file/vfs/vfs_tree.h" #include "lib/file/vfs/vfs_lookup.h" #include "lib/file/vfs/vfs_populate.h" #include "lib/file/vfs/file_cache.h" static const StatusDefinition vfsStatusDefinitions[] = { { ERR::VFS_DIR_NOT_FOUND, L"VFS directory not found" }, { ERR::VFS_FILE_NOT_FOUND, L"VFS file not found" }, { ERR::VFS_ALREADY_MOUNTED, L"VFS path already mounted" } }; STATUS_ADD_DEFINITIONS(vfsStatusDefinitions); static pthread_mutex_t vfs_mutex = PTHREAD_MUTEX_INITIALIZER; namespace { struct ScopedLock { ScopedLock() { pthread_mutex_lock(&vfs_mutex); } ~ScopedLock() { pthread_mutex_unlock(&vfs_mutex); } }; } // namespace class VFS : public IVFS { public: VFS(size_t cacheSize) : m_cacheSize(cacheSize), m_fileCache(m_cacheSize) , m_trace(CreateDummyTrace(8*MiB)) { } virtual Status Mount(const VfsPath& mountPoint, const OsPath& path, size_t flags /* = 0 */, size_t priority /* = 0 */) { ScopedLock s; if(!DirectoryExists(path)) { if(flags & VFS_MOUNT_MUST_EXIST) return ERR::VFS_DIR_NOT_FOUND; // NOWARN else RETURN_STATUS_IF_ERR(CreateDirectories(path, 0700)); } VfsDirectory* directory; WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(mountPoint, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE)); PRealDirectory realDirectory(new RealDirectory(path, priority, flags)); RETURN_STATUS_IF_ERR(vfs_Attach(directory, realDirectory)); return INFO::OK; } virtual Status GetFileInfo(const VfsPath& pathname, CFileInfo* pfileInfo) const { ScopedLock s; VfsDirectory* directory; VfsFile* file; Status ret = vfs_Lookup(pathname, &m_rootDirectory, directory, &file); if(!pfileInfo) // just indicate if the file exists without raising warnings. return ret; WARN_RETURN_STATUS_IF_ERR(ret); *pfileInfo = CFileInfo(file->Name(), file->Size(), file->MTime()); return INFO::OK; } virtual Status GetFilePriority(const VfsPath& pathname, size_t* ppriority) const { ScopedLock s; VfsDirectory* directory; VfsFile* file; RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file)); *ppriority = file->Priority(); return INFO::OK; } virtual Status GetDirectoryEntries(const VfsPath& path, CFileInfos* fileInfos, DirectoryNames* subdirectoryNames) const { ScopedLock s; VfsDirectory* directory; RETURN_STATUS_IF_ERR(vfs_Lookup(path, &m_rootDirectory, directory, 0)); if(fileInfos) { const VfsDirectory::VfsFiles& files = directory->Files(); fileInfos->clear(); fileInfos->reserve(files.size()); for(VfsDirectory::VfsFiles::const_iterator it = files.begin(); it != files.end(); ++it) { const VfsFile& file = it->second; fileInfos->push_back(CFileInfo(file.Name(), file.Size(), file.MTime())); } } if(subdirectoryNames) { const VfsDirectory::VfsSubdirectories& subdirectories = directory->Subdirectories(); subdirectoryNames->clear(); subdirectoryNames->reserve(subdirectories.size()); for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it) subdirectoryNames->push_back(it->first); } return INFO::OK; } virtual Status CreateFile(const VfsPath& pathname, const shared_ptr& fileContents, size_t size) { ScopedLock s; VfsDirectory* directory; Status st; st = vfs_Lookup(pathname, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_CREATE_ALWAYS); if (st == ERR::FILE_ACCESS) return ERR::FILE_ACCESS; WARN_RETURN_STATUS_IF_ERR(st); const PRealDirectory& realDirectory = directory->AssociatedDirectory(); const OsPath name = pathname.Filename(); RETURN_STATUS_IF_ERR(realDirectory->Store(name, fileContents, size)); // wipe out any cached blocks. this is necessary to cover the (rare) case // of file cache contents predating the file write. m_fileCache.Remove(pathname); const VfsFile file(name, size, time(0), realDirectory->Priority(), realDirectory); directory->AddFile(file); m_trace->NotifyStore(pathname, size); return INFO::OK; } virtual Status ReplaceFile(const VfsPath& pathname, const shared_ptr& fileContents, size_t size) { ScopedLock s; VfsDirectory* directory; VfsFile* file; Status st; st = vfs_Lookup(pathname, &m_rootDirectory, directory, &file, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE); // There is no such file, create it. if (st == ERR::VFS_FILE_NOT_FOUND) { s.~ScopedLock(); return CreateFile(pathname, fileContents, size); } WARN_RETURN_STATUS_IF_ERR(st); RealDirectory realDirectory(file->Loader()->Path(), file->Priority(), directory->AssociatedDirectory()->Flags()); RETURN_STATUS_IF_ERR(realDirectory.Store(pathname.Filename(), fileContents, size)); // See comment in CreateFile m_fileCache.Remove(pathname); directory->AddFile(*file); m_trace->NotifyStore(pathname, size); return INFO::OK; } virtual Status LoadFile(const VfsPath& pathname, shared_ptr& fileContents, size_t& size) { ScopedLock s; const bool isCacheHit = m_fileCache.Retrieve(pathname, fileContents, size); if(!isCacheHit) { VfsDirectory* directory; VfsFile* file; // per 2010-05-01 meeting, this shouldn't raise 'scary error // dialogs', which might fail to display the culprit pathname // instead, callers should log the error, including pathname. RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file)); fileContents = DummySharedPtr((u8*)0); size = file->Size(); if(size != 0) // (the file cache can't handle zero-length allocations) { if(size < m_cacheSize/2) // (avoid evicting lots of previous data) fileContents = m_fileCache.Reserve(size); if(fileContents) { RETURN_STATUS_IF_ERR(file->Loader()->Load(file->Name(), fileContents, file->Size())); m_fileCache.Add(pathname, fileContents, size); } else { RETURN_STATUS_IF_ERR(AllocateAligned(fileContents, size, maxSectorSize)); RETURN_STATUS_IF_ERR(file->Loader()->Load(file->Name(), fileContents, file->Size())); } } } stats_io_user_request(size); stats_cache(isCacheHit? CR_HIT : CR_MISS, size); m_trace->NotifyLoad(pathname, size); return INFO::OK; } virtual std::wstring TextRepresentation() const { ScopedLock s; std::wstring textRepresentation; textRepresentation.reserve(100*KiB); DirectoryDescriptionR(textRepresentation, m_rootDirectory, 0); return textRepresentation; } virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname) { ScopedLock s; VfsDirectory* directory; VfsFile* file; WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file)); realPathname = file->Loader()->Path() / pathname.Filename(); return INFO::OK; } virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname) { ScopedLock s; VfsDirectory* directory; WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, NULL)); realPathname = directory->AssociatedDirectory()->Path(); return INFO::OK; } virtual Status GetVirtualPath(const OsPath& realPathname, VfsPath& pathname) { ScopedLock s; const OsPath realPath = realPathname.Parent()/""; VfsPath path; RETURN_STATUS_IF_ERR(FindRealPathR(realPath, m_rootDirectory, L"", path)); pathname = path / realPathname.Filename(); return INFO::OK; } virtual Status RemoveFile(const VfsPath& pathname) { ScopedLock s; m_fileCache.Remove(pathname); VfsDirectory* directory; VfsFile* file; RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file)); directory->RemoveFile(file->Name()); return INFO::OK; } virtual Status RepopulateDirectory(const VfsPath& path) { ScopedLock s; VfsDirectory* directory; RETURN_STATUS_IF_ERR(vfs_Lookup(path, &m_rootDirectory, directory, 0)); directory->RequestRepopulate(); return INFO::OK; } virtual void Clear() { ScopedLock s; m_rootDirectory.Clear(); } private: Status FindRealPathR(const OsPath& realPath, const VfsDirectory& directory, const VfsPath& curPath, VfsPath& path) { PRealDirectory realDirectory = directory.AssociatedDirectory(); if(realDirectory && realDirectory->Path() == realPath) { path = curPath; return INFO::OK; } const VfsDirectory::VfsSubdirectories& subdirectories = directory.Subdirectories(); for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it) { const OsPath& subdirectoryName = it->first; const VfsDirectory& subdirectory = it->second; Status ret = FindRealPathR(realPath, subdirectory, curPath / subdirectoryName/"", path); if(ret == INFO::OK) return INFO::OK; } return ERR::PATH_NOT_FOUND; // NOWARN } size_t m_cacheSize; FileCache m_fileCache; PITrace m_trace; mutable VfsDirectory m_rootDirectory; }; //----------------------------------------------------------------------------- PIVFS CreateVfs(size_t cacheSize) { return PIVFS(new VFS(cacheSize)); } Index: ps/trunk/source/lib/file/vfs/vfs_path.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_path.cpp (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_path.cpp (revision 19899) @@ -1,24 +1,24 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/file/vfs/vfs_path.h" Index: ps/trunk/source/lib/file/vfs/vfs_tree.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_tree.cpp (revision 19898) +++ ps/trunk/source/lib/file/vfs/vfs_tree.cpp (revision 19899) @@ -1,251 +1,251 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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. */ /* * 'tree' of VFS directories and files */ #include "precompiled.h" #include "lib/file/vfs/vfs_tree.h" #include #include "lib/file/common/file_stats.h" #include "lib/sysdep/cpu.h" //----------------------------------------------------------------------------- VfsFile::VfsFile(const VfsPath& name, size_t size, time_t mtime, size_t priority, const PIFileLoader& loader) : m_name(name), m_size(size), m_mtime(mtime), m_priority(priority), m_loader(loader) { } //----------------------------------------------------------------------------- VfsDirectory::VfsDirectory() : m_shouldPopulate(0) { } static bool ShouldDelete(const VfsFile& file, const VfsFile& deletedFile) { // We only check priority here, a .DELETED file in a mod should not // delete files in that mod. For the same reason we ignore loose // .DELETED files next to an archive. return file.Priority() < deletedFile.Priority(); } static bool ShouldReplaceWith(const VfsFile& previousFile, const VfsFile& newFile) { // 1) priority (override mods) if(newFile.Priority() < previousFile.Priority()) return false; if(newFile.Priority() > previousFile.Priority()) return true; // 2) timestamp { const double howMuchNewer = difftime(newFile.MTime(), previousFile.MTime()); const double threshold = 2.0; // FAT timestamp resolution [seconds] if(howMuchNewer > threshold) // newer return true; if(howMuchNewer < -threshold) // older return false; // else: "equal" (tolerating small differences due to FAT's low // mtime resolution) } // 3) precedence (efficiency of file provider) if(newFile.Loader()->Precedence() < previousFile.Loader()->Precedence()) return false; return true; } void VfsDirectory::DeleteSubtree(const VfsFile& file) { ENSURE(file.Name().Extension() == L".DELETED"); const VfsPath basename = file.Name().Basename(); std::map::iterator fit = m_files.find(basename); if(fit != m_files.end() && ShouldDelete(fit->second, file)) m_files.erase(basename); std::map::iterator dit = m_subdirectories.find(basename); if(dit != m_subdirectories.end() && dit->second.DeleteTree(file)) m_subdirectories.erase(dit); } bool VfsDirectory::DeleteTree(const VfsFile& file) { for(std::map::iterator it = m_files.begin(); it != m_files.end();) if(ShouldDelete(it->second, file)) it = m_files.erase(it); else ++it; for(std::map::iterator it = m_subdirectories.begin(); it != m_subdirectories.end();) if(it->second.DeleteTree(file)) it = m_subdirectories.erase(it); else ++it; return m_files.empty() && m_subdirectories.empty(); } VfsFile* VfsDirectory::AddFile(const VfsFile& file) { std::pair value = std::make_pair(file.Name(), file); std::pair ret = m_files.insert(value); if(!ret.second) // already existed { VfsFile& previousFile = ret.first->second; const VfsFile& newFile = value.second; if(ShouldReplaceWith(previousFile, newFile)) previousFile = newFile; } else { stats_vfs_file_add(file.Size()); } return &(*ret.first).second; } // rationale: passing in a pre-constructed VfsDirectory and copying that into // our map would be slower and less convenient for the caller. VfsDirectory* VfsDirectory::AddSubdirectory(const VfsPath& name) { std::pair value = std::make_pair(name.string(), VfsDirectory()); std::pair ret = m_subdirectories.insert(value); return &(*ret.first).second; } void VfsDirectory::RemoveFile(const VfsPath& name) { m_files.erase(name.string()); } VfsFile* VfsDirectory::GetFile(const VfsPath& name) { VfsFiles::iterator it = m_files.find(name.string()); if(it == m_files.end()) return 0; return &it->second; } VfsDirectory* VfsDirectory::GetSubdirectory(const VfsPath& name) { VfsSubdirectories::iterator it = m_subdirectories.find(name.string()); if(it == m_subdirectories.end()) return 0; return &it->second; } void VfsDirectory::SetAssociatedDirectory(const PRealDirectory& realDirectory) { if(!cpu_CAS(&m_shouldPopulate, 0, 1)) DEBUG_WARN_ERR(ERR::LOGIC); // caller didn't check ShouldPopulate m_realDirectory = realDirectory; } bool VfsDirectory::ShouldPopulate() { return cpu_CAS(&m_shouldPopulate, 1, 0); // test and reset } void VfsDirectory::RequestRepopulate() { m_shouldPopulate = 1; } void VfsDirectory::Clear() { m_files.clear(); m_subdirectories.clear(); m_realDirectory.reset(); m_shouldPopulate = 0; } //----------------------------------------------------------------------------- std::wstring FileDescription(const VfsFile& file) { wchar_t timestamp[25]; const time_t mtime = file.MTime(); wcsftime(timestamp, ARRAY_SIZE(timestamp), L"%a %b %d %H:%M:%S %Y", localtime(&mtime)); wchar_t buf[200]; swprintf_s(buf, ARRAY_SIZE(buf), L"(%c; %6lu; %ls) %ls", file.Loader()->LocationCode(), (unsigned long)file.Size(), timestamp, file.Name().string().c_str()); return buf; } std::wstring FileDescriptions(const VfsDirectory& directory, size_t indentLevel) { VfsDirectory::VfsFiles files = directory.Files(); std::wstring descriptions; descriptions.reserve(100*files.size()); const std::wstring indentation(4*indentLevel, ' '); for(VfsDirectory::VfsFiles::const_iterator it = files.begin(); it != files.end(); ++it) { const VfsFile& file = it->second; descriptions += indentation; descriptions += FileDescription(file); descriptions += L"\n"; } return descriptions; } void DirectoryDescriptionR(std::wstring& descriptions, const VfsDirectory& directory, size_t indentLevel) { const std::wstring indentation(4*indentLevel, ' '); const VfsDirectory::VfsSubdirectories& subdirectories = directory.Subdirectories(); for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it) { const VfsPath& name = it->first; const VfsDirectory& subdirectory = it->second; descriptions += indentation; descriptions += std::wstring(L"[") + name.string() + L"]\n"; descriptions += FileDescriptions(subdirectory, indentLevel+1); DirectoryDescriptionR(descriptions, subdirectory, indentLevel+1); } } Index: ps/trunk/source/lib/fnv_hash.cpp =================================================================== --- ps/trunk/source/lib/fnv_hash.cpp (revision 19898) +++ ps/trunk/source/lib/fnv_hash.cpp (revision 19899) @@ -1,133 +1,133 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Fowler/Noll/Vo string hash */ #include "precompiled.h" // FNV1-A hash - good for strings. // if len = 0 (default), treat buf as a C-string; // otherwise, hash bytes of buf. u32 fnv_hash(const void* buf, size_t len) { u32 h = 0x811c9dc5u; // give distinct values for different length 0 buffers. // value taken from FNV; it has no special significance. const u8* p = (const u8*)buf; // expected case: string if(!len) { while(*p) { h ^= *p++; h *= 0x01000193u; } } else { size_t bytes_left = len; while(bytes_left != 0) { h ^= *p++; h *= 0x01000193u; bytes_left--; } } return h; } // FNV1-A hash - good for strings. // if len = 0 (default), treat buf as a C-string; // otherwise, hash bytes of buf. u64 fnv_hash64(const void* buf, size_t len) { u64 h = 0xCBF29CE484222325ull; // give distinct values for different length 0 buffers. // value taken from FNV; it has no special significance. const u8* p = (const u8*)buf; // expected case: string if(!len) { while(*p) { h ^= *p++; h *= 0x100000001B3ull; } } else { size_t bytes_left = len; while(bytes_left != 0) { h ^= *p++; h *= 0x100000001B3ull; bytes_left--; } } return h; } // special version for strings: first converts to lowercase // (useful for comparing mixed-case filenames). // note: still need , e.g. to support non-0-terminated strings u32 fnv_lc_hash(const char* str, size_t len) { u32 h = 0x811c9dc5u; // give distinct values for different length 0 buffers. // value taken from FNV; it has no special significance. // expected case: string if(!len) { while(*str) { h ^= tolower(*str++); h *= 0x01000193u; } } else { size_t bytes_left = len; while(bytes_left != 0) { h ^= tolower(*str++); h *= 0x01000193u; bytes_left--; } } return h; } Index: ps/trunk/source/lib/lib.cpp =================================================================== --- ps/trunk/source/lib/lib.cpp (revision 19898) +++ ps/trunk/source/lib/lib.cpp (revision 19899) @@ -1,108 +1,108 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * various utility functions. */ #include "precompiled.h" #include "lib/lib.h" #include #include #include #include "lib/app_hooks.h" #include "lib/sysdep/sysdep.h" //----------------------------------------------------------------------------- // type conversion // these avoid a common mistake in using >> (ANSI requires shift count be // less than the bit width of the type). u32 u64_hi(u64 x) { return (u32)(x >> 32); } u32 u64_lo(u64 x) { return (u32)(x & 0xFFFFFFFF); } u16 u32_hi(u32 x) { return (u16)(x >> 16); } u16 u32_lo(u32 x) { return (u16)(x & 0xFFFF); } u64 u64_from_u32(u32 hi, u32 lo) { u64 x = (u64)hi; x <<= 32; x |= lo; return x; } u32 u32_from_u16(u16 hi, u16 lo) { u32 x = (u32)hi; x <<= 16; x |= lo; return x; } // input in [0, 1); convert to u8 range u8 u8_from_double(double in) { if(!(0.0 <= in && in < 1.0)) { DEBUG_WARN_ERR(ERR::LOGIC); // clampf not in [0,1) return 255; } int l = (int)(in * 255.0); ENSURE((unsigned)l <= 255u); return (u8)l; } // input in [0, 1); convert to u16 range u16 u16_from_double(double in) { if(!(0.0 <= in && in < 1.0)) { DEBUG_WARN_ERR(ERR::LOGIC); // clampf not in [0,1) return 65535; } long l = (long)(in * 65535.0); ENSURE((unsigned long)l <= 65535u); return (u16)l; } Index: ps/trunk/source/lib/module_init.h =================================================================== --- ps/trunk/source/lib/module_init.h (revision 19898) +++ ps/trunk/source/lib/module_init.h (revision 19899) @@ -1,74 +1,74 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * helpers for module initialization/shutdown. */ #ifndef INCLUDED_MODULE_INIT #define INCLUDED_MODULE_INIT /** * initialization state of a module (class, source file, etc.) * must be initialized to zero (e.g. by defining as a static variable). * DO NOT change the value! **/ typedef intptr_t ModuleInitState; // intptr_t is required by cpu_CAS /** * calls a user-defined init function if initState is zero. * * @return INFO::SKIPPED if already initialized, a Status if the * previous invocation failed, or the value returned by the callback. * * postcondition: initState is "initialized" if the callback returned * INFO::OK, otherwise its Status return value (which prevents * shutdown from being called). * * thread-safe: subsequent callers spin until the callback returns * (this prevents using partially-initialized modules) * * note that callbacks typically reference static data and thus do not * require a function argument, but that can later be added if necessary. **/ LIB_API Status ModuleInit(volatile ModuleInitState* initState, Status (*init)()); /** * calls a user-defined shutdown function if initState is "initialized". * * @return INFO::OK if shutdown occurred, INFO::SKIPPED if initState was * zero (uninitialized), otherwise the Status returned by ModuleInit. * * postcondition: initState remains set to the Status, or has been * reset to zero to allow multiple init/shutdown pairs, e.g. in self-tests. * * note: there is no provision for reference-counting because that * turns out to be problematic (a user might call shutdown immediately * after init; if this is the first use of the module, it will * be shutdown prematurely, which is at least inefficient and * possibly dangerous). instead, shutdown should only be called when * cleanup is necessary (e.g. at exit before leak reporting) and * it is certain that the module is no longer in use. **/ LIB_API Status ModuleShutdown(volatile ModuleInitState* initState, void (*shutdown)()); #endif // #ifndef INCLUDED_MODULE_INIT Index: ps/trunk/source/lib/pch/pch_boost.h =================================================================== --- ps/trunk/source/lib/pch/pch_boost.h (revision 19898) +++ ps/trunk/source/lib/pch/pch_boost.h (revision 19899) @@ -1,60 +1,60 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_PCH_BOOST #define INCLUDED_PCH_BOOST #include "lib/external_libraries/suppress_boost_warnings.h" #if MSC_VERSION # define BOOST_HAS_STDINT_H #endif // Boost // .. if this package isn't going to be statically linked, we're better off // using Boost via DLL. (otherwise, we would have to ensure the exact same // compiler is used, which is a pain because MSC8, MSC9 and ICC 10 are in use) #ifndef LIB_STATIC_LINK # define BOOST_ALL_DYN_LINK #endif // don't compile get_system_category() etc, since we don't use them and they // sometimes cause problems when linking. // But Filesystem <= 1.43 requires boost::system::posix, so only disable if newer #include #if BOOST_VERSION >= 104400 # define BOOST_SYSTEM_NO_DEPRECATED #endif // the following boost libraries have been included in TR1 and are // thus deemed usable: #if BOOST_VERSION >= 104400 // Filesystem v3 is included since Boost 1.44 // v2 is deprecated since 1.46 and removed entirely in 1.50 # define BOOST_FILESYSTEM_VERSION 3 #else # define BOOST_FILESYSTEM_VERSION 2 #endif #include namespace fs = boost::filesystem; #endif // #ifndef INCLUDED_PCH_BOOST Index: ps/trunk/source/lib/posix/posix.h =================================================================== --- ps/trunk/source/lib/posix/posix.h (revision 19898) +++ ps/trunk/source/lib/posix/posix.h (revision 19899) @@ -1,132 +1,132 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * definitions for a subset of POSIX. */ /* [KEEP IN SYNC WITH WIKI] this header makes available commonly used POSIX (Portable Operating System Interface) definitions, e.g. thread, file I/O and socket APIs. on Linux and OS X we just include the requisite headers; Win32 doesn't really support POSIX (*), so we have to implement everything ourselves. rationale: this is preferable to a wrapper for several reasons: - less code (implementation is only needed on Win32) - no lock-in (the abstraction may prevent not-designed-for operations that the POSIX interface would have allowed) - familiarity (many coders already know POSIX) if a useful definition is missing, feel free to add it! implementation reference is the "Single Unix Specification v3" (http://www.unix.org/online.html) - it's similar to the POSIX standard (superset?) and freely available. * Win32 does have a POSIX subsystem (mandated by a government contract), but it is crippled. only apps with the PE header 'subsystem' field set to "POSIX" can use the appendant DLL, and then they can't call the regular Windows APIs. this is obviously unacceptable - GDI is needed to set up OpenGL. we therefore need to emulate POSIX functions using the Win32 API. fortunately, many POSIX functions are already implemented in the VC CRT and need only be renamed (e.g. _open, _stat). */ #ifndef INCLUDED_POSIX #define INCLUDED_POSIX #include // see isfinite comment below #if OS_WIN # include "lib/sysdep/os/win/wposix/wposix.h" #endif #include "lib/posix/posix_types.h" // disabled to reduce dependencies. include them where needed. //#include "lib/posix/posix_aio.h" //#include "lib/posix/posix_dlfcn.h" //#include "lib/posix/posix_filesystem.h" //#include "lib/posix/posix_mman.h" //#include "lib/posix/posix_pthread.h" //#include "lib/posix/posix_time.h" //#include "lib/posix/posix_utsname.h" // note: the following need only be #defined (instead of defining a // trampoline function) because the redefined functions are already // declared by standard headers. // provide C99 *snprintf functions if compiler doesn't already // (MinGW does, VC7.1 doesn't). #if MSC_VERSION # define snprintf _snprintf # define swprintf _snwprintf # define vsnprintf _vsnprintf # define vswprintf _vsnwprintf #endif // VC doesn't define str[n]casecmp #if MSC_VERSION #define strcasecmp _stricmp #define strncasecmp _strnicmp #define wcscasecmp _wcsicmp #define wcsncasecmp _wcsnicmp #endif #if OS_MACOSX # define EMULATE_WCSDUP 1 # define EMULATE_WCSCASECMP 1 #else # define EMULATE_WCSDUP 0 # define EMULATE_WCSCASECMP 0 #endif #if EMULATE_WCSDUP extern wchar_t* wcsdup(const wchar_t* str); #endif #if EMULATE_WCSCASECMP extern int wcscasecmp(const wchar_t* s1, const wchar_t* s2); #endif // Some systems have C99 support but in C++ they provide only std::isfinite // and not isfinite. C99 specifies that isfinite is a macro, so we can use // #ifndef and define it if it's not there already. // We've included above to make sure it defines that macro. #ifndef isfinite # if MSC_VERSION # define isfinite _finite # define isnan _isnan # else # define isfinite std::isfinite # define isnan std::isnan # endif #endif #endif // #ifndef INCLUDED_POSIX Index: ps/trunk/source/lib/posix/posix_filesystem.h =================================================================== --- ps/trunk/source/lib/posix/posix_filesystem.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_filesystem.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 // O_CREAT etc. #if OS_WIN # include "lib/sysdep/os/win/wposix/wfilesystem.h" #else # include # include # include #endif #include "lib/posix/posix_errno.h" // for user convenience Index: ps/trunk/source/lib/posix/posix_time.h =================================================================== --- ps/trunk/source/lib/posix/posix_time.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_time.h (revision 19899) @@ -1,29 +1,29 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #if OS_WIN # include "lib/sysdep/os/win/wposix/wtime.h" #else # include #endif #include "lib/posix/posix_errno.h" // for user convenience Index: ps/trunk/source/lib/precompiled.h =================================================================== --- ps/trunk/source/lib/precompiled.h (revision 19898) +++ ps/trunk/source/lib/precompiled.h (revision 19899) @@ -1,105 +1,105 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * precompiled header. must be the first non-comment part of every * source file (VC++ requirement). */ // some libraries have only a small number of source files, and the // overhead of including loads of headers here outweighs the improvements to // incremental rebuild performance. // they can set MINIMAL_PCH to 1 so we include far fewer headers (but // still do the global disabling of warnings and include config headers etc), // or set it to 2 to remove STL headers too (precompiling STL helps performance // in most non-tiny cases) #ifndef MINIMAL_PCH # define MINIMAL_PCH 0 #endif #include "lib/config.h" // CONFIG_ENABLE_BOOST, CONFIG_ENABLE_PCH #include "lib/sysdep/compiler.h" // MSC_VERSION, HAVE_PCH // must come before any STL headers are included #if MSC_VERSION # ifdef NDEBUG // release: disable all checks # define _HAS_ITERATOR_DEBUGGING 0 # define _SECURE_SCL 0 # endif #endif // disable some common and annoying warnings // (as soon as possible so that headers below are covered) #include "lib/pch/pch_warnings.h" #if ICC_VERSION #include // (must come before or (replaces them)) double __cdecl abs(double x); // not declared by mathimf #endif // // headers made available everywhere for convenience // #include "lib/posix/posix_types.h" // (must come before any system headers because it fixes off_t) #include "lib/code_annotation.h" #include "lib/sysdep/arch.h" #include "lib/sysdep/os.h" #include "lib/sysdep/stl.h" #include "lib/lib_api.h" #include "lib/types.h" #include "lib/debug.h" #include "lib/lib.h" #include "lib/secure_crt.h" #if CONFIG_ENABLE_BOOST # include "lib/pch/pch_boost.h" #endif #include #include using std::shared_ptr; // (must come after boost and common lib headers, but before re-enabling // warnings to avoid boost spew) #include "lib/posix/posix.h" // // precompiled headers // // if PCHs are supported and enabled, we make an effort to include all // system headers. otherwise, only a few central headers (e.g. types) // are pulled in and source files must include all the system headers // they use. this policy ensures good compile performance whether or not // PCHs are being used. #if CONFIG_ENABLE_PCH && HAVE_PCH // anything placed here won't need to be compiled in each translation unit, // but will cause a complete rebuild if they change. #include "lib/pch/pch_stdlib.h" #endif // #if CONFIG_ENABLE_PCH && HAVE_PCH Index: ps/trunk/source/lib/posix/tests/test_posix.h =================================================================== --- ps/trunk/source/lib/posix/tests/test_posix.h (revision 19898) +++ ps/trunk/source/lib/posix/tests/test_posix.h (revision 19899) @@ -1,37 +1,37 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/posix/posix.h" class TestPosix : public CxxTest::TestSuite { public: void test_wcsdup() { const wchar_t* a = L"test"; wchar_t* t = wcsdup(a); TS_ASSERT_WSTR_EQUALS(t, a); free(t); } }; Index: ps/trunk/source/lib/posix/posix_utsname.h =================================================================== --- ps/trunk/source/lib/posix/posix_utsname.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_utsname.h (revision 19899) @@ -1,29 +1,29 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #if OS_WIN # include "lib/sysdep/os/win/wposix/wutsname.h" #else # include #endif #include "lib/posix/posix_errno.h" // for user convenience Index: ps/trunk/source/lib/posix/posix_types.h =================================================================== --- ps/trunk/source/lib/posix/posix_types.h (revision 19898) +++ ps/trunk/source/lib/posix/posix_types.h (revision 19899) @@ -1,64 +1,64 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * lightweight header that defines POSIX types. */ #ifndef INCLUDED_POSIX_TYPES #define INCLUDED_POSIX_TYPES // this header defines e.g. ssize_t and int8_t without pulling in all // POSIX declarations. // included from lib/types.h in place of posix.h; this helps avoid conflicts // due to incompatible winsock definitions. #include "lib/sysdep/os.h" // OS_WIN // (must come before any system headers because it fixes off_t) #if OS_WIN # include "lib/sysdep/os/win/wposix/wposix_types.h" #else // unix/linux/glibc/gcc says that this macro has to be defined when including // stdint.h from C++ for stdint.h to define SIZE_MAX and friends # ifndef __STDC_LIMIT_MACROS # define __STDC_LIMIT_MACROS # endif # include # include # include # include # include # include // but sometimes it still doesn't get defined, so define it ourselves # ifndef SIZE_MAX # define SIZE_MAX ((size_t)-1) # endif # include #endif // #if !OS_WIN #endif // #ifndef INCLUDED_POSIX_TYPES Index: ps/trunk/source/lib/rand.cpp =================================================================== --- ps/trunk/source/lib/rand.cpp (revision 19898) +++ ps/trunk/source/lib/rand.cpp (revision 19899) @@ -1,80 +1,80 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * pseudorandom number generator */ #include "precompiled.h" #include "lib/rand.h" // avoids several common pitfalls; see discussion at // http://www.azillionmonkeys.com/qed/random.html // rand() is poorly implemented (e.g. in VC7) and only returns < 16 bits; // double that amount by concatenating 2 random numbers. // this is not to fix poor rand() randomness - the number returned will be // folded down to a much smaller interval anyway. instead, a larger XRAND_MAX // decreases the probability of having to repeat the loop. #if RAND_MAX < 65536 static const size_t XRAND_MAX = (RAND_MAX+1)*(RAND_MAX+1) - 1; static size_t xrand() { return rand()*(RAND_MAX+1) + rand(); } // rand() is already ok; no need to do anything. #else static const size_t XRAND_MAX = RAND_MAX; static size_t xrand() { return rand(); } #endif size_t rand(size_t min_inclusive, size_t max_exclusive) { const size_t range = (max_exclusive-min_inclusive); // huge interval or min >= max if(range == 0 || range > XRAND_MAX) { WARN_IF_ERR(ERR::INVALID_PARAM); return 0; } const size_t inv_range = XRAND_MAX / range; // generate random number in [0, range) // idea: avoid skewed distributions when doesn't evenly divide // XRAND_MAX by simply discarding values in the "remainder". // not expected to run often since XRAND_MAX is large. size_t x; do { x = xrand(); } while(x >= range * inv_range); x /= inv_range; x += min_inclusive; ENSURE(x < max_exclusive); return x; } Index: ps/trunk/source/lib/regex.h =================================================================== --- ps/trunk/source/lib/regex.h (revision 19898) +++ ps/trunk/source/lib/regex.h (revision 19899) @@ -1,43 +1,43 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * minimal regex implementation */ #ifndef INCLUDED_REGEX #define INCLUDED_REGEX /** * see if string matches pattern. * * @param s input string * @param w pseudo-regex to match against. case-insensitive; * may contain '?' and/or '*' wildcards. if NULL, matches everything. * * @return 1 if they match, otherwise 0. * * algorithm from http://www.codeproject.com/string/wildcmp.asp. **/ extern int match_wildcard(const wchar_t* s, const wchar_t* w); #endif // #ifndef INCLUDED_REGEX Index: ps/trunk/source/lib/res/graphics/ogl_tex.h =================================================================== --- ps/trunk/source/lib/res/graphics/ogl_tex.h (revision 19898) +++ ps/trunk/source/lib/res/graphics/ogl_tex.h (revision 19899) @@ -1,497 +1,497 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * wrapper for all OpenGL texturing calls. provides caching, hotloading * and lifetime management. */ /* [KEEP IN SYNC WITH WIKI!] Introduction ------------ This module simplifies use of textures in OpenGL. An easy-to-use load/upload/bind/free API is provided, which completely replaces direct access to OpenGL's texturing calls. It basically wraps tex.cpp's texture info in a resource object (see h_mgr.h) that maintains associated GL state and provides for reference counting, caching, hotloading and safe access. Additionally, the upload step provides for trading quality vs. speed and works around older hardware/drivers. Texture Parameters ------------------ OpenGL textures are conditioned on several parameters including filter and wrap mode. These are typically set once when the texture is created, but must survive reloads (1). To that end, all state (2) is set via ogl_tex_set_* (instead of direct glTexParameter calls) and re-applied after a reload. (1) the purpose of hotloading is to permit artists to see their changes in-game without having to restart the map. reloads where the texture looks different due to changed state are useless. (2) currently only filter and wrap mode. no other glTexParameter settings are used ATM; if that changes, add to OglTexState. Uploading to OpenGL ------------------- .. deserves some clarification. This entails calling glTexImage2D (or its variants for mipmaps/compressed textures) and transfers texture parameters and data from system memory to OpenGL (and thereby usually video memory). In so doing, choices are made as to the texture's internal representation (how it is stored in vmem) - in particular, the bit depth. This can trade performance (more/less data to copy) for quality (fidelity to original). We provide a mechanism that applies defaults to all uploads; this allows a global "quality" setting that can boost performance on older graphics cards without requiring anything else to be changed. Textures with specific quality needs can override this via ogl_tex_set_* or ogl_tex_upload parameters. Finally, provision is made for coping with hardware/drivers lacking support for S3TC decompression or mipmap generation: that can be done in software, if necessary. This avoids the need for alternate asset formats and lowers hardware requirements. While such cards probably won't run the app very well (due to their age and lack of other capabilities), this does make possible developing/testing on older machines/laptops. Caching and Texture Instances ----------------------------- Caching is both an advantage and drawback. When opening the same texture twice without previously freeing it, a reference to the first instance is returned. Therefore, be advised that concurrent use of the same texture but with differing parameters (e.g. upload quality) followed by a reload of the first instance will result in using the wrong parameters. For background and rationale why this is acceptable, see struct OglTex. Example Usage ------------- Note: to keep the examples simple, we leave out error handling by ignoring all return values. Each function will still raise a warning (assert) if it fails and passing e.g. invalid Handles will only cause the next function to fail, but real apps should check and report errors. 1) Basic usage: load texture from file. Handle hTexture = ogl_tex_load("filename.dds"); (void)ogl_tex_upload(hTexture); [when rendering:] (void)ogl_tex_bind(hTexture); [.. do something with OpenGL that uses the currently bound texture] [at exit:] * (done automatically, but this avoids it showing up as a leak) (void)ogl_tex_free(hTexture); 2) Advanced usage: wrap existing texture data, override filter, specify internal_format and use multitexturing. Tex t; const size_t flags = 0; * image is plain RGB, default orientation void* data = [pre-existing image] (void)tex_wrap(w, h, 24, flags, data, &t); Handle hCompositeAlphaMap = ogl_tex_wrap(&t, "(alpha map composite)"); (void)ogl_tex_set_filter(hCompositeAlphaMap, GL_LINEAR); (void)ogl_tex_upload(hCompositeAlphaMap, 0, 0, GL_INTENSITY); * (your responsibility! tex_wrap attaches a reference but it is * removed by ogl_tex_upload.) free(data); [when rendering:] (void)ogl_tex_bind(hCompositeAlphaMap, 1); [.. do something with OpenGL that uses the currently bound texture] [at exit:] * (done automatically, but this avoids it showing up as a leak) (void)ogl_tex_free(hCompositeAlphaMap); */ #ifndef INCLUDED_OGL_TEX #define INCLUDED_OGL_TEX #include "lib/res/handle.h" #include "lib/file/vfs/vfs.h" #include "lib/ogl.h" #include "lib/tex/tex.h" // // quality mechanism // /** * Quality flags for texture uploads. * Specify any of them to override certain aspects of the default. */ enum OglTexQualityFlags { /** * emphatically require full quality for this texture. * (q_flags are invalid if this is set together with any other bit) * rationale: the value 0 is used to indicate "use default flags" in * ogl_tex_upload and ogl_tex_set_defaults, so this is the only * way we can say "disregard default and do not reduce anything". */ OGL_TEX_FULL_QUALITY = 0x20, /** * store the texture at half the normal bit depth * (4 bits per pixel component, as opposed to 8). * this increases performance on older graphics cards due to * decreased size in vmem. it has no effect on * compressed textures because they have a fixed internal format. */ OGL_TEX_HALF_BPP = 0x10, /** * store the texture at half its original resolution. * this increases performance on older graphics cards due to * decreased size in vmem. * this is useful for also reducing quality of compressed textures, * which are not affected by OGL_TEX_HALF_BPP. * currently only implemented for images that contain mipmaps * (otherwise, we'd have to resample, which is slow). * note: scaling down to 1/4, 1/8, .. is easily possible without * extra work, so we leave some bits free for that. */ OGL_TEX_HALF_RES = 0x01 }; /** * Change default settings - these affect performance vs. quality. * May be overridden for individual textures via parameter to * ogl_tex_upload or ogl_tex_set_filter, respectively. * * @param q_flags quality flags. Pass 0 to keep the current setting * (initially OGL_TEX_FULL_QUALITY), or any combination of * OglTexQualityFlags. * @param filter mag/minification filter. Pass 0 to keep the current setting * (initially GL_LINEAR), or any valid OpenGL minification filter. */ extern void ogl_tex_set_defaults(int q_flags, GLint filter); // // open/close // /** * Load and return a handle to the texture. * * @param vfs * @param pathname * @param flags h_alloc flags. * @return Handle to texture or negative Status * for a list of supported formats, see tex.h's tex_load. */ extern Handle ogl_tex_load(const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0); /** * Find and return an existing texture object, if it has already been * loaded and is still in memory. * * @param pathname fn VFS filename of texture. * @return Handle to texture or negative Status */ extern Handle ogl_tex_find(const VfsPath& pathname); /** * Make the Tex object ready for use as an OpenGL texture * and return a handle to it. This will be as if its contents * had been loaded by ogl_tex_load. * * @param t Texture object. * @param vfs * @param pathname filename or description of texture. not strictly needed, * but would allow h_filename to return meaningful info for * purposes of debugging. * @param flags * @return Handle to texture or negative Status * * note: because we cannot guarantee that callers will pass distinct * "filenames", caching is disabled for the created object. this avoids * mistakenly reusing previous objects that share the same comment. * * we need only add bookkeeping information and "wrap" it in * a resource object (accessed via Handle), hence the name. */ extern Handle ogl_tex_wrap(Tex* t, const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0); /** * Release this texture reference. When the count reaches zero, all of * its associated resources are freed and further use made impossible. * * @param ht Texture handle. * @return Status */ extern Status ogl_tex_free(Handle& ht); // // set texture parameters // // these must be called before uploading; this simplifies // things and avoids calling glTexParameter twice. /** * Override default filter (see {@link #ogl_tex_set_defaults}) for * this texture. * * @param ht Texture handle * @param filter OpenGL minification and magnification filter * (rationale: see {@link OglTexState}) * @return Status * * Must be called before uploading (raises a warning if called afterwards). */ extern Status ogl_tex_set_filter(Handle ht, GLint filter); /** * Override default wrap mode (GL_REPEAT) for this texture. * * @param ht Texture handle * @param wrap_s OpenGL wrap mode for S coordinates * @param wrap_t OpenGL wrap mode for T coordinates * @return Status * * Must be called before uploading (raises a warning if called afterwards). */ extern Status ogl_tex_set_wrap(Handle ht, GLint wrap_s, GLint wrap_t); /** * Override default maximum anisotropic filtering for this texture. * * @param ht Texture handle * @param anisotropy Anisotropy value (must not be less than 1.0; should * usually be a power of two) * @return Status * * Must be called before uploading (raises a warning if called afterwards). */ extern Status ogl_tex_set_anisotropy(Handle ht, GLfloat anisotropy); // // upload // enum OglTexOverrides { OGL_TEX_S3TC, OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_ANISOTROPY }; enum OglTexAllow { OGL_TEX_DISABLE, OGL_TEX_ENABLE }; /** * Override the default decision and force/disallow use of the * given feature. Typically called from ah_override_gl_upload_caps. * * @param what Feature to influence. * @param allow Disable/enable flag. */ extern void ogl_tex_override(OglTexOverrides what, OglTexAllow allow); /** * Upload texture to OpenGL. * * @param ht Texture handle * @param fmt_ovr optional override for OpenGL format (e.g. GL_RGB), * which is decided from bpp / Tex flags * @param q_flags_ovr optional override for global default * OglTexQualityFlags * @param int_fmt_ovr optional override for OpenGL internal format * (e.g. GL_RGB8), which is decided from fmt / q_flags. * @return Status. * * Side Effects: * - enables texturing on TMU 0 and binds the texture to it; * - frees the texel data! see ogl_tex_get_data. */ extern Status ogl_tex_upload(const Handle ht, GLenum fmt_ovr = 0, int q_flags_ovr = 0, GLint int_fmt_ovr = 0); // // return information about the texture // /** * Retrieve dimensions and bit depth of the texture. * * @param ht Texture handle * @param w optional; will be filled with width * @param h optional; will be filled with height * @param bpp optional; will be filled with bits per pixel * @return Status */ extern Status ogl_tex_get_size(Handle ht, size_t* w, size_t* h, size_t* bpp); /** * Retrieve pixel format of the texture. * * @param ht Texture handle * @param flags optional; will be filled with TexFlags * @param fmt optional; will be filled with GL format * (it is determined during ogl_tex_upload and 0 before then) * @return Status */ extern Status ogl_tex_get_format(Handle ht, size_t* flags, GLenum* fmt); /** * Retrieve pixel data of the texture. * * @param ht Texture handle * @param p will be filled with pointer to texels. * @return Status * * Note: this memory is freed after a successful ogl_tex_upload for * this texture. After that, the pointer we retrieve is NULL but * the function doesn't fail (negative return value) by design. * If you still need to get at the data, add a reference before * uploading it or read directly from OpenGL (discouraged). */ extern Status ogl_tex_get_data(Handle ht, u8** p); /** * Retrieve number of bytes uploaded for the texture, including mipmaps. * size will be 0 if the texture has not been uploaded yet. * * @param ht Texture handle * @param size Will be filled with size in bytes * @return Status */ extern Status ogl_tex_get_uploaded_size(Handle ht, size_t* size); /** * Retrieve ARGB value of 1x1 mipmap level of the texture, * i.e. the average color of the whole texture. * * @param ht Texture handle * @param p will be filled with ARGB value (or 0 if texture does not have mipmaps) * @return Status * * Must be called before uploading (raises a warning if called afterwards). */ extern Status ogl_tex_get_average_color(Handle ht, u32* p); // // misc // /** * Bind texture to the specified unit in preparation for using it in * rendering. * * @param ht Texture handle. If 0, texturing is disabled on this unit. * @param unit Texture Mapping Unit number, typically 0 for the first. * @return Status * * Side Effects: * - changes the active texture unit; * - (if successful) texturing was enabled/disabled on that unit. * * Notes: * - assumes multitexturing is available. * - not necessary before calling ogl_tex_upload! * - on error, the unit's texture state is unchanged; see implementation. */ extern Status ogl_tex_bind(Handle ht, size_t unit = 0); /** * Return the GL handle of the loaded texture in *id, or 0 on failure. */ extern Status ogl_tex_get_texture_id(Handle ht, GLuint* id); /** * (partially) Transform pixel format of the texture. * * @param ht Texture handle. * @param flags the TexFlags that are to be @em changed. * @return Status * @see tex_transform * * Must be called before uploading (raises a warning if called afterwards). */ extern Status ogl_tex_transform(Handle ht, size_t flags); /** * Transform pixel format of the texture. * * @param ht Texture handle. * @param new_flags Flags desired new TexFlags indicating pixel format. * @return Status * @see tex_transform * * Must be called before uploading (raises a warning if called afterwards). * * Note: this is equivalent to ogl_tex_transform(ht, ht_flags^new_flags). */ extern Status ogl_tex_transform_to(Handle ht, size_t new_flags); /** * Return whether native S3TC texture compression support is available. * If not, textures will be decompressed automatically, hurting performance. * * @return true if native S3TC supported. * * ogl_tex_upload must be called at least once before this. */ extern bool ogl_tex_has_s3tc(); /** * Return whether anisotropic filtering support is available. * (The anisotropy might still be disabled or overridden by the driver * configuration.) * * @return true if anisotropic filtering supported. * * ogl_tex_upload must be called at least once before this. */ extern bool ogl_tex_has_anisotropy(); #endif // #ifndef INCLUDED_OGL_TEX Index: ps/trunk/source/lib/res/handle.h =================================================================== --- ps/trunk/source/lib/res/handle.h (revision 19898) +++ ps/trunk/source/lib/res/handle.h (revision 19899) @@ -1,43 +1,43 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * forward declaration of Handle (reduces dependencies) */ #ifndef INCLUDED_HANDLE #define INCLUDED_HANDLE /** * `handle' representing a reference to a resource (sound, texture, etc.) * * 0 is the (silently ignored) invalid handle value; < 0 is an error code. * * this is 64 bits because we want tags to remain unique. (tags are a * counter that disambiguate several subsequent uses of the same * resource array slot). 32-bit handles aren't enough because the index * field requires at least 12 bits, thus leaving only about 512K possible * tag values. **/ typedef i64 Handle; #endif // #ifndef INCLUDED_HANDLE Index: ps/trunk/source/lib/snd.cpp =================================================================== --- ps/trunk/source/lib/snd.cpp (revision 19898) +++ ps/trunk/source/lib/snd.cpp (revision 19899) @@ -1,66 +1,66 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * sound card detection. */ #include "precompiled.h" #include "lib/snd.h" #include #include #include "lib/external_libraries/openal.h" std::string snd_card; std::string snd_drv_ver; void snd_detect() { // OpenAL alGetString might not return anything interesting on certain platforms // (see https://stackoverflow.com/questions/28960638 for an example). // However our previous code supported only Windows, and alGetString does work on // Windows, so this is an improvement. // Sound cards const ALCchar* devices = nullptr; if (alcIsExtensionPresent(nullptr, "ALC_enumeration_EXT") == AL_TRUE) { if (alcIsExtensionPresent(nullptr, "ALC_enumerate_all_EXT") == AL_TRUE) devices = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); else devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); } WARN_IF_FALSE(devices); snd_card.clear(); do { snd_card += devices; devices += strlen(devices) + 1; snd_card += "; "; } while (*devices); // Driver version snd_drv_ver = alGetString(AL_VERSION); } Index: ps/trunk/source/lib/svn_revision.cpp =================================================================== --- ps/trunk/source/lib/svn_revision.cpp (revision 19898) +++ ps/trunk/source/lib/svn_revision.cpp (revision 19899) @@ -1,27 +1,27 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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" wchar_t svn_revision[] = #include "../../build/svn_revision/svn_revision.txt" ; Index: ps/trunk/source/lib/sysdep/arch/aarch64/aarch64.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/aarch64/aarch64.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/aarch64/aarch64.cpp (revision 19899) @@ -1,49 +1,49 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * routines specific to AArch64 */ #include "precompiled.h" #include "lib/sysdep/cpu.h" intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { return __sync_fetch_and_add(location, increment); } bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } const char* cpu_IdentifierString() { return "unknown"; // TODO } Index: ps/trunk/source/lib/sysdep/arch/ia32/ia32.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/ia32/ia32.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/ia32/ia32.cpp (revision 19899) @@ -1,98 +1,98 @@ -/* Copyright (c) 2011 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 * "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. */ /* * routines specific to IA-32 */ #include "precompiled.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/arch/ia32/ia32.h" #if MSC_VERSION // VC 2008 and ICC 12 differ in their declaration of _Interlocked* #if ICC_VERSION typedef long* P32; typedef __int64* P64; #else typedef volatile long* P32; typedef volatile __int64* P64; #endif bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { const intptr_t initial = _InterlockedCompareExchange((P32)location, newValue, expected); return initial == expected; } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { const i64 initial = _InterlockedCompareExchange64((P64)location, newValue, expected); return initial == expected; } intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { return _InterlockedExchangeAdd((P32)location, increment); } #elif OS_MACOSX #include intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { cassert(sizeof(intptr_t) == sizeof(int32_t)); return OSAtomicAdd32Barrier(increment, (volatile int32_t*)location); } bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { cassert(sizeof(intptr_t) == sizeof(void*)); return OSAtomicCompareAndSwapPtrBarrier((void*)expected, (void*)newValue, (void* volatile*)location); } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { return OSAtomicCompareAndSwap64Barrier(expected, newValue, location); } #elif GCC_VERSION intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { return __sync_fetch_and_add(location, increment); } bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } #endif Index: ps/trunk/source/lib/sysdep/arch/x86_x64/cache.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/cache.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/cache.cpp (revision 19899) @@ -1,660 +1,660 @@ -/* Copyright (c) 2011 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 * "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 "lib/sysdep/arch/x86_x64/cache.h" #include "lib/bits.h" #include "lib/alignment.h" #include "lib/module_init.h" #include "lib/sysdep/os_cpu.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" namespace x86_x64 { static const size_t maxTLBs = 2*2*4; // (level0, level1) x (D,I) x (4K, 2M, 4M, 1G) static size_t numTLBs = 0; static const size_t numCaches = x86_x64::Cache::maxLevels * 2 + maxTLBs; static Cache caches[numCaches]; static void AddCache(const x86_x64::Cache& cache) { ENSURE(cache.Validate()); if(cache.type == x86_x64::Cache::kData || cache.type == x86_x64::Cache::kUnified) caches[L1D + cache.level-1] = cache; if(cache.type == x86_x64::Cache::kInstruction || cache.type == x86_x64::Cache::kUnified) caches[L1I + cache.level-1] = cache; } static void AddTLB(const x86_x64::Cache& tlb) { ENSURE(tlb.Validate()); ENSURE(tlb.level == 1 || tlb.level == 2); // see maxTLBs ENSURE(numTLBs < maxTLBs); caches[TLB+numTLBs++] = tlb; } //----------------------------------------------------------------------------- // AMD // (Intel has subsequently added support for function 0x80000006, but // only returns ECX, i.e. L2 information.) namespace AMD { static x86_x64::Cache L1Cache(u32 reg, x86_x64::Cache::Type type) { x86_x64::Cache cache; cache.Initialize(1, type); const size_t lineSize = bits(reg, 0, 7); const size_t associativity = bits(reg, 16, 23); // 0 = reserved const size_t totalSize = bits(reg, 24, 31)*KiB; if(lineSize != 0 && associativity != 0 && totalSize != 0) { cache.numEntries = totalSize / lineSize; cache.entrySize = lineSize; cache.associativity = associativity; cache.sharedBy = 1; } return cache; } // applies to L2, L3 and TLB2 static const size_t associativityTable[16] = { 0, 1, 2, 0, 4, 0, 8, 0, 16, 0, 32, 48, 64, 96, 128, x86_x64::Cache::fullyAssociative }; static x86_x64::Cache L2Cache(u32 reg, x86_x64::Cache::Type type) { x86_x64::Cache cache; cache.Initialize(2, type); const size_t lineSize = bits(reg, 0, 7); const size_t idxAssociativity = bits(reg, 12, 15); // 0 = disabled const size_t totalSize = bits(reg, 16, 31)*KiB; if(lineSize != 0 && idxAssociativity != 0 && totalSize != 0) { cache.numEntries = totalSize / lineSize; cache.entrySize = lineSize; cache.associativity = associativityTable[idxAssociativity]; cache.sharedBy = 1; } return cache; } // (same as L2 except for the size) static x86_x64::Cache L3Cache(u32 reg, x86_x64::Cache::Type type) { x86_x64::Cache cache; cache.Initialize(3, type); const size_t lineSize = bits(reg, 0, 7); const size_t idxAssociativity = bits(reg, 12, 15); // 0 = disabled const size_t totalSize = bits(reg, 18, 31)*512*KiB; // (rounded down) // NB: some Athlon 64 X2 models have no L3 cache if(lineSize != 0 && idxAssociativity != 0 && totalSize != 0) { cache.numEntries = totalSize / lineSize; cache.entrySize = lineSize; cache.associativity = associativityTable[idxAssociativity]; cache.sharedBy = 1; } return cache; } static x86_x64::Cache TLB1(u32 reg, size_t bitOffset, size_t pageSize, x86_x64::Cache::Type type) { x86_x64::Cache cache; cache.Initialize(1, type); const size_t numEntries = bits(reg, bitOffset+0, bitOffset+ 7); const size_t associativity = bits(reg, bitOffset+8, bitOffset+15); // 0 = reserved if(numEntries != 0 && associativity != 0) { cache.numEntries = numEntries; cache.entrySize = pageSize; cache.associativity = associativity; cache.sharedBy = 1; } return cache; } static x86_x64::Cache TLB2(u32 reg, size_t bitOffset, size_t pageSize, x86_x64::Cache::Type type) { x86_x64::Cache cache; cache.Initialize(2, type); const size_t numEntries = bits(reg, bitOffset+ 0, bitOffset+11); const size_t idxAssociativity = bits(reg, bitOffset+12, bitOffset+15); // 0 = disabled if(numEntries != 0 && idxAssociativity != 0) { cache.numEntries = numEntries; cache.entrySize = pageSize; cache.associativity = associativityTable[idxAssociativity]; cache.sharedBy = 1; } return cache; } static void AddTLB2Pair(u32 reg, size_t pageSize) { x86_x64::Cache::Type type = x86_x64::Cache::kUnified; if(bits(reg, 16, 31) != 0) // not unified { AddTLB(TLB2(reg, 16, pageSize, x86_x64::Cache::kData)); type = x86_x64::Cache::kInstruction; } AddTLB(TLB2(reg, 0, pageSize, type)); } // AMD reports maxCpuidIdFunction > 4 but consider functions 2..4 to be // "reserved". cache characteristics are returned via ext. functions. static void DetectCacheAndTLB() { x86_x64::CpuidRegs regs = { 0 }; regs.eax = 0x80000005; if(x86_x64::cpuid(®s)) { AddCache(L1Cache(regs.ecx, x86_x64::Cache::kData)); AddCache(L1Cache(regs.edx, x86_x64::Cache::kInstruction)); AddTLB(TLB1(regs.eax, 0, 2*MiB, x86_x64::Cache::kInstruction)); AddTLB(TLB1(regs.eax, 16, 2*MiB, x86_x64::Cache::kData)); AddTLB(TLB1(regs.ebx, 0, 4*KiB, x86_x64::Cache::kInstruction)); AddTLB(TLB1(regs.ebx, 16, 4*KiB, x86_x64::Cache::kData)); } regs.eax = 0x80000006; if(x86_x64::cpuid(®s)) { AddCache(L2Cache(regs.ecx, x86_x64::Cache::kUnified)); AddCache(L3Cache(regs.edx, x86_x64::Cache::kUnified)); AddTLB2Pair(regs.eax, 2*MiB); AddTLB2Pair(regs.ebx, 4*KiB); } } } // namespace AMD //----------------------------------------------------------------------------- // CPUID.4 namespace CPUID4 { static bool DetectCache() { // note: level order is unspecified (see Intel AP-485) for(u32 count = 0; ; count++) { x86_x64::CpuidRegs regs = { 0 }; regs.eax = 4; regs.ecx = count; if(!x86_x64::cpuid(®s)) return false; const x86_x64::Cache::Type type = (x86_x64::Cache::Type)bits(regs.eax, 0, 4); if(type == x86_x64::Cache::kNull) // no more remaining break; const size_t level = (size_t)bits(regs.eax, 5, 7); const size_t partitions = (size_t)bits(regs.ebx, 12, 21)+1; const size_t sets = (size_t)bits(regs.ecx, 0, 31)+1; x86_x64::Cache cache; cache.Initialize(level, type); cache.entrySize = (size_t)bits(regs.ebx, 0, 11)+1; // (yes, this also uses +1 encoding) cache.associativity = (size_t)bits(regs.ebx, 22, 31)+1; cache.sharedBy = (size_t)bits(regs.eax, 14, 25)+1; cache.numEntries = cache.associativity * partitions * sets; AddCache(cache); } return true; } } // namespace CPUID4 //----------------------------------------------------------------------------- // CPUID.2 (descriptors) namespace CPUID2 { typedef u8 Descriptor; typedef std::vector Descriptors; static void AppendDescriptors(u32 reg, Descriptors& descriptors) { if(IsBitSet(reg, 31)) // register contents are reserved return; for(int pos = 24; pos >= 0; pos -= 8) { const u8 descriptor = (u8)bits(reg, pos, pos+7); if(descriptor != 0) descriptors.push_back(descriptor); } } static Descriptors GetDescriptors() { // ensure consistency by pinning to a CPU. // (don't use a hard-coded mask because process affinity may be restricted) const uintptr_t allProcessors = os_cpu_ProcessorMask(); const uintptr_t firstProcessor = allProcessors & -intptr_t(allProcessors); const uintptr_t prevAffinityMask = os_cpu_SetThreadAffinityMask(firstProcessor); x86_x64::CpuidRegs regs = { 0 }; regs.eax = 2; if(!x86_x64::cpuid(®s)) return Descriptors(); Descriptors descriptors; size_t iterations = bits(regs.eax, 0, 7); for(;;) // abort mid-loop (invoke CPUID exactly times) { AppendDescriptors(bits(regs.eax, 8, 31), descriptors); AppendDescriptors(regs.ebx, descriptors); AppendDescriptors(regs.ecx, descriptors); AppendDescriptors(regs.edx, descriptors); if(--iterations == 0) break; regs.eax = 2; const bool ok = x86_x64::cpuid(®s); ENSURE(ok); } os_cpu_SetThreadAffinityMask(prevAffinityMask); return descriptors; } // note: the following cannot be moved into a function because // ARRAY_SIZE's template argument must not reference a local type. enum Flags { // level (bits 0..1) L1 = 1, L2, L3, // type (bits 2..3) I = 0x04, // instruction D = 0x08, // data U = I|D // unified // largeSize (bits 4..31 with bits 0..3 zeroed): TLB entrySize or cache numEntries }; // (there are > 100 descriptors, so we squeeze all fields into 8 bytes.) struct Characteristics // POD { x86_x64::Cache::Type Type() const { switch(flags & U) { case D: return x86_x64::Cache::kData; case I: return x86_x64::Cache::kInstruction; case U: return x86_x64::Cache::kUnified; default: DEBUG_WARN_ERR(ERR::LOGIC); return x86_x64::Cache::kNull; } } size_t Level() const { const size_t level = flags & 3; ENSURE(level != 0); return level; } bool IsTLB() const { return smallSize >= 0; } size_t NumEntries() const { return IsTLB()? (size_t)smallSize : (flags & ~0xF); } size_t EntrySize() const { return IsTLB()? (flags & ~0xF) : (size_t)(-smallSize); } u8 descriptor; u8 associativity; i16 smallSize; // negative cache entrySize or TLB numEntries u32 flags; // level, type, largeSize }; static const u8 F = x86_x64::Cache::fullyAssociative; #define CACHE(descriptor, flags, totalSize, assoc, entrySize) { descriptor, assoc, -entrySize, flags | ((totalSize)/(entrySize)) } #define TLB(descriptor, flags, entrySize, assoc, numEntries) { descriptor, assoc, numEntries, flags | (entrySize) } // (we need to include cache descriptors because early Pentium4 don't implement CPUID.4) // references: [accessed 2011-02-26] // AP485 http://www.intel.com/Assets/PDF/appnote/241618.pdf // sdman http://www.intel.com/Assets/PDF/manual/253666.pdf // sandp http://www.sandpile.org/ia32/cpuid.htm // opsol http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/i86pc/os/cpuid.c static const Characteristics characteristicsTable[] = { TLB (0x01, L1|I, 4*KiB, 4, 32), TLB (0x02, L1|I, 4*MiB, F, 2), TLB (0x03, L1|D, 4*KiB, 4, 64), TLB (0x04, L1|D, 4*MiB, 4, 8), TLB (0x05, L1|D, 4*MiB, 4, 32), CACHE(0x06, L1|I, 8*KiB, 4, 32), CACHE(0x08, L1|I, 16*KiB, 4, 32), CACHE(0x09, L1|I, 32*KiB, 4, 64), CACHE(0x0A, L1|I, 8*KiB, 2, 32), TLB (0x0B, L1|I, 4*MiB, 4, 4), CACHE(0x0C, L1|D, 16*KiB, 4, 32), CACHE(0x0D, L1|D, 16*KiB, 4, 64), // opsol: 32B (would be redundant with 0x0C), AP485: 64B, sdman: 64B CACHE(0x0E, L1|D, 24*KiB, 6, 64), CACHE(0x21, L2|U, 256*KiB, 8, 64), CACHE(0x22, L3|U, 512*KiB, 4, 64), CACHE(0x23, L3|U, 1*MiB, 8, 64), CACHE(0x25, L3|U, 2*MiB, 8, 64), CACHE(0x29, L3|U, 4*MiB, 8, 64), CACHE(0x2c, L1|D, 32*KiB, 8, 64), CACHE(0x30, L1|I, 32*KiB, 8, 64), CACHE(0x39, L2|U, 128*KiB, 4, 64), CACHE(0x3A, L2|U, 192*KiB, 6, 64), CACHE(0x3B, L2|U, 128*KiB, 2, 64), CACHE(0x3C, L2|U, 256*KiB, 4, 64), CACHE(0x3D, L2|U, 384*KiB, 6, 64), CACHE(0x3E, L2|U, 512*KiB, 4, 64), CACHE(0x41, L2|U, 128*KiB, 4, 32), CACHE(0x42, L2|U, 256*KiB, 4, 32), CACHE(0x43, L2|U, 512*KiB, 4, 32), CACHE(0x44, L2|U, 1*MiB, 4, 32), CACHE(0x45, L2|U, 2*MiB, 4, 32), CACHE(0x46, L3|U, 4*MiB, 4, 64), CACHE(0x47, L3|U, 8*MiB, 8, 64), CACHE(0x48, L2|U, 3*MiB, 12, 64), CACHE(0x49, L2|U, 4*MiB, 16, 64), CACHE(0x49, L3|U, 4*MiB, 16, 64), CACHE(0x4A, L3|U, 6*MiB, 12, 64), CACHE(0x4B, L3|U, 8*MiB, 16, 64), CACHE(0x4C, L3|U, 12*MiB, 12, 64), CACHE(0x4D, L3|U, 16*MiB, 16, 64), CACHE(0x4E, L2|U, 6*MiB, 24, 64), TLB (0x4F, L1|I, 4*KiB, F, 32), // sandp: unknown assoc, opsol: full, AP485: unspecified TLB (0x50, L1|I, 4*KiB, F, 64), TLB (0x50, L1|I, 4*MiB, F, 64), TLB (0x50, L1|I, 2*MiB, F, 64), TLB (0x51, L1|I, 4*KiB, F, 128), TLB (0x51, L1|I, 4*MiB, F, 128), TLB (0x51, L1|I, 2*MiB, F, 128), TLB (0x52, L1|I, 4*KiB, F, 256), TLB (0x52, L1|I, 4*MiB, F, 256), TLB (0x52, L1|I, 2*MiB, F, 256), TLB (0x55, L1|I, 4*MiB, F, 7), TLB (0x55, L1|I, 2*MiB, F, 7), TLB (0x56, L1|D, 4*MiB, 4, 16), TLB (0x57, L1|D, 4*KiB, 4, 16), TLB (0x59, L1|D, 4*KiB, F, 16), TLB (0x5A, L1|D, 4*MiB, 4, 32), TLB (0x5A, L1|D, 2*MiB, 4, 32), TLB (0x5B, L1|D, 4*KiB, F, 64), TLB (0x5B, L1|D, 4*MiB, F, 64), TLB (0x5C, L1|D, 4*KiB, F, 128), TLB (0x5C, L1|D, 4*MiB, F, 128), TLB (0x5D, L1|D, 4*KiB, F, 256), TLB (0x5D, L1|D, 4*MiB, F, 256), CACHE(0x60, L1|D, 16*KiB, 8, 64), TLB (0x63, L1|D, 1*GiB, 4, 4), // speculation CACHE(0x66, L1|D, 8*KiB, 4, 64), CACHE(0x67, L1|D, 16*KiB, 4, 64), CACHE(0x68, L1|D, 32*KiB, 4, 64), CACHE(0x70, L1|I, 12*KiB, 8, 1), CACHE(0x71, L1|I, 16*KiB, 8, 1), CACHE(0x72, L1|I, 32*KiB, 8, 1), CACHE(0x73, L1|I, 64*KiB, 8, 1), TLB (0x76, L1|I, 4*MiB, F, 8), // AP485: internally inconsistent, sdman: TLB TLB (0x76, L1|I, 2*MiB, F, 8), CACHE(0x78, L2|U, 1*MiB, 4, 64), CACHE(0x79, L2|U, 128*KiB, 8, 64), CACHE(0x7A, L2|U, 256*KiB, 8, 64), CACHE(0x7B, L2|U, 512*KiB, 8, 64), CACHE(0x7C, L2|U, 1*MiB, 8, 64), CACHE(0x7D, L2|U, 2*MiB, 8, 64), CACHE(0x7F, L2|U, 512*KiB, 2, 64), CACHE(0x80, L2|U, 512*KiB, 8, 64), CACHE(0x82, L2|U, 256*KiB, 8, 32), CACHE(0x83, L2|U, 512*KiB, 8, 32), CACHE(0x84, L2|U, 1*MiB, 8, 32), CACHE(0x85, L2|U, 2*MiB, 8, 32), CACHE(0x86, L2|U, 512*KiB, 4, 64), CACHE(0x87, L2|U, 1*MiB, 8, 64), TLB (0xB0, L1|I, 4*KiB, 4, 128), TLB (0xB1, L1|I, 2*MiB, 4, 8), TLB (0xB1, L1|I, 4*MiB, 4, 4), TLB (0xB2, L1|I, 4*KiB, 4, 64), TLB (0xB3, L1|D, 4*KiB, 4, 128), TLB (0xB3, L1|D, 4*MiB, 4, 128), TLB (0xB4, L1|D, 4*KiB, 4, 256), TLB (0xB4, L1|D, 4*MiB, 4, 256), TLB (0xB5, L1|I, 4*KiB, 4, 128), // speculation TLB (0xB6, L1|I, 4*KiB, 8, 128), // http://software.intel.com/en-us/forums/topic/401012 TLB (0xBA, L1|D, 4*KiB, 4, 64), TLB (0xC0, L1|D, 4*KiB, 4, 8), TLB (0xC0, L1|D, 4*MiB, 4, 8), TLB (0xC1, L2|U, 4*KiB, 8, 1024), // http://software.intel.com/en-us/forums/topic/401012 TLB (0xC1, L2|U, 4*MiB, 8, 1024), TLB (0xC1, L2|U, 2*MiB, 8, 1024), TLB (0xCA, L2|U, 4*KiB, 4, 512), CACHE(0xD0, L3|U, 512*KiB, 4, 64), CACHE(0xD1, L3|U, 1*MiB, 4, 64), CACHE(0xD2, L3|U, 2*MiB, 4, 64), CACHE(0xD6, L3|U, 1*MiB, 8, 64), CACHE(0xD7, L3|U, 2*MiB, 8, 64), CACHE(0xD8, L3|U, 4*MiB, 8, 64), CACHE(0xDC, L3|U, 3*MiB/2, 12, 64), CACHE(0xDD, L3|U, 3*MiB, 12, 64), CACHE(0xDE, L3|U, 6*MiB, 12, 64), CACHE(0xE2, L3|U, 2*MiB, 16, 64), CACHE(0xE3, L3|U, 4*MiB, 16, 64), CACHE(0xE4, L3|U, 8*MiB, 16, 64), CACHE(0xEA, L3|U, 12*MiB, 24, 64), CACHE(0xEB, L3|U, 18*MiB, 24, 64), CACHE(0xEC, L3|U, 24*MiB, 24, 64), }; #undef CACHE #undef TLB static const Characteristics* CharacteristicsFromDescriptor(Descriptor descriptor) { // note: we can't use bsearch because characteristicsTable contains multiple // entries with the same descriptor. for(size_t i = 0; i < ARRAY_SIZE(characteristicsTable); i++) { const Characteristics& characteristics = characteristicsTable[i]; if(characteristics.descriptor == descriptor) return &characteristics; } debug_printf("Unknown cache/TLB descriptor 0x%x\n", (unsigned int)descriptor); return 0; } enum DescriptorFlags { SKIP_CACHE_DESCRIPTORS = 1, NO_LAST_LEVEL_CACHE = 2, PREFETCH64 = 64, PREFETCH128 = 128 }; static bool HandleSpecialDescriptor(Descriptor descriptor, size_t& descriptorFlags) { switch(descriptor) { case 0: // carries no information return true; case 0x40: descriptorFlags |= NO_LAST_LEVEL_CACHE; return true; case 0xF0: descriptorFlags |= PREFETCH64; return true; case 0xF1: descriptorFlags |= PREFETCH128; return true; case 0xFF: // descriptors don't include caches (use CPUID.4 instead) descriptorFlags |= SKIP_CACHE_DESCRIPTORS; return true; default: return false; } } static void DetectCacheAndTLB(size_t& descriptorFlags) { const Descriptors descriptors = GetDescriptors(); for(Descriptors::const_iterator it = descriptors.begin(); it != descriptors.end(); ++it) { const Descriptor descriptor = *it; if(HandleSpecialDescriptor(descriptor, descriptorFlags)) continue; const Characteristics* characteristics = CharacteristicsFromDescriptor(*it); if(!characteristics) continue; if((descriptorFlags & SKIP_CACHE_DESCRIPTORS) && !characteristics->IsTLB()) continue; x86_x64::Cache cache; cache.Initialize(characteristics->Level(), characteristics->Type()); cache.numEntries = characteristics->NumEntries(); cache.entrySize = characteristics->EntrySize(); cache.associativity = characteristics->associativity; cache.sharedBy = 1; // (safe default) if(characteristics->IsTLB()) AddTLB(cache); else AddCache(cache); } } } // namespace CPUID2 static Status DetectCacheAndTLB() { // ensure all cache entries are initialized (DetectCache* might not set them all) for(size_t idxLevel = 0; idxLevel < x86_x64::Cache::maxLevels; idxLevel++) { caches[L1D+idxLevel].Initialize(idxLevel+1, x86_x64::Cache::kData); caches[L1I+idxLevel].Initialize(idxLevel+1, x86_x64::Cache::kInstruction); } if(x86_x64::Vendor() == x86_x64::VENDOR_AMD) AMD::DetectCacheAndTLB(); else { size_t descriptorFlags = 0; if(CPUID4::DetectCache()) // success, ignore less reliable CPUID.2 cache information descriptorFlags |= CPUID2::SKIP_CACHE_DESCRIPTORS; CPUID2::DetectCacheAndTLB(descriptorFlags); } // sanity checks for(size_t idxLevel = 0; idxLevel < x86_x64::Cache::maxLevels; idxLevel++) { ENSURE(caches[L1D+idxLevel].type == x86_x64::Cache::kData || caches[L1D+idxLevel].type == x86_x64::Cache::kUnified); ENSURE(caches[L1D+idxLevel].level == idxLevel+1); ENSURE(caches[L1D+idxLevel].Validate() == true); ENSURE(caches[L1I+idxLevel].type == x86_x64::Cache::kInstruction || caches[L1I+idxLevel].type == x86_x64::Cache::kUnified); ENSURE(caches[L1I+idxLevel].level == idxLevel+1); ENSURE(caches[L1I+idxLevel].Validate() == true); } for(size_t i = 0; i < numTLBs; i++) ENSURE(caches[TLB+i].Validate() == true); return INFO::OK; } const x86_x64::Cache* Caches(size_t idxCache) { static ModuleInitState initState; ModuleInit(&initState, DetectCacheAndTLB); if(idxCache >= TLB+numTLBs) return 0; return &caches[idxCache]; } } // namespace x86_x64 Index: ps/trunk/source/lib/sysdep/arch/x86_x64/tests/test_topology.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/tests/test_topology.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/tests/test_topology.h (revision 19899) @@ -1,36 +1,36 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/sysdep/arch/x86_x64/topology.h" class TestTopology : public CxxTest::TestSuite { public: void test_run() { TS_ASSERT_LESS_THAN_EQUALS(1u, topology::NumPackages()); TS_ASSERT_LESS_THAN_EQUALS(1u, topology::CoresPerPackage()); TS_ASSERT_LESS_THAN_EQUALS(1u, topology::LogicalPerCore()); } }; Index: ps/trunk/source/lib/sysdep/arch/x86_x64/x86_x64.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/x86_x64.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/x86_x64.h (revision 19899) @@ -1,182 +1,182 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * CPU-specific routines common to 32 and 64-bit x86 */ #ifndef INCLUDED_X86_X64 #define INCLUDED_X86_X64 #include "lib/lib_api.h" #if !ARCH_X86_X64 #error "including x86_x64.h without ARCH_X86_X64=1" #endif #if MSC_VERSION #include // __rdtsc #endif namespace x86_x64 { /** * registers used/returned by cpuid **/ #pragma pack(push, 1) // (allows casting to int*) struct CpuidRegs { u32 eax; u32 ebx; u32 ecx; u32 edx; }; #pragma pack(pop) /** * invoke CPUID instruction. * @param regs input/output registers. * regs->eax must be set to the desired function. * some functions (e.g. 4) require regs->ecx to be set as well. * rationale: this interface (input/output structure vs. function parameters) * avoids unnecessary copying/initialization if some inputs aren't needed * and allows graceful expansion to functions that require further inputs. * @return true on success or false if the sub-function isn't supported. **/ LIB_API bool cpuid(CpuidRegs* regs); /** * CPU vendor. * (this is exposed because some CPUID functions are vendor-specific.) * (an enum is easier to compare than the original string values.) **/ enum Vendors { VENDOR_UNKNOWN, VENDOR_INTEL, VENDOR_AMD }; LIB_API Vendors Vendor(); enum Models { MODEL_NEHALEM_EP = 0x1A, // Bloomfield (X35xx), Gainestown (X55xx) MODEL_NEHALEM_EP_2 = 0x1E, // Clarksfield, Lynnfield (X34xx), Jasper Forest (C35xx, C55xx) MODEL_I7_I5 = 0x1F, // similar to 1E; mentioned in 253665-041US, no codename known MODEL_CLARKDALE = 0x25, // Arrandale, Clarkdale (L34xx) MODEL_WESTMERE_EP = 0x2C, // Gulftown (X36xx, X56xx) MODEL_NEHALEM_EX = 0x2E, // Beckton (X75xx) MODEL_WESTMERE_EX = 0x2F, // Gulftown uarch, Beckton package (E7-48xx) MODEL_SANDY_BRIDGE = 0x2A, // (E3-12xx, E5-26xx) MODEL_SANDY_BRIDGE_2 = 0x2D, // (E5-26xx, E5-46xx) }; LIB_API size_t Model(); LIB_API size_t Family(); /** * @return the colloquial processor generation * (5 = Pentium, 6 = Pentium Pro/II/III / K6, 7 = Pentium4 / Athlon, 8 = Core / Opteron) **/ LIB_API size_t Generation(); /** * bit indices of CPU capability flags (128 bits). * values are defined by IA-32 CPUID feature flags - do not change! **/ enum Caps { // standard (ecx) - currently only defined by Intel CAP_SSE3 = 0+0, // Streaming SIMD Extensions 3 CAP_EST = 0+7, // Enhanced Speedstep Technology CAP_SSSE3 = 0+9, // Supplemental Streaming SIMD Extensions 3 CAP_SSE41 = 0+19, // Streaming SIMD Extensions 4.1 CAP_SSE42 = 0+20, // Streaming SIMD Extensions 4.2 // standard (edx) CAP_FPU = 32+0, // Floating Point Unit CAP_TSC = 32+4, // TimeStamp Counter CAP_MSR = 32+5, // Model Specific Registers CAP_CMOV = 32+15, // Conditional MOVe CAP_TM_SCC = 32+22, // Thermal Monitoring and Software Controlled Clock CAP_MMX = 32+23, // MultiMedia eXtensions CAP_SSE = 32+25, // Streaming SIMD Extensions CAP_SSE2 = 32+26, // Streaming SIMD Extensions 2 CAP_HT = 32+28, // HyperThreading // extended (ecx) CAP_AMD_CMP_LEGACY = 64+1, // N-core and CAP_HT is falsely set // extended (edx) CAP_AMD_MP = 96+19, // MultiProcessing capable; reserved on AMD64 CAP_AMD_MMX_EXT = 96+22, CAP_AMD_3DNOW_PRO = 96+30, CAP_AMD_3DNOW = 96+31 }; /** * @return whether the CPU supports the indicated Cap / feature flag. **/ LIB_API bool Cap(Caps cap); LIB_API void GetCapBits(u32* d0, u32* d1, u32* d2, u32* d3); //----------------------------------------------------------------------------- // stateless /** * @return the current value of the TimeStampCounter (a counter of * CPU cycles since power-on, which is useful for high-resolution timing * but potentially differs between multiple CPUs) * * notes: * - a macro avoids call overhead, which is important for TIMER_ACCRUE. * - x64 RDTSC writes to edx:eax and clears the upper halves of rdx and rax. **/ #if MSC_VERSION static inline u64 rdtsc() { return __rdtsc(); } #else LIB_API u64 rdtsc(); #endif /** * trigger a breakpoint inside this function when it is called. **/ LIB_API void DebugBreak(); /** * measure the CPU clock frequency via rdtsc and timer_Time. * (it follows that this must not be called from WHRT init.) * this takes several milliseconds (i.e. much longer than * os_cpu_ClockFrequency) but delivers accurate measurements. **/ LIB_API double ClockFrequency(); } // namespace x86_x64 #endif // #ifndef INCLUDED_X86_X64 Index: ps/trunk/source/lib/regex.cpp =================================================================== --- ps/trunk/source/lib/regex.cpp (revision 19898) +++ ps/trunk/source/lib/regex.cpp (revision 19899) @@ -1,75 +1,75 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // minimal regex implementation #include "precompiled.h" int match_wildcard(const wchar_t* s, const wchar_t* w) { if(!w) return 1; // saved position in both strings, used to expand '*': // s2 is advanced until match. // initially 0 - we abort on mismatch before the first '*'. const wchar_t* s2 = 0; const wchar_t* w2 = 0; while(*s) { const wchar_t wc = *w; if(wc == '*') { // wildcard string ended with * => match. if(*++w == '\0') return 1; w2 = w; s2 = s+1; } // match one character else if(towupper(wc) == towupper(*s) || wc == '?') { w++; s++; } // mismatched character else { // no '*' found yet => mismatch. if(!s2) return 0; // resume at previous position+1 w = w2; s = s2++; } } // strip trailing * in wildcard string while(*w == '*') w++; return (*w == '\0'); } Index: ps/trunk/source/lib/res/graphics/ogl_tex.cpp =================================================================== --- ps/trunk/source/lib/res/graphics/ogl_tex.cpp (revision 19898) +++ ps/trunk/source/lib/res/graphics/ogl_tex.cpp (revision 19899) @@ -1,1144 +1,1144 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * wrapper for all OpenGL texturing calls. provides caching, hotloading * and lifetime management. */ #include "precompiled.h" #include "ogl_tex.h" #include #include "lib/app_hooks.h" #include "lib/ogl.h" #include "lib/bits.h" #include "lib/sysdep/gfx.h" #include "lib/tex/tex.h" #include "lib/res/h_mgr.h" #include "lib/fnv_hash.h" //---------------------------------------------------------------------------- // OpenGL helper routines //---------------------------------------------------------------------------- static bool filter_valid(GLint filter) { switch(filter) { case GL_NEAREST: case GL_LINEAR: case GL_NEAREST_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_NEAREST: case GL_NEAREST_MIPMAP_LINEAR: case GL_LINEAR_MIPMAP_LINEAR: return true; default: return false; } } static bool wrap_valid(GLint wrap) { switch(wrap) { #if !CONFIG2_GLES case GL_CLAMP: case GL_CLAMP_TO_BORDER: #endif case GL_CLAMP_TO_EDGE: case GL_REPEAT: case GL_MIRRORED_REPEAT: return true; default: return false; } } static bool are_mipmaps_needed(size_t width, size_t height, GLint filter) { // can't upload the entire texture; we're going to skip some // levels until it no longer exceeds the OpenGL dimension limit. if((GLint)width > ogl_max_tex_size || (GLint)height > ogl_max_tex_size) return true; switch(filter) { case GL_NEAREST_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_NEAREST: case GL_NEAREST_MIPMAP_LINEAR: case GL_LINEAR_MIPMAP_LINEAR: return true; default: return false; } } static bool fmt_is_s3tc(GLenum fmt) { switch(fmt) { case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: #if !CONFIG2_GLES case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: #endif return true; default: return false; } } // determine OpenGL texture format, given and Tex . static GLint choose_fmt(size_t bpp, size_t flags) { const bool alpha = (flags & TEX_ALPHA) != 0; const bool bgr = (flags & TEX_BGR ) != 0; const bool grey = (flags & TEX_GREY ) != 0; const size_t dxt = flags & TEX_DXT; // S3TC if(dxt != 0) { switch(dxt) { case DXT1A: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; case 1: return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; #if !CONFIG2_GLES case 3: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; case 5: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; #endif default: DEBUG_WARN_ERR(ERR::LOGIC); // invalid DXT value return 0; } } // uncompressed switch(bpp) { case 8: ENSURE(grey); return GL_LUMINANCE; case 16: return GL_LUMINANCE_ALPHA; case 24: ENSURE(!alpha); #if CONFIG2_GLES // GLES never supports BGR ENSURE(!bgr); return GL_RGB; #else return bgr? GL_BGR : GL_RGB; #endif case 32: ENSURE(alpha); // GLES can support BGRA via GL_EXT_texture_format_BGRA8888 // (TODO: can we rely on support for that extension?) return bgr? GL_BGRA_EXT : GL_RGBA; default: DEBUG_WARN_ERR(ERR::LOGIC); // invalid bpp return 0; } UNREACHABLE; } //---------------------------------------------------------------------------- // quality mechanism //---------------------------------------------------------------------------- static GLint default_filter = GL_LINEAR; // one of the GL *minify* filters static int default_q_flags = OGL_TEX_FULL_QUALITY; // OglTexQualityFlags static bool q_flags_valid(int q_flags) { const size_t bits = OGL_TEX_FULL_QUALITY|OGL_TEX_HALF_BPP|OGL_TEX_HALF_RES; // unrecognized bits are set - invalid if((q_flags & ~bits) != 0) return false; // "full quality" but other reduction bits are set - invalid if(q_flags & OGL_TEX_FULL_QUALITY && q_flags & ~OGL_TEX_FULL_QUALITY) return false; return true; } // change default settings - these affect performance vs. quality. // may be overridden for individual textures via parameter to // ogl_tex_upload or ogl_tex_set_filter, respectively. // // pass 0 to keep the current setting; defaults and legal values are: // - q_flags: OGL_TEX_FULL_QUALITY; combination of OglTexQualityFlags // - filter: GL_LINEAR; any valid OpenGL minification filter void ogl_tex_set_defaults(int q_flags, GLint filter) { if(q_flags) { ENSURE(q_flags_valid(q_flags)); default_q_flags = q_flags; } if(filter) { ENSURE(filter_valid(filter)); default_filter = filter; } } // choose an internal format for based on the given q_flags. static GLint choose_int_fmt(GLenum fmt, int q_flags) { // true => 4 bits per component; otherwise, 8 const bool half_bpp = (q_flags & OGL_TEX_HALF_BPP) != 0; // early-out for S3TC textures: they don't need an internal format // (because upload is via glCompressedTexImage2DARB), but we must avoid // triggering the default case below. we might as well return a // meaningful value (i.e. int_fmt = fmt). if(fmt_is_s3tc(fmt)) return fmt; #if CONFIG2_GLES UNUSED2(half_bpp); // GLES only supports internal format == external format return fmt; #else switch(fmt) { // 8bpp case GL_LUMINANCE: return half_bpp? GL_LUMINANCE4 : GL_LUMINANCE8; case GL_INTENSITY: return half_bpp? GL_INTENSITY4 : GL_INTENSITY8; case GL_ALPHA: return half_bpp? GL_ALPHA4 : GL_ALPHA8; // 16bpp case GL_LUMINANCE_ALPHA: return half_bpp? GL_LUMINANCE4_ALPHA4 : GL_LUMINANCE8_ALPHA8; // 24bpp case GL_RGB: case GL_BGR: // note: BGR can't be used as internal format return half_bpp? GL_RGB4 : GL_RGB8; // 32bpp case GL_RGBA: case GL_BGRA: // note: BGRA can't be used as internal format return half_bpp? GL_RGBA4 : GL_RGBA8; default: { wchar_t buf[100]; swprintf_s(buf, ARRAY_SIZE(buf), L"choose_int_fmt: fmt 0x%x isn't covered! please add it", fmt); DEBUG_DISPLAY_ERROR(buf); DEBUG_WARN_ERR(ERR::LOGIC); // given fmt isn't covered! please add it. // fall back to a reasonable default return half_bpp? GL_RGB4 : GL_RGB8; } } UNREACHABLE; #endif // #if CONFIG2_GLES } //---------------------------------------------------------------------------- // texture state to allow seamless reload //---------------------------------------------------------------------------- // see "Texture Parameters" in docs. // all GL state tied to the texture that must be reapplied after reload. // (this mustn't get too big, as it's stored in the already sizeable OglTex) struct OglTexState { // glTexParameter // note: there are more options, but they do not look to // be important and will not be applied after a reload! // in particular, LOD_BIAS isn't needed because that is set for // the entire texturing unit via glTexEnv. // .. texture filter // note: this is the minification filter value; magnification filter // is GL_NEAREST if it's GL_NEAREST, otherwise GL_LINEAR. // we don't store mag_filter explicitly because it // doesn't appear useful - either apps can tolerate LINEAR, or // mipmaps aren't called for and filter could be NEAREST anyway). GLint filter; // .. wrap mode GLint wrap_s; GLint wrap_t; // .. anisotropy // note: ignored unless EXT_texture_filter_anisotropic is supported. GLfloat anisotropy; }; // fill the given state object with default values. static void state_set_to_defaults(OglTexState* ots) { ots->filter = default_filter; ots->wrap_s = GL_REPEAT; ots->wrap_t = GL_REPEAT; ots->anisotropy = 1.0f; } // send all state to OpenGL (actually the currently bound texture). // called from ogl_tex_upload. static void state_latch(OglTexState* ots) { // filter const GLint filter = ots->filter; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); const GLint mag_filter = (filter == GL_NEAREST)? GL_NEAREST : GL_LINEAR; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); // wrap const GLint wrap_s = ots->wrap_s; const GLint wrap_t = ots->wrap_t; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t); // .. only CLAMP and REPEAT are guaranteed to be available. // if we're using one of the others, we squelch the error that // may have resulted if this GL implementation is old. #if !CONFIG2_GLES if((wrap_s != GL_CLAMP && wrap_s != GL_REPEAT) || (wrap_t != GL_CLAMP && wrap_t != GL_REPEAT)) ogl_SquelchError(GL_INVALID_ENUM); #endif // anisotropy const GLfloat anisotropy = ots->anisotropy; if (anisotropy != 1.0f && ogl_tex_has_anisotropy()) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy); } } //---------------------------------------------------------------------------- // texture resource object //---------------------------------------------------------------------------- // ideally we would split OglTex into data and state objects as in // SndData / VSrc. this gives us the benefits of caching while still // leaving each "instance" (state object, which owns a data reference) // free to change its state. however, unlike in OpenAL, there is no state // independent of the data object - all parameters are directly tied to the // GL texture object. therefore, splitting them up is impossible. // (we shouldn't even keep the texel data in memory since that's already // covered by the FS cache). // // given that multiple "instances" share the state stored here, we conclude: // - a refcount is necessary to prevent ogl_tex_upload from freeing // as long as other instances are active. // - concurrent use risks cross-talk (if the 2nd "instance" changes state and // the first is reloaded, its state may change to that of the 2nd) // // as bad as it sounds, the latter issue isn't a problem: we do not expect // multiple instances of the same texture where someone changes its filter. // even if it is reloaded, the differing state is not critical. // the alternative is even worse: disabling *all* caching/reuse would // really hurt performance and h_mgr doesn't support only disallowing // reuse of active objects (this would break the index lookup code, since // multiple instances may then exist). // note: make sure these values fit inside OglTex.flags (only 16 bits) enum OglTexFlags { // "the texture is currently uploaded"; reset in dtor. OT_IS_UPLOADED = 1, // "the enclosed Tex object is valid and holds a texture"; // reset in dtor and after ogl_tex_upload's tex_free. OT_TEX_VALID = 2, //size_t tex_valid : 1; // "reload() should automatically re-upload the texture" (because // it had been uploaded before the reload); never reset. OT_NEED_AUTO_UPLOAD = 4, // (used for validating flags) OT_ALL_FLAGS = OT_IS_UPLOADED|OT_TEX_VALID|OT_NEED_AUTO_UPLOAD }; struct OglTex { Tex t; // allocated by OglTex_reload; indicates the texture is currently uploaded. GLuint id; // ogl_tex_upload calls choose_fmt to determine these from . // however, its caller may override those values via parameters. // note: these are stored here to allow retrieving via ogl_tex_get_format; // they are only used within ogl_tex_upload. GLenum fmt; GLint int_fmt; OglTexState state; // OglTexQualityFlags u8 q_flags; // to which Texture Mapping Unit was this bound? u8 tmu; u16 flags; u32 uploaded_size; }; H_TYPE_DEFINE(OglTex); static void OglTex_init(OglTex* ot, va_list args) { Tex* wrapped_tex = va_arg(args, Tex*); if(wrapped_tex) { ot->t = *wrapped_tex; // indicate ot->t is now valid, thus skipping loading from file. // note: ogl_tex_wrap prevents actual reloads from happening. ot->flags |= OT_TEX_VALID; } state_set_to_defaults(&ot->state); ot->q_flags = default_q_flags; } static void OglTex_dtor(OglTex* ot) { if(ot->flags & OT_TEX_VALID) { ot->t.free(); ot->flags &= ~OT_TEX_VALID; } // note: do not check if OT_IS_UPLOADED is set, because we allocate // OglTex.id without necessarily having done an upload. glDeleteTextures(1, &ot->id); ot->id = 0; ot->flags &= ~OT_IS_UPLOADED; ot->uploaded_size = 0; } static Status OglTex_reload(OglTex* ot, const PIVFS& vfs, const VfsPath& pathname, Handle h) { // we're reusing a freed but still in-memory OglTex object if(ot->flags & OT_IS_UPLOADED) return INFO::OK; // if we don't already have the texture in memory (*), load from file. // * this happens if the texture is "wrapped". if(!(ot->flags & OT_TEX_VALID)) { shared_ptr file; size_t fileSize; RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize)); if(ot->t.decode(file, fileSize) >= 0) ot->flags |= OT_TEX_VALID; } glGenTextures(1, &ot->id); // if it had already been uploaded before this reload, // re-upload it (this also does state_latch). if(ot->flags & OT_NEED_AUTO_UPLOAD) (void)ogl_tex_upload(h); return INFO::OK; } static Status OglTex_validate(const OglTex* ot) { if(ot->flags & OT_TEX_VALID) { RETURN_STATUS_IF_ERR(ot->t.validate()); // width, height // (note: this is done here because tex.cpp doesn't impose any // restrictions on dimensions, while OpenGL does). size_t w = ot->t.m_Width; size_t h = ot->t.m_Height; // .. == 0; texture file probably not loaded successfully. if(w == 0 || h == 0) WARN_RETURN(ERR::_11); // .. not power-of-2. // note: we can't work around this because both NV_texture_rectangle // and subtexture require work for the client (changing tex coords). // TODO: ARB_texture_non_power_of_two if(!is_pow2(w) || !is_pow2(h)) WARN_RETURN(ERR::_13); // no longer verify dimensions are less than ogl_max_tex_size, // because we just use the higher mip levels in that case. } // texture state if(!filter_valid(ot->state.filter)) WARN_RETURN(ERR::_14); if(!wrap_valid(ot->state.wrap_s)) WARN_RETURN(ERR::_15); if(!wrap_valid(ot->state.wrap_t)) WARN_RETURN(ERR::_16); // misc if(!q_flags_valid(ot->q_flags)) WARN_RETURN(ERR::_17); if(ot->tmu >= 128) // unexpected that there will ever be this many WARN_RETURN(ERR::_18); if(ot->flags > OT_ALL_FLAGS) WARN_RETURN(ERR::_19); // .. note: don't check ot->fmt and ot->int_fmt - they aren't set // until during ogl_tex_upload. return INFO::OK; } static Status OglTex_to_string(const OglTex* ot, wchar_t* buf) { swprintf_s(buf, H_STRING_LEN, L"OglTex id=%u flags=%x", ot->id, (unsigned int)ot->flags); return INFO::OK; } // load and return a handle to the texture given in . // for a list of supported formats, see tex.h's tex_load. Handle ogl_tex_load(const PIVFS& vfs, const VfsPath& pathname, size_t flags) { Tex* wrapped_tex = 0; // we're loading from file return h_alloc(H_OglTex, vfs, pathname, flags, wrapped_tex); } // return Handle to an existing object, if it has been loaded and // is still in memory; otherwise, a negative error code. Handle ogl_tex_find(const VfsPath& pathname) { const uintptr_t key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0])); return h_find(H_OglTex, key); } // make the given Tex object ready for use as an OpenGL texture // and return a handle to it. this will be as if its contents // had been loaded by ogl_tex_load. // // we need only add bookkeeping information and "wrap" it in // a resource object (accessed via Handle), hence the name. // // isn't strictly needed but should describe the texture so that // h_filename will return a meaningful comment for debug purposes. // note: because we cannot guarantee that callers will pass distinct // "filenames", caching is disabled for the created object. this avoids // mistakenly reusing previous objects that share the same comment. Handle ogl_tex_wrap(Tex* t, const PIVFS& vfs, const VfsPath& pathname, size_t flags) { // this object may not be backed by a file ("may", because // someone could do tex_load and then ogl_tex_wrap). // if h_mgr asks for a reload, the dtor will be called but // we won't be able to reconstruct it. therefore, disallow reloads. // (they are improbable anyway since caller is supposed to pass a // 'descriptive comment' instead of filename, but don't rely on that) // also disable caching as explained above. flags |= RES_DISALLOW_RELOAD|RES_NO_CACHE; return h_alloc(H_OglTex, vfs, pathname, flags, t); } // free all resources associated with the texture and make further // use of it impossible. (subject to refcount) Status ogl_tex_free(Handle& ht) { return h_free(ht, H_OglTex); } //---------------------------------------------------------------------------- // state setters (see "Texture Parameters" in docs) //---------------------------------------------------------------------------- // we require the below functions be called before uploading; this avoids // potentially redundant glTexParameter calls (we'd otherwise need to always // set defaults because we don't know if an override is forthcoming). // raise a debug warning if the texture has already been uploaded // (except in the few cases where this is allowed; see below). // this is so that you will notice incorrect usage - only one instance of a // texture should be active at a time, because otherwise they vie for // control of one shared OglTexState. static void warn_if_uploaded(Handle ht, const OglTex* ot) { #ifndef NDEBUG // we do not require users of this module to remember if they've // already uploaded a texture (inconvenient). since they also can't // tell if the texture was newly loaded (due to h_alloc interface), // we have to squelch this warning in 2 cases: // - it's ogl_tex_loaded several times (i.e. refcount > 1) and the // caller (typically a higher-level LoadTexture) is setting filter etc. // - caller is using our Handle as a caching mechanism, and calls // ogl_tex_set_* before every use of the texture. note: this // need not fall under the above check, e.g. if freed but cached. // workaround is that ogl_tex_set_* won't call us if the // same state values are being set (harmless anyway). intptr_t refs = h_get_refcnt(ht); if(refs > 1) return; // don't complain if(ot->flags & OT_IS_UPLOADED) DEBUG_WARN_ERR(ERR::LOGIC); // ogl_tex_set_*: texture already uploaded and shouldn't be changed #else // (prevent warnings; the alternative of wrapping all call sites in // #ifndef is worse) UNUSED2(ht); UNUSED2(ot); #endif } // override default filter (as set above) for this texture. // must be called before uploading (raises a warning if called afterwards). // filter is as defined by OpenGL; it is applied for both minification and // magnification (for rationale and details, see OglTexState) Status ogl_tex_set_filter(Handle ht, GLint filter) { H_DEREF(ht, OglTex, ot); if(!filter_valid(filter)) WARN_RETURN(ERR::INVALID_PARAM); if(ot->state.filter != filter) { warn_if_uploaded(ht, ot); ot->state.filter = filter; } return INFO::OK; } // override default wrap mode (GL_REPEAT) for this texture. // must be called before uploading (raises a warning if called afterwards). // wrap is as defined by OpenGL. Status ogl_tex_set_wrap(Handle ht, GLint wrap_s, GLint wrap_t) { H_DEREF(ht, OglTex, ot); if(!wrap_valid(wrap_s)) WARN_RETURN(ERR::INVALID_PARAM); if(!wrap_valid(wrap_t)) WARN_RETURN(ERR::INVALID_PARAM); if(ot->state.wrap_s != wrap_s || ot->state.wrap_t != wrap_t) { warn_if_uploaded(ht, ot); ot->state.wrap_s = wrap_s; ot->state.wrap_t = wrap_t; } return INFO::OK; } // override default anisotropy for this texture. // must be called before uploading (raises a warning if called afterwards). Status ogl_tex_set_anisotropy(Handle ht, GLfloat anisotropy) { H_DEREF(ht, OglTex, ot); if(anisotropy < 1.0f) WARN_RETURN(ERR::INVALID_PARAM); if(ot->state.anisotropy != anisotropy) { warn_if_uploaded(ht, ot); ot->state.anisotropy = anisotropy; } return INFO::OK; } //---------------------------------------------------------------------------- // upload //---------------------------------------------------------------------------- // OpenGL has several features that are helpful for uploading but not // available in all implementations. we check for their presence but // provide for user override (in case they don't work on a card/driver // combo we didn't test). // tristate; -1 is undecided static int have_auto_mipmap_gen = -1; static int have_s3tc = -1; static int have_anistropy = -1; // override the default decision and force/disallow use of the // given feature. should be called from ah_override_gl_upload_caps. void ogl_tex_override(OglTexOverrides what, OglTexAllow allow) { ENSURE(allow == OGL_TEX_ENABLE || allow == OGL_TEX_DISABLE); const bool enable = (allow == OGL_TEX_ENABLE); switch(what) { case OGL_TEX_S3TC: have_s3tc = enable; break; case OGL_TEX_AUTO_MIPMAP_GEN: have_auto_mipmap_gen = enable; break; case OGL_TEX_ANISOTROPY: have_anistropy = enable; break; default: DEBUG_WARN_ERR(ERR::LOGIC); // invalid break; } } // detect caps (via OpenGL extension list) and give an app_hook the chance to // override this (e.g. via list of card/driver combos on which S3TC breaks). // called once from the first ogl_tex_upload. static void detect_gl_upload_caps() { // note: gfx_card will be empty if running in quickstart mode; // in that case, we won't be able to check for known faulty cards. // detect features, but only change the variables if they were at // "undecided" (if overrides were set before this, they must remain). if(have_auto_mipmap_gen == -1) { have_auto_mipmap_gen = ogl_HaveExtension("GL_SGIS_generate_mipmap"); } if(have_s3tc == -1) { #if CONFIG2_GLES // some GLES implementations have GL_EXT_texture_compression_dxt1 // but that only supports DXT1 so we can't use it anyway have_s3tc = 0; #else // note: we don't bother checking for GL_S3_s3tc - it is incompatible // and irrelevant (was never widespread). have_s3tc = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", NULL) == 0; #endif } if(have_anistropy == -1) { have_anistropy = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"); } // allow app hook to make ogl_tex_override calls if(AH_IS_DEFINED(override_gl_upload_caps)) { ah_override_gl_upload_caps(); } // no app hook defined - have our own crack at blacklisting some hardware. else { const std::wstring cardName = gfx::CardName(); // rationale: janwas's laptop's S3 card blows up if S3TC is used // (oh, the irony). it'd be annoying to have to share this between all // projects, hence this default implementation here. if(cardName == L"S3 SuperSavage/IXC 1014") ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE); } } // take care of mipmaps. if they are called for by , either // arrange for OpenGL to create them, or see to it that the Tex object // contains them (if need be, creating them in software). // sets *plevels_to_skip to influence upload behavior (depending on // whether mipmaps are needed and the quality settings). // returns 0 to indicate success; otherwise, caller must disable // mipmapping by switching filter to e.g. GL_LINEAR. static Status get_mipmaps(Tex* t, GLint filter, int q_flags, int* plevels_to_skip) { // decisions: // .. does filter call for uploading mipmaps? const bool need_mipmaps = are_mipmaps_needed(t->m_Width, t->m_Height, filter); // .. does the image data include mipmaps? (stored as separate // images after the regular texels) const bool includes_mipmaps = (t->m_Flags & TEX_MIPMAPS) != 0; // .. is this texture in S3TC format? (more generally, "compressed") const bool is_s3tc = (t->m_Flags & TEX_DXT) != 0; *plevels_to_skip = TEX_BASE_LEVEL_ONLY; if(!need_mipmaps) return INFO::OK; // image already contains pregenerated mipmaps; we need do nothing. // this is the nicest case, because they are fastest to load // (no extra processing needed) and typically filtered better than // if automatically generated. if(includes_mipmaps) *plevels_to_skip = 0; // t contains mipmaps // OpenGL supports automatic generation; we need only // activate that and upload the base image. #if !CONFIG2_GLES else if(have_auto_mipmap_gen) { // note: we assume GL_GENERATE_MIPMAP and GL_GENERATE_MIPMAP_SGIS // have the same values - it's heavily implied by the spec // governing 'promoted' ARB extensions and just plain makes sense. glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); } #endif // image is S3TC-compressed and the previous 2 alternatives weren't // available; we're going to cheat and just disable mipmapping. // rationale: having tex_transform add mipmaps would be slow (since // all<->all transforms aren't implemented, it'd have to decompress // from S3TC first), and DDS images ought to include mipmaps! else if(is_s3tc) return ERR::FAIL; // NOWARN // image is uncompressed and we're on an old OpenGL implementation; // we will generate mipmaps in software. else { RETURN_STATUS_IF_ERR(t->transform_to(t->m_Flags|TEX_MIPMAPS)); *plevels_to_skip = 0; // t contains mipmaps } // t contains mipmaps; we can apply our resolution reduction trick: if(*plevels_to_skip == 0) { // if OpenGL's texture dimension limit is too small, use the // higher mipmap levels. NB: the minimum guaranteed size is // far too low, and menu background textures may be large. GLint w = (GLint)t->m_Width, h = (GLint)t->m_Height; while(w > ogl_max_tex_size || h > ogl_max_tex_size) { (*plevels_to_skip)++; w /= 2; h /= 2; // doesn't matter if either dimension drops to 0 } // this saves texture memory by skipping some of the lower // (high-resolution) mip levels. // // note: we don't just use GL_TEXTURE_BASE_LEVEL because it would // require uploading unused levels, which is wasteful. // .. can be expanded to reduce to 1/4, 1/8 by encoding factor in q_flags. if(q_flags & OGL_TEX_HALF_RES) (*plevels_to_skip)++; } return INFO::OK; } // tex_util_foreach_mipmap callbacks: upload the given level to OpenGL. struct UploadParams { GLenum fmt; GLint int_fmt; u32* uploaded_size; }; static void upload_level(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_data_size, void* RESTRICT cbData) { const UploadParams* up = (const UploadParams*)cbData; glTexImage2D(GL_TEXTURE_2D, (GLint)level, up->int_fmt, (GLsizei)level_w, (GLsizei)level_h, 0, up->fmt, GL_UNSIGNED_BYTE, level_data); *up->uploaded_size += (u32)level_data_size; } static void upload_compressed_level(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_data_size, void* RESTRICT cbData) { const UploadParams* up = (const UploadParams*)cbData; pglCompressedTexImage2DARB(GL_TEXTURE_2D, (GLint)level, up->fmt, (GLsizei)level_w, (GLsizei)level_h, 0, (GLsizei)level_data_size, level_data); *up->uploaded_size += (u32)level_data_size; } // upload the texture in the specified (internal) format. // split out of ogl_tex_upload because it was too big. // // pre: is valid for OpenGL use; texture is bound. static void upload_impl(Tex* t, GLenum fmt, GLint int_fmt, int levels_to_skip, u32* uploaded_size) { const GLsizei w = (GLsizei)t->m_Width; const GLsizei h = (GLsizei)t->m_Height; const size_t bpp = t->m_Bpp; const u8* data = (const u8*)t->get_data(); const UploadParams up = { fmt, int_fmt, uploaded_size }; if(t->m_Flags & TEX_DXT) tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 4, upload_compressed_level, (void*)&up); else tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 1, upload_level, (void*)&up); } // upload the texture to OpenGL. // if not 0, parameters override the following: // fmt_ovr : OpenGL format (e.g. GL_RGB) decided from bpp / Tex flags; // q_flags_ovr : global default "quality vs. performance" flags; // int_fmt_ovr : internal format (e.g. GL_RGB8) decided from fmt / q_flags. // // side effects: // - enables texturing on TMU 0 and binds the texture to it; // - frees the texel data! see ogl_tex_get_data. Status ogl_tex_upload(const Handle ht, GLenum fmt_ovr, int q_flags_ovr, GLint int_fmt_ovr) { ONCE(detect_gl_upload_caps()); H_DEREF(ht, OglTex, ot); Tex* t = &ot->t; ENSURE(q_flags_valid(q_flags_ovr)); // we don't bother verifying *fmt_ovr - there are too many values // upload already happened; no work to do. // (this also happens if a cached texture is "loaded") if(ot->flags & OT_IS_UPLOADED) return INFO::OK; if(ot->flags & OT_TEX_VALID) { // decompress S3TC if that's not supported by OpenGL. if((t->m_Flags & TEX_DXT) && !have_s3tc) (void)t->transform_to(t->m_Flags & ~TEX_DXT); // determine fmt and int_fmt, allowing for user override. ot->fmt = choose_fmt(t->m_Bpp, t->m_Flags); if(fmt_ovr) ot->fmt = fmt_ovr; if(q_flags_ovr) ot->q_flags = q_flags_ovr; ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags); if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr; ot->uploaded_size = 0; // now actually send to OpenGL: ogl_WarnIfError(); { // (note: we know ht is valid due to H_DEREF, but ogl_tex_bind can // fail in debug builds if OglTex.id isn't a valid texture name) RETURN_STATUS_IF_ERR(ogl_tex_bind(ht, ot->tmu)); int levels_to_skip; if(get_mipmaps(t, ot->state.filter, ot->q_flags, &levels_to_skip) < 0) // error => disable mipmapping ot->state.filter = GL_LINEAR; // (note: if first time, applies our defaults/previous overrides; // otherwise, replays all state changes) state_latch(&ot->state); upload_impl(t, ot->fmt, ot->int_fmt, levels_to_skip, &ot->uploaded_size); } ogl_WarnIfError(); ot->flags |= OT_IS_UPLOADED; // see rationale for at declaration of OglTex. intptr_t refs = h_get_refcnt(ht); if(refs == 1) { // note: we verify above that OT_TEX_VALID is set ot->flags &= ~OT_TEX_VALID; } } ot->flags |= OT_NEED_AUTO_UPLOAD; return INFO::OK; } //---------------------------------------------------------------------------- // getters //---------------------------------------------------------------------------- // retrieve texture dimensions and bits per pixel. // all params are optional and filled if non-NULL. Status ogl_tex_get_size(Handle ht, size_t* w, size_t* h, size_t* bpp) { H_DEREF(ht, OglTex, ot); if(w) *w = ot->t.m_Width; if(h) *h = ot->t.m_Height; if(bpp) *bpp = ot->t.m_Bpp; return INFO::OK; } // retrieve TexFlags and the corresponding OpenGL format. // the latter is determined during ogl_tex_upload and is 0 before that. // all params are optional and filled if non-NULL. Status ogl_tex_get_format(Handle ht, size_t* flags, GLenum* fmt) { H_DEREF(ht, OglTex, ot); if(flags) *flags = ot->t.m_Flags; if(fmt) { ENSURE(ot->flags & OT_IS_UPLOADED); *fmt = ot->fmt; } return INFO::OK; } // retrieve pointer to texel data. // // note: this memory is freed after a successful ogl_tex_upload for // this texture. after that, the pointer we retrieve is NULL but // the function doesn't fail (negative return value) by design. // if you still need to get at the data, add a reference before // uploading it or read directly from OpenGL (discouraged). Status ogl_tex_get_data(Handle ht, u8** p) { H_DEREF(ht, OglTex, ot); *p = ot->t.get_data(); return INFO::OK; } Status ogl_tex_get_uploaded_size(Handle ht, size_t* size) { H_DEREF(ht, OglTex, ot); *size = ot->uploaded_size; return INFO::OK; } // retrieve color of 1x1 mipmap level extern Status ogl_tex_get_average_color(Handle ht, u32* p) { H_DEREF(ht, OglTex, ot); warn_if_uploaded(ht, ot); *p = ot->t.get_average_color(); return INFO::OK; } //---------------------------------------------------------------------------- // misc API //---------------------------------------------------------------------------- // bind the texture to the specified unit [number] in preparation for // using it in rendering. if is 0, texturing is disabled instead. // // side effects: // - changes the active texture unit; // - (if return value is 0:) texturing was enabled/disabled on that unit. // // notes: // - assumes multitexturing is available. // - not necessary before calling ogl_tex_upload! // - on error, the unit's texture state is unchanged; see implementation. Status ogl_tex_bind(Handle ht, size_t unit) { // note: there are many call sites of glActiveTextureARB, so caching // those and ignoring redundant sets isn't feasible. pglActiveTextureARB((int)(GL_TEXTURE0+unit)); // special case: disable texturing if(ht == 0) { #if !CONFIG2_GLES glDisable(GL_TEXTURE_2D); #endif return INFO::OK; } // if this fails, the texture unit's state remains unchanged. // we don't bother catching that and disabling texturing because a // debug warning is raised anyway, and it's quite unlikely. H_DEREF(ht, OglTex, ot); ot->tmu = (u8)unit; // if 0, there's a problem in the OglTex reload/dtor logic. // binding it results in whiteness, which can have many causes; // we therefore complain so this one can be ruled out. ENSURE(ot->id != 0); #if !CONFIG2_GLES glEnable(GL_TEXTURE_2D); #endif glBindTexture(GL_TEXTURE_2D, ot->id); return INFO::OK; } Status ogl_tex_get_texture_id(Handle ht, GLuint* id) { *id = 0; H_DEREF(ht, OglTex, ot); *id = ot->id; return INFO::OK; } // apply the specified transforms (as in tex_transform) to the image. // must be called before uploading (raises a warning if called afterwards). Status ogl_tex_transform(Handle ht, size_t transforms) { H_DEREF(ht, OglTex, ot); Status ret = ot->t.transform(transforms); return ret; } // change the pixel format to that specified by . // (note: this is equivalent to ogl_tex_transform(ht, ht_flags^new_flags). Status ogl_tex_transform_to(Handle ht, size_t new_flags) { H_DEREF(ht, OglTex, ot); Status ret = ot->t.transform_to(new_flags); return ret; } // return whether native S3TC support is available bool ogl_tex_has_s3tc() { // ogl_tex_upload must be called before this ENSURE(have_s3tc != -1); return (have_s3tc != 0); } // return whether anisotropic filtering support is available bool ogl_tex_has_anisotropy() { // ogl_tex_upload must be called before this ENSURE(have_anistropy != -1); return (have_anistropy != 0); } Index: ps/trunk/source/lib/res/h_mgr.h =================================================================== --- ps/trunk/source/lib/res/h_mgr.h (revision 19898) +++ ps/trunk/source/lib/res/h_mgr.h (revision 19899) @@ -1,432 +1,432 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * handle manager for resources. */ /* [KEEP IN SYNC WITH WIKI] introduction ------------ a resource is an instance of a specific type of game data (e.g. texture), described by a control block (example fields: format, pointer to tex data). this module allocates storage for the control blocks, which are accessed via handle. it also provides support for transparently reloading resources from disk (allows in-game editing of data), and caches resource data. finally, it frees all resources at exit, preventing leaks. handles ------- handles are an indirection layer between client code and resources (represented by their control blocks, which contains/points to its data). they allow an important check not possible with a direct pointer: guaranteeing the handle references a given resource /instance/. problem: code C1 allocates a resource, and receives a pointer p to its control block. C1 passes p on to C2, and later frees it. now other code allocates a resource, and happens to reuse the free slot pointed to by p (also possible if simply allocating from the heap). when C2 accesses p, the pointer is valid, but we cannot tell that it is referring to a resource that had already been freed. big trouble. solution: each allocation receives a unique tag (a global counter that is large enough to never overflow). Handles include this tag, as well as a reference (array index) to the control block, which isn't directly accessible. when dereferencing the handle, we check if the handle's tag matches the copy stored in the control block. this protects against stale handle reuse, double-free, and accidentally referencing other resources. type: each handle has an associated type. these must be checked to prevent using textures as sounds, for example. with the manual vtbl scheme, this type is actually a pointer to the resource object's vtbl, and is set up via H_TYPE_DEFINE. this means that types are private to the module that declared the handle; knowledge of the type ensures the caller actually declared, and owns the resource. guide to defining and using resources ------------------------------------- 1) choose a name for the resource, used to represent all resources of this type. we will call ours "Res1"; all below occurrences of this must be replaced with the actual name (exact spelling). why? the vtbl builder defines its functions as e.g. Res1_reload; your actual definition must match. 2) declare its control block: struct Res1 { void* data; // data loaded from file size_t flags; // set when resource is created }; Note that all control blocks are stored in fixed-size slots (HDATA_USER_SIZE bytes), so squeezing the size of your data doesn't help unless yours is the largest. 3) build its vtbl: H_TYPE_DEFINE(Res1); this defines the symbol H_Res1, which is used whenever the handle manager needs its type. it is only accessible to this module (file scope). note that it is actually a pointer to the vtbl. this must come before uses of H_Res1, and after the CB definition; there are no restrictions WRT functions, because the macro forward-declares what it needs. 4) implement all 'virtual' functions from the resource interface. note that inheritance isn't really possible with this approach - all functions must be defined, even if not needed. -- init: one-time init of the control block. called from h_alloc. precondition: control block is initialized to 0. static void Type_init(Res1* r, va_list args) { r->flags = va_arg(args, int); } if the caller of h_alloc passed additional args, they are available in args. if init references more args than were passed, big trouble. however, this is a bug in your code, and cannot be triggered maliciously. only your code knows the resource type, and it is the only call site of h_alloc. there is no provision for indicating failure. if one-time init fails (rare, but one example might be failure to allocate memory that is for the lifetime of the resource, instead of in reload), it will have to set the control block state such that reload will fail. -- reload: does all initialization of the resource that requires its source file. called after init; also after dtor every time the file is reloaded. static Status Type_reload(Res1* r, const VfsPath& pathname, Handle); { // already loaded; done if(r->data) return 0; r->data = malloc(100); if(!r->data) WARN_RETURN(ERR::NO_MEM); // (read contents of into r->data) return 0; } reload must abort if the control block data indicates the resource has already been loaded! example: if texture's reload is called first, it loads itself from file (triggering file.reload); afterwards, file.reload will be called again. we can't avoid this, because the handle manager doesn't know anything about dependencies (here, texture -> file). return value: 0 if successful (includes 'already loaded'), negative error code otherwise. if this fails, the resource is freed (=> dtor is called!). note that any subsequent changes to the resource state must be stored in the control block and 'replayed' when reloading. example: when uploading a texture, store the upload parameters (filter, internal format); when reloading, upload again accordingly. -- dtor: frees all data allocated by init and reload. called after reload has indicated failure, before reloading a resource, after h_free, or at exit (if the resource is still extant). except when reloading, the control block will be zeroed afterwards. static void Type_dtor(Res1* r); { free(r->data); } again no provision for reporting errors - there's no one to act on it if called at exit. you can ENSURE or log the error, though. be careful to correctly handle the different cases in which this routine can be called! some flags should persist across reloads (e.g. choices made during resource init time that must remain valid), while everything else *should be zeroed manually* (to behave correctly when reloading). be advised that this interface may change; a "prepare for reload" method or "compact/free extraneous resources" may be added. -- validate: makes sure the resource control block is in a valid state. returns 0 if all is well, or a negative error code. called automatically when the Handle is dereferenced or freed. static Status Type_validate(const Res1* r); { const int permissible_flags = 0x01; if(debug_IsPointerBogus(r->data)) WARN_RETURN(ERR::_1); if(r->flags & ~permissible_flags) WARN_RETURN(ERR::_2); return 0; } 5) provide your layer on top of the handle manager: Handle res1_load(const VfsPath& pathname, int my_flags) { // passes my_flags to init return h_alloc(H_Res1, pathname, 0, my_flags); } Status res1_free(Handle& h) { // control block is automatically zeroed after this. return h_free(h, H_Res1); } (this layer allows a res_load interface on top of all the loaders, and is necessary because your module is the only one that knows H_Res1). 6) done. the resource will be freed at exit (if not done already). here's how to access the control block, given a : a) H_DEREF(h, Res1, r); creates a variable r of type Res1*, which points to the control block of the resource referenced by h. returns "invalid handle" (a negative error code) on failure. b) Res1* r = h_user_data(h, H_Res1); if(!r) ; // bail useful if H_DEREF's error return (of type signed integer) isn't acceptable. otherwise, prefer a) - this is pretty clunky, and we could switch H_DEREF to throwing an exception on error. */ #ifndef INCLUDED_H_MGR #define INCLUDED_H_MGR // do not include from public header files! // handle.h declares type Handle, and avoids making // everything dependent on this (rather often updated) header. #include // type init routines get va_list of args #ifndef INCLUDED_HANDLE #include "handle.h" #endif #include "lib/file/vfs/vfs.h" extern void h_mgr_init(); extern void h_mgr_shutdown(); // handle type (for 'type safety' - can't use a texture handle as a sound) // registering extension for each module is bad - some may use many // (e.g. texture - many formats). // handle manager shouldn't know about handle types /* ///xxx advantage of manual vtbl: no boilerplate init, h_alloc calls ctor directly, make sure it fits in the memory slot vtbl contains sizeof resource data, and name! but- has to handle variable params, a bit ugly */ // 'manual vtbl' type id // handles have a type, to prevent using e.g. texture handles as a sound. // // alternatives: // - enum of all handle types (smaller, have to pass all methods to h_alloc) // - class (difficult to compare type, handle manager needs to know of all users) // // checked in h_alloc: // - user_size must fit in what the handle manager provides // - name must not be 0 // // init: user data is initially zeroed // dtor: user data is zeroed afterwards // reload: if this resource type is opened by another resource's reload, // our reload routine MUST check if already opened! This is relevant when // a file is reloaded: if e.g. a sound object opens a file, the handle // manager calls the reload routines for the 2 handles in unspecified order. // ensuring the order would require a tag field that can't overflow - // not really guaranteed with 32-bit handles. it'd also be more work // to sort the handles by creation time, or account for several layers of // dependencies. struct H_VTbl { void (*init)(void* user, va_list); Status (*reload)(void* user, const PIVFS& vfs, const VfsPath& pathname, Handle); void (*dtor)(void* user); Status (*validate)(const void* user); Status (*to_string)(const void* user, wchar_t* buf); size_t user_size; const wchar_t* name; }; typedef H_VTbl* H_Type; #define H_TYPE_DEFINE(type)\ /* forward decls */\ static void type##_init(type*, va_list);\ static Status type##_reload(type*, const PIVFS&, const VfsPath&, Handle);\ static void type##_dtor(type*);\ static Status type##_validate(const type*);\ static Status type##_to_string(const type*, wchar_t* buf);\ static H_VTbl V_##type =\ {\ (void (*)(void*, va_list))type##_init,\ (Status (*)(void*, const PIVFS&, const VfsPath&, Handle))type##_reload,\ (void (*)(void*))type##_dtor,\ (Status (*)(const void*))type##_validate,\ (Status (*)(const void*, wchar_t*))type##_to_string,\ sizeof(type), /* control block size */\ WIDEN(#type) /* name */\ };\ static H_Type H_##type = &V_##type // note: we cast to void* pointers so the functions can be declared to // take the control block pointers, instead of requiring a cast in each. // the forward decls ensure the function signatures are correct. // convenience macro for h_user_data: // casts its return value to the control block type. // use if H_DEREF's returning a negative error code isn't acceptable. #define H_USER_DATA(h, type) (type*)h_user_data(h, H_##type) // even more convenient wrapper for h_user_data: // declares a pointer (), assigns it H_USER_DATA, and has // the user's function return a negative error code on failure. // // note: don't use STMT - var decl must be visible to "caller" #define H_DEREF(h, type, var)\ /* h already indicates an error - return immediately to pass back*/\ /* that specific error, rather than only ERR::INVALID_HANDLE*/\ if(h < 0)\ WARN_RETURN((Status)h);\ type* const var = H_USER_DATA(h, type);\ if(!var)\ WARN_RETURN(ERR::INVALID_HANDLE); // all functions check the passed tag (part of the handle) and type against // the internal values. if they differ, an error is returned. // h_alloc flags enum { // alias for RES_TEMP scope. the handle will not be kept open. RES_NO_CACHE = 0x01, // not cached, and will never reuse a previous instance RES_UNIQUE = RES_NO_CACHE|0x10, // object is requesting it never be reloaded (e.g. because it's not // backed by a file) RES_DISALLOW_RELOAD = 0x20 }; const size_t H_STRING_LEN = 256; // allocate a new handle. // if key is 0, or a (key, type) handle doesn't exist, // some free entry is used. // otherwise, a handle to the existing object is returned, // and HDATA.size != 0. //// user_size is checked to make sure the user data fits in the handle data space. // dtor is associated with type and called when the object is freed. // handle data is initialized to 0; optionally, a pointer to it is returned. extern Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0, ...); extern Status h_free(Handle& h, H_Type type); // Forcibly frees all handles of a specified type. void h_mgr_free_type(const H_Type type); // find and return a handle by key (typically filename hash) // currently O(log n). // // HACK: currently can't find RES_UNIQUE handles, because there // may be multiple instances of them, breaking the lookup data structure. extern Handle h_find(H_Type type, uintptr_t key); // returns a void* pointer to the control block of the resource , // or 0 on error (i.e. h is invalid or of the wrong type). // prefer using H_DEREF or H_USER_DATA. extern void* h_user_data(Handle h, H_Type type); extern VfsPath h_filename(Handle h); extern Status h_reload(const PIVFS& vfs, const VfsPath& pathname); // force the resource to be freed immediately, even if cached. // tag is not checked - this allows the first Handle returned // (whose tag will change after being 'freed', but remaining in memory) // to later close the object. // this is used when reinitializing the sound engine - // at that point, all (cached) OpenAL resources must be freed. extern Status h_force_free(Handle h, H_Type type); // increment Handle 's reference count. // only meant to be used for objects that free a Handle in their dtor, // so that they are copy-equivalent and can be stored in a STL container. // do not use this to implement refcounting on top of the Handle scheme, // e.g. loading a Handle once and then passing it around. instead, have each // user load the resource; refcounting is done under the hood. extern void h_add_ref(Handle h); // retrieve the internal reference count or a negative error code. // background: since h_alloc has no way of indicating whether it // allocated a new handle or reused an existing one, counting references // within resource control blocks is impossible. since that is sometimes // necessary (always wrapping objects in Handles is excessive), we // provide access to the internal reference count. extern intptr_t h_get_refcnt(Handle h); #endif // #ifndef INCLUDED_H_MGR Index: ps/trunk/source/lib/self_test.h =================================================================== --- ps/trunk/source/lib/self_test.h (revision 19898) +++ ps/trunk/source/lib/self_test.h (revision 19899) @@ -1,139 +1,139 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_SELF_TEST #define INCLUDED_SELF_TEST // for convenience, to avoid having to include all of these manually #include "lib/status.h" #include "lib/os_path.h" #include "lib/posix/posix.h" #define CXXTEST_HAVE_EH #define CXXTEST_HAVE_STD // If HAVE_STD wasn't defined at the point the ValueTraits header was included // this header won't have been included and the default traits will be used for // all variables... So fix that now ;-) #include #include // TODO: The CStr and Vector code should not be in lib/ // Perform nice printing of CStr, based on std::string #include "ps/CStr.h" namespace CxxTest { CXXTEST_TEMPLATE_INSTANTIATION class ValueTraits : public ValueTraits { public: ValueTraits( const CStr8 &s ) : ValueTraits( s.c_str() ) {} }; CXXTEST_COPY_CONST_TRAITS( CStr8 ); CXXTEST_TEMPLATE_INSTANTIATION class ValueTraits : public ValueTraits { public: ValueTraits( const CStrW &s ) : ValueTraits( s.c_str() ) {} }; CXXTEST_COPY_CONST_TRAITS( CStrW ); } // Perform nice printing of vectors #include "maths/FixedVector3D.h" #include "maths/Vector3D.h" namespace CxxTest { template<> class ValueTraits { CFixedVector3D v; std::string str; public: ValueTraits(const CFixedVector3D& v) : v(v) { std::stringstream s; s << "[" << v.X.ToDouble() << ", " << v.Y.ToDouble() << ", " << v.Z.ToDouble() << "]"; str = s.str(); } const char* asString() const { return str.c_str(); } }; template<> class ValueTraits { CVector3D v; std::string str; public: ValueTraits(const CVector3D& v) : v(v) { std::stringstream s; s << "[" << v.X << ", " << v.Y << ", " << v.Z << "]"; str = s.str(); } const char* asString() const { return str.c_str(); } }; } #define TS_ASSERT_OK(expr) TS_ASSERT_EQUALS((expr), INFO::OK) #define TSM_ASSERT_OK(m, expr) TSM_ASSERT_EQUALS(m, (expr), INFO::OK) #define TS_ASSERT_STR_EQUALS(str1, str2) TS_ASSERT_EQUALS(std::string(str1), std::string(str2)) #define TSM_ASSERT_STR_EQUALS(m, str1, str2) TSM_ASSERT_EQUALS(m, std::string(str1), std::string(str2)) #define TS_ASSERT_WSTR_EQUALS(str1, str2) TS_ASSERT_EQUALS(std::wstring(str1), std::wstring(str2)) #define TSM_ASSERT_WSTR_EQUALS(m, str1, str2) TSM_ASSERT_EQUALS(m, std::wstring(str1), std::wstring(str2)) #define TS_ASSERT_PATH_EQUALS(path1, path2) TS_ASSERT_EQUALS((path1).string(), (path2).string()) #define TSM_ASSERT_PATH_EQUALS(m, path1, path2) TSM_ASSERT_EQUALS(m, (path1).string(), (path2).string()) bool ts_str_contains(const std::string& str1, const std::string& str2); // defined in test_setup.cpp bool ts_str_contains(const std::wstring& str1, const std::wstring& str2); // defined in test_setup.cpp #define TS_ASSERT_STR_CONTAINS(str1, str2) TSM_ASSERT(str1, ts_str_contains(str1, str2)) #define TS_ASSERT_STR_NOT_CONTAINS(str1, str2) TSM_ASSERT(str1, !ts_str_contains(str1, str2)) #define TS_ASSERT_WSTR_CONTAINS(str1, str2) TSM_ASSERT(str1, ts_str_contains(str1, str2)) #define TS_ASSERT_WSTR_NOT_CONTAINS(str1, str2) TSM_ASSERT(str1, !ts_str_contains(str1, str2)) template std::vector ts_make_vector(T* start, size_t size_bytes) { return std::vector(start, start+(size_bytes/sizeof(T))); } #define TS_ASSERT_VECTOR_EQUALS_ARRAY(vec1, array) TS_ASSERT_EQUALS(vec1, ts_make_vector((array), sizeof(array))) #define TS_ASSERT_VECTOR_CONTAINS(vec1, element) TS_ASSERT(std::find((vec1).begin(), (vec1).end(), element) != (vec1).end()); class ScriptInterface; // Script-based testing setup (defined in test_setup.cpp). Defines TS_* functions. void ScriptTestSetup(ScriptInterface&); // Default game data directory // (TODO: game-specific functions like this probably shouldn't be inside lib/, but it's useful // here since lots of tests use it) OsPath DataDir(); // defined in test_setup.cpp #endif // #ifndef INCLUDED_SELF_TEST Index: ps/trunk/source/lib/status.h =================================================================== --- ps/trunk/source/lib/status.h (revision 19898) +++ ps/trunk/source/lib/status.h (revision 19899) @@ -1,471 +1,471 @@ -/* Copyright (c) 2011 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 * "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. */ /* * error handling system: defines status codes, translates them to/from * other schemes (e.g. errno), associates them with descriptive text, * simplifies propagating errors / checking if functions failed. */ /** Error handling system Why Error Codes? ---------------- To convey information about what failed, the alternatives are unique integral codes and direct pointers to descriptive text. Both occupy the same amount of space, but codes are easier to internationalize. Method of Propagating Errors ---------------------------- When a low-level function has failed, this must be conveyed to the higher-level application logic across several functions on the call stack. There are two alternatives: 1) check at each call site whether a function failed; if so, return to the caller. 2) throw an exception. We will discuss the advantages and disadvantages of exceptions, which are the opposites of call site checking. - performance: they shouldn't be used in time-critical code. - predictability: exceptions can come up almost anywhere, so it is hard to say what execution path will be taken. - interoperability: not compatible with other languages. + readability: cleans up code by separating application logic and error handling. however, this is also a disadvantage because it may be difficult to see at a glance if a piece of code does error checking at all. + visibility: errors are more likely to be seen than relying on callers to check return codes; less reliant on discipline. Both have their place. Our recommendation is to throw error code exceptions when checking call sites and propagating errors becomes tedious. However, inter-module boundaries should always return error codes for interoperability with other languages. Simplifying Call-Site Checking ------------------------------ As mentioned above, this approach requires discipline. We provide "enforcer" macros to simplify this task by propagating errors to the calling function. Consider the following example: Status status = doWork(); if(status != INFO::OK) return status; This can be replaced by: RETURN_STATUS_IF_ERR(doWork()); This provides a visible sign that the code handles errors but reduces clutter. When to warn the user? ---------------------- When a function fails, there are 2 places we can raise a warning: as soon as the error condition is known, or higher on the call stack. We prefer the former because it is easier to ensure that all possible return paths have been covered: search for all "return ERR::*" or "return StatusFrom*" that are not followed by a "// NOWARN" comment. The latter approach also risks multiple warnings along the call stack for the same error. Note the special case of "validator" functions that e.g. verify the state of an object: we now discuss pros/cons of just returning errors without warning, and having their callers take care of that. + they typically have many return paths (-> increased code size) - this is balanced by validators that have many call sites. - we want all return statements wrapped for consistency and easily checking if any were forgotten - adding // NOWARN to each validator return statement would be tedious. - there is no advantage to checking at the call site; the call stack indicates which caller of the validator failed anyway. Validator functions should therefore also use WARN_RETURN. Numbering Scheme ---------------- Each module header defines its own error codes to avoid a full rebuild whenever a new code is added. Error codes start at -100000 (warnings are positive, but the corresponding negative value should not be used to avoid confusion). This scheme avoids collisions with all other known error codes. Each header gets 100 possible values; the tens value may be used to denote groups within that header. The subsystem is denoted by the ten-thousands digit: 0 general 1 file 2 res (resource management) 3 sysdep (system-dependent) 4 win (Windows-specific) To summarize: +/-1SHHCC (S=subsystem, HH=header, CC=code number) 10 general 00CC misc 03CC path 04CC debug 05CC debug_stl 06CC secure_crt 07CC wchar 11 file 01CC vfs 03CC file 04CC archive 12 res 00CC h_mgr 01CC tex 13 sysdep 00CC cpu 01CC os_cpu 14 win 00CC whrt **/ #ifndef INCLUDED_STATUS #define INCLUDED_STATUS #include "lib/lib_api.h" // an integral type allows defining error codes in separate headers, // but is not as type-safe as an enum. use Lint's 'strong type' checking // to catch errors such as Status Func() { return 1; }. // this must be i64 because some functions may multiplex Status with // file offsets/sizes in their return value. typedef i64 Status; // associates a status code with a description [and errno_equivalent]. struct StatusDefinition // POD { Status status; // typically a string literal; must remain valid until end of program. const wchar_t* description; // omit initializer (or initialize to 0) if there is no errno equivalent. int errno_equivalent; }; // retrieving description and errno_equivalent requires going through all // StatusDefinition instances. we avoid dynamic memory allocation (which // is problematic because status codes may be needed before _cinit) by // organizing them into a linked list, with nodes residing in static storage. // since modules may introduce many status codes, they are stored in an // array, aka "bucket", which includes a link to the next bucket. // initialized via STATUS_ADD_DEFINITIONS; opaque. struct StatusDefinitionBucket // POD { const StatusDefinition* definitions; size_t numDefinitions; StatusDefinitionBucket* next; }; /** * (called via STATUS_ADD_DEFINITIONS) * * @param bucket is being added; its definitions and numDefinitions must * already be initialized. * @return previous bucket in list, suitable for initializing bucket->next. * * (this function must be callable as a static initializer; initializing * next avoids the need for a separate dummy variable) **/ LIB_API StatusDefinitionBucket* StatusAddDefinitions(StatusDefinitionBucket* bucket); /** * add a module's array of StatusDefinition to the list. * typically invoked at file scope. * @param definitions name (identifier) of the array **/ #define STATUS_ADD_DEFINITIONS(definitions) static StatusDefinitionBucket definitions##_bucket = { definitions, ARRAY_SIZE(definitions), StatusAddDefinitions(&definitions##_bucket) } /** * generate textual description of a Status. * * @param buf destination buffer (allows generating strings with * the code's numerical value if no definition is found) * @param max_chars size of buffer [characters] * @return buf (allows using this function in expressions) **/ LIB_API wchar_t* StatusDescription(Status status, wchar_t* buf, size_t max_chars); /** * @return the errno equivalent of a Status. * * used in wposix - underlying functions return Status but must be * translated to errno at e.g. the mmap interface level. higher-level code * that calls mmap will in turn convert back to Status. **/ extern int ErrnoFromStatus(Status status); /** * @return Status equivalent of errno, or ERR::FAIL if there's no equivalent. * * NB: reset errno to 0 before calling POSIX functions to avoid confusion * with previous errors. **/ extern Status StatusFromErrno(); // note: other conversion routines (e.g. to/from Win32) are implemented in // the corresponding modules to keep this header portable. //----------------------------------------------------------------------------- // propagation macros // warn and return a status. use when an error is first detected to // begin propagating it to callers. #define WARN_RETURN(status)\ do\ {\ DEBUG_WARN_ERR(status);\ return status;\ }\ while(0) // warn if expression is negative, i.e. an error. // (this macro is more convenient than ENSURE) #define WARN_IF_ERR(expression)\ do\ {\ const Status status_ = (expression);\ if(status_ < 0)\ DEBUG_WARN_ERR(status_);\ }\ while(0) // return expression if it is negative, i.e. pass on errors to // the caller. use when failures are common/expected. #define RETURN_STATUS_IF_ERR(expression)\ do\ {\ const Status status_ = (expression);\ if(status_ < 0)\ return status_;\ }\ while(0) // warn and return expression if it is negative. // use if a function doesn't raise warnings when it returns errors. #define WARN_RETURN_STATUS_IF_ERR(expression)\ do\ {\ const Status status_ = (expression);\ if(status_ < 0)\ {\ DEBUG_WARN_ERR(status_);\ return status_;\ }\ }\ while(0) // warn and throw a status. use when an error is first detected to // begin propagating it to callers. #define WARN_THROW(status)\ do\ {\ DEBUG_WARN_ERR(status);\ throw status;\ }\ while(0) // throw expression if it is negative. use to propagate // expected errors from constructors. #define THROW_STATUS_IF_ERR(expression)\ do\ {\ const Status status_ = (expression);\ if(status_ < 0)\ throw status_;\ }\ while(0) // warn and throw expression if it is negative. use to propagate // errors from constructors. #define WARN_THROW_STATUS_IF_ERR(expression)\ do\ {\ const Status status_ = (expression);\ if(status_ < 0)\ {\ DEBUG_WARN_ERR(status_);\ throw status_;\ }\ }\ while(0) // if expression (typically the invocation of a callback) evaluates to: // - INFO::OK, do nothing; // - INFO::ALL_COMPLETE, return INFO::OK; // - anything else, return that. #define RETURN_STATUS_FROM_CALLBACK(expression)\ do\ {\ const Status status_ = (expression);\ if(status_ == INFO::ALL_COMPLETE)\ return INFO::OK;\ else if(status_ != INFO::OK)\ return status_;\ }\ while(0) // return 0 if expression is negative. use in functions that return pointers. #define RETURN_0_IF_ERR(expression)\ do\ {\ const Status status_ = (expression);\ if(status_ < 0)\ return 0;\ }\ while(0) // warn if expression is false, i.e. zero. #define WARN_IF_FALSE(expression)\ do\ {\ if(!(expression))\ debug_warn(L"FYI: WARN_IF_FALSE reports that a function failed. Feel free to ignore or suppress this warning.");\ }\ while(0) // warn and return 0 if expression is false, i.e. zero. #define WARN_RETURN_0_IF_FALSE(expression)\ do\ {\ if(!(expression))\ {\ debug_warn(L"FYI: WARN_RETURN_0_IF_FALSE reports that a function failed. Feel free to ignore or suppress this warning.");\ return 0;\ }\ }\ while(0) //----------------------------------------------------------------------------- // shared status code definitions namespace INFO { const Status OK = 0; // note: these values are > 100 to allow multiplexing them with // coroutine return values, which return completion percentage. // notify caller that nothing was done. const Status SKIPPED = +100001; // function is incapable of doing the requested task with the given inputs. // this implies SKIPPED, but also conveys a bit more information. const Status CANNOT_HANDLE = +100002; // function is meant to be called repeatedly, and now indicates that // all jobs are complete. const Status ALL_COMPLETE = +100003; } // namespace INFO namespace ERR { const Status FAIL = -1; // unknown failure // general const Status LOGIC = -100010; const Status EXCEPTION = -100011; const Status TIMED_OUT = -100012; const Status REENTERED = -100013; const Status CORRUPTED = -100014; const Status ABORTED = -100015; // invalid values (usually function arguments) const Status INVALID_ALIGNMENT = -100020; const Status INVALID_OFFSET = -100021; const Status INVALID_HANDLE = -100022; const Status INVALID_POINTER = -100023; const Status INVALID_SIZE = -100024; const Status INVALID_FLAG = -100025; const Status INVALID_PARAM = -100026; const Status INVALID_VERSION = -100027; // system limitations const Status AGAIN = -100030; const Status LIMIT = -100031; const Status NOT_SUPPORTED = -100032; const Status NO_MEM = -100033; // these are for cases where we just want a distinct value to display and // a symbolic name + string would be overkill (e.g. the various // test cases in a validate() call). they are shared between multiple // functions; when something fails, the stack trace will show in which // one it was => these errors are unambiguous. // there are 3 tiers - 1..9 are used in most functions, 11..19 are // used in a function that calls another validator and 21..29 are // for for functions that call 2 other validators (this avoids // ambiguity as to which error actually happened where) const Status _1 = -100101; const Status _2 = -100102; const Status _3 = -100103; const Status _4 = -100104; const Status _5 = -100105; const Status _6 = -100106; const Status _7 = -100107; const Status _8 = -100108; const Status _9 = -100109; const Status _11 = -100111; const Status _12 = -100112; const Status _13 = -100113; const Status _14 = -100114; const Status _15 = -100115; const Status _16 = -100116; const Status _17 = -100117; const Status _18 = -100118; const Status _19 = -100119; const Status _21 = -100121; const Status _22 = -100122; const Status _23 = -100123; const Status _24 = -100124; const Status _25 = -100125; const Status _26 = -100126; const Status _27 = -100127; const Status _28 = -100128; const Status _29 = -100129; } // namespace ERR #endif // #ifndef INCLUDED_STATUS Index: ps/trunk/source/lib/sysdep/acpi.h =================================================================== --- ps/trunk/source/lib/sysdep/acpi.h (revision 19898) +++ ps/trunk/source/lib/sysdep/acpi.h (revision 19899) @@ -1,111 +1,111 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * minimal subset of ACPI */ #ifndef INCLUDED_ACPI #define INCLUDED_ACPI #pragma pack(push, 1) // common header for all ACPI tables struct AcpiTable { char signature[4]; u32 size; // table size [bytes], including header u8 revision; u8 checksum; // to make sum of entire table == 0 char oemId[6]; char oemTableId[8]; u32 oemRevision; char creatorId[4]; u32 creatorRevision; }; enum AcpiAddressSpace { // (these are not generally powers-of-two - some values have been omitted.) ACPI_AS_MEMORY = 0, ACPI_AS_IO = 1, ACPI_AS_PCI_CONFIG = 2, ACPI_AS_SMBUS = 4 }; // address of a struct or register struct AcpiGenericAddress { u8 addressSpaceId; u8 registerBitWidth; u8 registerBitOffset; u8 accessSize; u64 address; }; struct FADT // signature is FACP! { AcpiTable header; u8 unused1[40]; u32 pmTimerPortAddress; u8 unused2[16]; u16 c2Latency; // [us] u16 c3Latency; // [us] u8 unused3[5]; u8 dutyWidth; u8 unused4[6]; u32 flags; // (ACPI4 defines additional fields after this) bool IsDutyCycleSupported() const { return dutyWidth != 0; } bool IsC2Supported() const { return c2Latency <= 100; // magic value specified by ACPI } bool IsC3Supported() const { return c3Latency <= 1000; // see above } }; #pragma pack(pop) /** * @param signature e.g. "RSDT" * @return pointer to internal storage (valid until acpi_Shutdown()) * * note: the first call may be slow, e.g. if a kernel-mode driver is * loaded. subsequent requests will be faster since tables are cached. **/ LIB_API const AcpiTable* acpi_GetTable(const char* signature); /** * invalidates all pointers returned by acpi_GetTable. **/ LIB_API void acpi_Shutdown(); #endif // #ifndef INCLUDED_ACPI Index: ps/trunk/source/lib/sysdep/arch/arm/arm.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/arm/arm.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/arm/arm.cpp (revision 19899) @@ -1,49 +1,49 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ /* * routines specific to ARM */ #include "precompiled.h" #include "lib/sysdep/cpu.h" intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { return __sync_fetch_and_add(location, increment); } bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } const char* cpu_IdentifierString() { return "unknown"; // TODO } Index: ps/trunk/source/lib/sysdep/arch/x86_x64/apic.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/apic.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/apic.h (revision 19899) @@ -1,49 +1,49 @@ -/* Copyright (c) 2011 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 * "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. */ #ifndef INCLUDED_X86_X64_APIC #define INCLUDED_X86_X64_APIC typedef u8 ApicId; // not necessarily contiguous values /** * @return APIC ID of the currently executing processor or zero if the * platform does not have an xAPIC (i.e. 7th generation x86 or below). * * rationale: the alternative of accessing the APIC mmio registers is not * feasible - mahaf_MapPhysicalMemory only works reliably on WinXP. we also * don't want to interfere with the OS's constant use of the APIC registers. **/ LIB_API ApicId GetApicId(); // if this returns false, apicId = contiguousId = processor. // otherwise, there are unspecified but bijective mappings between // apicId<->contiguousId and apicId<->processor. LIB_API bool AreApicIdsReliable(); LIB_API size_t ProcessorFromApicId(ApicId apicId); LIB_API size_t ContiguousIdFromApicId(ApicId apicId); LIB_API ApicId ApicIdFromProcessor(size_t contiguousId); LIB_API ApicId ApicIdFromContiguousId(size_t contiguousId); #endif // #ifndef INCLUDED_X86_X64_APIC Index: ps/trunk/source/lib/sysdep/arch/x86_x64/msr.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/msr.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/msr.h (revision 19899) @@ -1,69 +1,69 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * model-specific registers */ #ifndef INCLUDED_X86_X64_MSR #define INCLUDED_X86_X64_MSR namespace MSR { enum ModelSpecificRegisters { // architectural (will not change on future processors) IA32_MISC_ENABLE = 0x1A0, IA32_ENERGY_PERF_BIAS = 0x1B0, // requires HasEnergyPerfBias // PMU v1 IA32_PMC0 = 0x0C1, IA32_PERFEVTSEL0 = 0x186, // PMU v2 IA32_PERF_GLOBAL_STATUS = 0x38E, IA32_PERF_GLOBAL_CTRL = 0x38F, IA32_PERF_GLOBAL_OVF_CTRL = 0x390, // Nehalem and later PLATFORM_INFO = 0x0CE, // requires HasPlatformInfo // Nehalem, Westmere (requires HasUncore) UNCORE_PERF_GLOBAL_CTRL = 0x391, UNCORE_PERF_GLOBAL_STATUS = 0x392, UNCORE_PERF_GLOBAL_OVF_CTRL = 0x393, UNCORE_PMC0 = 0x3B0, UNCORE_PERFEVTSEL0 = 0x3C0 }; LIB_API bool IsAccessible(); LIB_API bool HasEnergyPerfBias(); LIB_API bool HasPlatformInfo(); LIB_API bool HasUncore(); LIB_API u64 Read(u64 reg); LIB_API void Write(u64 reg, u64 value); } // namespace MSR #endif // #ifndef INCLUDED_X86_X64_MSR Index: ps/trunk/source/lib/sysdep/arch/x86_x64/x86_x64.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/x86_x64.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/x86_x64.cpp (revision 19899) @@ -1,522 +1,522 @@ -/* Copyright (c) 2011 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 * "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. */ /* * CPU-specific routines common to 32 and 64-bit x86 */ #include "precompiled.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" #include #include #include #include #include #include "lib/posix/posix_pthread.h" #include "lib/bits.h" #include "lib/timer.h" #include "lib/module_init.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/os_cpu.h" #if MSC_VERSION # include // __rdtsc #endif namespace x86_x64 { #if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 150030729 // VC10+ and VC9 SP1: __cpuidex is already available #elif GCC_VERSION # if defined(__i386__) && defined(__PIC__) # define __cpuidex(regsArray, level, index)\ __asm__ __volatile__ ("pushl %%ebx\n"\ "cpuid\n"\ "mov %%ebx,%1\n"\ "popl %%ebx"\ : "=a" ((regsArray)[0]), "=r" ((regsArray)[1]), "=c" ((regsArray)[2]), "=d" ((regsArray)[3])\ : "0" (level), "2" (index)); # else # define __cpuidex(regsArray, level, index)\ __asm__ __volatile__ ("cpuid"\ : "=a" ((regsArray)[0]), "=b" ((regsArray)[1]), "=c" ((regsArray)[2]), "=d" ((regsArray)[3])\ : "0" (level), "2" (index)); # endif #else # error "compiler not supported" #endif // some of this module's functions are frequently called but require // non-trivial initialization, so caching is helpful. isInitialized // flags aren't thread-safe, so we use ModuleInit. calling it from // every function is a bit wasteful, but it is convenient to avoid // requiring users to pass around a global state object. // one big Init() would be prone to deadlock if its subroutines also // call a public function (that re-enters ModuleInit), so each // function gets its own initState. //----------------------------------------------------------------------------- // CPUID static void Invoke_cpuid(CpuidRegs* regs) { cassert(sizeof(regs->eax) == sizeof(int)); cassert(sizeof(*regs) == 4*sizeof(int)); __cpuidex((int*)regs, regs->eax, regs->ecx); } static u32 cpuid_maxFunction; static u32 cpuid_maxExtendedFunction; static Status InitCpuid() { CpuidRegs regs = { 0 }; regs.eax = 0; Invoke_cpuid(®s); cpuid_maxFunction = regs.eax; regs.eax = 0x80000000; Invoke_cpuid(®s); cpuid_maxExtendedFunction = regs.eax; return INFO::OK; } bool cpuid(CpuidRegs* regs) { static ModuleInitState initState; ModuleInit(&initState, InitCpuid); const u32 function = regs->eax; if(function > cpuid_maxExtendedFunction) return false; if(function < 0x80000000 && function > cpuid_maxFunction) return false; Invoke_cpuid(regs); return true; } //----------------------------------------------------------------------------- // capability bits // treated as 128 bit field; order: std ecx, std edx, ext ecx, ext edx // keep in sync with enum Cap! static u32 caps[4]; static ModuleInitState capsInitState; static Status InitCaps() { CpuidRegs regs = { 0 }; regs.eax = 1; if(cpuid(®s)) { caps[0] = regs.ecx; caps[1] = regs.edx; } regs.eax = 0x80000001; if(cpuid(®s)) { caps[2] = regs.ecx; caps[3] = regs.edx; } return INFO::OK; } bool Cap(Caps cap) { ModuleInit(&capsInitState, InitCaps); const size_t index = cap >> 5; const size_t bit = cap & 0x1F; if(index >= ARRAY_SIZE(caps)) { DEBUG_WARN_ERR(ERR::INVALID_PARAM); return false; } return IsBitSet(caps[index], bit); } void GetCapBits(u32* d0, u32* d1, u32* d2, u32* d3) { ModuleInit(&capsInitState, InitCaps); *d0 = caps[0]; *d1 = caps[1]; *d2 = caps[2]; *d3 = caps[3]; } //----------------------------------------------------------------------------- // vendor static Vendors vendor; static Status InitVendor() { CpuidRegs regs = { 0 }; regs.eax = 0; if(!cpuid(®s)) DEBUG_WARN_ERR(ERR::CPU_FEATURE_MISSING); // copy regs to string // note: 'strange' ebx,edx,ecx reg order is due to ModR/M encoding order. char vendorString[13]; memcpy(&vendorString[0], ®s.ebx, 4); memcpy(&vendorString[4], ®s.edx, 4); memcpy(&vendorString[8], ®s.ecx, 4); vendorString[12] = '\0'; // 0-terminate if(!strcmp(vendorString, "AuthenticAMD")) vendor = x86_x64::VENDOR_AMD; else if(!strcmp(vendorString, "GenuineIntel")) vendor = x86_x64::VENDOR_INTEL; else { DEBUG_WARN_ERR(ERR::CPU_UNKNOWN_VENDOR); vendor = x86_x64::VENDOR_UNKNOWN; } return INFO::OK; } Vendors Vendor() { static ModuleInitState initState; ModuleInit(&initState, InitVendor); return vendor; } //----------------------------------------------------------------------------- // signature static size_t model; static size_t family; static ModuleInitState signatureInitState; static Status InitSignature() { CpuidRegs regs = { 0 }; regs.eax = 1; if(!cpuid(®s)) DEBUG_WARN_ERR(ERR::CPU_FEATURE_MISSING); model = bits(regs.eax, 4, 7); family = bits(regs.eax, 8, 11); const size_t extendedModel = bits(regs.eax, 16, 19); const size_t extendedFamily = bits(regs.eax, 20, 27); if(family == 0xF) family += extendedFamily; if(family == 0xF || (Vendor() == x86_x64::VENDOR_INTEL && family == 6)) model += extendedModel << 4; return INFO::OK; } size_t Model() { ModuleInit(&signatureInitState, InitSignature); return model; } size_t Family() { ModuleInit(&signatureInitState, InitSignature); return family; } //----------------------------------------------------------------------------- // identifier string /// functor to remove substrings from the CPU identifier string class StringStripper { public: StringStripper(char* string, size_t max_chars) : m_string(string), m_max_chars(max_chars) { } // remove all instances of substring from m_string void operator()(const char* substring) { const size_t substring_length = strlen(substring); for(;;) { char* substring_pos = strstr(m_string, substring); if(!substring_pos) break; const size_t substring_ofs = substring_pos - m_string; const size_t num_chars = m_max_chars - substring_ofs - substring_length; memmove(substring_pos, substring_pos+substring_length, num_chars); } } private: char* m_string; size_t m_max_chars; }; // 3 calls x 4 registers x 4 bytes = 48 + 0-terminator static char identifierString[48+1]; static Status InitIdentifierString() { // get brand string (if available) char* pos = identifierString; bool gotBrandString = true; for(u32 function = 0x80000002; function <= 0x80000004; function++) { CpuidRegs regs = { 0 }; regs.eax = function; gotBrandString &= cpuid(®s); memcpy(pos, ®s, 16); pos += 16; } // fall back to manual detect of CPU type because either: // - CPU doesn't support brand string (we use a flag to indicate this // rather than comparing against a default value because it is safer); // - the brand string is useless, e.g. "Unknown". this happens on // some older boards whose BIOS reprograms the string for CPUs it // doesn't recognize. if(!gotBrandString || strncmp(identifierString, "Unknow", 6) == 0) { const size_t family = Family(); const size_t model = Model(); switch(Vendor()) { case x86_x64::VENDOR_AMD: // everything else is either too old, or should have a brand string. if(family == 6) { if(model == 3 || model == 7) strcpy_s(identifierString, ARRAY_SIZE(identifierString), "AMD Duron"); else if(model <= 5) strcpy_s(identifierString, ARRAY_SIZE(identifierString), "AMD Athlon"); else { if(Cap(x86_x64::CAP_AMD_MP)) strcpy_s(identifierString, ARRAY_SIZE(identifierString), "AMD Athlon MP"); else strcpy_s(identifierString, ARRAY_SIZE(identifierString), "AMD Athlon XP"); } } break; case x86_x64::VENDOR_INTEL: // everything else is either too old, or should have a brand string. if(family == 6) { if(model == 1) strcpy_s(identifierString, ARRAY_SIZE(identifierString), "Intel Pentium Pro"); else if(model == 3 || model == 5) strcpy_s(identifierString, ARRAY_SIZE(identifierString), "Intel Pentium II"); else if(model == 6) strcpy_s(identifierString, ARRAY_SIZE(identifierString), "Intel Celeron"); else strcpy_s(identifierString, ARRAY_SIZE(identifierString), "Intel Pentium III"); } break; default: strcpy_s(identifierString, ARRAY_SIZE(identifierString), "Unknown, non-Intel/AMD"); break; } } // identifierString already holds a valid brand string; pretty it up. else { const char* const undesiredStrings[] = { "(tm)", "(TM)", "(R)", "CPU ", " " }; std::for_each(undesiredStrings, undesiredStrings+ARRAY_SIZE(undesiredStrings), StringStripper(identifierString, strlen(identifierString)+1)); // note: Intel brand strings include a frequency, but we can't rely // on it because the CPU may be overclocked. we'll leave it in the // string to show measurement accuracy and if SpeedStep is active. } return INFO::OK; } static const char* IdentifierString() { static ModuleInitState initState; ModuleInit(&initState, InitIdentifierString); return identifierString; } //----------------------------------------------------------------------------- // miscellaneous stateless functions #if !MSC_VERSION // ensure not already defined in header u64 rdtsc() { #if GCC_VERSION // GCC supports "portable" assembly for both x86 and x64 volatile u32 lo, hi; __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return u64_from_u32(hi, lo); #endif } #endif void DebugBreak() { #if MSC_VERSION __debugbreak(); #elif GCC_VERSION // note: this probably isn't necessary, since unix_debug_break // (SIGTRAP) is most probably available if GCC_VERSION. // we include it for completeness, though. __asm__ __volatile__ ("int $3"); #endif } //----------------------------------------------------------------------------- // CPU frequency // set scheduling priority and restore when going out of scope. class ScopedSetPriority { public: ScopedSetPriority(int newPriority) { // get current scheduling policy and priority pthread_getschedparam(pthread_self(), &m_oldPolicy, &m_oldParam); // set new priority sched_param newParam = {0}; newParam.sched_priority = newPriority; pthread_setschedparam(pthread_self(), SCHED_FIFO, &newParam); } ~ScopedSetPriority() { // restore previous policy and priority. pthread_setschedparam(pthread_self(), m_oldPolicy, &m_oldParam); } private: int m_oldPolicy; sched_param m_oldParam; }; // note: this function uses timer.cpp!timer_Time, which is implemented via // whrt.cpp on Windows. double ClockFrequency() { // if the TSC isn't available, there's really no good way to count the // actual CPU clocks per known time interval, so bail. // note: loop iterations ("bogomips") are not a reliable measure due // to differing IPC and compiler optimizations. if(!Cap(x86_x64::CAP_TSC)) return -1.0; // impossible value // increase priority to reduce interference while measuring. const int priority = sched_get_priority_max(SCHED_FIFO)-1; ScopedSetPriority ssp(priority); // note: no need to "warm up" cpuid - it will already have been // called several times by the time this code is reached. // (background: it's used in rdtsc() to serialize instruction flow; // the first call is documented to be slower on Intel CPUs) size_t numSamples = 16; // if clock is low-res, do less samples so it doesn't take too long. // balance measuring time (~ 10 ms) and accuracy (< 0.1% error - // ok for using the TSC as a time reference) if(timer_Resolution() >= 1e-3) numSamples = 8; std::vector samples(numSamples); for(size_t i = 0; i < numSamples; i++) { double dt; i64 dc; // (i64 instead of u64 for faster conversion to double) // count # of clocks in max{1 tick, 1 ms}: // .. wait for start of tick. const double t0 = timer_Time(); u64 c1; double t1; do { // note: timer_Time effectively has a long delay (up to 5 us) // before returning the time. we call it before rdtsc to // minimize the delay between actually sampling time / TSC, // thus decreasing the chance for interference. // (if unavoidable background activity, e.g. interrupts, // delays the second reading, inaccuracy is introduced). t1 = timer_Time(); c1 = rdtsc(); } while(t1 == t0); // .. wait until start of next tick and at least 1 ms elapsed. do { const double t2 = timer_Time(); const u64 c2 = rdtsc(); dc = (i64)(c2 - c1); dt = t2 - t1; } while(dt < 1e-3); // .. freq = (delta_clocks) / (delta_seconds); // rdtsc/timer overhead is negligible. const double freq = dc / dt; samples[i] = freq; } std::sort(samples.begin(), samples.end()); // median filter (remove upper and lower 25% and average the rest). // note: don't just take the lowest value! it could conceivably be // too low, if background processing delays reading c1 (see above). double sum = 0.0; const size_t lo = numSamples/4, hi = 3*numSamples/4; for(size_t i = lo; i < hi; i++) sum += samples[i]; const double clockFrequency = sum / (hi-lo); return clockFrequency; } } // namespace x86_x64 const char* cpu_IdentifierString() { return x86_x64::IdentifierString(); } Index: ps/trunk/source/lib/rand.h =================================================================== --- ps/trunk/source/lib/rand.h (revision 19898) +++ ps/trunk/source/lib/rand.h (revision 19899) @@ -1,37 +1,37 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * pseudorandom number generator */ #ifndef INCLUDED_RAND #define INCLUDED_RAND /** * return random integer in [min, max). * avoids several common pitfalls; see discussion at * http://www.azillionmonkeys.com/qed/random.html **/ LIB_API size_t rand(size_t min_inclusive, size_t max_exclusive); #endif // #ifndef INCLUDED_RAND Index: ps/trunk/source/lib/res/graphics/cursor.h =================================================================== --- ps/trunk/source/lib/res/graphics/cursor.h (revision 19898) +++ ps/trunk/source/lib/res/graphics/cursor.h (revision 19899) @@ -1,57 +1,57 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * mouse cursors (either via OpenGL texture or hardware) */ #ifndef INCLUDED_GRAPHICS_CURSOR #define INCLUDED_GRAPHICS_CURSOR #include "lib/file/vfs/vfs.h" /** * Draw the cursor on-screen. * * @param vfs * @param name Base name of cursor or zero to hide the cursor. * @param x,y Coordinates [pixels] (origin at lower left) * (the origin is convenient for drawing via OpenGL, but requires the * mouse Y coordinate to be subtracted from the client area height. * Making the caller responsible for this avoids a dependency on * the g_yres global variable.) * @param scale Scale factor for drawing size the cursor. * @param forceGL Require the OpenGL cursor implementation, not hardware cursor * * Uses a hardware mouse cursor where available, otherwise a * portable OpenGL implementation. **/ extern Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL); /** * Forcibly frees all cursor handles. * * Currently used just prior to SDL shutdown. */ void cursor_shutdown(); #endif // #ifndef INCLUDED_GRAPHICS_CURSOR Index: ps/trunk/source/lib/res/h_mgr.cpp =================================================================== --- ps/trunk/source/lib/res/h_mgr.cpp (revision 19898) +++ ps/trunk/source/lib/res/h_mgr.cpp (revision 19899) @@ -1,819 +1,819 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * handle manager for resources. */ #include "precompiled.h" #include "h_mgr.h" #include #include // CHAR_BIT #include #include #include // std::bad_alloc #include "lib/fnv_hash.h" #include "lib/allocators/overrun_protector.h" #include "lib/allocators/pool.h" #include "lib/module_init.h" #include "lib/posix/posix_pthread.h" namespace ERR { static const Status H_IDX_INVALID = -120000; // totally invalid static const Status H_IDX_UNUSED = -120001; // beyond current cap static const Status H_TAG_MISMATCH = -120003; static const Status H_TYPE_MISMATCH = -120004; static const Status H_ALREADY_FREED = -120005; } static const StatusDefinition hStatusDefinitions[] = { { ERR::H_IDX_INVALID, L"Handle index completely out of bounds" }, { ERR::H_IDX_UNUSED, L"Handle index exceeds high-water mark" }, { ERR::H_TAG_MISMATCH, L"Handle tag mismatch (stale reference?)" }, { ERR::H_TYPE_MISMATCH, L"Handle type mismatch" }, { ERR::H_ALREADY_FREED, L"Handle already freed" } }; STATUS_ADD_DEFINITIONS(hStatusDefinitions); // rationale // // why fixed size control blocks, instead of just allocating dynamically? // it is expected that resources be created and freed often. this way is // much nicer to the memory manager. defining control blocks larger than // the allotted space is caught by h_alloc (made possible by the vtbl builder // storing control block size). it is also efficient to have all CBs in an // more or less contiguous array (see below). // // why a manager, instead of a simple pool allocator? // we need a central list of resources for freeing at exit, checking if a // resource has already been loaded (for caching), and when reloading. // may as well keep them in an array, rather than add a list and index. // // handle // // 0 = invalid handle value // < 0 is an error code (we assume < 0 <==> MSB is set - // true for 1s and 2s complement and sign-magnitude systems) // fields: // (shift value = # bits between LSB and field LSB. // may be larger than the field type - only shift Handle vars!) // - index (0-based) of control block in our array. // (field width determines maximum currently open handles) #define IDX_BITS 16 static const u64 IDX_MASK = (1l << IDX_BITS) - 1; // - tag (1-based) ensures the handle references a certain resource instance. // (field width determines maximum unambiguous resource allocs) typedef i64 Tag; #define TAG_BITS 48 static const u64 TAG_MASK = 0xFFFFFFFF; // safer than (1 << 32) - 1 // make sure both fields fit within a Handle variable cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT); // return the handle's index field (always non-negative). // no error checking! static inline size_t h_idx(const Handle h) { return (size_t)(h & IDX_MASK) - 1; } // return the handle's tag field. // no error checking! static inline Tag h_tag(Handle h) { return h >> IDX_BITS; } // build a handle from index and tag. // can't fail. static inline Handle handle(size_t idx, u64 tag) { const size_t idxPlusOne = idx+1; ENSURE(idxPlusOne <= IDX_MASK); ENSURE((tag & IDX_MASK) == 0); Handle h = tag | idxPlusOne; ENSURE(h > 0); return h; } // // internal per-resource-instance data // // chosen so that all current resource structs are covered. static const size_t HDATA_USER_SIZE = 104; struct HDATA { // we only need the tag, because it is trivial to compute // &HDATA from idx and vice versa. storing the entire handle // avoids needing to extract the tag field. Handle h; // NB: will be overwritten by pool_free uintptr_t key; intptr_t refs; // smaller bit fields combined into 1 // .. if set, do not actually release the resource (i.e. call dtor) // when the handle is h_free-d, regardless of the refcount. // set by h_alloc; reset on exit and by housekeeping. u32 keep_open : 1; // .. HACK: prevent adding to h_find lookup index if flags & RES_UNIQUE // (because those handles might have several instances open, // which the index can't currently handle) u32 unique : 1; u32 disallow_reload : 1; H_Type type; // for statistics size_t num_derefs; // storing PIVFS here is not a good idea since this often isn't // `freed' due to caching (and there is no dtor), so // the VFS reference count would never reach zero. VfsPath pathname; u8 user[HDATA_USER_SIZE]; }; // max data array entries. compared to last_in_use => signed. static const ssize_t hdata_cap = (1ul << IDX_BITS)/4; // pool of fixed-size elements allows O(1) alloc and free; // there is a simple mapping between HDATA address and index. static Pool hpool; // error checking strategy: // all handles passed in go through h_data(Handle, Type) // get a (possibly new) array entry. // // fails if idx is out of bounds. static Status h_data_from_idx(ssize_t idx, HDATA*& hd) { // don't check if idx is beyond the current high-water mark, because // we might be allocating a new entry. subsequent tag checks protect // against using unallocated entries. if(size_t(idx) >= size_t(hdata_cap)) // also detects negative idx WARN_RETURN(ERR::H_IDX_INVALID); hd = (HDATA*)(hpool.da.base + idx*hpool.el_size); hd->num_derefs++; return INFO::OK; } static ssize_t h_idx_from_data(HDATA* hd) { if(!pool_contains(&hpool, hd)) WARN_RETURN(ERR::INVALID_POINTER); return (uintptr_t(hd) - uintptr_t(hpool.da.base))/hpool.el_size; } // get HDATA for the given handle. // only uses (and checks) the index field. // used by h_force_close (which must work regardless of tag). static inline Status h_data_no_tag(const Handle h, HDATA*& hd) { ssize_t idx = (ssize_t)h_idx(h); RETURN_STATUS_IF_ERR(h_data_from_idx(idx, hd)); // need to verify it's in range - h_data_from_idx can only verify that // it's < maximum allowable index. if(uintptr_t(hd) > uintptr_t(hpool.da.base)+hpool.da.pos) WARN_RETURN(ERR::H_IDX_UNUSED); return INFO::OK; } static bool ignoreDoubleFree = false; // get HDATA for the given handle. // also verifies the tag field. // used by functions callable for any handle type, e.g. h_filename. static inline Status h_data_tag(Handle h, HDATA*& hd) { RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd)); if(hd->key == 0) // HDATA was wiped out and hd->h overwritten by pool_free { if(ignoreDoubleFree) return ERR::H_ALREADY_FREED; // NOWARN (see ignoreDoubleFree) else WARN_RETURN(ERR::H_ALREADY_FREED); } if(h != hd->h) WARN_RETURN(ERR::H_TAG_MISMATCH); return INFO::OK; } // get HDATA for the given handle. // also verifies the type. // used by most functions accessing handle data. static Status h_data_tag_type(const Handle h, const H_Type type, HDATA*& hd) { RETURN_STATUS_IF_ERR(h_data_tag(h, hd)); // h_alloc makes sure type isn't 0, so no need to check that here. if(hd->type != type) { debug_printf("h_mgr: expected type %s, got %s\n", utf8_from_wstring(hd->type->name).c_str(), utf8_from_wstring(type->name).c_str()); WARN_RETURN(ERR::H_TYPE_MISMATCH); } return INFO::OK; } //----------------------------------------------------------------------------- // lookup data structure //----------------------------------------------------------------------------- // speed up h_find (called every h_alloc) // multimap, because we want to add handles of differing type but same key // (e.g. a VFile and Tex object for the same underlying filename hash key) // // store index because it's smaller and Handle can easily be reconstructed // // // note: there may be several RES_UNIQUE handles of the same type and key // (e.g. sound files - several instances of a sound definition file). // that wasn't foreseen here, so we'll just refrain from adding to the index. // that means they won't be found via h_find - no biggie. typedef boost::unordered_multimap Key2Idx; typedef Key2Idx::iterator It; static OverrunProtector key2idx_wrapper; enum KeyRemoveFlag { KEY_NOREMOVE, KEY_REMOVE }; static Handle key_find(uintptr_t key, H_Type type, KeyRemoveFlag remove_option = KEY_NOREMOVE) { Key2Idx* key2idx = key2idx_wrapper.get(); if(!key2idx) WARN_RETURN(ERR::NO_MEM); // initial return value: "not found at all, or it's of the // wrong type". the latter happens when called by h_alloc to // check if e.g. a Tex object already exists; at that time, // only the corresponding VFile exists. Handle ret = -1; std::pair range = key2idx->equal_range(key); for(It it = range.first; it != range.second; ++it) { ssize_t idx = it->second; HDATA* hd; if(h_data_from_idx(idx, hd) != INFO::OK) continue; if(hd->type != type || hd->key != key) continue; // found a match if(remove_option == KEY_REMOVE) key2idx->erase(it); ret = hd->h; break; } key2idx_wrapper.lock(); return ret; } static void key_add(uintptr_t key, Handle h) { Key2Idx* key2idx = key2idx_wrapper.get(); if(!key2idx) return; const ssize_t idx = h_idx(h); // note: MSDN documentation of stdext::hash_multimap is incorrect; // there is no overload of insert() that returns pair. (void)key2idx->insert(std::make_pair(key, idx)); key2idx_wrapper.lock(); } static void key_remove(uintptr_t key, H_Type type) { Handle ret = key_find(key, type, KEY_REMOVE); ENSURE(ret > 0); } //---------------------------------------------------------------------------- // h_alloc //---------------------------------------------------------------------------- static void warn_if_invalid(HDATA* hd) { #ifndef NDEBUG H_VTbl* vtbl = hd->type; // validate HDATA // currently nothing to do; is checked by h_alloc and // the others have no invariants we could check. // have the resource validate its user_data Status err = vtbl->validate(hd->user); ENSURE(err == INFO::OK); // make sure empty space in control block isn't touched // .. but only if we're not storing a filename there const u8* start = hd->user + vtbl->user_size; const u8* end = hd->user + HDATA_USER_SIZE; for(const u8* p = start; p < end; p++) ENSURE(*p == 0); // else: handle user data was overrun! #else UNUSED2(hd); #endif } static Status type_validate(H_Type type) { if(!type) WARN_RETURN(ERR::INVALID_PARAM); if(type->user_size > HDATA_USER_SIZE) WARN_RETURN(ERR::LIMIT); if(type->name == 0) WARN_RETURN(ERR::INVALID_PARAM); return INFO::OK; } static Tag gen_tag() { static Tag tag; tag += (1ull << IDX_BITS); // it's not easy to detect overflow, because compilers // are allowed to assume it'll never happen. however, // pow(2, 64-IDX_BITS) is "enough" anyway. return tag; } static Handle reuse_existing_handle(uintptr_t key, H_Type type, size_t flags) { if(flags & RES_NO_CACHE) return 0; // object of specified key and type doesn't exist yet Handle h = h_find(type, key); if(h <= 0) return 0; HDATA* hd; RETURN_STATUS_IF_ERR(h_data_tag_type(h, type, hd)); // h_find means this won't fail hd->refs += 1; // we are reactivating a closed but cached handle. // need to generate a new tag so that copies of the // previous handle can no longer access the resource. // (we don't need to reset the tag in h_free, because // use before this fails due to refs > 0 check in h_user_data). if(hd->refs == 1) { const Tag tag = gen_tag(); h = handle(h_idx(h), tag); // can't fail hd->h = h; } return h; } static Status call_init_and_reload(Handle h, H_Type type, HDATA* hd, const PIVFS& vfs, const VfsPath& pathname, va_list* init_args) { Status err = INFO::OK; H_VTbl* vtbl = type; // exact same thing but for clarity // init if(vtbl->init) vtbl->init(hd->user, *init_args); // reload if(vtbl->reload) { // catch exception to simplify reload funcs - let them use new() try { err = vtbl->reload(hd->user, vfs, pathname, h); if(err == INFO::OK) warn_if_invalid(hd); } catch(std::bad_alloc&) { err = ERR::NO_MEM; } } return err; } static Handle alloc_new_handle(H_Type type, const PIVFS& vfs, const VfsPath& pathname, uintptr_t key, size_t flags, va_list* init_args) { HDATA* hd = (HDATA*)pool_alloc(&hpool, 0); if(!hd) WARN_RETURN(ERR::NO_MEM); new(&hd->pathname) VfsPath; ssize_t idx = h_idx_from_data(hd); RETURN_STATUS_IF_ERR(idx); // (don't want to do this before the add-reference exit, // so as not to waste tags for often allocated handles.) const Tag tag = gen_tag(); Handle h = handle(idx, tag); // can't fail. hd->h = h; hd->key = key; hd->type = type; hd->refs = 1; if(!(flags & RES_NO_CACHE)) hd->keep_open = 1; if(flags & RES_DISALLOW_RELOAD) hd->disallow_reload = 1; hd->unique = (flags & RES_UNIQUE) != 0; hd->pathname = pathname; if(key && !hd->unique) key_add(key, h); Status err = call_init_and_reload(h, type, hd, vfs, pathname, init_args); if(err < 0) goto fail; return h; fail: // reload failed; free the handle hd->keep_open = 0; // disallow caching (since contents are invalid) (void)h_free(h, type); // (h_free already does WARN_IF_ERR) // note: since some uses will always fail (e.g. loading sounds if // g_Quickstart), do not complain here. return (Handle)err; } static pthread_mutex_t h_mutex; // (the same class is defined in vfs.cpp, but it is easier to // just duplicate it to avoid having to specify the mutex. // such a class exists in ps/ThreadUtil.h, but we can't // take a dependency on that module here.) struct H_ScopedLock { H_ScopedLock() { pthread_mutex_lock(&h_mutex); } ~H_ScopedLock() { pthread_mutex_unlock(&h_mutex); } }; // any further params are passed to type's init routine Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags, ...) { H_ScopedLock s; RETURN_STATUS_IF_ERR(type_validate(type)); const uintptr_t key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0])); // see if we can reuse an existing handle Handle h = reuse_existing_handle(key, type, flags); RETURN_STATUS_IF_ERR(h); // .. successfully reused the handle; refcount increased if(h > 0) return h; // .. need to allocate a new one: va_list args; va_start(args, flags); h = alloc_new_handle(type, vfs, pathname, key, flags, &args); va_end(args); return h; // alloc_new_handle already does WARN_RETURN_STATUS_IF_ERR } //----------------------------------------------------------------------------- static void h_free_hd(HDATA* hd) { if(hd->refs > 0) hd->refs--; // still references open or caching requests it stays - do not release. if(hd->refs > 0 || hd->keep_open) return; // actually release the resource (call dtor, free control block). // h_alloc makes sure type != 0; if we get here, it still is H_VTbl* vtbl = hd->type; // call its destructor // note: H_TYPE_DEFINE currently always defines a dtor, but play it safe if(vtbl->dtor) vtbl->dtor(hd->user); if(hd->key && !hd->unique) key_remove(hd->key, hd->type); #ifndef NDEBUG // to_string is slow for some handles, so avoid calling it if unnecessary if(debug_filter_allows("H_MGR|")) { wchar_t buf[H_STRING_LEN]; if(vtbl->to_string(hd->user, buf) < 0) wcscpy_s(buf, ARRAY_SIZE(buf), L"(error)"); debug_printf("H_MGR| free %s %s accesses=%lu %s\n", utf8_from_wstring(hd->type->name).c_str(), hd->pathname.string8().c_str(), (unsigned long)hd->num_derefs, utf8_from_wstring(buf).c_str()); } #endif hd->pathname.~VfsPath(); // FIXME: ugly hack, but necessary to reclaim memory memset(hd, 0, sizeof(*hd)); pool_free(&hpool, hd); } Status h_free(Handle& h, H_Type type) { H_ScopedLock s; // 0-initialized or an error code; don't complain because this // happens often and is harmless. if(h <= 0) return INFO::OK; // wipe out the handle to prevent reuse but keep a copy for below. const Handle h_copy = h; h = 0; HDATA* hd; RETURN_STATUS_IF_ERR(h_data_tag_type(h_copy, type, hd)); h_free_hd(hd); return INFO::OK; } //---------------------------------------------------------------------------- // remaining API void* h_user_data(const Handle h, const H_Type type) { HDATA* hd; if(h_data_tag_type(h, type, hd) != INFO::OK) return 0; if(!hd->refs) { // note: resetting the tag is not enough (user might pass in its value) DEBUG_WARN_ERR(ERR::LOGIC); // no references to resource (it's cached, but someone is accessing it directly) return 0; } warn_if_invalid(hd); return hd->user; } VfsPath h_filename(const Handle h) { // don't require type check: should be usable for any handle, // even if the caller doesn't know its type. HDATA* hd; if(h_data_tag(h, hd) != INFO::OK) return VfsPath(); return hd->pathname; } // TODO: what if iterating through all handles is too slow? Status h_reload(const PIVFS& vfs, const VfsPath& pathname) { H_ScopedLock s; const u32 key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0])); // destroy (note: not free!) all handles backed by this file. // do this before reloading any of them, because we don't specify reload // order (the parent resource may be reloaded first, and load the child, // whose original data would leak). for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size)) { if(hd->key == 0 || hd->key != key || hd->disallow_reload) continue; hd->type->dtor(hd->user); } Status ret = INFO::OK; // now reload all affected handles size_t i = 0; for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size), i++) { if(hd->key == 0 || hd->key != key || hd->disallow_reload) continue; Status err = hd->type->reload(hd->user, vfs, hd->pathname, hd->h); // don't stop if an error is encountered - try to reload them all. if(err < 0) { h_free(hd->h, hd->type); if(ret == 0) // don't overwrite first error ret = err; } else warn_if_invalid(hd); } return ret; } Handle h_find(H_Type type, uintptr_t key) { H_ScopedLock s; return key_find(key, type); } // force the resource to be freed immediately, even if cached. // tag is not checked - this allows the first Handle returned // (whose tag will change after being 'freed', but remaining in memory) // to later close the object. // this is used when reinitializing the sound engine - // at that point, all (cached) OpenAL resources must be freed. Status h_force_free(Handle h, H_Type type) { H_ScopedLock s; // require valid index; ignore tag; type checked below. HDATA* hd; RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd)); if(hd->type != type) WARN_RETURN(ERR::H_TYPE_MISMATCH); hd->keep_open = 0; hd->refs = 0; h_free_hd(hd); return INFO::OK; } // increment Handle 's reference count. // only meant to be used for objects that free a Handle in their dtor, // so that they are copy-equivalent and can be stored in a STL container. // do not use this to implement refcounting on top of the Handle scheme, // e.g. loading a Handle once and then passing it around. instead, have each // user load the resource; refcounting is done under the hood. void h_add_ref(Handle h) { HDATA* hd; if(h_data_tag(h, hd) != INFO::OK) return; ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?! hd->refs++; } // retrieve the internal reference count or a negative error code. // background: since h_alloc has no way of indicating whether it // allocated a new handle or reused an existing one, counting references // within resource control blocks is impossible. since that is sometimes // necessary (always wrapping objects in Handles is excessive), we // provide access to the internal reference count. intptr_t h_get_refcnt(Handle h) { HDATA* hd; RETURN_STATUS_IF_ERR(h_data_tag(h, hd)); ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?! return hd->refs; } static ModuleInitState initState; static Status Init() { // lock must be recursive (e.g. h_alloc calls h_find) pthread_mutexattr_t attr; int err; err = pthread_mutexattr_init(&attr); ENSURE(err == 0); err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); ENSURE(err == 0); err = pthread_mutex_init(&h_mutex, &attr); ENSURE(err == 0); err = pthread_mutexattr_destroy(&attr); ENSURE(err == 0); RETURN_STATUS_IF_ERR(pool_create(&hpool, hdata_cap*sizeof(HDATA), sizeof(HDATA))); return INFO::OK; } static void Shutdown() { debug_printf("H_MGR| shutdown. any handle frees after this are leaks!\n"); // objects that store handles to other objects are destroyed before their // children, so the subsequent forced destruction of the child here will // raise a double-free warning unless we ignore it. (#860, #915, #920) ignoreDoubleFree = true; H_ScopedLock s; // forcibly close all open handles for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size)) { // it's already been freed; don't free again so that this // doesn't look like an error. if(hd->key == 0) continue; // disable caching; we need to release the resource now. hd->keep_open = 0; hd->refs = 0; h_free_hd(hd); } pool_destroy(&hpool); } void h_mgr_free_type(const H_Type type) { ignoreDoubleFree = true; H_ScopedLock s; // forcibly close all open handles of the specified type for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size)) { // free if not previously freed and only free the proper type if (hd->key == 0 || hd->type != type) continue; // disable caching; we need to release the resource now. hd->keep_open = 0; hd->refs = 0; h_free_hd(hd); } } void h_mgr_init() { ModuleInit(&initState, Init); } void h_mgr_shutdown() { ModuleShutdown(&initState, Shutdown); } Index: ps/trunk/source/lib/secure_crt.h =================================================================== --- ps/trunk/source/lib/secure_crt.h (revision 19898) +++ ps/trunk/source/lib/secure_crt.h (revision 19899) @@ -1,122 +1,122 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * partial implementation of VC8's secure CRT functions */ #ifndef INCLUDED_SECURE_CRT #define INCLUDED_SECURE_CRT #include #include "lib/status.h" namespace ERR { const Status STRING_NOT_TERMINATED = -100600; } // if the platform lacks a secure CRT implementation, we'll provide one. #if MSC_VERSION # define EMULATE_SECURE_CRT 0 #else # define EMULATE_SECURE_CRT 1 #endif #if EMULATE_SECURE_CRT // (conflicts with glibc definitions) #if !OS_UNIX || OS_MACOSX || OS_OPENBSD // return length [in characters] of a string, not including the trailing // null character. to protect against access violations, only the // first characters are examined; if the null character is // not encountered by then, is returned. // strnlen is available on OpenBSD #if !OS_OPENBSD extern size_t strnlen(const char* str, size_t max_len); #endif extern size_t wcsnlen(const wchar_t* str, size_t max_len); #endif // copy at most (not including trailing null) from // into , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: padding with zeroes is not called for by NG1031. extern int strncpy_s(char* dst, size_t max_dst_chars, const char* src, size_t max_src_chars); extern int wcsncpy_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src, size_t max_src_chars); // copy (including trailing null) into , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: implemented as tncpy_s(dst, max_dst_chars, src, SIZE_MAX) extern int strcpy_s(char* dst, size_t max_dst_chars, const char* src); extern int wcscpy_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src); // append at most (not including trailing null) from // to , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. extern int strncat_s(char* dst, size_t max_dst_chars, const char* src, size_t max_src_chars); extern int wcsncat_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src, size_t max_src_chars); // append to , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: implemented as tncat_s(dst, max_dst_chars, src, SIZE_MAX) extern int strcat_s(char* dst, size_t max_dst_chars, const char* src); extern int wcscat_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src); extern int vsprintf_s(char* dst, size_t max_dst_chars, const char* fmt, va_list ap) VPRINTF_ARGS(3); extern int vswprintf_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* fmt, va_list ap) VWPRINTF_ARGS(3); extern int sprintf_s(char* buf, size_t max_chars, const char* fmt, ...) PRINTF_ARGS(3); extern int swprintf_s(wchar_t* buf, size_t max_chars, const wchar_t* fmt, ...) WPRINTF_ARGS(3); // we'd like to avoid deprecation warnings caused by scanf. selective // 'undeprecation' isn't possible, replacing all stdio declarations with // our own deprecation scheme is a lot of work, suppressing all deprecation // warnings would cause important other warnings to be missed, and avoiding // scanf outright isn't convenient. // the remaining alternative is using scanf_s where available and otherwise // defining it to scanf. note that scanf_s has a different API: // any %s or %c or %[ format specifier's buffer must be followed by a // size parameter. callers must either avoid these, or provide two codepaths // (use scanf #if EMULATE_SECURE_CRT, otherwise scanf_s). #define scanf_s scanf #define wscanf_s wscanf #define fscanf_s fscanf #define fwscanf_s fwscanf #define sscanf_s sscanf #define swscanf_s swscanf #endif // #if EMULATE_SECURE_CRT #endif // #ifndef INCLUDED_SECURE_CRT Index: ps/trunk/source/lib/status.cpp =================================================================== --- ps/trunk/source/lib/status.cpp (revision 19898) +++ ps/trunk/source/lib/status.cpp (revision 19899) @@ -1,177 +1,177 @@ -/* Copyright (c) 2011 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 * "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. */ /* * error handling system: defines status codes, translates them to/from * other schemes (e.g. errno), associates them with descriptive text, * simplifies propagating errors / checking if functions failed. */ #include "precompiled.h" #include "lib/status.h" #include #include #include "lib/posix/posix_errno.h" static StatusDefinitionBucket* buckets; StatusDefinitionBucket* StatusAddDefinitions(StatusDefinitionBucket* bucket) { // insert at front of list StatusDefinitionBucket* next = buckets; buckets = bucket; return next; } static const StatusDefinition* DefinitionFromStatus(Status status) { for(const StatusDefinitionBucket* bucket = buckets; bucket; bucket = bucket->next) { for(size_t i = 0; i < bucket->numDefinitions; i++) { if(bucket->definitions[i].status == status) return &bucket->definitions[i]; } } return 0; } static const StatusDefinition* DefinitionFromErrno(int errno_equivalent) { for(const StatusDefinitionBucket* bucket = buckets; bucket; bucket = bucket->next) { for(size_t i = 0; i < bucket->numDefinitions; i++) { if(bucket->definitions[i].errno_equivalent == errno_equivalent) return &bucket->definitions[i]; } } return 0; } wchar_t* StatusDescription(Status status, wchar_t* buf, size_t max_chars) { const StatusDefinition* def = DefinitionFromStatus(status); if(def) { wcscpy_s(buf, max_chars, def->description); return buf; } swprintf_s(buf, max_chars, L"Unknown error (%lld, 0x%llX)", (long long)status, (unsigned long long)status); return buf; } int ErrnoFromStatus(Status status) { const StatusDefinition* def = DefinitionFromStatus(status); if(def && def->errno_equivalent != 0) return def->errno_equivalent; // the set of errnos in wposix.h doesn't have an "unknown error". // we use this one as a default because it's not expected to come up often. return EPERM; } Status StatusFromErrno() { if(errno == 0) return INFO::OK; const StatusDefinition* def = DefinitionFromErrno(errno); return def? def->status : ERR::FAIL; } //----------------------------------------------------------------------------- static const StatusDefinition statusDefs[] = { // INFO::OK doesn't really need a string because calling StatusDescription(0) // should never happen, but we'll play it safe. { INFO::OK, L"No error reported here" }, { ERR::FAIL, L"Function failed (no details available)" }, { INFO::SKIPPED, L"Skipped (not an error)" }, { INFO::CANNOT_HANDLE, L"Cannot handle (not an error)" }, { INFO::ALL_COMPLETE, L"All complete (not an error)" }, { ERR::LOGIC, L"Logic error in code" }, { ERR::EXCEPTION, L"Caught an exception" }, { ERR::TIMED_OUT, L"Timed out" }, { ERR::REENTERED, L"Single-call function was reentered" }, { ERR::CORRUPTED, L"File/memory data is corrupted" }, { ERR::ABORTED, L"Operation aborted" }, { ERR::INVALID_ALIGNMENT, L"Invalid alignment", EINVAL }, { ERR::INVALID_OFFSET, L"Invalid offset", EINVAL }, { ERR::INVALID_HANDLE, L"Invalid handle", EINVAL }, { ERR::INVALID_POINTER, L"Invalid pointer", EINVAL }, { ERR::INVALID_SIZE, L"Invalid size", EINVAL }, { ERR::INVALID_FLAG, L"Invalid flag", EINVAL }, { ERR::INVALID_PARAM, L"Invalid parameter", EINVAL }, { ERR::INVALID_VERSION, L"Invalid version", EINVAL }, { ERR::AGAIN, L"Try again later", EAGAIN }, { ERR::LIMIT, L"Fixed limit exceeded", E2BIG }, { ERR::NOT_SUPPORTED, L"Function not supported", ENOSYS }, { ERR::NO_MEM, L"Not enough memory", ENOMEM}, { ERR::_1, L"Case 1" }, { ERR::_2, L"Case 2" }, { ERR::_3, L"Case 3" }, { ERR::_4, L"Case 4" }, { ERR::_5, L"Case 5" }, { ERR::_6, L"Case 6" }, { ERR::_7, L"Case 7" }, { ERR::_8, L"Case 8" }, { ERR::_9, L"Case 9" }, { ERR::_11, L"Case 11" }, { ERR::_12, L"Case 12" }, { ERR::_13, L"Case 13" }, { ERR::_14, L"Case 14" }, { ERR::_15, L"Case 15" }, { ERR::_16, L"Case 16" }, { ERR::_17, L"Case 17" }, { ERR::_18, L"Case 18" }, { ERR::_19, L"Case 19" }, { ERR::_21, L"Case 21" }, { ERR::_22, L"Case 22" }, { ERR::_23, L"Case 23" }, { ERR::_24, L"Case 24" }, { ERR::_25, L"Case 25" }, { ERR::_26, L"Case 26" }, { ERR::_27, L"Case 27" }, { ERR::_28, L"Case 28" }, { ERR::_29, L"Case 29" } }; STATUS_ADD_DEFINITIONS(statusDefs); Index: ps/trunk/source/lib/sysdep/acpi.cpp =================================================================== --- ps/trunk/source/lib/sysdep/acpi.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/acpi.cpp (revision 19899) @@ -1,378 +1,378 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/acpi.h" #include "lib/byte_order.h" #include "lib/sysdep/cpu.h" #include "lib/module_init.h" #define ENABLE_MAHAF 0 #if ENABLE_MAHAF # include "lib/sysdep/os/win/mahaf.h" #else # include "lib/sysdep/os/win/wfirmware.h" #endif #pragma pack(1) typedef const volatile u8* PCV_u8; typedef const volatile AcpiTable* PCV_AcpiTable; //----------------------------------------------------------------------------- // table static AcpiTable* AllocateTable(size_t size) { ENSURE(size >= sizeof(AcpiTable)); return (AcpiTable*)malloc(size); } template static void DeallocateTable(const T* table) { free((void*)table); } // return 8-bit checksum of a buffer (should be 0) static u8 ComputeChecksum(PCV_u8 buf, size_t numBytes) { // (can't use std::accumulate - we need 8-bit wraparound) u8 sum = 0; for(PCV_u8 p = buf; p < buf+numBytes; p++) sum = u8((sum + *p) & 0xFF); return sum; } static bool ValidateTable(const AcpiTable* table, const char* signature = 0) { if(!table) return false; // caller knowns the signature; make sure it matches if(signature) { if(memcmp(table->signature, signature, 4) != 0) return false; } // no specific signature is called for, just validate the characters. else { for(size_t i = 0; i < 4; i++) { const char c = table->signature[i]; // "ASF!" and "____" have been encountered if(!isalpha(c) && c != '_' && c != '!') return false; } } // must be at least as large as the common header if(table->size < sizeof(AcpiTable)) return false; // checksum of table must be 0 // .. AMIBIOS OEMB table has an incorrect checksum (off-by-one), // so don't complain about any OEM tables (ignored anyway). const bool isOemTable = (memcmp(table->signature, "OEM", 3) == 0); if(!isOemTable) { if(ComputeChecksum((PCV_u8)table, table->size) != 0) return false; } return true; } #if ENABLE_MAHAF //----------------------------------------------------------------------------- // exception-safe transactional map/use/unmap // note: if the OS happens to unmap our physical memory, the Unsafe* // functions may crash. we catch this via SEH; on Unix, we'd need handlers // for SIGBUS and/or SIGSEGV. the code is safe in that it releases the // mapped memory and returns an error code. static void* SUCCEEDED = (void*)(intptr_t)1; static void* FAILED = (void*)(intptr_t)-1; typedef void* (*UnsafeFunction)(PCV_u8 mem, size_t numBytes, void* arg); static void* CallWithSafetyBlanket(UnsafeFunction func, PCV_u8 mem, size_t numBytes, void* arg) { #if MSC_VERSION __try { return func(mem, numBytes, arg); } __except(1) { return FAILED; } #else return func(mem, numBytes, arg); #endif } static void* TransactPhysicalMemory(uintptr_t physicalAddress, size_t numBytes, UnsafeFunction func, void* arg = 0) { PCV_u8 mem = (PCV_u8)mahaf_MapPhysicalMemory(physicalAddress, numBytes); if(!mem) return FAILED; void* ret = CallWithSafetyBlanket(func, mem, numBytes, arg); mahaf_UnmapPhysicalMemory((volatile void*)mem); return ret; } //----------------------------------------------------------------------------- // Root System Descriptor Pointer struct BiosDataArea { u16 serialBase[4]; u16 parallelBase[3]; u16 ebdaSegment; }; typedef const volatile BiosDataArea* PCV_BiosDataArea; static void* UnsafeReadEbdaPhysicalAddress(PCV_u8 mem, size_t numBytes, void* UNUSED(arg)) { ENSURE(numBytes >= sizeof(BiosDataArea)); PCV_BiosDataArea bda = (PCV_BiosDataArea)mem; const uintptr_t ebdaPhysicalAddress = ((uintptr_t)bda->ebdaSegment) * 16; return (void*)ebdaPhysicalAddress; } struct RSDP { char signature[8]; // "RSD PTR " u8 checksum; // sum of this struct = 0 char oemId[6]; u8 revision; // 0 for 1.0, 2 for 2.0 u32 rsdtPhysicalAddress; }; typedef const volatile RSDP* PCV_RSDP; static const size_t RSDP_ALIGNMENT = 16; static void* UnsafeLocateAndRetrieveRsdp(PCV_u8 buf, size_t numBytes, void* arg) { ENSURE(numBytes >= sizeof(RSDP)); for(PCV_u8 p = buf; p < buf+numBytes; p += RSDP_ALIGNMENT) { RSDP* prsdp = (RSDP*)p; if(memcmp(prsdp->signature, "RSD PTR ", 8) != 0) continue; if(ComputeChecksum(p, sizeof(RSDP)) != 0) continue; memcpy(arg, prsdp, sizeof(RSDP)); return SUCCEEDED; } return FAILED; } static bool RetrieveRsdp(RSDP& rsdp) { // See ACPIspec30b, section 5.2.5.1: // RSDP is either in the first KIB of the extended BIOS data area, void* ret = TransactPhysicalMemory(0x400, 0x100, UnsafeReadEbdaPhysicalAddress); if(ret != FAILED) { const uintptr_t ebdaPhysicalAddress = (uintptr_t)ret; ret = TransactPhysicalMemory(ebdaPhysicalAddress, 0x400, UnsafeLocateAndRetrieveRsdp, &rsdp); if(ret == SUCCEEDED) return true; } // or in read-only BIOS memory. ret = TransactPhysicalMemory(0xE0000, 0x20000, UnsafeLocateAndRetrieveRsdp, &rsdp); if(ret == SUCCEEDED) return true; return false; // not found } //----------------------------------------------------------------------------- // copy tables from physical memory static void* UnsafeAllocateAndCopyTable(PCV_u8 mem, size_t numBytes, void* arg) { ENSURE(numBytes >= sizeof(AcpiTable)); PCV_AcpiTable table = (PCV_AcpiTable)mem; const size_t tableSize = table->size; // physical memory window is smaller than the table // (caller will map a larger window and call us again) if(numBytes < tableSize) { memcpy(arg, &tableSize, sizeof(size_t)); return 0; } PCV_u8 copy = (PCV_u8)AllocateTable(tableSize); if(!copy) return FAILED; memcpy((void*)copy, (const void*)mem, tableSize); return (void*)copy; } static const AcpiTable* AllocateAndCopyTable(uintptr_t physicalAddress) { // ACPI table sizes are not known until they've been mapped. since that // is slow, we don't always want to do it twice. the solution is to map // enough for a typical table; if that is too small, realloc and map again. static const size_t initialSize = 4*KiB; size_t actualSize = 0; void* ret = TransactPhysicalMemory(physicalAddress, initialSize, UnsafeAllocateAndCopyTable, &actualSize); // initialSize was too small; actualSize has been set if(ret == 0) ret = TransactPhysicalMemory(physicalAddress, actualSize, UnsafeAllocateAndCopyTable); // *either* of the above calls failed to allocate memory if(ret == FAILED) return 0; return (const AcpiTable*)ret; } #endif // ENABLE_MAHAF static void AllocateAndCopyTables(const AcpiTable**& tables, size_t& numTables) { #if ENABLE_MAHAF if(mahaf_IsPhysicalMappingDangerous()) return; if(mahaf_Init() != INFO::OK) return; RSDP rsdp; if(!RetrieveRsdp(rsdp)) return; // Root System Descriptor Table struct RSDT { AcpiTable header; u32 tableAddresses[1]; }; const RSDT* rsdt = (const RSDT*)AllocateAndCopyTable(rsdp.rsdtPhysicalAddress); if(!ValidateTable(&rsdt->header, "RSDT")) { DeallocateTable(rsdt); return; } numTables = (rsdt->header.size - sizeof(AcpiTable)) / sizeof(rsdt->tableAddresses[0]); ENSURE(numTables != 0); tables = new const AcpiTable*[numTables]; for(size_t i = 0; i < numTables; i++) tables[i] = AllocateAndCopyTable(rsdt->tableAddresses[i]); DeallocateTable(rsdt); #else const wfirmware::Provider provider = FOURCC_BE('A','C','P','I'); const wfirmware::TableIds tableIDs = wfirmware::GetTableIDs(provider); numTables = tableIDs.size(); tables = new const AcpiTable*[numTables]; for(size_t i = 0; i < numTables; i++) { wfirmware::Table table = wfirmware::GetTable(provider, tableIDs[i]); ENSURE(!table.empty()); tables[i] = AllocateTable(table.size()); memcpy((void*)tables[i], &table[0], table.size()); } #endif // to prevent callers from choking on invalid tables, we // zero out the corresponding tables[] entries. for(size_t i = 0; i < numTables; i++) { if(!ValidateTable(tables[i])) { DeallocateTable(tables[i]); tables[i] = 0; } } } //----------------------------------------------------------------------------- // note: avoid global std::map etc. because we may be called before _cinit static const AcpiTable** tables; // tables == 0 <=> not initialized static const AcpiTable* invalidTables; // tables == &invalidTables => init failed static size_t numTables; void acpi_Shutdown() { if(tables) { for(size_t i = 0; i < numTables; i++) DeallocateTable(tables[i]); SAFE_ARRAY_DELETE(tables); numTables = 0; } #if ENABLE_MAHAF mahaf_Shutdown(); #endif } const AcpiTable* acpi_GetTable(const char* signature) { if(cpu_CAS(&tables, (const AcpiTable**)0, &invalidTables)) AllocateAndCopyTables(tables, numTables); // (typically only a few tables, linear search is OK) for(size_t i = 0; i < numTables; i++) { const AcpiTable* table = tables[i]; if(!table) continue; // skip invalid tables, e.g. OEM (see above) if(strncmp(table->signature, signature, 4) == 0) return table; } return 0; // no matching AND valid table found } Index: ps/trunk/source/lib/sysdep/arch/amd64/amd64.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/amd64/amd64.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/amd64/amd64.h (revision 19899) @@ -1,34 +1,34 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * C++ and inline asm implementations of AMD64 functions */ #ifndef INCLUDED_AMD64 #define INCLUDED_AMD64 #if !ARCH_AMD64 #error "including amd64.h without ARCH_AMD64=1" #endif #endif // #ifndef INCLUDED_AMD64 Index: ps/trunk/source/lib/sysdep/arch/x86_x64/apic.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/apic.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/apic.cpp (revision 19899) @@ -1,155 +1,155 @@ -/* Copyright (c) 2011 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 * "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 "lib/sysdep/arch/x86_x64/apic.h" #include "lib/bits.h" #include "lib/module_init.h" #include "lib/sysdep/cpu.h" // ERR::CPU_FEATURE_MISSING #include "lib/sysdep/os_cpu.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" ApicId GetApicId() { x86_x64::CpuidRegs regs = { 0 }; regs.eax = 1; // note: CPUID function 1 is always supported, but only processors with // an xAPIC (e.g. P4/Athlon XP) will return a nonzero ID. bool ok = x86_x64::cpuid(®s); ASSERT(ok); UNUSED2(ok); const u8 apicId = (u8)bits(regs.ebx, 24, 31); return apicId; } static size_t numIds; static ApicId processorApicIds[os_cpu_MaxProcessors]; static ApicId sortedApicIds[os_cpu_MaxProcessors]; static Status GetAndValidateApicIds() { numIds = os_cpu_NumProcessors(); struct StoreEachProcessorsApicId { static void Callback(size_t processor, uintptr_t UNUSED(data)) { processorApicIds[processor] = GetApicId(); } }; // (can fail due to restrictions on our process affinity or lack of // support for affinity masks in OS X.) RETURN_STATUS_IF_ERR(os_cpu_CallByEachCPU(StoreEachProcessorsApicId::Callback, 0)); std::copy(processorApicIds, processorApicIds+numIds, sortedApicIds); std::sort(sortedApicIds, sortedApicIds+numIds); ApicId* const end = std::unique(sortedApicIds, sortedApicIds+numIds); const size_t numUnique = end-sortedApicIds; // all IDs are zero - system lacks an xAPIC. // (NB: we exclude single-processor systems in this test - // having one zero-valued ID is legitimate) if(numUnique == 1 && sortedApicIds[0] == 0 && numIds != 1) { debug_printf("APIC: all zero\n"); return ERR::CPU_FEATURE_MISSING; // NOWARN } // not all unique - probably running in a VM whose emulation is // imperfect or doesn't allow access to all processors. if(numUnique != numIds) { debug_printf("APIC: not unique\n"); return ERR::FAIL; // NOWARN } return INFO::OK; } static Status InitApicIds() { const Status status = GetAndValidateApicIds(); if(status < 0) // failed { // generate fake but legitimate APIC IDs for(size_t processor = 0; processor < numIds; processor++) processorApicIds[processor] = sortedApicIds[processor] = (ApicId)processor; } return status; } static ModuleInitState apicInitState; bool AreApicIdsReliable() { ModuleInit(&apicInitState, InitApicIds); if(apicInitState < 0) return false; return true; } static size_t IndexFromApicId(const ApicId* apicIds, ApicId apicId) { ModuleInit(&apicInitState, InitApicIds); const ApicId* pos = std::find(apicIds, apicIds+numIds, apicId); if(pos == apicIds+numIds) { DEBUG_WARN_ERR(ERR::LOGIC); return 0; } const size_t index = pos - apicIds; return index; } size_t ProcessorFromApicId(ApicId apicId) { return IndexFromApicId(processorApicIds, apicId); } size_t ContiguousIdFromApicId(ApicId apicId) { return IndexFromApicId(sortedApicIds, apicId); } static ApicId ApicIdFromIndex(const ApicId* apicIds, size_t index) { ModuleInit(&apicInitState, InitApicIds); ASSERT(index < numIds); return apicIds[index]; } ApicId ApicIdFromProcessor(size_t processor) { return ApicIdFromIndex(processorApicIds, processor); } ApicId ApicIdFromContiguousId(size_t contiguousId) { return ApicIdFromIndex(sortedApicIds, contiguousId); } Index: ps/trunk/source/lib/sysdep/arch/x86_x64/msr.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/msr.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/msr.cpp (revision 19899) @@ -1,141 +1,141 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/arch/x86_x64/msr.h" #include "lib/sysdep/os/win/mahaf.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" namespace MSR { bool IsAccessible() { if(!x86_x64::Cap(x86_x64::CAP_MSR)) return false; // only read/writable from ring 0, so we need the driver. if(mahaf_Init() < 0) return false; return true; } bool HasEnergyPerfBias() { #if 1 // the documentation is unclear. until it improves, disable // this, lest we provoke a GPF. return false; #else if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL) return false; if(x86_x64::Family() < 6) return false; if(x86_x64::Model() < 0xE) return false; return true; #endif } bool HasPlatformInfo() { if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL) return false; if(x86_x64::Family() != 6) return false; switch(x86_x64::Model()) { // section 34.4 in 253665-041US case x86_x64::MODEL_NEHALEM_EP: case x86_x64::MODEL_NEHALEM_EP_2: case x86_x64::MODEL_NEHALEM_EX: case x86_x64::MODEL_I7_I5: return true; // section 34.5 case x86_x64::MODEL_CLARKDALE: case x86_x64::MODEL_WESTMERE_EP: return true; // section 34.6 case x86_x64::MODEL_WESTMERE_EX: return true; // section 34.7 case x86_x64::MODEL_SANDY_BRIDGE: case x86_x64::MODEL_SANDY_BRIDGE_2: return true; default: return false; } } bool HasUncore() { if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL) return false; if(x86_x64::Family() != 6) return false; switch(x86_x64::Model()) { // Xeon 5500 / i7 (section B.4.1 in 253669-037US) case 0x1A: // Bloomfield, Gainstown case 0x1E: // Clarksfield, Lynnfield, Jasper Forest case 0x1F: return true; // Xeon 5600 / Westmere (section B.5) case 0x25: // Clarkdale, Arrandale case 0x2C: // Gulftown return true; default: return false; } } u64 Read(u64 reg) { return mahaf_ReadModelSpecificRegister(reg); } void Write(u64 reg, u64 value) { mahaf_WriteModelSpecificRegister(reg, value); } } // namespace MSR Index: ps/trunk/source/lib/sysdep/arch/x86_x64/topology.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/topology.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/topology.h (revision 19899) @@ -1,111 +1,111 @@ -/* Copyright (c) 2011 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 * "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. */ /* * detection of CPU and cache topology. * thread-safe, no explicit initialization is required. */ #ifndef INCLUDED_X86_X64_TOPOLOGY #define INCLUDED_X86_X64_TOPOLOGY #include "lib/sysdep/arch/x86_x64/apic.h" // ApicId namespace topology { //----------------------------------------------------------------------------- // cpu // the CPU topology, i.e. how many packages, cores and logical processors are // actually present and enabled, is useful for parameterizing parallel // algorithms, especially on NUMA systems. // // note: OS abstractions usually only mention "processors", which could be // any mix of the above. /** * @return number of *enabled* CPU packages / sockets. **/ LIB_API size_t NumPackages(); /** * @return number of *enabled* CPU cores per package. * (2 on dual-core systems) **/ LIB_API size_t CoresPerPackage(); /** * @return number of *enabled* logical processors (aka Hyperthreads) * per core. (2 on P4 EE) **/ LIB_API size_t LogicalPerCore(); /** * @return index of processor package/socket in [0, NumPackages()) **/ LIB_API size_t PackageFromApicId(ApicId apicId); /** * @return index of processor core in [0, CoresPerPackage()) **/ LIB_API size_t CoreFromApicId(ApicId apicId); /** * @return index of logical processor in [0, LogicalPerCore()) **/ LIB_API size_t LogicalFromApicId(ApicId apicId); /** * @param idxPackage, idxCore, idxLogical return values of *FromApicId * @return APIC ID (see note at AreApicIdsReliable) **/ LIB_API ApicId ApicIdFromIndices(size_t idxPackage, size_t idxCore, size_t idxLogical); //----------------------------------------------------------------------------- // L2 cache // knowledge of the cache topology, i.e. which processors share which caches, // can be used to reduce contention and increase effective capacity by // assigning the partner processors to work on the same dataset. // // example: Intel Core2 micro-architectures feature L2 caches shared by // two cores. /** * @return number of distinct L2 caches. **/ LIB_API size_t NumCaches(); /** * @return L2 cache number (zero-based) to which the given processor belongs. **/ LIB_API size_t CacheFromProcessor(size_t processor); /** * @return bit-mask of all processors sharing the given cache. **/ LIB_API uintptr_t ProcessorMaskFromCache(size_t cache); } // namespace topology #endif // #ifndef INCLUDED_X86_X64_TOPOLOGY Index: ps/trunk/source/lib/res/graphics/cursor.cpp =================================================================== --- ps/trunk/source/lib/res/graphics/cursor.cpp (revision 19898) +++ ps/trunk/source/lib/res/graphics/cursor.cpp (revision 19899) @@ -1,366 +1,366 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * mouse cursors (either via OpenGL texture or hardware) */ #include "precompiled.h" #include "cursor.h" #include #include #include #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/res/h_mgr.h" #include "lib/sysdep/cursor.h" #include "ogl_tex.h" // On Windows, allow runtime choice between system cursors and OpenGL // cursors (Windows = more responsive, OpenGL = more consistent with what // the game sees) #if OS_WIN || OS_UNIX # define ALLOW_SYS_CURSOR 1 #else # define ALLOW_SYS_CURSOR 0 #endif class SDLCursor { SDL_Surface* surface; SDL_Cursor* cursor; public: Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale) { shared_ptr file; size_t fileSize; RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize)); Tex t; RETURN_STATUS_IF_ERR(t.decode(file, fileSize)); // convert to required BGRA format. const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT; RETURN_STATUS_IF_ERR(t.transform_to(flags)); void* bgra_img = t.get_data(); if(!bgra_img) WARN_RETURN(ERR::FAIL); surface = SDL_CreateRGBSurfaceFrom(bgra_img, (int)t.m_Width, (int)t.m_Height, 32, (int)t.m_Width*4, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if(!surface) return ERR::FAIL; if(scale != 1.0) { SDL_Surface* scaled_surface = SDL_CreateRGBSurface(0, surface->w * scale, surface->h * scale, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if(!scaled_surface) return ERR::FAIL; if(SDL_BlitScaled(surface, NULL, scaled_surface, NULL)) return ERR::FAIL; SDL_FreeSurface(surface); surface = scaled_surface; } cursor = SDL_CreateColorCursor(surface, hotspotx_, hotspoty_); if(!cursor) return ERR::FAIL; return INFO::OK; } void set() { SDL_SetCursor(cursor); } void destroy() { SDL_FreeCursor(cursor); SDL_FreeSurface(surface); } }; // no init is necessary because this is stored in struct Cursor, which // is 0-initialized by h_mgr. class GLCursor { Handle ht; GLint w, h; int hotspotx, hotspoty; public: Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale) { ht = ogl_tex_load(vfs, pathname); RETURN_STATUS_IF_ERR(ht); size_t width, height; (void)ogl_tex_get_size(ht, &width, &height, 0); w = (GLint)(width * scale); h = (GLint)(height * scale); hotspotx = hotspotx_; hotspoty = hotspoty_; (void)ogl_tex_set_filter(ht, GL_NEAREST); (void)ogl_tex_upload(ht); return INFO::OK; } void destroy() { // note: we're stored in a resource => ht is initially 0 => // this is safe, no need for an is_valid flag (void)ogl_tex_free(ht); } void draw(int x, int y) const { #if CONFIG2_GLES UNUSED2(x); UNUSED2(y); #warning TODO: implement cursors for GLES #else (void)ogl_tex_bind(ht); glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_QUADS); glTexCoord2i(1, 0); glVertex2i( x-hotspotx+w, y+hotspoty ); glTexCoord2i(0, 0); glVertex2i( x-hotspotx, y+hotspoty ); glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h ); glTexCoord2i(1, 1); glVertex2i( x-hotspotx+w, y+hotspoty-h ); glEnd(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); #endif } Status validate() const { const GLint A = 128; // no cursor is expected to get this big if(w > A || h > A || hotspotx > A || hotspoty > A) WARN_RETURN(ERR::_1); if(ht < 0) WARN_RETURN(ERR::_2); return INFO::OK; } }; enum CursorKind { CK_Default, CK_SDL, CK_OpenGL }; struct Cursor { double scale; // require kind == CK_OpenGL after reload bool forceGL; CursorKind kind; // valid iff kind == CK_SDL SDLCursor sdl_cursor; // valid iff kind == CK_OpenGL GLCursor gl_cursor; }; H_TYPE_DEFINE(Cursor); static void Cursor_init(Cursor* c, va_list args) { c->scale = va_arg(args, double); c->forceGL = (va_arg(args, int) != 0); } static void Cursor_dtor(Cursor* c) { switch(c->kind) { case CK_Default: break; // nothing to do case CK_SDL: c->sdl_cursor.destroy(); break; case CK_OpenGL: c->gl_cursor.destroy(); break; default: DEBUG_WARN_ERR(ERR::LOGIC); break; } } static Status Cursor_reload(Cursor* c, const PIVFS& vfs, const VfsPath& name, Handle) { const VfsPath pathname(VfsPath(L"art/textures/cursors") / name); // read pixel offset of the cursor's hotspot [the bit of it that's // drawn at (g_mouse_x,g_mouse_y)] from file. int hotspotx = 0, hotspoty = 0; { const VfsPath pathnameHotspot = pathname.ChangeExtension(L".txt"); shared_ptr buf; size_t size; RETURN_STATUS_IF_ERR(vfs->LoadFile(pathnameHotspot, buf, size)); std::wstringstream s(std::wstring((const wchar_t*)buf.get(), size)); s >> hotspotx >> hotspoty; } const VfsPath pathnameImage = pathname.ChangeExtension(L".png"); // try loading as SDL2 cursor if(!c->forceGL && c->sdl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK) c->kind = CK_SDL; // fall back to GLCursor (system cursor code is disabled or failed) else if(c->gl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK) c->kind = CK_OpenGL; // everything failed, leave cursor unchanged else c->kind = CK_Default; return INFO::OK; } static Status Cursor_validate(const Cursor* c) { switch(c->kind) { case CK_Default: break; // nothing to do case CK_SDL: break; // nothing to do case CK_OpenGL: RETURN_STATUS_IF_ERR(c->gl_cursor.validate()); break; default: WARN_RETURN(ERR::_2); break; } return INFO::OK; } static Status Cursor_to_string(const Cursor* c, wchar_t* buf) { const wchar_t* type; switch(c->kind) { case CK_Default: type = L"default"; break; case CK_SDL: type = L"sdl"; break; case CK_OpenGL: type = L"gl"; break; default: DEBUG_WARN_ERR(ERR::LOGIC); type = L"?"; break; } swprintf_s(buf, H_STRING_LEN, L"cursor (%ls)", type); return INFO::OK; } // note: these standard resource interface functions are not exposed to the // caller. all we need here is storage for the sys_cursor / GLCursor and // a name -> data lookup mechanism, both provided by h_mgr. // in other words, we continually create/free the cursor resource in // cursor_draw and trust h_mgr's caching to absorb it. static Handle cursor_load(const PIVFS& vfs, const VfsPath& name, double scale, bool forceGL) { return h_alloc(H_Cursor, vfs, name, 0, scale, (int)forceGL); } void cursor_shutdown() { h_mgr_free_type(H_Cursor); } static Status cursor_free(Handle& h) { return h_free(h, H_Cursor); } Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL) { // hide the cursor if(!name) { SDL_ShowCursor(SDL_DISABLE); return INFO::OK; } Handle hc = cursor_load(vfs, name, scale, forceGL); // TODO: if forceGL changes at runtime after a cursor is first created, // we might reuse a cached version of the cursor with the old forceGL flag RETURN_STATUS_IF_ERR(hc); // silently ignore failures H_DEREF(hc, Cursor, c); switch(c->kind) { case CK_Default: break; case CK_SDL: c->sdl_cursor.set(); SDL_ShowCursor(SDL_ENABLE); break; case CK_OpenGL: c->gl_cursor.draw(x, y); SDL_ShowCursor(SDL_DISABLE); break; default: DEBUG_WARN_ERR(ERR::LOGIC); break; } (void)cursor_free(hc); return INFO::OK; } Index: ps/trunk/source/lib/res/graphics/tests/test_tex.h =================================================================== --- ps/trunk/source/lib/res/graphics/tests/test_tex.h (revision 19898) +++ ps/trunk/source/lib/res/graphics/tests/test_tex.h (revision 19899) @@ -1,121 +1,121 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/tex/tex.h" #include "lib/tex/tex_codec.h" #include "lib/allocators/shared_ptr.h" class TestTex : public CxxTest::TestSuite { void generate_encode_decode_compare(size_t w, size_t h, size_t flags, size_t bpp, const OsPath& extension) { // generate test data const size_t size = w*h*bpp/8; shared_ptr img(new u8[size], ArrayDeleter()); std::generate(img.get(), img.get()+size, rand); // create the DynArray that will be wrapped in a Tex Object DynArray da; // Once the Tex created here goes out of scope, the DynArray should be freed { // wrap in Tex Tex t; TS_ASSERT_OK(t.wrap(w, h, bpp, flags, img, 0)); // encode to file format TS_ASSERT_OK(t.encode(extension, &da)); memset(&t, 0, sizeof(t)); // decode from file format shared_ptr ptr = DummySharedPtr(da.base); TS_ASSERT_OK(t.decode(ptr, da.cur_size)); // make sure pixel format gets converted completely to plain TS_ASSERT_OK(t.transform_to(0)); // compare img TS_ASSERT_SAME_DATA(t.get_data(), img.get(), size); } // cleanup TS_ASSERT_OK(da_free(&da)); } public: // have mipmaps be created for a test image; check resulting size and pixels void test_mipmap_create() { static u8 imgData[] = { 0x10,0x20,0x30, 0x40,0x60,0x80, 0xA0,0xA4,0xA8, 0xC0,0xC1,0xC2 }; shared_ptr img = DummySharedPtr(imgData); // assumes 2x2 box filter algorithm with rounding static const u8 mipmap[] = { 0x6C,0x79,0x87 }; Tex t; TS_ASSERT_OK(t.wrap(2, 2, 24, 0, img, 0)); TS_ASSERT_OK(t.transform_to(TEX_MIPMAPS)); const u8* const out_img = t.get_data(); TS_ASSERT_EQUALS((int)t.img_size(), 12+3); TS_ASSERT_SAME_DATA(out_img, imgData, 12); TS_ASSERT_SAME_DATA(out_img+12, mipmap, 3); } void test_img_size() { shared_ptr img(new u8[100*100*4], ArrayDeleter()); Tex t; TS_ASSERT_OK(t.wrap(100, 100, 32, TEX_ALPHA, img, 0)); TS_ASSERT_EQUALS((int)t.img_size(), 40000); // DXT rounds up to 4x4 blocks; DXT1a is 4bpp Tex t2; TS_ASSERT_OK(t2.wrap(97, 97, 4, DXT1A, img, 0)); TS_ASSERT_EQUALS((int)t2.img_size(), 5000); } void test_s3tc_decode() { const size_t w = 4, h = 4, bpp = 4; const size_t size = w*h/2; shared_ptr img(new u8[size], ArrayDeleter()); memcpy(img.get(), "\xFF\xFF\x00\x00\x00\xAA\xFF\x55", 8); // gradient from white to black const u8 expected[] = "\xFF\xFF\xFF" "\xFF\xFF\xFF" "\xFF\xFF\xFF" "\xFF\xFF\xFF" "\xAA\xAA\xAA" "\xAA\xAA\xAA" "\xAA\xAA\xAA" "\xAA\xAA\xAA" "\x55\x55\x55" "\x55\x55\x55" "\x55\x55\x55" "\x55\x55\x55" "\x00\x00\x00" "\x00\x00\x00" "\x00\x00\x00" "\x00\x00\x00"; const size_t flags = TEX_DXT&1; // wrap in Tex Tex t; TS_ASSERT_OK(t.wrap(w, h, bpp, flags, img, 0)); // decompress S3TC TS_ASSERT_OK(t.transform_to(0)); // compare img TS_ASSERT_SAME_DATA(t.get_data(), expected, 48); } }; Index: ps/trunk/source/lib/secure_crt.cpp =================================================================== --- ps/trunk/source/lib/secure_crt.cpp (revision 19898) +++ ps/trunk/source/lib/secure_crt.cpp (revision 19899) @@ -1,295 +1,295 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * partial implementation of VC8's secure CRT functions */ #include "precompiled.h" #include #include #include #include "lib/secure_crt.h" #if OS_ANDROID # include #endif // we were included from wsecure_crt.cpp; skip all stuff that // must only be done once. #ifndef WSECURE_CRT static const StatusDefinition secureCrtStatusDefinitions[] = { { ERR::STRING_NOT_TERMINATED, L"Invalid string (no 0 terminator found in buffer)" } }; STATUS_ADD_DEFINITIONS(secureCrtStatusDefinitions); #endif // written against http://std.dkuug.dk/jtc1/sc22/wg14/www/docs/n1031.pdf . // optimized for size - e.g. strcpy calls strncpy with n = SIZE_MAX. // since char and wide versions of these functions are basically the same, // this source file implements generic versions and bridges the differences // with these macros. wsecure_crt.cpp #defines WSECURE_CRT and // includes this file. // Note: These defines are all #undef:ed at the end of the file - remember to // add a corresponding #undef when adding a #define. #ifdef WSECURE_CRT # define tchar wchar_t # define tstring std::wstring # define T(string_literal) L ## string_literal # define tnlen wcsnlen # define tncpy_s wcsncpy_s # define tcpy_s wcscpy_s # define tncat_s wcsncat_s # define tcat_s wcscat_s # define tcmp wcscmp # define tcpy wcscpy # define tvsnprintf vswprintf // used by implementation # define tvsprintf_s vswprintf_s # define tsprintf_s swprintf_s #else # define tchar char # define tstring std::string # define T(string_literal) string_literal # define tnlen strnlen # define tncpy_s strncpy_s # define tcpy_s strcpy_s # define tncat_s strncat_s # define tcat_s strcat_s # define tcmp strcmp # define tcpy strcpy # define tvsnprintf vsnprintf // used by implementation # define tvsprintf_s vsprintf_s # define tsprintf_s sprintf_s #endif // #ifdef WSECURE_CRT // return and raise an assertion if doesn't hold. // usable as a statement. #define ENFORCE(condition, err_to_warn, retval) STMT(\ if(!(condition)) \ { \ DEBUG_WARN_ERR(err_to_warn); \ return retval; \ } \ ) // raise a debug warning if is the size of a pointer. // catches bugs such as: tchar* s = ..; tcpy_s(s, sizeof(s), T("..")); // if warnings get annoying, replace with debug_printf. usable as a statement. // // currently disabled due to high risk of false positives. #define WARN_IF_PTR_LEN(len)\ /* ENSURE(len != sizeof(char*)); */ // skip our implementation if already available, but not the // self-test and the t* defines (needed for test). #if EMULATE_SECURE_CRT #if !OS_UNIX || OS_MACOSX || OS_OPENBSD // return length [in characters] of a string, not including the trailing // null character. to protect against access violations, only the // first characters are examined; if the null character is // not encountered by then, is returned. size_t tnlen(const tchar* str, size_t max_len) { // note: we can't bail - what would the return value be? ENSURE(str != 0); WARN_IF_PTR_LEN(max_len); size_t len; for(len = 0; len < max_len; len++) if(*str++ == '\0') break; return len; } #endif // !OS_UNIX #if OS_ANDROID static tstring androidFormat(const tchar *fmt) { // FIXME handle %%hs, %%ls, etc tstring ret(fmt); boost::algorithm::replace_all(ret, T("%ls"), T("%S")); boost::algorithm::replace_all(ret, T("%hs"), T("%s")); return ret; } #endif // copy at most (not including trailing null) from // into , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: padding with zeroes is not called for by N1031. int tncpy_s(tchar* dst, size_t max_dst_chars, const tchar* src, size_t max_src_chars) { // the MS implementation returns EINVAL and allows dst = 0 if // max_dst_chars = max_src_chars = 0. no mention of this in // 3.6.2.1.1, so don't emulate that behavior. ENFORCE(dst != 0, ERR::INVALID_POINTER, EINVAL); ENFORCE(max_dst_chars != 0, ERR::INVALID_SIZE, EINVAL); // N1031 says ERANGE, MSDN/MSVC says EINVAL *dst = '\0'; // in case src ENFORCE is triggered ENFORCE(src != 0, ERR::INVALID_POINTER, EINVAL); WARN_IF_PTR_LEN(max_dst_chars); WARN_IF_PTR_LEN(max_src_chars); // copy string until null character encountered or limit reached. // optimized for size (less comparisons than MS impl) and // speed (due to well-predicted jumps; we don't bother unrolling). tchar* p = dst; size_t chars_left = std::min(max_dst_chars, max_src_chars); while(chars_left != 0) { // success: reached end of string normally. if((*p++ = *src++) == '\0') return 0; chars_left--; } // which limit did we hit? // .. dst, and last character wasn't null: overflow. if(max_dst_chars <= max_src_chars) { *dst = '\0'; ENFORCE(0, ERR::INVALID_SIZE, ERANGE); } // .. source: success, but still need to null-terminate the destination. *p = '\0'; return 0; } // copy (including trailing null) into , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. int tcpy_s(tchar* dst, size_t max_dst_chars, const tchar* src) { return tncpy_s(dst, max_dst_chars, src, SIZE_MAX); } // append to , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. int tncat_s(tchar* dst, size_t max_dst_chars, const tchar* src, size_t max_src_chars) { ENFORCE(dst != 0, ERR::INVALID_POINTER, EINVAL); ENFORCE(max_dst_chars != 0, ERR::INVALID_SIZE, EINVAL); // N1031 says ERANGE, MSDN/MSVC says EINVAL // src is checked in tncpy_s // WARN_IF_PTR_LEN not necessary: both max_dst_chars and max_src_chars // are checked by tnlen / tncpy_s (respectively). const size_t dst_len = tnlen(dst, max_dst_chars); if(dst_len == max_dst_chars) { *dst = '\0'; ENFORCE(0, ERR::STRING_NOT_TERMINATED, EINVAL); // N1031/MSDN says ERANGE, MSVC says EINVAL } tchar* const end = dst+dst_len; const size_t chars_left = max_dst_chars-dst_len; int ret = tncpy_s(end, chars_left, src, max_src_chars); // if tncpy_s overflowed, we need to clear the start of our string // (not just the appended part). can't do that by default, because // the beginning of dst is not changed in normal operation. if(ret != 0) *dst = '\0'; return ret; } // append to , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: implemented as tncat_s(dst, max_dst_chars, src, SIZE_MAX) int tcat_s(tchar* dst, size_t max_dst_chars, const tchar* src) { return tncat_s(dst, max_dst_chars, src, SIZE_MAX); } int tvsprintf_s(tchar* dst, size_t max_dst_chars, const tchar* fmt, va_list ap) { if(!dst || !fmt || max_dst_chars == 0) { errno = EINVAL; return -1; } #if OS_ANDROID // Workaround for https://code.google.com/p/android/issues/detail?id=109074 // (vswprintf doesn't null-terminate strings) memset(dst, 0, max_dst_chars * sizeof(tchar)); const int ret = tvsnprintf(dst, max_dst_chars, androidFormat(fmt).c_str(), ap); #else const int ret = tvsnprintf(dst, max_dst_chars, fmt, ap); #endif if(ret < 0 || ret >= int(max_dst_chars)) // not enough space { dst[0] = '\0'; return -1; } return ret; // negative if error, else # chars written (excluding '\0') } int tsprintf_s(tchar* buf, size_t max_chars, const tchar* fmt, ...) { va_list ap; va_start(ap, fmt); int len = tvsprintf_s(buf, max_chars, fmt, ap); va_end(ap); return len; } #endif // #if EMULATE_SECURE_CRT #undef tchar #undef T #undef tnlen #undef tncpy_s #undef tcpy_s #undef tncat_s #undef tcat_s #undef tcmp #undef tcpy #undef tvsnprintf #undef tvsprintf_s #undef tsprintf_s Index: ps/trunk/source/lib/snd.h =================================================================== --- ps/trunk/source/lib/snd.h (revision 19898) +++ ps/trunk/source/lib/snd.h (revision 19899) @@ -1,47 +1,47 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * sound card detection. */ #ifndef INCLUDED_SND #define INCLUDED_SND #include /** * description of sound card. **/ extern std::string snd_card; /** * sound driver identification and version. **/ extern std::string snd_drv_ver; /** * detect sound card and set the above information. **/ extern void snd_detect(); #endif // #ifndef INCLUDED_SND Index: ps/trunk/source/lib/svn_revision.h =================================================================== --- ps/trunk/source/lib/svn_revision.h (revision 19898) +++ ps/trunk/source/lib/svn_revision.h (revision 19899) @@ -1,23 +1,23 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ extern wchar_t svn_revision[]; Index: ps/trunk/source/lib/sysdep/arch/amd64/amd64.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/amd64/amd64.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/amd64/amd64.cpp (revision 19899) @@ -1,103 +1,103 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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" #if ARCH_AMD64 #include "lib/sysdep/cpu.h" #include "lib/sysdep/arch/amd64/amd64.h" void cpu_ConfigureFloatingPoint() { // 64-bit CPUs use SSE2 for all floating-point operations, so we // don't need to change the FPU control word. } #if MSC_VERSION // VC 2008 and ICC 12 differ in their declaration of _Interlocked* #if ICC_VERSION typedef __int64* P64; #else typedef volatile __int64* P64; #endif bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { const intptr_t initial = _InterlockedCompareExchange64((P64)location, newValue, expected); return initial == expected; } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { const i64 initial = _InterlockedCompareExchange64((P64)location, newValue, expected); return initial == expected; } intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { return _InterlockedExchangeAdd64((P64)location, increment); } #elif OS_MACOSX #include intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { cassert(sizeof(intptr_t) == sizeof(int64_t)); return OSAtomicAdd64Barrier(increment, (volatile int64_t*)location); } bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { cassert(sizeof(intptr_t) == sizeof(void*)); return OSAtomicCompareAndSwapPtrBarrier((void*)expected, (void*)newValue, (void* volatile*)location); } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { return OSAtomicCompareAndSwap64Barrier(expected, newValue, location); } #elif GCC_VERSION intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment) { return __sync_fetch_and_add(location, increment); } bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue) { return __sync_bool_compare_and_swap(location, expected, newValue); } #endif #endif // ARCH_AMD64 Index: ps/trunk/source/lib/sysdep/arch/ia32/ia32.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/ia32/ia32.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/ia32/ia32.h (revision 19899) @@ -1,46 +1,46 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * routines specific to IA-32 */ #ifndef INCLUDED_IA32 #define INCLUDED_IA32 #if !ARCH_IA32 # error "including ia32.h without ARCH_IA32=1" #endif /** * check if there is an IA-32 CALL instruction right before ret_addr. * @return INFO::OK if so and ERR::FAIL if not. * * also attempts to determine the call target. if that is possible * (directly addressed relative or indirect jumps), it is stored in * target, which is otherwise 0. * * this function is used for walking the call stack. **/ LIB_API Status ia32_GetCallTarget(void* ret_addr, void*& target); #endif // #ifndef INCLUDED_IA32 Index: ps/trunk/source/lib/sysdep/arch/x86_x64/cache.h =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/cache.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/cache.h (revision 19899) @@ -1,144 +1,144 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #ifndef INCLUDED_X86_X64_CACHE #define INCLUDED_X86_X64_CACHE namespace x86_x64 { struct Cache // POD (may be used before static constructors) { enum Type { // (values match the CPUID.4 definition) kNull, kData, kInstruction, kUnified // note: further values are "reserved" }; static const size_t maxLevels = 4; static const size_t fullyAssociative = 0xFF; // (CPUID.4 definition) /** * 1..maxLevels **/ size_t level; /** * never kNull **/ Type type; /** * if 0, the cache is disabled and all other values are zero **/ size_t numEntries; /** * NB: cache entries are lines, TLB entries are pages **/ size_t entrySize; /** * = fullyAssociative or the actual ways of associativity **/ size_t associativity; /** * how many logical processors share this cache? **/ size_t sharedBy; void Initialize(size_t level, Type type) { this->level = level; this->type = type; numEntries = 0; entrySize = 0; associativity = 0; sharedBy = 0; ENSURE(Validate()); } bool Validate() const { if(!(1 <= level && level <= maxLevels)) return false; if(type == kNull) return false; if(numEntries == 0) // disabled { if(entrySize != 0) return false; if(associativity != 0) return false; if(sharedBy != 0) return false; } else { if(entrySize == 0) return false; if(associativity == 0 || associativity > fullyAssociative) return false; if(sharedBy == 0) return false; } return true; } u64 TotalSize() const { return u64(numEntries)*entrySize; } }; enum IdxCache { // (AddCache relies upon this order) L1D = 1, L2D, L3D, L4D, L1I, L2I, L3I, L4I, TLB }; /** * @return 0 if idxCache >= TLB+numTLBs, otherwise a valid pointer to * a Cache whose numEntries is 0 if disabled / not present. **/ LIB_API const Cache* Caches(size_t idxCache); } // namespace x86_x64 #endif // #ifndef INCLUDED_X86_X64_CACHE Index: ps/trunk/source/lib/sysdep/arch/x86_x64/topology.cpp =================================================================== --- ps/trunk/source/lib/sysdep/arch/x86_x64/topology.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/arch/x86_x64/topology.cpp (revision 19899) @@ -1,474 +1,474 @@ -/* Copyright (c) 2011 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 * "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. */ /* * detection of CPU and cache topology */ #include "precompiled.h" #include "lib/sysdep/arch/x86_x64/topology.h" #include #include "lib/bits.h" #include "lib/module_init.h" #include "lib/sysdep/cpu.h" // ERR::CPU_FEATURE_MISSING #include "lib/sysdep/os_cpu.h" #include "lib/sysdep/numa.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" #include "lib/sysdep/arch/x86_x64/cache.h" #include "lib/sysdep/arch/x86_x64/apic.h" namespace topology { //--------------------------------------------------------------------------------------------------------------------- // detect *maximum* number of cores/packages/caches. // note: some of them may be disabled by the OS or BIOS. // note: Intel Appnote 485 assures us that they are uniform across packages. static size_t MaxCoresPerPackage() { // assume single-core unless one of the following applies: size_t maxCoresPerPackage = 1; x86_x64::CpuidRegs regs = { 0 }; switch(x86_x64::Vendor()) { case x86_x64::VENDOR_INTEL: regs.eax = 4; regs.ecx = 0; if(x86_x64::cpuid(®s)) maxCoresPerPackage = bits(regs.eax, 26, 31)+1; break; case x86_x64::VENDOR_AMD: regs.eax = 0x80000008; if(x86_x64::cpuid(®s)) maxCoresPerPackage = bits(regs.ecx, 0, 7)+1; break; default: break; } return maxCoresPerPackage; } static size_t MaxLogicalPerCore() { struct IsHyperthreadingCapable { bool operator()() const { // definitely not if(!x86_x64::Cap(x86_x64::CAP_HT)) return false; // multi-core AMD systems falsely set the HT bit for reasons of // compatibility. we'll just ignore it, because clearing it might // confuse other callers. if(x86_x64::Vendor() == x86_x64::VENDOR_AMD && x86_x64::Cap(x86_x64::CAP_AMD_CMP_LEGACY)) return false; return true; } }; if(IsHyperthreadingCapable()()) { x86_x64::CpuidRegs regs = { 0 }; regs.eax = 1; if(!x86_x64::cpuid(®s)) DEBUG_WARN_ERR(ERR::CPU_FEATURE_MISSING); const size_t logicalPerPackage = bits(regs.ebx, 16, 23); const size_t maxCoresPerPackage = MaxCoresPerPackage(); // cores ought to be uniform WRT # logical processors ENSURE(logicalPerPackage % maxCoresPerPackage == 0); const size_t maxLogicalPerCore = logicalPerPackage / maxCoresPerPackage; return maxLogicalPerCore; } else return 1; } static size_t MaxLogicalPerCache() { return x86_x64::Caches(x86_x64::L2D)->sharedBy; } //--------------------------------------------------------------------------------------------------------------------- // CPU topology interface // APIC IDs consist of variable-length bit fields indicating the logical, // core, package and cache IDs. Vol3a says they aren't guaranteed to be // contiguous, but that also applies to the individual fields. // for example, quad-core E5630 CPUs report 4-bit core IDs 0, 1, 6, 7. struct ApicField // POD { size_t operator()(size_t bits) const { return (bits >> shift) & mask; } size_t mask; // zero for zero-width fields size_t shift; }; struct CpuTopology // POD { size_t numProcessors; // total reported by OS ApicField logical; ApicField core; ApicField package; // how many are actually enabled size_t logicalPerCore; size_t coresPerPackage; size_t numPackages; }; static CpuTopology cpuTopology; static ModuleInitState cpuInitState; static Status InitCpuTopology() { cpuTopology.numProcessors = os_cpu_NumProcessors(); const size_t maxLogicalPerCore = MaxLogicalPerCore(); const size_t maxCoresPerPackage = MaxCoresPerPackage(); const size_t maxPackages = 256; // "enough" const size_t logicalWidth = ceil_log2(maxLogicalPerCore); const size_t coreWidth = ceil_log2(maxCoresPerPackage); const size_t packageWidth = ceil_log2(maxPackages); cpuTopology.logical.mask = bit_mask(logicalWidth); cpuTopology.core.mask = bit_mask(coreWidth); cpuTopology.package.mask = bit_mask(packageWidth); cpuTopology.logical.shift = 0; cpuTopology.core.shift = logicalWidth; cpuTopology.package.shift = logicalWidth + coreWidth; if(AreApicIdsReliable()) { struct NumUniqueValuesInField { size_t operator()(const ApicField& apicField) const { std::bitset values; for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { const ApicId apicId = ApicIdFromProcessor(processor); const size_t value = apicField(apicId); values.set(value); } return values.count(); } }; cpuTopology.logicalPerCore = NumUniqueValuesInField()(cpuTopology.logical); cpuTopology.coresPerPackage = NumUniqueValuesInField()(cpuTopology.core); cpuTopology.numPackages = NumUniqueValuesInField()(cpuTopology.package); } else // processor lacks an xAPIC, or IDs are invalid { struct MinPackages { size_t operator()(size_t maxCoresPerPackage, size_t maxLogicalPerCore) const { const size_t numNodes = numa_NumNodes(); const size_t logicalPerNode = PopulationCount(numa_ProcessorMaskFromNode(0)); // NB: some cores or logical processors may be disabled. const size_t maxLogicalPerPackage = maxCoresPerPackage*maxLogicalPerCore; const size_t minPackagesPerNode = DivideRoundUp(logicalPerNode, maxLogicalPerPackage); return minPackagesPerNode*numNodes; } }; // we can't differentiate between cores and logical processors. // since the former are less likely to be disabled, we seek the // maximum feasible number of cores and minimal number of packages: const size_t minPackages = MinPackages()(maxCoresPerPackage, maxLogicalPerCore); for(size_t numPackages = minPackages; numPackages <= cpuTopology.numProcessors; numPackages++) { if(cpuTopology.numProcessors % numPackages != 0) continue; const size_t logicalPerPackage = cpuTopology.numProcessors / numPackages; const size_t minCoresPerPackage = DivideRoundUp(logicalPerPackage, maxLogicalPerCore); for(size_t coresPerPackage = maxCoresPerPackage; coresPerPackage >= minCoresPerPackage; coresPerPackage--) { if(logicalPerPackage % coresPerPackage != 0) continue; const size_t logicalPerCore = logicalPerPackage / coresPerPackage; if(logicalPerCore <= maxLogicalPerCore) { ENSURE(cpuTopology.numProcessors == numPackages*coresPerPackage*logicalPerCore); cpuTopology.logicalPerCore = logicalPerCore; cpuTopology.coresPerPackage = coresPerPackage; cpuTopology.numPackages = numPackages; return INFO::OK; } } } DEBUG_WARN_ERR(ERR::LOGIC); // didn't find a feasible topology } return INFO::OK; } size_t NumPackages() { ModuleInit(&cpuInitState, InitCpuTopology); return cpuTopology.numPackages; } size_t CoresPerPackage() { ModuleInit(&cpuInitState, InitCpuTopology); return cpuTopology.coresPerPackage; } size_t LogicalPerCore() { ModuleInit(&cpuInitState, InitCpuTopology); return cpuTopology.logicalPerCore; } size_t LogicalFromApicId(ApicId apicId) { const size_t contiguousId = ContiguousIdFromApicId(apicId); return contiguousId % cpuTopology.logicalPerCore; } size_t CoreFromApicId(ApicId apicId) { const size_t contiguousId = ContiguousIdFromApicId(apicId); return (contiguousId / cpuTopology.logicalPerCore) % cpuTopology.coresPerPackage; } size_t PackageFromApicId(ApicId apicId) { const size_t contiguousId = ContiguousIdFromApicId(apicId); return contiguousId / (cpuTopology.logicalPerCore * cpuTopology.coresPerPackage); } ApicId ApicIdFromIndices(size_t idxLogical, size_t idxCore, size_t idxPackage) { ModuleInit(&cpuInitState, InitCpuTopology); size_t contiguousId = 0; ENSURE(idxPackage < cpuTopology.numPackages); contiguousId += idxPackage; contiguousId *= cpuTopology.coresPerPackage; ENSURE(idxCore < cpuTopology.coresPerPackage); contiguousId += idxCore; contiguousId *= cpuTopology.logicalPerCore; ENSURE(idxLogical < cpuTopology.logicalPerCore); contiguousId += idxLogical; ENSURE(contiguousId < cpuTopology.numProcessors); return ApicIdFromContiguousId(contiguousId); } //--------------------------------------------------------------------------------------------------------------------- // cache topology // note: Windows 2003 GetLogicalProcessorInformation provides similar // functionality but returns incorrect results. (it claims all cores in // an Intel Core2 Quad processor share a single L2 cache.) class CacheRelations { public: /** * add processor to the processor mask owned by cache identified by \ **/ void Add(u8 cacheId, size_t processor) { SharedCache* cache = Find(cacheId); if(!cache) { m_caches.push_back(cacheId); cache = &m_caches.back(); } cache->Add(processor); } size_t NumCaches() const { return m_caches.size(); } /** * store topology in an array (one entry per cache) of masks * representing the processors that share a cache. **/ void StoreProcessorMasks(uintptr_t* cachesProcessorMask) { for(size_t i = 0; i < NumCaches(); i++) cachesProcessorMask[i] = m_caches[i].ProcessorMask(); } private: /** * stores ID and tracks which processors share this cache **/ class SharedCache { public: SharedCache(u8 cacheId) : m_cacheId(cacheId), m_processorMask(0) { } bool Matches(u8 cacheId) const { return m_cacheId == cacheId; } void Add(size_t processor) { m_processorMask |= uintptr_t(1) << processor; } uintptr_t ProcessorMask() const { return m_processorMask; } private: u8 m_cacheId; uintptr_t m_processorMask; }; SharedCache* Find(u8 cacheId) { for(size_t i = 0; i < m_caches.size(); i++) { if(m_caches[i].Matches(cacheId)) return &m_caches[i]; } return 0; } std::vector m_caches; }; static void DetermineCachesProcessorMask(uintptr_t* cachesProcessorMask, size_t& numCaches) { CacheRelations cacheRelations; if(AreApicIdsReliable()) { const size_t numBits = ceil_log2(MaxLogicalPerCache()); const u8 cacheIdMask = u8((0xFF << numBits) & 0xFF); for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { const ApicId apicId = ApicIdFromProcessor(processor); const u8 cacheId = u8(apicId & cacheIdMask); cacheRelations.Add(cacheId, processor); } } else { for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { // assume each processor has exactly one cache with matching IDs const u8 cacheId = (u8)processor; cacheRelations.Add(cacheId, processor); } } numCaches = cacheRelations.NumCaches(); cacheRelations.StoreProcessorMasks(cachesProcessorMask); } static void DetermineProcessorsCache(const uintptr_t* cachesProcessorMask, size_t numCaches, size_t* processorsCache, size_t numProcessors) { for(size_t cache = 0; cache < numCaches; cache++) { // write to all entries that share this cache const uintptr_t processorMask = cachesProcessorMask[cache]; for(size_t processor = 0; processor < numProcessors; processor++) { if(IsBitSet(processorMask, processor)) { ENSURE(processorsCache[processor] == 0); processorsCache[processor] = cache; } } } } //--------------------------------------------------------------------------------------------------------------------- // cache topology interface struct CacheTopology // POD { size_t numCaches; size_t processorsCache[os_cpu_MaxProcessors]; uintptr_t cachesProcessorMask[os_cpu_MaxProcessors]; }; static CacheTopology cacheTopology; static ModuleInitState cacheInitState; static Status InitCacheTopology() { ModuleInit(&cpuInitState, InitCpuTopology); DetermineCachesProcessorMask(cacheTopology.cachesProcessorMask, cacheTopology.numCaches); DetermineProcessorsCache(cacheTopology.cachesProcessorMask, cacheTopology.numCaches, cacheTopology.processorsCache, os_cpu_NumProcessors()); return INFO::OK; } size_t NumCaches() { ModuleInit(&cacheInitState, InitCacheTopology); return cacheTopology.numCaches; } size_t CacheFromProcessor(size_t processor) { ModuleInit(&cacheInitState, InitCacheTopology); ENSURE(processor < os_cpu_NumProcessors()); return cacheTopology.processorsCache[processor]; } uintptr_t ProcessorMaskFromCache(size_t cache) { ModuleInit(&cacheInitState, InitCacheTopology); ENSURE(cache < cacheTopology.numCaches); return cacheTopology.cachesProcessorMask[cache]; } } // namespace topology Index: ps/trunk/source/lib/sysdep/cpu.cpp =================================================================== --- ps/trunk/source/lib/sysdep/cpu.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/cpu.cpp (revision 19899) @@ -1,70 +1,70 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * CPU and memory detection. */ #include "precompiled.h" #include "lib/sysdep/cpu.h" static const StatusDefinition cpuStatusDefinitions[] = { { ERR::CPU_FEATURE_MISSING, L"This CPU doesn't support a required feature" }, { ERR::CPU_UNKNOWN_OPCODE, L"Disassembly failed" }, { ERR::CPU_UNKNOWN_VENDOR, L"CPU vendor unknown" } }; STATUS_ADD_DEFINITIONS(cpuStatusDefinitions); // ensure the actual pointer size matches expectations on the most common // architectures (IA-32 and AMD64) - just in case the predefined macros // are wrong or misleading. #if ARCH_IA32 cassert(sizeof(void*) == 4); #elif ARCH_AMD64 cassert(sizeof(void*) == 8); cassert(sizeof(i64) == sizeof(intptr_t)); #endif cassert(sizeof(void*) == sizeof(intptr_t)); static void TestCAS64() { volatile i64 var = 1; cpu_CAS64(&var, 1ull, 2ull); ENSURE(var == 2ull); } static void TestAtomicAdd() { volatile intptr_t i1 = 1; intptr_t prev = cpu_AtomicAdd(&i1, 1); ENSURE(prev == 1); ENSURE(i1 == 2); } void cpu_Test() { TestCAS64(); TestAtomicAdd(); } Index: ps/trunk/source/lib/sysdep/compiler.h =================================================================== --- ps/trunk/source/lib/sysdep/compiler.h (revision 19898) +++ ps/trunk/source/lib/sysdep/compiler.h (revision 19899) @@ -1,127 +1,127 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * compiler-specific macros and fixes */ #ifndef INCLUDED_COMPILER #define INCLUDED_COMPILER // detect compiler and its version (0 if not present, otherwise // major*100 + minor). note that more than one *_VERSION may be // non-zero due to interoperability (e.g. ICC with MSC). // .. VC #ifdef _MSC_VER # define MSC_VERSION _MSC_VER #else # define MSC_VERSION 0 #endif // .. ICC (VC-compatible, GCC-compatible) #if defined(__INTEL_COMPILER) # define ICC_VERSION __INTEL_COMPILER #else # define ICC_VERSION 0 #endif // .. LCC (VC-compatible) #if defined(__LCC__) # define LCC_VERSION __LCC__ #else # define LCC_VERSION 0 #endif // .. GCC #ifdef __GNUC__ # define GCC_VERSION (__GNUC__*100 + __GNUC_MINOR__) #else # define GCC_VERSION 0 #endif // .. Clang/LLVM (GCC-compatible) // use Clang's feature checking macros to check for availability of features // http://clang.llvm.org/docs/LanguageExtensions.html#feature-checking-macros #ifdef __clang__ # define CLANG_VERSION (__clang_major__*100 + __clang_minor__) #else # define CLANG_VERSION 0 #endif // Clang/LLVM feature check macro compatibility #ifndef __has_feature # define __has_feature(x) 0 #endif // are PreCompiled Headers supported? #if MSC_VERSION # define HAVE_PCH 1 #elif defined(USING_PCH) # define HAVE_PCH 1 #else # define HAVE_PCH 0 #endif // check if compiling in pure C mode (not C++) with support for C99. // (this is more convenient than testing __STDC_VERSION__ directly) // // note: C99 provides several useful but disjunct bits of functionality. // unfortunately, most C++ compilers do not offer a complete implementation. // however, many of these features are likely to be added to C++, and/or are // already available as extensions. what we'll do is add a HAVE_ macro for // each feature and test those instead. they are set if HAVE_C99, or also if // the compiler happens to support something compatible. // // rationale: lying about __STDC_VERSION__ via Premake so as to enable support // for some C99 functions doesn't work. Mac OS X headers would then use the // restrict keyword, which is never supported by g++ (because that might // end up breaking valid C++98 programs). #define HAVE_C99 0 #ifdef __STDC_VERSION__ # if __STDC_VERSION__ >= 199901L # undef HAVE_C99 # define HAVE_C99 1 # endif #endif // Streaming SIMD Extensions (not supported by all GCC) // this only ascertains compiler support; use x86_x64::Cap to // check whether the instructions are supported by the CPU. #ifndef HAVE_SSE # if GCC_VERSION && defined(__SSE__) # define HAVE_SSE 1 # elif MSC_VERSION // also includes ICC # define HAVE_SSE 1 # else # define HAVE_SSE 0 # endif #endif #ifndef HAVE_SSE2 # if GCC_VERSION && defined(__SSE2__) # define HAVE_SSE2 1 # elif MSC_VERSION // also includes ICC # define HAVE_SSE2 1 # else # define HAVE_SSE2 0 # endif #endif #endif // #ifndef INCLUDED_COMPILER Index: ps/trunk/source/lib/sysdep/dir_watch.h =================================================================== --- ps/trunk/source/lib/sysdep/dir_watch.h (revision 19898) +++ ps/trunk/source/lib/sysdep/dir_watch.h (revision 19899) @@ -1,101 +1,101 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * portable directory change notification API. */ #ifndef INCLUDED_DIR_WATCH #define INCLUDED_DIR_WATCH #include "lib/os_path.h" struct DirWatch; typedef shared_ptr PDirWatch; /** * start watching a single directory for changes. * * @param path (must end in slash) * @param dirWatch opaque smart pointer to the watch state; used to * manage its lifetime (this is deemed more convenient than a * separate dir_watch_Remove interface). * * clients typically want to watch entire directory subtrees (e.g. a mod), * which is supported by Windows but not FAM. to reduce overhead, the * Windows backend always watches subtrees, but portable clients should * still add a watch for each subdirectory (the shared watch state is * reference-counted). * rationale: since the VFS has per-directory data structures, it is * convenient to store PDirWatch there instead of creating a second * tree structure here. **/ LIB_API Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch); class DirWatchNotification { public: enum EType { Created, Deleted, Changed }; DirWatchNotification(const OsPath& pathname, EType type) : pathname(pathname), type(type) { } const OsPath& Pathname() const { return pathname; } EType Type() const { return type; } private: OsPath pathname; EType type; }; typedef std::vector DirWatchNotifications; /** * return all pending directory watch notifications. * * @param notifications receives any pending notifications in unspecified order. * @return Status (INFO::OK doesn't imply notifications were returned) * * note: the run time of this function is independent of the number of * directory watches and number of files. * * rationale for a polling interface: users (e.g. the main game loop) * typically want to receive change notifications at a single point, * rather than deal with the complexity of asynchronous notifications. **/ LIB_API Status dir_watch_Poll(DirWatchNotifications& notifications); #endif // #ifndef INCLUDED_DIR_WATCH Index: ps/trunk/source/lib/sysdep/clipboard.h =================================================================== --- ps/trunk/source/lib/sysdep/clipboard.h (revision 19898) +++ ps/trunk/source/lib/sysdep/clipboard.h (revision 19899) @@ -1,38 +1,38 @@ -/* Copyright (c) 2011 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 * "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. */ #ifndef INCLUDED_SYSDEP_CLIPBOARD #define INCLUDED_SYSDEP_CLIPBOARD // "copy" text into the clipboard. replaces previous contents. extern Status sys_clipboard_set(const wchar_t* text); // allow "pasting" from clipboard. // @return current clipboard text or 0 if not representable as text. // callers are responsible for passing this pointer to sys_clipboard_free. extern wchar_t* sys_clipboard_get(); // free memory returned by sys_clipboard_get. // @param copy is ignored if 0. extern Status sys_clipboard_free(wchar_t* copy); #endif // #ifndef INCLUDED_SYSDEP_CLIPBOARD Index: ps/trunk/source/lib/sysdep/cursor.h =================================================================== --- ps/trunk/source/lib/sysdep/cursor.h (revision 19898) +++ ps/trunk/source/lib/sysdep/cursor.h (revision 19899) @@ -1,74 +1,74 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * mouse cursor */ #ifndef INCLUDED_SYSDEP_CURSOR #define INCLUDED_SYSDEP_CURSOR typedef void* sys_cursor; /** * Create a cursor from the given color image. * * @param w,h Image dimensions [pixels]. the maximum value is * implementation-defined; 32x32 is typical and safe. * @param bgra_img cursor image (BGRA format, bottom-up). * It is copied and can be freed after this call returns. * @param hx,hy 'hotspot', i.e. offset from the upper-left corner to the * position where mouse clicks are registered. * @param cursor Is 0 if the return code indicates failure, otherwise * a valid cursor that must be sys_cursor_free-ed when no longer needed. **/ extern Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor); /** * Create a transparent cursor (used to hide the system cursor). * * @param cursor is 0 if the return code indicates failure, otherwise * a valid cursor that must be sys_cursor_free-ed when no longer needed. **/ extern Status sys_cursor_create_empty(sys_cursor* cursor); /** * override the current system cursor. * * @param cursor can be 0 to restore the default. **/ extern Status sys_cursor_set(sys_cursor cursor); /** * destroy the indicated cursor and frees its resources. * * @param cursor if currently in use, the default cursor is restored first. **/ extern Status sys_cursor_free(sys_cursor cursor); /** * reset any cached cursor data. * on some systems, this is needed when resetting the SDL video subsystem. **/ extern Status sys_cursor_reset(); #endif // #ifndef INCLUDED_SYSDEP_CURSOR Index: ps/trunk/source/lib/sysdep/arch.h =================================================================== --- ps/trunk/source/lib/sysdep/arch.h (revision 19898) +++ ps/trunk/source/lib/sysdep/arch.h (revision 19899) @@ -1,82 +1,82 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * CPU architecture detection. */ #ifndef INCLUDED_ARCH #define INCLUDED_ARCH // detect target CPU architecture via predefined macros // .. IA-32 #if defined(_M_IX86) || defined(__X86__) || defined(_X86_) || defined(__i386__) || defined(__i386) || defined(i386) # define ARCH_IA32 1 #else # define ARCH_IA32 0 #endif // .. IA-64 #if defined(_M_IA64) || defined(__ia64__) # define ARCH_IA64 1 #else # define ARCH_IA64 0 #endif // .. AMD64 #if defined(_M_X64) || defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) # define ARCH_AMD64 1 #else # define ARCH_AMD64 0 #endif // .. Alpha #if defined(_M_ALPHA) || defined(__alpha__) || defined(__alpha) # define ARCH_ALPHA 1 #else # define ARCH_ALPHA 0 #endif // .. ARM #if defined(__arm__) # define ARCH_ARM 1 #else # define ARCH_ARM 0 #endif // .. AArch64 (ARM64) #if defined(__aarch64__) # define ARCH_AARCH64 1 #else # define ARCH_AARCH64 0 #endif // .. MIPS #if defined(__MIPS__) || defined(__mips__) || defined(__mips) # define ARCH_MIPS 1 #else # define ARCH_MIPS 0 #endif // ensure exactly one architecture has been detected #if (ARCH_IA32+ARCH_IA64+ARCH_AMD64+ARCH_ALPHA+ARCH_ARM+ARCH_AARCH64+ARCH_MIPS) != 1 # error "architecture not correctly detected (either none or multiple ARCH_* defined)" #endif // "X86_X64"-specific code requires either IA-32 or AMD64 #define ARCH_X86_X64 (ARCH_IA32|ARCH_AMD64) #endif // #ifndef INCLUDED_ARCH Index: ps/trunk/source/lib/sysdep/cpu.h =================================================================== --- ps/trunk/source/lib/sysdep/cpu.h (revision 19898) +++ ps/trunk/source/lib/sysdep/cpu.h (revision 19899) @@ -1,100 +1,100 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * CPU and memory detection. */ #ifndef INCLUDED_CPU #define INCLUDED_CPU #include "lib/sysdep/compiler.h" namespace ERR { const Status CPU_FEATURE_MISSING = -130000; const Status CPU_UNKNOWN_OPCODE = -130001; const Status CPU_UNKNOWN_VENDOR = -130002; } //----------------------------------------------------------------------------- // CPU detection /** * @return string identifying the CPU (usually a cleaned-up version of the * brand string) **/ LIB_API const char* cpu_IdentifierString(); //----------------------------------------------------------------------------- // lock-free support routines /** * add a signed value to a variable without the possibility of interference * from other threads/CPUs. * * @return the previous value. **/ LIB_API intptr_t cpu_AtomicAdd(volatile intptr_t* location, intptr_t increment); /** * atomic "compare and swap". * * @param location address of the word to compare and possibly overwrite * @param expected its expected value * @param newValue the value with which to replace it * @return false if the target word doesn't match the expected value, * otherwise true (also overwriting the contents of location) **/ LIB_API bool cpu_CAS(volatile intptr_t* location, intptr_t expected, intptr_t newValue); LIB_API bool cpu_CAS64(volatile i64* location, i64 expected, i64 newValue); /** * specialization of cpu_CAS for pointer types. this avoids error-prone * casting in user code. **/ template inline bool cpu_CAS(volatile T* location, T expected, T new_value) { return cpu_CAS((volatile intptr_t*)location, (intptr_t)expected, (intptr_t)new_value); } LIB_API void cpu_Test(); /** * pause in spin-wait loops, as a performance optimisation. **/ inline void cpu_Pause() { #if MSC_VERSION && ARCH_X86_X64 _mm_pause(); #elif GCC_VERSION && ARCH_X86_X64 __asm__ __volatile__( "rep; nop" : : : "memory" ); #endif } #endif // #ifndef INCLUDED_CPU Index: ps/trunk/source/lib/sysdep/filesystem.h =================================================================== --- ps/trunk/source/lib/sysdep/filesystem.h (revision 19898) +++ ps/trunk/source/lib/sysdep/filesystem.h (revision 19899) @@ -1,123 +1,123 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * wchar_t versions of POSIX filesystem functions */ #ifndef INCLUDED_SYSDEP_FILESYSTEM #define INCLUDED_SYSDEP_FILESYSTEM #include "lib/os_path.h" #include "lib/posix/posix_filesystem.h" // mode_t // // dirent.h // struct WDIR; struct wdirent { // note: SUSv3 describes this as a "char array" but of unspecified size. // we declare as a pointer to avoid having to copy the string. wchar_t* d_name; }; extern WDIR* wopendir(const OsPath& path); extern wdirent* wreaddir(WDIR*); // return status for the file returned by the last successful // wreaddir call from the given directory stream. // currently sets st_size, st_mode, and st_mtime; the rest are zeroed. // non-portable, but considerably faster than stat(). used by dir_ForEachSortedEntry. extern int wreaddir_stat_np(WDIR*, struct stat*); extern int wclosedir(WDIR*); // // fcntl.h // // transfer directly to/from user's buffer. // treated as a request to enable aio. #ifndef O_DIRECT // i.e. Windows or OS X #define O_DIRECT 0x10000000 // (value does not conflict with any current Win32 _O_* flags.) #endif // Win32 _wsopen_s does not open files in a manner compatible with waio. // if its aio_read/aio_write are to be used, waio_open must (also) be called. // calling both is possible but wasteful and unsafe, since it prevents // file sharing restrictions, which are the only way to prevent // exposing previous data as a side effect of waio_Preallocate. // applications shouldn't mix aio and synchronous I/O anyway, so we // want wopen to call either waio_open or _wsopen_s. // since waio requires callers to respect the FILE_FLAG_NO_BUFFERING // restrictions (sector alignment), and IRIX/BSD/Linux O_DIRECT imposes // similar semantics, we treat that flag as a request to enable aio. extern int wopen(const OsPath& pathname, int oflag); extern int wopen(const OsPath& pathname, int oflag, mode_t mode); extern int wclose(int fd); // // unistd.h // // waio requires offsets and sizes to be multiples of the sector size. // to allow arbitrarily sized files, we truncate them after I/O. // however, ftruncate cannot be used since it is also subject to the // sector-alignment requirement. instead, the file must be closed and // this function called. LIB_API int wtruncate(const OsPath& pathname, off_t length); LIB_API int wunlink(const OsPath& pathname); LIB_API int wrmdir(const OsPath& path); // // stdio.h // LIB_API int wrename(const OsPath& pathnameOld, const OsPath& pathnameNew); // // stdlib.h // LIB_API OsPath wrealpath(const OsPath& pathname); // // sys/stat.h // LIB_API int wstat(const OsPath& pathname, struct stat* buf); LIB_API int wmkdir(const OsPath& path, mode_t mode); #endif // #ifndef INCLUDED_SYSDEP_FILESYSTEM Index: ps/trunk/source/lib/sysdep/os/android/android.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/android/android.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/android/android.cpp (revision 19899) @@ -1,120 +1,120 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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 "lib/sysdep/sysdep.h" #include "lib/sysdep/cursor.h" #include "lib/external_libraries/libsdl.h" Status sys_clipboard_set(const wchar_t* UNUSED(text)) { return INFO::OK; } wchar_t* sys_clipboard_get() { return NULL; } Status sys_clipboard_free(wchar_t* UNUSED(copy)) { return INFO::OK; } namespace gfx { Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq) { #warning TODO: implement gfx::GetVideoMode properly for Android if(xres) *xres = 800; if(yres) *yres = 480; if(bpp) *bpp = 32; if(freq) *freq = 0; return INFO::OK; } } // stub implementation of sys_cursor* functions // note: do not return ERR_NOT_IMPLEMENTED or similar because that // would result in WARN_ERRs. Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor) { UNUSED2(w); UNUSED2(h); UNUSED2(hx); UNUSED2(hy); UNUSED2(bgra_img); *cursor = 0; return INFO::OK; } // returns a dummy value representing an empty cursor Status sys_cursor_create_empty(sys_cursor* cursor) { *cursor = (void*)1; // any non-zero value, since the cursor NULL has special meaning return INFO::OK; } // replaces the current system cursor with the one indicated. need only be // called once per cursor; pass 0 to restore the default. Status sys_cursor_set(sys_cursor cursor) { if (cursor) // dummy empty cursor SDL_ShowCursor(SDL_DISABLE); else // restore default cursor SDL_ShowCursor(SDL_ENABLE); return INFO::OK; } // destroys the indicated cursor and frees its resources. if it is // currently the system cursor, the default cursor is restored first. Status sys_cursor_free(sys_cursor cursor) { // bail now to prevent potential confusion below; there's nothing to do. if(!cursor) return INFO::OK; SDL_ShowCursor(SDL_ENABLE); return INFO::OK; } Status sys_cursor_reset() { return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/bsd/dir_watch.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/bsd/dir_watch.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/bsd/dir_watch.cpp (revision 19899) @@ -1,36 +1,36 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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 "lib/sysdep/dir_watch.h" // stub implementations Status dir_watch_Add(const OsPath& UNUSED(path), PDirWatch& UNUSED(dirWatch)) { return INFO::OK; } Status dir_watch_Poll(DirWatchNotifications& UNUSED(notifications)) { return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/linux/linux.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/linux/linux.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/linux/linux.cpp (revision 19899) @@ -1,54 +1,54 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "lib/sysdep/sysdep.h" #include "lib/sysdep/os/unix/unix_executable_pathname.h" static bool getPathFromProc(char* buffer, size_t length) { int pos = readlink("/proc/self/exe", buffer, length-1); if (pos <= 0) return false; buffer[pos] = '\0'; char* endOfPath = strrchr(buffer, '/'); if (endOfPath == NULL) return false; ++endOfPath; *endOfPath = '\0'; return true; } OsPath sys_ExecutablePathname() { // Check /proc for the path char pathBuffer[PATH_MAX]; if (getPathFromProc(pathBuffer, sizeof(pathBuffer))) return pathBuffer; return unix_ExecutablePathname(); } Index: ps/trunk/source/lib/sysdep/os/osx/osx.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx.cpp (revision 19899) @@ -1,186 +1,186 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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 "lib/lib.h" #include "lib/sysdep/sysdep.h" #include "lib/sysdep/gfx.h" #include "lib/utf8.h" #include "osx_bundle.h" #include "osx_pasteboard.h" #include #include // MAC_OS_X_VERSION_MIN_REQUIRED #include #include // _NSGetExecutablePath // Ignore deprecation warnings for 10.5 backwards compatibility #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" Status sys_clipboard_set(const wchar_t* text) { Status ret = INFO::OK; std::string str = utf8_from_wstring(text); bool ok = osx_SendStringToPasteboard(str); if (!ok) ret = ERR::FAIL; return ret; } wchar_t* sys_clipboard_get() { wchar_t* ret = NULL; std::string str; bool ok = osx_GetStringFromPasteboard(str); if (ok) { // TODO: this is yucky, why are we passing around wchar_t*? std::wstring wstr = wstring_from_utf8(str); size_t len = wcslen(wstr.c_str()); ret = (wchar_t*)malloc((len+1)*sizeof(wchar_t)); std::copy(wstr.c_str(), wstr.c_str()+len, ret); ret[len] = 0; } return ret; } Status sys_clipboard_free(wchar_t* copy) { free(copy); return INFO::OK; } namespace gfx { Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq) { if(xres) *xres = (int)CGDisplayPixelsWide(kCGDirectMainDisplay); if(yres) *yres = (int)CGDisplayPixelsHigh(kCGDirectMainDisplay); if(bpp) { #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // CGDisplayBitsPerPixel was deprecated in OS X 10.6 if (CGDisplayCopyDisplayMode != NULL) { CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay); CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(currentMode); if (CFStringCompare(pixelEncoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) *bpp = 32; else if (CFStringCompare(pixelEncoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) *bpp = 16; else if (CFStringCompare(pixelEncoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) *bpp = 8; else // error *bpp = 0; // We're responsible for this CFRelease(pixelEncoding); CGDisplayModeRelease(currentMode); } else { #endif // fallback to 10.5 API CFDictionaryRef currentMode = CGDisplayCurrentMode(kCGDirectMainDisplay); CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(currentMode, kCGDisplayBitsPerPixel); CFNumberGetValue(num, kCFNumberIntType, bpp); #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 } #endif } if(freq) { #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 if (CGDisplayCopyDisplayMode != NULL) { CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(kCGDirectMainDisplay); *freq = (int)CGDisplayModeGetRefreshRate(currentMode); // We're responsible for this CGDisplayModeRelease(currentMode); } else { #endif // fallback to 10.5 API CFDictionaryRef currentMode = CGDisplayCurrentMode(kCGDirectMainDisplay); CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(currentMode, kCGDisplayRefreshRate); CFNumberGetValue(num, kCFNumberIntType, freq); #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 } #endif } return INFO::OK; } Status GetMonitorSize(int& width_mm, int& height_mm) { CGSize screenSize = CGDisplayScreenSize(kCGDirectMainDisplay); if (screenSize.width == 0 || screenSize.height == 0) return ERR::FAIL; width_mm = screenSize.width; height_mm = screenSize.height; return INFO::OK; } } // namespace gfx OsPath sys_ExecutablePathname() { OsPath path; // On OS X we might be a bundle, return the bundle path as the executable name, // i.e. /path/to/0ad.app instead of /path/to/0ad.app/Contents/MacOS/pyrogenesis if (osx_IsAppBundleValid()) { path = osx_GetBundlePath(); ENSURE(!path.empty()); } else { char temp[PATH_MAX]; u32 size = PATH_MAX; if (_NSGetExecutablePath(temp, &size) == 0) { char name[PATH_MAX]; realpath(temp, name); path = OsPath(name); } } return path; } #pragma GCC diagnostic pop // restore user flags Index: ps/trunk/source/lib/sysdep/os/osx/osx_pasteboard.mm =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_pasteboard.mm (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_pasteboard.mm (revision 19899) @@ -1,95 +1,95 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #import #import // MAC_OS_X_VERSION_MIN_REQUIRED #import #import #import "osx_pasteboard.h" bool osx_GetStringFromPasteboard(std::string& out) { NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSString* string = nil; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // As of 10.6, pasteboards can hold multiple items if ([pasteboard respondsToSelector: @selector(readObjectsForClasses:)]) { NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil]; NSDictionary* options = [NSDictionary dictionary]; NSArray* copiedItems = [pasteboard readObjectsForClasses:classes options:options]; // We only need to support a single item, so grab the first string if (copiedItems != nil && [copiedItems count] > 0) string = [copiedItems objectAtIndex:0]; else return false; // No strings found on pasteboard } else { #endif // fallback to 10.5 API // Verify that there is a string available for us NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil]; if ([pasteboard availableTypeFromArray:types] != nil) string = [pasteboard stringForType:NSStringPboardType]; else return false; // No strings found on pasteboard #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 } #endif if (string != nil) out = std::string([string UTF8String]); else return false; // fail return true; // success } bool osx_SendStringToPasteboard(const std::string& string) { // We're only working with strings, so we don't need to lazily write // anything (otherwise we'd need to set up an owner and data provider) NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSString* type; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 if ([pasteboard respondsToSelector: @selector(clearContents)]) { type = NSPasteboardTypeString; [pasteboard clearContents]; } else { #endif // fallback to 10.5 API type = NSStringPboardType; NSArray* types = [NSArray arrayWithObjects: type, nil]; // Roughly equivalent to clearContents followed by addTypes:owner [pasteboard declareTypes:types owner:nil]; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 } #endif // May raise a NSPasteboardCommunicationException BOOL ok = [pasteboard setString:[NSString stringWithUTF8String:string.c_str()] forType:type]; return ok == YES; } Index: ps/trunk/source/lib/sysdep/os/osx/osx_sys_cursor.mm =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_sys_cursor.mm (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_sys_cursor.mm (revision 19899) @@ -1,104 +1,104 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ #import "precompiled.h" #import "lib/sysdep/cursor.h" #import #import #import //TODO: make sure these are threadsafe Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor) { NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:0 pixelsWide:w pixelsHigh:h bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:w*4 bitsPerPixel:0]; if (!bitmap) { debug_printf("sys_cursor_create: Error creating NSBitmapImageRep!\n"); return ERR::FAIL; } u8* planes[5]; [bitmap getBitmapDataPlanes:planes]; const u8* bgra = static_cast(bgra_img); u8* dst = planes[0]; for (int i = 0; i < w*h*4; i += 4) { dst[i] = bgra[i+2]; dst[i+1] = bgra[i+1]; dst[i+2] = bgra[i]; dst[i+3] = bgra[i+3]; } NSImage* image = [[NSImage alloc] init]; if (!image) { [bitmap release]; debug_printf("sys_cursor_create: Error creating NSImage!\n"); return ERR::FAIL; } [image addRepresentation:bitmap]; [bitmap release]; NSCursor* impl = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(hx, hy)]; [image release]; if (!impl) { debug_printf("sys_cursor_create: Error creating NSCursor!\n"); return ERR::FAIL; } *cursor = static_cast(impl); return INFO::OK; } Status sys_cursor_free(sys_cursor cursor) { NSCursor* impl = static_cast(cursor); [impl release]; return INFO::OK; } Status sys_cursor_create_empty(sys_cursor* cursor) { static u8 empty_bgra[] = {0, 0, 0, 0}; sys_cursor_create(1, 1, reinterpret_cast(empty_bgra), 0, 0, cursor); return INFO::OK; } Status sys_cursor_set(sys_cursor cursor) { NSCursor* impl = static_cast(cursor); [impl set]; return INFO::OK; } Status sys_cursor_reset() { return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/unix/udbg.h =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/udbg.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/udbg.h (revision 19899) @@ -1,30 +1,30 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // sysdep/unix/udbg.h: included from sysdep/debug.h and sysdep/unix/debug.cpp #ifndef INCLUDED_UDBG #define INCLUDED_UDBG extern void udbg_launch_debugger(); #endif // #ifndef INCLUDED_UDBG Index: ps/trunk/source/lib/sysdep/os/unix/unix_executable_pathname.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/unix_executable_pathname.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/unix_executable_pathname.cpp (revision 19899) @@ -1,73 +1,73 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "lib/sysdep/sysdep.h" #define GNU_SOURCE #include "mocks/dlfcn.h" #include "mocks/unistd.h" #include OsPath unix_ExecutablePathname() { // Find the executable's filename Dl_info dl_info; memset(&dl_info, 0, sizeof(dl_info)); if (!T::dladdr((void *)sys_ExecutablePathname, &dl_info) || !dl_info.dli_fname) return OsPath(); const char* path = dl_info.dli_fname; // If this looks like an absolute path, use realpath to get the normalized // path (with no '.' or '..') if (path[0] == '/') { char resolved[PATH_MAX]; if (!realpath(path, resolved)) return OsPath(); return resolved; } // If this looks like a relative path, resolve against cwd and use realpath if (strchr(path, '/')) { char cwd[PATH_MAX]; if (!T::getcwd(cwd, PATH_MAX)) return OsPath(); char absolute[PATH_MAX]; int ret = snprintf(absolute, PATH_MAX, "%s/%s", cwd, path); if (ret < 0 || ret >= PATH_MAX) return OsPath(); // path too long, or other error char resolved[PATH_MAX]; if (!realpath(absolute, resolved)) return OsPath(); return resolved; } // If it's not a path at all, i.e. it's just a filename, we'd // probably have to search through PATH to find it. // That's complex and should be uncommon, so don't bother. return OsPath(); } Index: ps/trunk/source/lib/sysdep/numa.h =================================================================== --- ps/trunk/source/lib/sysdep/numa.h (revision 19898) +++ ps/trunk/source/lib/sysdep/numa.h (revision 19899) @@ -1,74 +1,74 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_NUMA #define INCLUDED_NUMA //----------------------------------------------------------------------------- // node topology /** * @return number of NUMA "nodes" (i.e. groups of CPUs with local memory). **/ LIB_API size_t numa_NumNodes(); /** * @param processor * @return node number (zero-based) to which \ belongs. **/ LIB_API size_t numa_NodeFromProcessor(size_t processor); /** * @param node * @return bit-mask of all processors constituting \. **/ LIB_API uintptr_t numa_ProcessorMaskFromNode(size_t node); //----------------------------------------------------------------------------- // memory /** * @param node * @return bytes of memory available for allocation on \. **/ LIB_API size_t numa_AvailableMemory(size_t node); /** * @return the ratio between maximum and minimum times that one processor * from each node required to fill a globally allocated array. * in other words, this is the maximum slowdown for NUMA-oblivious * memory accesses. Microsoft guidelines require it to be <= 3. **/ LIB_API double numa_Factor(); /** * @return an indication of whether memory pages are node-interleaved. * * note: this requires ACPI access, which may not be available on * least-permission accounts. the default is to return false so as * not to cause callers to panic and trigger performance warnings. **/ LIB_API bool numa_IsMemoryInterleaved(); #endif // #ifndef INCLUDED_NUMA Index: ps/trunk/source/lib/sysdep/os/bsd/bsd.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/bsd/bsd.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/bsd/bsd.cpp (revision 19899) @@ -1,31 +1,31 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "lib/sysdep/sysdep.h" #include "lib/sysdep/os/unix/unix_executable_pathname.h" OsPath sys_ExecutablePathname() { return unix_ExecutablePathname(); } Index: ps/trunk/source/lib/sysdep/os/linux/ldbg.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/linux/ldbg.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/linux/ldbg.cpp (revision 19899) @@ -1,153 +1,153 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // Note: This used to use BFD to get more useful debugging information. // (See SVN r8270 if you want that code.) // That requires binutils at runtime, which hits some bugs and library versioning // issues on some Linux distros. // The debugging info reported by this code with BFD wasn't especially useful, // and it's easy enough to get people to run in gdb if we want a proper backtrace. // So we now go with the simple approach of not using BFD. #include "precompiled.h" #include "lib/sysdep/sysdep.h" #include "lib/debug.h" #if OS_ANDROID // Android NDK doesn't support backtrace() // TODO: use unwind.h or similar? void* debug_GetCaller(void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { return NULL; } Status debug_DumpStack(wchar_t* UNUSED(buf), size_t UNUSED(max_chars), void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { return ERR::NOT_SUPPORTED; } Status debug_ResolveSymbol(void* UNUSED(ptr_of_interest), wchar_t* UNUSED(sym_name), wchar_t* UNUSED(file), int* UNUSED(line)) { return ERR::NOT_SUPPORTED; } #else #include void* debug_GetCaller(void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { // bt[0] == this function // bt[1] == our caller // bt[2] == the first caller they are interested in // HACK: we currently don't support lastFuncToSkip (would require debug information), // instead just returning the caller of the function calling us void *bt[3]; int bt_size = backtrace(bt, 3); if (bt_size < 3) return NULL; return bt[2]; } Status debug_DumpStack(wchar_t* buf, size_t max_chars, void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { static const size_t N_FRAMES = 16; void *bt[N_FRAMES]; int bt_size = 0; wchar_t *bufpos = buf; wchar_t *bufend = buf + max_chars; bt_size = backtrace(bt, ARRAY_SIZE(bt)); // Assumed max length of a single print-out static const size_t MAX_OUT_CHARS = 1024; for (size_t i = 0; (int)i < bt_size && bufpos+MAX_OUT_CHARS < bufend; i++) { wchar_t file[DEBUG_FILE_CHARS]; wchar_t symbol[DEBUG_SYMBOL_CHARS]; int line; int len; if (debug_ResolveSymbol(bt[i], symbol, file, &line) == 0) { if (file[0]) len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p) %ls:%d %ls\n", bt[i], file, line, symbol); else len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p) %ls\n", bt[i], symbol); } else { len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p)\n", bt[i]); } if (len < 0) { // MAX_OUT_CHARS exceeded, realistically this was caused by some // mindbogglingly long symbol name... replace the end with an // ellipsis and a newline memcpy(&bufpos[MAX_OUT_CHARS-6], L"...\n", 5*sizeof(wchar_t)); len = MAX_OUT_CHARS; } bufpos += len; } return INFO::OK; } Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line) { if (sym_name) *sym_name = 0; if (file) *file = 0; if (line) *line = 0; char** symbols = backtrace_symbols(&ptr_of_interest, 1); if (symbols) { swprintf_s(sym_name, DEBUG_SYMBOL_CHARS, L"%hs", symbols[0]); free(symbols); // (Note that this will usually return a pretty useless string, // because we compile with -fvisibility=hidden and there won't be // any exposed symbols for backtrace_symbols to report.) return INFO::OK; } else { return ERR::FAIL; } } #endif void debug_SetThreadName(char const* UNUSED(name)) { // Currently unimplemented } Index: ps/trunk/source/lib/sysdep/os/osx/odbg.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/odbg.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/odbg.cpp (revision 19899) @@ -1,49 +1,49 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // note: the BFD stuff *could* be used on other platforms, if we saw the // need for it. #include "precompiled.h" #include "lib/sysdep/sysdep.h" #include "lib/debug.h" void* debug_GetCaller(void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { return NULL; } Status debug_DumpStack(wchar_t* UNUSED(buf), size_t UNUSED(max_chars), void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { return ERR::NOT_SUPPORTED; } Status debug_ResolveSymbol(void* UNUSED(ptr_of_interest), wchar_t* UNUSED(sym_name), wchar_t* UNUSED(file), int* UNUSED(line)) { return ERR::NOT_SUPPORTED; } void debug_SetThreadName(char const* UNUSED(name)) { // Currently unimplemented } Index: ps/trunk/source/lib/sysdep/os/osx/osx_pasteboard.h =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_pasteboard.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_pasteboard.h (revision 19899) @@ -1,47 +1,47 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ #ifndef OSX_PASTEBOARD_H #define OSX_PASTEBOARD_H /** * @file * C++ interface to Cocoa implementation for pasteboards */ /** * Get a string from the pasteboard * * @param[out] out pasteboard string in UTF-8 encoding, if found * @return true if string was found on pasteboard and successfully retrieved, false otherwise */ bool osx_GetStringFromPasteboard(std::string& out); /** * Store a string on the pasteboard * * @param[in] string string to store in UTF-8 encoding * @return true if string was successfully sent to pasteboard, false on error */ bool osx_SendStringToPasteboard(const std::string& string); #endif // OSX_PASTEBOARD_H Index: ps/trunk/source/lib/sysdep/os/osx/osx_stl_fixes.h =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_stl_fixes.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_stl_fixes.h (revision 19899) @@ -1,121 +1,121 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #ifndef OSX_STL_FIXES_H #define OSX_STL_FIXES_H #include #include #include // MAC_OS_X_VERSION_* /** * This file adds some explicit template instantiations that are * declared external on 10.6+ SDKs but are missing from stdc++ on 10.5 * (this causes a failure to load due to missing symbols on 10.5) **/ #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 _GLIBCXX_BEGIN_NAMESPACE(std) // ostream_insert.h: # if _GLIBCXX_EXTERN_TEMPLATE template ostream& __ostream_insert(ostream&, const char*, streamsize); # ifdef _GLIBCXX_USE_WCHAR_T template wostream& __ostream_insert(wostream&, const wchar_t*, streamsize); # endif # endif // istream.tcc: # if _GLIBCXX_EXTERN_TEMPLATE template istream& istream::_M_extract(unsigned short&); template istream& istream::_M_extract(unsigned int&); template istream& istream::_M_extract(long&); template istream& istream::_M_extract(unsigned long&); template istream& istream::_M_extract(bool&); # ifdef _GLIBCXX_USE_LONG_LONG template istream& istream::_M_extract(long long&); template istream& istream::_M_extract(unsigned long long&); # endif template istream& istream::_M_extract(float&); template istream& istream::_M_extract(double&); template istream& istream::_M_extract(long double&); template istream& istream::_M_extract(void*&); template class basic_iostream; # ifdef _GLIBCXX_USE_WCHAR_T template wistream& wistream::_M_extract(unsigned short&); template wistream& wistream::_M_extract(unsigned int&); template wistream& wistream::_M_extract(long&); template wistream& wistream::_M_extract(unsigned long&); template wistream& wistream::_M_extract(bool&); # ifdef _GLIBCXX_USE_LONG_LONG template wistream& wistream::_M_extract(long long&); template wistream& wistream::_M_extract(unsigned long long&); # endif template wistream& wistream::_M_extract(float&); template wistream& wistream::_M_extract(double&); template wistream& wistream::_M_extract(long double&); template wistream& wistream::_M_extract(void*&); template class basic_iostream; # endif # endif // ostream.tcc: # if _GLIBCXX_EXTERN_TEMPLATE template ostream& ostream::_M_insert(long); template ostream& ostream::_M_insert(unsigned long); template ostream& ostream::_M_insert(bool); # ifdef _GLIBCXX_USE_LONG_LONG template ostream& ostream::_M_insert(long long); template ostream& ostream::_M_insert(unsigned long long); # endif template ostream& ostream::_M_insert(double); template ostream& ostream::_M_insert(long double); template ostream& ostream::_M_insert(const void*); # ifdef _GLIBCXX_USE_WCHAR_T template wostream& wostream::_M_insert(long); template wostream& wostream::_M_insert(unsigned long); template wostream& wostream::_M_insert(bool); # ifdef _GLIBCXX_USE_LONG_LONG template wostream& wostream::_M_insert(long long); template wostream& wostream::_M_insert(unsigned long long); # endif template wostream& wostream::_M_insert(double); template wostream& wostream::_M_insert(long double); template wostream& wostream::_M_insert(const void*); # endif # endif _GLIBCXX_END_NAMESPACE #endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 #endif // OSX_STL_FIXES_H Index: ps/trunk/source/lib/sysdep/os/unix/udbg.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/udbg.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/udbg.cpp (revision 19899) @@ -1,114 +1,114 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* udbg.cpp This file contains debug helpers that are common for all unix systems. See linux/ldbg.cpp for the linux-specific stuff (Using BFD and backtrace() for symbol lookups and backtraces) */ #include "precompiled.h" #include #include #include #include "lib/timer.h" #include "lib/sysdep/sysdep.h" #include "lib/debug.h" #include "lib/utf8.h" Status debug_CaptureContext(void* UNUSED(context)) { // (not needed unless/until we support stack traces) return INFO::SKIPPED; } void debug_break() { kill(getpid(), SIGTRAP); } #define DEBUGGER_WAIT 3 #define DEBUGGER_CMD "gdb" #define DEBUGGER_ARG_FORMAT "--pid=%d" #define DEBUGGER_BREAK_AFTER_WAIT 0 /* Start the debugger and tell it to attach to the current process/thread (called by display_error) */ void udbg_launch_debugger() { pid_t orgpid=getpid(); pid_t ret=fork(); if (ret == 0) { // Child Process: exec() gdb (Debugger), set to attach to old fork char buf[16]; snprintf(buf, 16, DEBUGGER_ARG_FORMAT, orgpid); int ret=execlp(DEBUGGER_CMD, DEBUGGER_CMD, buf, NULL); // In case of success, we should never get here anyway, though... if (ret != 0) { perror("Debugger launch failed"); } } else if (ret > 0) { // Parent (original) fork: debug_printf("Sleeping until debugger attaches.\nPlease wait.\n"); sleep(DEBUGGER_WAIT); } else // fork error, ret == -1 { perror("Debugger launch: fork failed"); } } #if OS_ANDROID #include void debug_puts(const char* text) { __android_log_print(ANDROID_LOG_WARN, "pyrogenesis", "%s", text); } #else void debug_puts(const char* text) { printf("%s", text); fflush(stdout); } #endif int debug_IsPointerBogus(const void* UNUSED(p)) { // TODO: maybe this should do some checks return false; } Index: ps/trunk/source/lib/sysdep/os/unix/unix.h =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/unix.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/unix.h (revision 19899) @@ -1,26 +1,26 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef sysdep_unix_unix_h__ #define sysdep_unix_unix_h__ #endif Index: ps/trunk/source/lib/sysdep/gfx.h =================================================================== --- ps/trunk/source/lib/sysdep/gfx.h (revision 19898) +++ ps/trunk/source/lib/sysdep/gfx.h (revision 19899) @@ -1,70 +1,70 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * graphics card detection. */ #ifndef INCLUDED_GFX #define INCLUDED_GFX namespace gfx { /** * @return description of graphics card, * or L"" if unknown. **/ LIB_API std::wstring CardName(); /** * @return string describing the graphics driver and its version, * or L"" if unknown. **/ LIB_API std::wstring DriverInfo(); /** * not implemented **/ LIB_API size_t MemorySizeMiB(); /** * (useful for choosing a new video mode) * * @param xres, yres (optional out) resolution [pixels] * @param bpp (optional out) bits per pixel * @param freq (optional out) vertical refresh rate [Hz] * @return Status (if negative, outputs were left unchanged) **/ LIB_API Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq); /** * (useful for determining aspect ratio) * * @param width_mm (out) screen width [mm] * @param height_mm (out) screen height [mm] * @return Status (if if negative, outputs were left unchanged) **/ LIB_API Status GetMonitorSize(int& width_mm, int& height_mm); } // namespace gfx #endif // #ifndef INCLUDED_GFX Index: ps/trunk/source/lib/sysdep/os/bsd/bdbg.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/bsd/bdbg.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/bsd/bdbg.cpp (revision 19899) @@ -1,129 +1,129 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ // Note: This used to use BFD to get more useful debugging information. // (See SVN r8270 if you want that code.) // That requires binutils at runtime, which hits some bugs and library versioning // issues on some Linux distros. // The debugging info reported by this code with BFD wasn't especially useful, // and it's easy enough to get people to run in gdb if we want a proper backtrace. // So we now go with the simple approach of not using BFD. #include "precompiled.h" #include "lib/sysdep/sysdep.h" #include "lib/debug.h" #include void* debug_GetCaller(void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { // bt[0] == this function // bt[1] == our caller // bt[2] == the first caller they are interested in // HACK: we currently don't support lastFuncToSkip (would require debug information), // instead just returning the caller of the function calling us void *bt[3]; int bt_size = backtrace(bt, 3); if (bt_size < 3) return NULL; return bt[2]; } Status debug_DumpStack(wchar_t* buf, size_t max_chars, void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip)) { static const size_t N_FRAMES = 16; void *bt[N_FRAMES]; int bt_size = 0; wchar_t *bufpos = buf; wchar_t *bufend = buf + max_chars; bt_size = backtrace(bt, ARRAY_SIZE(bt)); // Assumed max length of a single print-out static const size_t MAX_OUT_CHARS = 1024; for (size_t i = 0; (int)i < bt_size && bufpos+MAX_OUT_CHARS < bufend; i++) { wchar_t file[DEBUG_FILE_CHARS]; wchar_t symbol[DEBUG_SYMBOL_CHARS]; int line; int len; if (debug_ResolveSymbol(bt[i], symbol, file, &line) == 0) { if (file[0]) len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p) %ls:%d %ls\n", bt[i], file, line, symbol); else len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p) %ls\n", bt[i], symbol); } else { len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p)\n", bt[i]); } if (len < 0) { // MAX_OUT_CHARS exceeded, realistically this was caused by some // mindbogglingly long symbol name... replace the end with an // ellipsis and a newline memcpy(&bufpos[MAX_OUT_CHARS-6], L"...\n", 5*sizeof(wchar_t)); len = MAX_OUT_CHARS; } bufpos += len; } return INFO::OK; } Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line) { if (sym_name) *sym_name = 0; if (file) *file = 0; if (line) *line = 0; char** symbols = backtrace_symbols(&ptr_of_interest, 1); if (symbols) { swprintf_s(sym_name, DEBUG_SYMBOL_CHARS, L"%hs", symbols[0]); free(symbols); // (Note that this will usually return a pretty useless string, // because we compile with -fvisibility=hidden and there won't be // any exposed symbols for backtrace_symbols to report.) return INFO::OK; } else { return ERR::FAIL; } } void debug_SetThreadName(char const* UNUSED(name)) { // Currently unimplemented } Index: ps/trunk/source/lib/sysdep/os/linux/lcpu.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/linux/lcpu.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/linux/lcpu.cpp (revision 19899) @@ -1,197 +1,197 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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 "lib/sysdep/os_cpu.h" #include "lib/alignment.h" #include "lib/bits.h" #include "lib/module_init.h" #if OS_LINUX #include "valgrind.h" #endif size_t os_cpu_NumProcessors() { static size_t numProcessors; if(numProcessors == 0) { // Valgrind reports the number of real CPUs, but only emulates a single CPU. // That causes problems when we expect all those CPUs to be distinct, so // just pretend there's only one CPU if (RUNNING_ON_VALGRIND) numProcessors = 1; else { long res = sysconf(_SC_NPROCESSORS_CONF); ENSURE(res != -1); numProcessors = (size_t)res; } } return numProcessors; } uintptr_t os_cpu_ProcessorMask() { static uintptr_t processorMask; if(!processorMask) processorMask = bit_mask(os_cpu_NumProcessors()); return processorMask; } size_t os_cpu_PageSize() { static size_t pageSize; if(!pageSize) pageSize = (size_t)sysconf(_SC_PAGESIZE); return pageSize; } size_t os_cpu_LargePageSize() { // assume they're unsupported. return 0; } size_t os_cpu_QueryMemorySize() { const uint64_t memorySize = (uint64_t)sysconf(_SC_PHYS_PAGES) * os_cpu_PageSize(); return size_t(memorySize / MiB); } size_t os_cpu_MemoryAvailable() { const uint64_t memoryAvailableBytes = (uint64_t)sysconf(_SC_AVPHYS_PAGES) * os_cpu_PageSize(); const size_t memoryAvailable = size_t(memoryAvailableBytes / MiB); return memoryAvailable; } #if OS_ANDROID // the current Android NDK (r7-crystax-4) doesn't support sched_setaffinity, // so provide a stub implementation instead uintptr_t os_cpu_SetThreadAffinityMask(uintptr_t UNUSED(processorMask)) { // not yet implemented return os_cpu_ProcessorMask(); } #else // glibc __CPU_SETSIZE=1024 is smaller than required on some Linux (4096), // but the CONFIG_NR_CPUS in a header may not reflect the actual kernel, // so we have to detect the limit at runtime. // (see http://trac.wildfiregames.com/ticket/547 for additional information) static size_t maxCpus; static bool IsMaxCpusSufficient() { const size_t setSize = CPU_ALLOC_SIZE(maxCpus); cpu_set_t* set = CPU_ALLOC(maxCpus); ENSURE(set); const int ret = sched_getaffinity(0, setSize, set); CPU_FREE(set); if(ret == 0) return true; ENSURE(errno == EINVAL); return false; } static Status DetectMaxCpus() { // the most I have ever heard of is CONFIG_NR_CPUS=4096, // and even that limit should be enough for years and years. for(maxCpus = 64; maxCpus <= 65536; maxCpus *= 2) { if(IsMaxCpusSufficient()) return INFO::OK; } return ERR::FAIL; } uintptr_t os_cpu_SetThreadAffinityMask(uintptr_t processorMask) { static ModuleInitState maxCpusInitState; (void)ModuleInit(&maxCpusInitState, DetectMaxCpus); const size_t setSize = CPU_ALLOC_SIZE(maxCpus); cpu_set_t* set = CPU_ALLOC(maxCpus); ENSURE(set); uintptr_t previousProcessorMask = 0; { int ret = sched_getaffinity(0, setSize, set); ENSURE(ret == 0); for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { if(CPU_ISSET_S(processor, setSize, set)) previousProcessorMask |= uintptr_t(1) << processor; } } CPU_ZERO_S(setSize, set); for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { if(IsBitSet(processorMask, processor)) CPU_SET_S(processor, setSize, set); } int ret = sched_setaffinity(0, setSize, set); ENSURE(ret == 0); // (The process gets migrated immediately by the setaffinity call) CPU_FREE(set); return previousProcessorMask; } #endif Status os_cpu_CallByEachCPU(OsCpuCallback cb, uintptr_t cbData) { const uintptr_t previousAffinity = os_cpu_SetThreadAffinityMask(os_cpu_ProcessorMask()); for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { const uintptr_t processorMask = uintptr_t(1) << processor; os_cpu_SetThreadAffinityMask(processorMask); cb(processor, cbData); } (void)os_cpu_SetThreadAffinityMask(previousAffinity); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/osx/ocpu.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/ocpu.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/ocpu.cpp (revision 19899) @@ -1,121 +1,121 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/os_cpu.h" #include "lib/alignment.h" #include "lib/bits.h" #include size_t os_cpu_NumProcessors() { static size_t numProcessors; if(numProcessors == 0) { // Mac OS X doesn't have sysconf(_SC_NPROCESSORS_CONF) int mib[]={CTL_HW, HW_NCPU}; int ncpus; size_t len = sizeof(ncpus); int ret = sysctl(mib, 2, &ncpus, &len, NULL, 0); ENSURE(ret != -1); numProcessors = (size_t)ncpus; } return numProcessors; } uintptr_t os_cpu_ProcessorMask() { static uintptr_t processorMask; if(!processorMask) processorMask = bit_mask(os_cpu_NumProcessors()); return processorMask; } size_t os_cpu_PageSize() { static size_t pageSize; if(!pageSize) pageSize = (size_t)sysconf(_SC_PAGESIZE); return pageSize; } size_t os_cpu_LargePageSize() { // assume they're unsupported. return 0; } size_t os_cpu_QueryMemorySize() { size_t memorySize = 0; size_t len = sizeof(memorySize); // Argh, the API doesn't seem to be const-correct /*const*/ int mib[2] = { CTL_HW, HW_PHYSMEM }; sysctl(mib, 2, &memorySize, &len, 0, 0); memorySize /= MiB; return memorySize; } size_t os_cpu_MemoryAvailable() { size_t memoryAvailable = 0; size_t len = sizeof(memoryAvailable); // Argh, the API doesn't seem to be const-correct /*const*/ int mib[2] = { CTL_HW, HW_USERMEM }; sysctl(mib, 2, &memoryAvailable, &len, 0, 0); memoryAvailable /= MiB; return memoryAvailable; } uintptr_t os_cpu_SetThreadAffinityMask(uintptr_t UNUSED(processorMask)) { // not yet implemented. when doing so, see http://developer.apple.com/releasenotes/Performance/RN-AffinityAPI/ return os_cpu_ProcessorMask(); } Status os_cpu_CallByEachCPU(OsCpuCallback cb, uintptr_t cbData) { for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { const uintptr_t processorMask = uintptr_t(1) << processor; os_cpu_SetThreadAffinityMask(processorMask); cb(processor, cbData); } return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/osx/osx_bundle.mm =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_bundle.mm (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_bundle.mm (revision 19899) @@ -1,132 +1,132 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #import // MAC_OS_X_VERSION_MIN_REQUIRED #import #import #import "osx_bundle.h" #define STRINGIZE2(id) # id #define STRINGIZE(id) STRINGIZE2(id) // Pass the bundle identifier string as a build option #ifdef BUNDLE_IDENTIFIER static const char* BUNDLE_ID_STR = STRINGIZE(BUNDLE_IDENTIFIER); #else static const char* BUNDLE_ID_STR = ""; #endif bool osx_IsAppBundleValid() { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // Check for the existence of bundle with correct identifier property // (can't just use mainBundle because that can return a bundle reference // even for a loose binary!) NSBundle *bundle = [NSBundle bundleWithIdentifier: [NSString stringWithUTF8String: BUNDLE_ID_STR]]; [pool drain]; return bundle != nil; } std::string osx_GetBundlePath() { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; std::string path; NSBundle *bundle = [NSBundle bundleWithIdentifier: [NSString stringWithUTF8String: BUNDLE_ID_STR]]; if (bundle != nil) { NSString *pathStr; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Retrieve NSURL and convert to POSIX path, then get C-string // encoded as UTF-8, and use it to construct std::string // NSURL:path "If the receiver does not conform to RFC 1808, returns nil." if ([bundle respondsToSelector: @selector(bundleURL)]) pathStr = [[bundle bundleURL] path]; else #endif pathStr = [bundle bundlePath]; if (pathStr != nil) path = std::string([pathStr UTF8String]); } [pool drain]; return path; } std::string osx_GetBundleResourcesPath() { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; std::string path; NSBundle *bundle = [NSBundle bundleWithIdentifier: [NSString stringWithUTF8String: BUNDLE_ID_STR]]; if (bundle != nil) { NSString *pathStr; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Retrieve NSURL and convert to POSIX path, then get C-string // encoded as UTF-8, and use it to construct std::string // NSURL:path "If the receiver does not conform to RFC 1808, returns nil." if ([bundle respondsToSelector: @selector(resourceURL)]) pathStr = [[bundle resourceURL] path]; else #endif pathStr = [bundle resourcePath]; if (pathStr != nil) path = std::string([pathStr UTF8String]); } [pool drain]; return path; } std::string osx_GetBundleFrameworksPath() { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; std::string path; NSBundle *bundle = [NSBundle bundleWithIdentifier: [NSString stringWithUTF8String: BUNDLE_ID_STR]]; if (bundle != nil) { NSString *pathStr; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Retrieve NSURL and convert to POSIX path, then get C-string // encoded as UTF-8, and use it to construct std::string // NSURL:path "If the receiver does not conform to RFC 1808, returns nil." if ([bundle respondsToSelector: @selector(privateFrameworksURL)]) pathStr = [[bundle privateFrameworksURL] path]; else #endif pathStr = [bundle privateFrameworksPath]; if (pathStr != nil) path = std::string([pathStr UTF8String]); } [pool drain]; return path; } Index: ps/trunk/source/lib/sysdep/os/osx/osx_paths.mm =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_paths.mm (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_paths.mm (revision 19899) @@ -1,73 +1,73 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #import // MAC_OS_X_VERSION_MIN_REQUIRED #import #import #import "osx_paths.h" // Helper function static std::string getUserDirectoryPath(NSSearchPathDirectory directory) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; std::string result; NSArray* paths; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Returns array of NSURL objects which are preferred for file paths if ([NSFileManager instancesRespondToSelector:@selector(URLsForDirectory)]) paths = [[NSFileManager defaultManager] URLsForDirectory:directory inDomains:NSUserDomainMask]; else #endif // fallback to 10.5 API paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, true); if ([paths count] > 0) { NSString* pathStr; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Retrieve first NSURL and convert to POSIX path, then get C-string // encoded as UTF-8, and use it to construct std::string // NSURL:path "If the receiver does not conform to RFC 1808, returns nil." if ([NSFileManager instancesRespondToSelector:@selector(URLsForDirectory)]) pathStr = [[paths objectAtIndex:0] path]; else #endif // fallback to 10.5 API pathStr = [paths objectAtIndex:0]; if (pathStr != nil) result = std::string([pathStr UTF8String]); } [pool drain]; return result; } std::string osx_GetAppSupportPath() { return getUserDirectoryPath(NSApplicationSupportDirectory); } std::string osx_GetCachesPath() { return getUserDirectoryPath(NSCachesDirectory); } Index: ps/trunk/source/lib/sysdep/os/osx/osx_sys_version.mm =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_sys_version.mm (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_sys_version.mm (revision 19899) @@ -1,69 +1,69 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #import // MAC_OS_X_VERSION_MIN_REQUIRED #import #import #import #import "osx_sys_version.h" void GetSystemVersion(int &major, int &minor, int &bugfix) { // grand central dispatch only available on 10.6+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 if (dispatch_once != nil) { // sensible default static int mMajor = 10; static int mMinor = 8; static int mBugfix = 0; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString* versionString = [[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"] objectForKey:@"ProductVersion"]; NSArray* versions = [versionString componentsSeparatedByString:@"."]; check(versions.count >= 2); if (versions.count >= 1) mMajor = [[versions objectAtIndex:0] integerValue]; if (versions.count >= 2) mMinor = [[versions objectAtIndex:1] integerValue]; if (versions.count >= 3) mBugfix = [[versions objectAtIndex:2] integerValue]; }); major = mMajor; minor = mMinor; bugfix = mBugfix; } else { #endif // fallback to 10.5 API // just return 10.5.0, it's unlikely we support an earlier OS // and unlikely we care about bugfix versions major = 10; minor = 5; bugfix = 0; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 } #endif } Index: ps/trunk/source/lib/sysdep/os/unix/unix.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/unix.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/unix.cpp (revision 19899) @@ -1,380 +1,380 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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/utf8.h" #include "lib/sysdep/sysdep.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 bool sys_IsDebuggerPresent() { return false; } std::wstring sys_WideFromArgv(const char* argv_i) { // argv is usually UTF-8 on Linux, unsure about OS X.. return wstring_from_utf8(argv_i); } // 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 } #if OS_MACOSX || OS_ANDROID static ErrorReactionInternal try_gui_display_error(const wchar_t* text, bool manual_break, bool allow_suppress, bool no_continue) { // TODO: implement this, in a way that doesn't rely on X11 // and doesn't occasionally cause crazy errors like // "The process has forked and you cannot use this // CoreFoundation functionality safely. You MUST exec()." return ERI_NOT_IMPLEMENTED; } #else static ErrorReactionInternal try_gui_display_error(const wchar_t* text, bool manual_break, bool allow_suppress, bool no_continue) { // We'll run xmessage via fork/exec. // To avoid bad interaction between fork and pthreads, the child process // should only call async-signal-safe functions before exec. // So prepare all the child's data in advance, before forking: Status 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 all here // and will clean them up later (except in the child process where // memory leaks don't matter) 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"), strdup(buttons), strdup("-default"), strdup(defaultButton), strdup(message.c_str()), NULL }; pid_t cpid = fork(); if(cpid == -1) { for(char* const* a = argv; *a; ++a) free(*a); return ERI_NOT_IMPLEMENTED; } if(cpid == 0) { // This is the child process // Set ASCII charset, to avoid font warnings from xmessage setenv("LC_ALL", "C", 1); // NOTE: setenv is not async-signal-safe, so we shouldn't really use // it here (it might want some mutex that was held by another thread // in the parent process and that will never be freed within this // process). But setenv/getenv are not guaranteed reentrant either, // and this error-reporting function might get called from a non-main // thread, so we can't just call setenv before forking as it might // break the other threads. And we can't just clone environ manually // inside the parent thread and use execve, because other threads might // be calling setenv and will break our iteration over environ. // In the absence of a good easy solution, and given that this is only // an error-reporting function and shouldn't get called frequently, // we'll just do setenv after the fork and hope that it fails // extremely rarely. execv(cmd, argv); // If exec returns, it failed //fprintf(stderr, "Error running %s: %d\n", cmd, errno); exit(-1); } // This is the parent process // Avoid memory leaks for(char* const* a = argv; *a; ++a) free(*a); int status = 0; waitpid(cpid, &status, 0); // If it didn't exist successfully, fall back to the non-GUI prompt if(!WIFEXITED(status)) return ERI_NOT_IMPLEMENTED; switch(WEXITSTATUS(status)) { case 103: // Debugger udbg_launch_debugger(); //-fallthrough case 102: // Break if(manual_break) return ERI_BREAK; debug_break(); return ERI_CONTINUE; case 100: // Continue if(!no_continue) return ERI_CONTINUE; // continue isn't allowed, so this was invalid input. return ERI_NOT_IMPLEMENTED; case 101: // Suppress if(allow_suppress) return ERI_SUPPRESS; // suppress isn't allowed, so this was invalid input. return ERI_NOT_IMPLEMENTED; case 104: // Exit abort(); return ERI_EXIT; // placebo; never reached } // Unexpected return value - fall back to the non-GUI prompt return ERI_NOT_IMPLEMENTED; } #endif ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags) { debug_printf("%s\n\n", utf8_from_wstring(text).c_str()); 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 ErrorReactionInternal ret = try_gui_display_error(text, manual_break, allow_suppress, no_continue); if (ret != ERI_NOT_IMPLEMENTED) return ret; #if OS_ANDROID // Android has no easy way to get user input here, // so continue or exit automatically if(no_continue) abort(); else return ERI_CONTINUE; #else // 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 ERI_BREAK; debug_break(); return ERI_CONTINUE; case 'c': case 'C': if(!no_continue) return ERI_CONTINUE; // continue isn't allowed, so this was invalid input. loop again. break; case 's': case 'S': if(allow_suppress) return ERI_SUPPRESS; // suppress isn't allowed, so this was invalid input. loop again. break; case 'e': case 'E': abort(); return ERI_EXIT; // placebo; never reached } } #endif } Status sys_StatusDescription(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; } // 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? #if OS_ANDROID #warning TODO: sys_get_user_name: do something more appropriate and more thread-safe char* buf = getlogin(); if (buf) return std::wstring(buf, buf + strlen(buf)); #else char buf[256]; if (getlogin_r(buf, ARRAY_SIZE(buf)) == 0) return std::wstring(buf, buf + strlen(buf)); #endif return L""; } Status 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; } Status sys_get_proxy_config(const std::wstring& UNUSED(url), std::wstring& UNUSED(proxy)) { return INFO::SKIPPED; } Status 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("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; } } FILE* sys_OpenFile(const OsPath& pathname, const char* mode) { return fopen(OsString(pathname).c_str(), mode); } Index: ps/trunk/source/lib/sysdep/os/unix/unuma.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/unuma.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/unuma.cpp (revision 19899) @@ -1,59 +1,59 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/numa.h" #include "lib/bits.h" #include "lib/sysdep/os_cpu.h" size_t numa_NumNodes() { return 1; } size_t numa_NodeFromProcessor(size_t UNUSED(processor)) { return 0; } uintptr_t numa_ProcessorMaskFromNode(size_t node) { ENSURE(node == 0); return bit_mask(os_cpu_NumProcessors()); } size_t numa_AvailableMemory(size_t node) { ENSURE(node == 0); return os_cpu_MemoryAvailable(); } double numa_Factor() { return 1.0; } bool numa_IsMemoryInterleaved() { return false; } Index: ps/trunk/source/lib/sysdep/gfx.cpp =================================================================== --- ps/trunk/source/lib/sysdep/gfx.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/gfx.cpp (revision 19899) @@ -1,101 +1,101 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * graphics card detection. */ #include "precompiled.h" #include "lib/sysdep/gfx.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #if OS_WIN # include "lib/sysdep/os/win/wgfx.h" #endif namespace gfx { std::wstring CardName() { // GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards. // On top of that WMI can cause crashes with Nvidia Optimus and some netbooks // see http://trac.wildfiregames.com/ticket/1952 // http://trac.wildfiregames.com/ticket/1575 wchar_t cardName[128]; const char* vendor = (const char*)glGetString(GL_VENDOR); const char* renderer = (const char*)glGetString(GL_RENDERER); // (happens if called before ogl_Init or between glBegin and glEnd.) if(!vendor || !renderer) return L""; swprintf_s(cardName, ARRAY_SIZE(cardName), L"%hs %hs", vendor, renderer); // remove crap from vendor names. (don't dare touch the model name - // it's too risky, there are too many different strings) #define SHORTEN(what, charsToKeep)\ if(!wcsncmp(cardName, what, ARRAY_SIZE(what)-1))\ memmove(cardName+charsToKeep, cardName+ARRAY_SIZE(what)-1, (wcslen(cardName)-(ARRAY_SIZE(what)-1)+1)*sizeof(wchar_t)); SHORTEN(L"ATI Technologies Inc.", 3); SHORTEN(L"NVIDIA Corporation", 6); SHORTEN(L"S3 Graphics", 2); // returned by EnumDisplayDevices SHORTEN(L"S3 Graphics, Incorporated", 2); // returned by GL_VENDOR #undef SHORTEN return cardName; } std::wstring DriverInfo() { std::wstring driverInfo; #if OS_WIN driverInfo = wgfx_DriverInfo(); if(driverInfo.empty()) #endif { const char* version = (const char*)glGetString(GL_VERSION); if(version) { // add "OpenGL" to differentiate this from the real driver version // (returned by platform-specific detect routines). driverInfo = std::wstring(L"OpenGL ") + std::wstring(version, version+strlen(version)); } } if(driverInfo.empty()) return L"(unknown)"; return driverInfo; } size_t MemorySizeMiB() { // TODO: not implemented, SDL_GetVideoInfo only works on some platforms in SDL 1.2 // and no replacement is available in SDL2, and it can crash with Nvidia Optimus // see http://trac.wildfiregames.com/ticket/2145 debug_warn(L"MemorySizeMiB not implemented"); return 0; } } // namespace gfx Index: ps/trunk/source/lib/sysdep/os/bsd/bcpu.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/bsd/bcpu.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/bsd/bcpu.cpp (revision 19899) @@ -1,125 +1,125 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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 "lib/sysdep/os_cpu.h" #include "lib/alignment.h" #include "lib/bits.h" #include "lib/module_init.h" #include "valgrind.h" #include #include size_t os_cpu_NumProcessors() { static size_t numProcessors; if(numProcessors == 0) { // Valgrind reports the number of real CPUs, but only emulates a single CPU. // That causes problems when we expect all those CPUs to be distinct, so // just pretend there's only one CPU if (RUNNING_ON_VALGRIND) numProcessors = 1; else { long res = sysconf(_SC_NPROCESSORS_CONF); ENSURE(res != -1); numProcessors = (size_t)res; } } return numProcessors; } uintptr_t os_cpu_ProcessorMask() { static uintptr_t processorMask; if(!processorMask) processorMask = bit_mask(os_cpu_NumProcessors()); return processorMask; } size_t os_cpu_PageSize() { static size_t pageSize; if(!pageSize) pageSize = (size_t)sysconf(_SC_PAGESIZE); return pageSize; } size_t os_cpu_LargePageSize() { // assume they're unsupported. return 0; } size_t os_cpu_QueryMemorySize() { size_t memorySize = 0; size_t len = sizeof(memorySize); // Argh, the API doesn't seem to be const-correct /*const*/ int mib[2] = { CTL_HW, HW_PHYSMEM }; sysctl(mib, 2, &memorySize, &len, 0, 0); memorySize /= MiB; return memorySize; } size_t os_cpu_MemoryAvailable() { size_t memoryAvailable = 0; size_t len = sizeof(memoryAvailable); // Argh, the API doesn't seem to be const-correct /*const*/ int mib[2] = { CTL_HW, HW_USERMEM }; sysctl(mib, 2, &memoryAvailable, &len, 0, 0); memoryAvailable /= MiB; return memoryAvailable; } uintptr_t os_cpu_SetThreadAffinityMask(uintptr_t UNUSED(processorMask)) { // not yet implemented return os_cpu_ProcessorMask(); } Status os_cpu_CallByEachCPU(OsCpuCallback cb, uintptr_t cbData) { for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { const uintptr_t processorMask = uintptr_t(1) << processor; os_cpu_SetThreadAffinityMask(processorMask); cb(processor, cbData); } return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/linux/dir_watch_inotify.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/linux/dir_watch_inotify.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/linux/dir_watch_inotify.cpp (revision 19899) @@ -1,271 +1,271 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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 "lib/sysdep/dir_watch.h" #include "lib/sysdep/sysdep.h" #include "ps/CLogger.h" #include #include #include struct NotificationEvent { std::string filename; uint32_t code; int wd; }; // To avoid deadlocks and slow synchronous reads, it's necessary to use a // separate thread for reading events from inotify. // So we just spawn a thread to push events into this list, then swap it out // when someone calls dir_watch_Poll. // (We assume STL memory allocation is thread-safe.) static std::vector g_notifications; static pthread_t g_event_loop_thread; // Mutex must wrap all accesses of g_notifications // while the event loop thread is running static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; // trool; -1 = init failed and all operations will be aborted silently. // this is so that each dir_* call doesn't complain if the system's // inotify is broken or unavailable. static int initialized = 0; // Inotify file descriptor static int inotifyfd; // With inotify, using a map seems to be a good alternative to FAM's userdata typedef std::map DirWatchMap; static DirWatchMap g_paths; struct DirWatch { DirWatch() : reqnum(-1) { } ~DirWatch() { ENSURE(initialized > 0); inotify_rm_watch(inotifyfd, reqnum); } OsPath path; int reqnum; }; // for atexit static void inotify_deinit() { close(inotifyfd); #ifdef __BIONIC__ #warning TODO: pthread_cancel not supported on Bionic #else pthread_cancel(g_event_loop_thread); #endif // NOTE: POSIX threads are (by default) only cancellable inside particular // functions (like 'select'), so this should safely terminate while it's // in select/etc (and won't e.g. cancel while it's holding the // mutex) // Wait for the thread to finish pthread_join(g_event_loop_thread, NULL); } static void inotify_event_loop_process_events() { // Buffer for reading the events. // Need to be careful about overflow here. char buffer[65535] = {0}; // Event iterator ssize_t buffer_i = 0; // Total size of all the events ssize_t r; // Size & struct for the current event size_t event_size; struct inotify_event *pevent; r = read(inotifyfd, buffer, 65535); if(r <= 0) return; while(buffer_i < r) { NotificationEvent ne; pevent = (struct inotify_event *) &buffer[buffer_i]; event_size = offsetof(struct inotify_event, name) + pevent->len; ne.wd = pevent->wd; ne.filename = pevent->name; ne.code = pevent->mask; pthread_mutex_lock(&g_mutex); g_notifications.push_back(ne); pthread_mutex_unlock(&g_mutex); buffer_i += event_size; } } static void* inotify_event_loop(void*) { while(true) { fd_set fdrset; FD_ZERO(&fdrset); FD_SET(inotifyfd, &fdrset); errno = 0; // Block with select until there's events waiting while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0) { if(errno == EINTR) { // interrupted - try again FD_ZERO(&fdrset); FD_SET(inotifyfd, &fdrset); } else if(errno == EBADF) { // probably just lost the connection to inotify - kill the thread debug_printf("inotify_event_loop: Invalid file descriptor inotifyfd=%d\n", inotifyfd); return NULL; } else { // oops debug_printf("inotify_event_loop: select error errno=%d\n", errno); return NULL; } errno = 0; } if(FD_ISSET(inotifyfd, &fdrset)) inotify_event_loop_process_events(); } } Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch) { char resolved[PATH_MAX + 1]; // init already failed; don't try again or complain if(initialized == -1) return ERR::FAIL; // NOWARN if(!initialized) { errno = 0; if((inotifyfd = inotify_init()) < 0) { // Check for error ? int err = errno; initialized = -1; LOGERROR("Error initializing inotify file descriptor; hotloading will be disabled, errno=%d", err); errno = err; return StatusFromErrno(); // NOWARN } errno = 0; int ret = pthread_create(&g_event_loop_thread, NULL, &inotify_event_loop, NULL); if (ret != 0) { initialized = -1; LOGERROR("Error creating inotify event loop thread; hotloading will be disabled, err=%d", ret); errno = ret; return StatusFromErrno(); // NOWARN } initialized = 1; atexit(inotify_deinit); } PDirWatch tmpDirWatch(new DirWatch); errno = 0; int wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE); if (wd < 0) WARN_RETURN(StatusFromErrno()); dirWatch.swap(tmpDirWatch); dirWatch->path = path; dirWatch->reqnum = wd; g_paths.insert(std::make_pair(wd, dirWatch)); return INFO::OK; } Status dir_watch_Poll(DirWatchNotifications& notifications) { if(initialized == -1) return ERR::FAIL; // NOWARN if(!initialized) // XXX Fix Atlas instead of suppressing the warning return ERR::FAIL; //WARN_RETURN(ERR::LOGIC); std::vector polled_notifications; pthread_mutex_lock(&g_mutex); g_notifications.swap(polled_notifications); pthread_mutex_unlock(&g_mutex); for(size_t i = 0; i < polled_notifications.size(); ++i) { DirWatchNotification::EType type; // TODO: code is actually a bitmask, so this is slightly incorrect switch(polled_notifications[i].code) { case IN_CLOSE_WRITE: type = DirWatchNotification::Changed; break; case IN_CREATE: type = DirWatchNotification::Created; break; case IN_DELETE: type = DirWatchNotification::Deleted; break; default: continue; } DirWatchMap::iterator it = g_paths.find(polled_notifications[i].wd); if(it != g_paths.end()) { OsPath filename = Path(OsString(it->second->path).append(polled_notifications[i].filename)); notifications.push_back(DirWatchNotification(filename, type)); } else { debug_printf("dir_watch_Poll: Notification with invalid watch descriptor wd=%d\n", polled_notifications[i].wd); } } // nothing new; try again later return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/osx/dir_watch.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/dir_watch.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/dir_watch.cpp (revision 19899) @@ -1,202 +1,202 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "lib/sysdep/dir_watch.h" #include "lib/file/file_system.h" #include "osx_sys_version.h" #include "lib/os_path.h" #include "lib/file/file.h" #include "lib/posix/posix_filesystem.h" // mode_t #include // MAC_OS_X_VERSION_MIN_REQUIRED #include #include #include "ps/CLogger.h" static FSEventStreamRef g_Stream = NULL; struct DirWatch { OsPath path; int reqnum; }; typedef std::vector DirWatchMap; static DirWatchMap g_Paths; static DirWatchMap g_RootPaths; static DirWatchNotifications g_QueuedDirs; static bool CanRunNotifications() { int major = 0; int minor = 0; int bugfix = 0; GetSystemVersion( major, minor, bugfix); if ((major == 10 && minor >= 7) || major >= 11) return true; return false; } #if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 #define kFSEventStreamCreateFlagFileEvents 0x00000010 #define kFSEventStreamEventFlagItemIsFile 0x00010000 #define kFSEventStreamEventFlagItemRemoved 0x00000200 #define kFSEventStreamEventFlagItemRenamed 0x00000800 #define kFSEventStreamEventFlagItemCreated 0x00000100 #define kFSEventStreamEventFlagItemModified 0x00001000 #endif static void fsevent_callback( ConstFSEventStreamRef UNUSED(streamRef), void * UNUSED(clientCallBackInfo), size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId UNUSED(eventIds)[] ) { unsigned long i; char **paths = (char **)eventPaths; for (i=0; ipath.string().c_str(), eventPath.string().c_str() ) ) isWatched = true; } if ( ! isWatched ) return; OsPath filename = Path( eventPath.string().c_str() ); if ( eventType & kFSEventStreamEventFlagItemIsFile) { if ( eventType & kFSEventStreamEventFlagItemRemoved ) g_QueuedDirs.push_back(DirWatchNotification( filename.string().c_str(), DirWatchNotification::Deleted )); else if ( eventType & kFSEventStreamEventFlagItemRenamed ) g_QueuedDirs.push_back(DirWatchNotification( filename.string().c_str(), DirWatchNotification::Deleted )); else if ( eventType & kFSEventStreamEventFlagItemCreated ) g_QueuedDirs.push_back(DirWatchNotification( filename.string().c_str(), DirWatchNotification::Created )); else if ( eventType & kFSEventStreamEventFlagItemModified ) g_QueuedDirs.push_back(DirWatchNotification( filename.string().c_str(), DirWatchNotification::Changed )); } } } static FSEventStreamRef CreateEventStream( DirWatchMap path ) { if ( ( g_Stream == NULL ) && CanRunNotifications() && !path.empty() ) { CFStringRef* pathLists = (CFStringRef*)malloc( sizeof(CFStringRef*) * path.size() ); int index = 0; for ( DirWatchMap::iterator it = path.begin() ; it != path.end(); ++it) { pathLists[index] = CFStringCreateWithFileSystemRepresentation( NULL, OsString(it->path).c_str()); index++; } CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)pathLists, index, NULL); FSEventStreamContext *callbackInfo = NULL; FSEventStreamRef stream = FSEventStreamCreate(NULL, &fsevent_callback, callbackInfo, pathsToWatch, kFSEventStreamEventIdSinceNow, 1.0, kFSEventStreamCreateFlagFileEvents ); CFRelease( pathsToWatch ); free( pathLists ); FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); if (!FSEventStreamStart(stream)) debug_warn(L"event_loop FSEventStreamStart failed!"); else return stream; } return NULL; } static void DeleteEventStream() { if ( g_Stream != NULL ) { FSEventStreamStop(g_Stream); FSEventStreamInvalidate(g_Stream); FSEventStreamRelease(g_Stream); g_Stream = NULL; } } Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch) { PDirWatch tmpDirWatch(new DirWatch); dirWatch.swap(tmpDirWatch); dirWatch->path = path; dirWatch->reqnum = 0; g_Paths.push_back( *dirWatch ); bool alreadyInsideRootPath = false; for ( DirWatchMap::iterator it = g_RootPaths.begin() ; it != g_RootPaths.end(); ++it) { if ( path_is_subpath( path.string().c_str(), it->path.string().c_str() ) ) alreadyInsideRootPath = true; } if ( !alreadyInsideRootPath ) { DeleteEventStream(); g_RootPaths.push_back( *dirWatch ); } return INFO::OK; } Status dir_watch_Poll(DirWatchNotifications& notifications) { if ( g_Stream == NULL ) { g_Stream = CreateEventStream( g_RootPaths ); } else { for ( DirWatchNotifications::iterator it = g_QueuedDirs.begin() ; it != g_QueuedDirs.end(); ++it) notifications.push_back(DirWatchNotification( *it )); g_QueuedDirs.clear(); } return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/osx/osx_bundle.h =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_bundle.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_bundle.h (revision 19899) @@ -1,63 +1,63 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ #ifndef OSX_BUNDLE_H #define OSX_BUNDLE_H /** * @file * C++ interface to Cocoa implementation for getting bundle information */ /** * Check if app is running in a valid bundle * * @return true if valid bundle reference was found matching identifier * property "com.wildfiregames.0ad" */ bool osx_IsAppBundleValid(); /** * Get the system path to the bundle itself * * @return string containing POSIX-style path in UTF-8 encoding, * else empty string if an error occurred. */ std::string osx_GetBundlePath(); /** * Get the system path to the bundle's Resources directory * * @return string containing POSIX-style path in UTF-8 encoding, * else empty string if an error occurred. */ std::string osx_GetBundleResourcesPath(); /** * Get the system path to the bundle's Frameworks directory * * @return string containing POSIX-style path in UTF-8 encoding, * else empty string if an error occurred. */ std::string osx_GetBundleFrameworksPath(); #endif // OSX_BUNDLE_H Index: ps/trunk/source/lib/sysdep/os/osx/osx_paths.h =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_paths.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_paths.h (revision 19899) @@ -1,47 +1,47 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ #ifndef OSX_PATHS_H #define OSX_PATHS_H /** * @file * C++ interface to Cocoa implementation for retrieving standard OS X paths */ /** * Get the user's Application Support path (typically ~/Library/Application Support) * * @return string containing POSIX-style path in UTF-8 encoding, * else empty string if an error occurred. */ std::string osx_GetAppSupportPath(); /** * Get the user's Caches path (typically ~/Library/Caches) * * @return string containing POSIX-style path in UTF-8 encoding, * else empty string if an error occurred. */ std::string osx_GetCachesPath(); #endif // OSX_PATHS_H Index: ps/trunk/source/lib/sysdep/os/osx/osx_sys_version.h =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_sys_version.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/osx/osx_sys_version.h (revision 19899) @@ -1,28 +1,28 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #ifndef OSX_SYS_VERSION_H #define OSX_SYS_VERSION_H void GetSystemVersion( int &major, int &minor, int &bugfix ); #endif //OSX_SYS_VERSION_H Index: ps/trunk/source/lib/sysdep/os/unix/ufilesystem.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/ufilesystem.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/ufilesystem.cpp (revision 19899) @@ -1,160 +1,160 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Unix implementation of wchar_t versions of POSIX filesystem functions */ #include "precompiled.h" #include "lib/sysdep/filesystem.h" #include "lib/path.h" #include struct WDIR { DIR* d; wchar_t name[PATH_MAX]; wdirent ent; }; #if OS_ANDROID // The Crystax NDK seems to do weird things with opendir etc. // To avoid that, load the symbols directly from the real libc // and use them instead. #include static void* libc; static DIR* (*libc_opendir)(const char*); static dirent* (*libc_readdir)(DIR*); static int (*libc_closedir)(DIR*); void init_libc() { if (libc) return; libc = dlopen("/system/lib/libc.so", RTLD_LAZY); ENSURE(libc); libc_opendir = (DIR*(*)(const char*))dlsym(libc, "opendir"); libc_readdir = (dirent*(*)(DIR*))dlsym(libc, "readdir"); libc_closedir = (int(*)(DIR*))dlsym(libc, "closedir"); ENSURE(libc_opendir && libc_readdir && libc_closedir); } #define opendir libc_opendir #define readdir libc_readdir #define closedir libc_closedir #else void init_libc() { } #endif WDIR* wopendir(const OsPath& path) { init_libc(); DIR* d = opendir(OsString(path).c_str()); if(!d) return 0; WDIR* wd = new WDIR; wd->d = d; wd->name[0] = '\0'; wd->ent.d_name = wd->name; return wd; } struct wdirent* wreaddir(WDIR* wd) { dirent* ent = readdir(wd->d); if(!ent) return 0; wcscpy_s(wd->name, ARRAY_SIZE(wd->name), OsPath(ent->d_name).string().c_str()); return &wd->ent; } int wclosedir(WDIR* wd) { int ret = closedir(wd->d); delete wd; return ret; } int wopen(const OsPath& pathname, int oflag) { ENSURE(!(oflag & O_CREAT)); return open(OsString(pathname).c_str(), oflag); } int wopen(const OsPath& pathname, int oflag, mode_t mode) { return open(OsString(pathname).c_str(), oflag, mode); } int wclose(int fd) { return close(fd); } int wtruncate(const OsPath& pathname, off_t length) { return truncate(OsString(pathname).c_str(), length); } int wunlink(const OsPath& pathname) { return unlink(OsString(pathname).c_str()); } int wrmdir(const OsPath& path) { return rmdir(OsString(path).c_str()); } int wrename(const OsPath& pathnameOld, const OsPath& pathnameNew) { return rename(OsString(pathnameOld).c_str(), OsString(pathnameNew).c_str()); } OsPath wrealpath(const OsPath& pathname) { char resolvedBuf[PATH_MAX]; const char* resolved = realpath(OsString(pathname).c_str(), resolvedBuf); if(!resolved) return OsPath(); return resolved; } int wstat(const OsPath& pathname, struct stat* buf) { return stat(OsString(pathname).c_str(), buf); } int wmkdir(const OsPath& path, mode_t mode) { return mkdir(OsString(path).c_str(), mode); } Index: ps/trunk/source/lib/sysdep/os/unix/unix_executable_pathname.h =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/unix_executable_pathname.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/unix_executable_pathname.h (revision 19899) @@ -1,28 +1,28 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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. */ #ifndef INCLUDED_UNIX_EXECUTABLE_PATHNAME #define INCLUDED_UNIX_EXECUTABLE_PATHNAME OsPath unix_ExecutablePathname(); #endif // INCLUDED_UNIX_EXECUTABLE_PATHNAME Index: ps/trunk/source/lib/sysdep/os/unix/uvm.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/uvm.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/uvm.cpp (revision 19899) @@ -1,136 +1,136 @@ -/* Copyright (c) 2011 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 * "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 "lib/sysdep/vm.h" #include "lib/alignment.h" // "anonymous" effectively means mapping /dev/zero, but is more efficient. // MAP_ANONYMOUS is not in SUSv3, but is a very common extension. // unfortunately, MacOS X only defines MAP_ANON, which Solaris says is // deprecated. workaround there: define MAP_ANONYMOUS in terms of MAP_ANON. #ifndef MAP_ANONYMOUS # define MAP_ANONYMOUS MAP_ANON #endif static const int mmap_flags = MAP_PRIVATE|MAP_ANONYMOUS; namespace vm { void* ReserveAddressSpace(size_t size, size_t UNUSED(commitSize), PageType UNUSED(pageType), int UNUSED(prot)) { errno = 0; void* p = mmap(0, size, PROT_NONE, mmap_flags|MAP_NORESERVE, -1, 0); if(p == MAP_FAILED) return 0; return p; } void ReleaseAddressSpace(void* p, size_t size) { ENSURE(size != 0); errno = 0; if(munmap(p, size) != 0) DEBUG_WARN_ERR(StatusFromErrno()); } bool Commit(uintptr_t address, size_t size, PageType UNUSED(pageType), int prot) { if(prot == PROT_NONE) // would be understood as a request to decommit { DEBUG_WARN_ERR(ERR::INVALID_PARAM); return false; } errno = 0; if(mmap((void*)address, size, prot, mmap_flags|MAP_FIXED, -1, 0) == MAP_FAILED) return false; if(prot != (PROT_READ|PROT_WRITE)) (void)Protect(address, size, prot); return true; } bool Decommit(uintptr_t address, size_t size) { errno = 0; if(mmap((void*)address, size, PROT_NONE, mmap_flags|MAP_NORESERVE|MAP_FIXED, -1, 0) == MAP_FAILED) return false; return true; } bool Protect(uintptr_t address, size_t size, int prot) { errno = 0; if(mprotect((void*)address, size, prot) != 0) { DEBUG_WARN_ERR(ERR::FAIL); return false; } return true; } void* Allocate(size_t size, PageType pageType, int prot) { void* p = ReserveAddressSpace(size); if(!p) return 0; if(!Commit(uintptr_t(p), size, pageType, prot)) { ReleaseAddressSpace(p, size); return 0; } return p; } void Free(void* p, size_t size) { // (only the Windows implementation distinguishes between Free and ReleaseAddressSpace) vm::ReleaseAddressSpace(p, size); } void BeginOnDemandCommits() { // not yet implemented, but possible with a signal handler } void EndOnDemandCommits() { // not yet implemented, but possible with a signal handler } void DumpStatistics() { // we haven't collected any statistics } } // namespace vm Index: ps/trunk/source/lib/sysdep/os/unix/unuma.h =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/unuma.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/unuma.h (revision 19899) @@ -1,26 +1,26 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_UNUMA #define INCLUDED_UNUMA #endif // #ifndef INCLUDED_UNUMA Index: ps/trunk/source/lib/sysdep/os/win/aken/aken.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/aken/aken.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/aken/aken.h (revision 19899) @@ -1,116 +1,116 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Aken driver interface */ // Aken - custodian of the ferryboat to the underworld in Egyptian mythology, // and a driver that shuttles between applications and kernel mode resources. #ifndef INCLUDED_AKEN #define INCLUDED_AKEN #define AKEN_NAME L"Aken" // device type #define FILE_DEVICE_AKEN 53498 // in the "User Defined" range." #define AKEN_IOCTL 0x800 // 0x800..0xFFF are for 'customer' use. #define IOCTL_AKEN_READ_PORT CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+0, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AKEN_WRITE_PORT CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+1, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AKEN_MAP CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+2, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AKEN_UNMAP CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+3, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AKEN_READ_MSR CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+4, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AKEN_WRITE_MSR CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+5, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_AKEN_READ_PMC CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+6, METHOD_BUFFERED, FILE_ANY_ACCESS) // input and output data structures for the IOCTLs #pragma pack(push, 1) typedef struct AkenReadPortIn_ { USHORT port; UCHAR numBytes; } AkenReadPortIn; typedef struct AkenReadPortOut_ { DWORD32 value; } AkenReadPortOut; typedef struct AkenWritePortIn_ { DWORD32 value; USHORT port; UCHAR numBytes; } AkenWritePortIn; typedef struct AkenMapIn_ { // note: fixed-width types allow the 32 or 64-bit Mahaf wrapper to // interoperate with the 32 or 64-bit Aken driver. DWORD64 physicalAddress; DWORD64 numBytes; } AkenMapIn; typedef struct AkenMapOut_ { DWORD64 virtualAddress; } AkenMapOut; typedef struct AkenUnmapIn_ { DWORD64 virtualAddress; } AkenUnmapIn; typedef struct AkenReadRegisterIn_ { DWORD64 reg; } AkenReadRegisterIn; typedef struct AkenReadRegisterOut_ { DWORD64 value; } AkenReadRegisterOut; typedef struct AkenWriteRegisterIn_ { DWORD64 reg; DWORD64 value; } AkenWriteRegisterIn; #pragma pack(pop) #endif // #ifndef INCLUDED_AKEN Index: ps/trunk/source/lib/sysdep/os/win/aken/aken.c =================================================================== --- ps/trunk/source/lib/sysdep/os/win/aken/aken.c (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/aken/aken.c (revision 19899) @@ -1,526 +1,526 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // note: staticdv cannot yet check C++ code. #include #include "aken.h" #include "intrinsics.h" #define WIN32_NAME L"\\DosDevices\\Aken" #define DEVICE_NAME L"\\Device\\Aken" // placate PREfast DRIVER_INITIALIZE DriverEntry; __drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH AkenCreate; __drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH AkenClose; __drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH AkenDeviceControl; DRIVER_UNLOAD AkenUnload; // this driver isn't large, but it's still slightly nicer to make its // functions pageable and thus not waste precious non-paged pool. // #pragma code_seg is more convenient than specifying alloc_text for // every other function. #pragma alloc_text(INIT, DriverEntry) // => discardable #pragma code_seg(push, "PAGE") //----------------------------------------------------------------------------- // memory mapping //----------------------------------------------------------------------------- /* there are three approaches to mapping physical memory: (http://www.microsoft.com/whdc/driver/kernel/mem-mgmt.mspx) - MmMapIoSpace (http://support.microsoft.com/kb/189327/en-us). despite the name, it maps physical pages of any kind by allocating PTEs. very easy to implement, but occupies precious kernel address space. possible bugs: http://www.osronline.com/showThread.cfm?link=96737 http://support.microsoft.com/kb/925793/en-us - ZwMapViewOfSection of PhysicalMemory (http://tinyurl.com/yozmgy). the code is a bit bulky, but the WinXP API prevents mapping pages with conflicting attributes (see below). - MmMapLockedPagesSpecifyCache or MmGetSystemAddressForMdlSafe (http://www.osronline.com/article.cfm?id=423). note: the latter is a macro that calls the former. this is the 'normal' and fully documented way, but it doesn't appear able to map a fixed physical address. (MmAllocatePagesForMdl understandably doesn't work since some pages we want to map are marked as unavailable for allocation, and I don't see another documented way to fill an MDL with PFNs.) our choice here is forced by a very insidious issue. if someone else has already mapped a page with different attributes (e.g. cacheable), TLBs may end up corrupted, leading to disaster. the search for a documented means of accessing the page frame database (to check if mapped anywhere and determine the previously set attributes) has not borne fruit, so we must use ZwMapViewOfSection. (if taking this up again, see http://www.woodmann.com/forum/archive/index.php/t-6516.html ) note that we guess if the page will have been mapped as cacheable and even try the opposite if that turns out to have been incorrect. */ static int IsMemoryUncacheable(DWORD64 physicalAddress64) { PAGED_CODE(); // original PC memory - contains BIOS if(physicalAddress64 < 0x100000) return 1; return 0; } static NTSTATUS AkenMapPhysicalMemory(const DWORD64 physicalAddress64, const DWORD64 numBytes64, DWORD64* virtualAddress64) { NTSTATUS ntStatus; HANDLE hMemory; LARGE_INTEGER physicalAddress; // convenience physicalAddress.QuadPart = physicalAddress64; PAGED_CODE(); // get handle to PhysicalMemory object { OBJECT_ATTRIBUTES objectAttributes; UNICODE_STRING objectName = RTL_CONSTANT_STRING(L"\\Device\\PhysicalMemory"); const ULONG attributes = OBJ_CASE_INSENSITIVE; const HANDLE rootDirectory = 0; InitializeObjectAttributes(&objectAttributes, &objectName, attributes, rootDirectory, (PSECURITY_DESCRIPTOR)0); ntStatus = ZwOpenSection(&hMemory, SECTION_ALL_ACCESS, &objectAttributes); if(!NT_SUCCESS(ntStatus)) { KdPrint(("AkenMapPhysicalMemory: ZwOpenSection failed\n")); return ntStatus; } } // add a reference (required to prevent the handle from being deleted) { PVOID physicalMemorySection = NULL; const POBJECT_TYPE objectType = 0; // allowed since specifying KernelMode ntStatus = ObReferenceObjectByHandle(hMemory, SECTION_ALL_ACCESS, objectType, KernelMode, &physicalMemorySection, 0); if(!NT_SUCCESS(ntStatus)) { KdPrint(("AkenMapPhysicalMemory: ObReferenceObjectByHandle failed\n")); goto close_handle; } } // note: mapmem.c does HalTranslateBusAddress, but we only care about // system memory. translating doesn't appear to be necessary, even if // much existing code uses it (probably due to cargo cult). // map desired memory into user PTEs { const HANDLE hProcess = (HANDLE)-1; PVOID virtualBaseAddress = 0; // let ZwMapViewOfSection pick const ULONG zeroBits = 0; // # high-order bits in address that must be 0 SIZE_T mappedSize = (SIZE_T)numBytes64; // will receive the actual page-aligned size LARGE_INTEGER physicalBaseAddress = physicalAddress; // will be rounded down to 64KB boundary const SECTION_INHERIT inheritDisposition = ViewShare; const ULONG allocationType = 0; ULONG protect = PAGE_READWRITE; if(IsMemoryUncacheable(physicalAddress64)) protect |= PAGE_NOCACHE; ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect); if(!NT_SUCCESS(ntStatus)) { // try again with the opposite cacheability attribute protect ^= PAGE_NOCACHE; ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect); if(!NT_SUCCESS(ntStatus)) { KdPrint(("AkenMapPhysicalMemory: ZwMapViewOfSection failed\n")); goto close_handle; } } // the mapping rounded our physical base address down to the nearest // 64KiB boundary, so adjust the virtual address accordingly. { const DWORD32 numBytesRoundedDown = physicalAddress.LowPart - physicalBaseAddress.LowPart; ASSERT(numBytesRoundedDown < 0x10000); *virtualAddress64 = (DWORD64)virtualBaseAddress + numBytesRoundedDown; } } ntStatus = STATUS_SUCCESS; close_handle: // closing the handle even on success means that callers won't have to // pass it back when unmapping. why does this work? ZwMapViewOfSection // apparently adds a reference to hMemory. ZwClose(hMemory); return ntStatus; } static NTSTATUS AkenUnmapPhysicalMemory(const DWORD64 virtualAddress) { PAGED_CODE(); { const HANDLE hProcess = (HANDLE)-1; PVOID baseAddress = (PVOID)virtualAddress; NTSTATUS ntStatus = ZwUnmapViewOfSection(hProcess, baseAddress); if(!NT_SUCCESS(ntStatus)) { KdPrint(("AkenUnmapPhysicalMemory: ZwUnmapViewOfSection failed\n")); return ntStatus; } } return STATUS_SUCCESS; } //----------------------------------------------------------------------------- // helper functions called from DeviceControl //----------------------------------------------------------------------------- static NTSTATUS AkenIoctlReadPort(PVOID buf, const ULONG inSize, ULONG* outSize) { DWORD32 value; PAGED_CODE(); if(inSize != sizeof(AkenReadPortIn) || *outSize != sizeof(AkenReadPortOut)) return STATUS_BUFFER_TOO_SMALL; { const AkenReadPortIn* in = (const AkenReadPortIn*)buf; const USHORT port = in->port; const UCHAR numBytes = in->numBytes; switch(numBytes) { case 1: value = (DWORD32)READ_PORT_UCHAR((PUCHAR)port); break; case 2: value = (DWORD32)READ_PORT_USHORT((PUSHORT)port); break; case 4: value = (DWORD32)READ_PORT_ULONG((PULONG)port); break; default: return STATUS_INVALID_PARAMETER; } } { AkenReadPortOut* out = (AkenReadPortOut*)buf; out->value = value; } return STATUS_SUCCESS; } static NTSTATUS AkenIoctlWritePort(PVOID buf, const ULONG inSize, ULONG* outSize) { PAGED_CODE(); if(inSize != sizeof(AkenWritePortIn) || *outSize != 0) return STATUS_BUFFER_TOO_SMALL; { const AkenWritePortIn* in = (const AkenWritePortIn*)buf; const DWORD32 value = in->value; const USHORT port = in->port; const UCHAR numBytes = in->numBytes; switch(numBytes) { case 1: WRITE_PORT_UCHAR((PUCHAR)port, (UCHAR)(value & 0xFF)); break; case 2: WRITE_PORT_USHORT((PUSHORT)port, (USHORT)(value & 0xFFFF)); break; case 4: WRITE_PORT_ULONG((PULONG)port, value); break; default: return STATUS_INVALID_PARAMETER; } } return STATUS_SUCCESS; } static NTSTATUS AkenIoctlMap(PVOID buf, const ULONG inSize, ULONG* outSize) { DWORD64 virtualAddress; NTSTATUS ntStatus; PAGED_CODE(); if(inSize != sizeof(AkenMapIn) || *outSize != sizeof(AkenMapOut)) return STATUS_BUFFER_TOO_SMALL; { const AkenMapIn* in = (const AkenMapIn*)buf; const DWORD64 physicalAddress = in->physicalAddress; const DWORD64 numBytes = in->numBytes; ntStatus = AkenMapPhysicalMemory(physicalAddress, numBytes, &virtualAddress); } { AkenMapOut* out = (AkenMapOut*)buf; out->virtualAddress = virtualAddress; } return ntStatus; } static NTSTATUS AkenIoctlUnmap(PVOID buf, const ULONG inSize, ULONG* outSize) { NTSTATUS ntStatus; PAGED_CODE(); if(inSize != sizeof(AkenUnmapIn) || *outSize != 0) return STATUS_BUFFER_TOO_SMALL; { const AkenUnmapIn* in = (const AkenUnmapIn*)buf; const DWORD64 virtualAddress = in->virtualAddress; ntStatus = AkenUnmapPhysicalMemory(virtualAddress); } return ntStatus; } static NTSTATUS AkenIoctlReadModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize) { DWORD64 value; PAGED_CODE(); if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut)) return STATUS_BUFFER_TOO_SMALL; { const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf; const DWORD64 reg = in->reg; value = __readmsr((int)reg); } { AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf; out->value = value; } return STATUS_SUCCESS; } static NTSTATUS AkenIoctlWriteModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize) { PAGED_CODE(); if(inSize != sizeof(AkenWriteRegisterIn) || *outSize != 0) return STATUS_BUFFER_TOO_SMALL; { const AkenWriteRegisterIn* in = (const AkenWriteRegisterIn*)buf; const DWORD64 reg = in->reg; const DWORD64 value = in->value; __writemsr((unsigned long)reg, value); } return STATUS_SUCCESS; } static NTSTATUS AkenIoctlReadPerformanceMonitoringCounter(PVOID buf, const ULONG inSize, ULONG* outSize) { DWORD64 value; PAGED_CODE(); if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut)) return STATUS_BUFFER_TOO_SMALL; { const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf; const DWORD64 reg = in->reg; value = __readpmc((unsigned long)reg); } { AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf; out->value = value; } return STATUS_SUCCESS; } static NTSTATUS AkenIoctlUnknown(PVOID buf, const ULONG inSize, ULONG* outSize) { PAGED_CODE(); KdPrint(("AkenIoctlUnknown\n")); *outSize = 0; return STATUS_INVALID_DEVICE_REQUEST; } typedef NTSTATUS (*AkenIoctl)(PVOID buf, ULONG inSize, ULONG* outSize); static AkenIoctl AkenIoctlFromCode(ULONG ioctlCode) { PAGED_CODE(); switch(ioctlCode) { case IOCTL_AKEN_READ_PORT: return AkenIoctlReadPort; case IOCTL_AKEN_WRITE_PORT: return AkenIoctlWritePort; case IOCTL_AKEN_MAP: return AkenIoctlMap; case IOCTL_AKEN_UNMAP: return AkenIoctlUnmap; case IOCTL_AKEN_READ_MSR: return AkenIoctlReadModelSpecificRegister; case IOCTL_AKEN_WRITE_MSR: return AkenIoctlWriteModelSpecificRegister; default: return AkenIoctlUnknown; } } //----------------------------------------------------------------------------- // entry points //----------------------------------------------------------------------------- static NTSTATUS AkenCreate(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) { PAGED_CODE(); irp->IoStatus.Status = STATUS_SUCCESS; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } static NTSTATUS AkenClose(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) { PAGED_CODE(); // same as AkenCreate ATM irp->IoStatus.Status = STATUS_SUCCESS; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } static NTSTATUS AkenDeviceControl(IN PDEVICE_OBJECT deviceObject, IN PIRP irp) { PAGED_CODE(); { // get buffer from IRP. all our IOCTLs are METHOD_BUFFERED, so buf is // allocated by the I/O manager and used for both input and output. PVOID buf = irp->AssociatedIrp.SystemBuffer; PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(irp); ULONG ioctlCode = irpStack->Parameters.DeviceIoControl.IoControlCode; const ULONG inSize = irpStack->Parameters.DeviceIoControl.InputBufferLength; ULONG outSize = irpStack->Parameters.DeviceIoControl.OutputBufferLength; // modified by AkenIoctl* const AkenIoctl akenIoctl = AkenIoctlFromCode(ioctlCode); const NTSTATUS ntStatus = akenIoctl(buf, inSize, &outSize); irp->IoStatus.Information = outSize; // number of bytes to copy from buf to user's buffer irp->IoStatus.Status = ntStatus; IoCompleteRequest(irp, IO_NO_INCREMENT); return ntStatus; } } static VOID AkenUnload(IN PDRIVER_OBJECT driverObject) { PAGED_CODE(); KdPrint(("AkenUnload\n")); { UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME); IoDeleteSymbolicLink(&win32Name); } if(driverObject->DeviceObject) IoDeleteDevice(driverObject->DeviceObject); } #pragma code_seg(pop) // make sure we don't countermand the alloc_text NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath) { UNICODE_STRING deviceName = RTL_CONSTANT_STRING(DEVICE_NAME); // create device object PDEVICE_OBJECT deviceObject; { const ULONG deviceExtensionSize = 0; const ULONG deviceCharacteristics = FILE_DEVICE_SECURE_OPEN; const BOOLEAN exlusive = TRUE; NTSTATUS ntStatus = IoCreateDevice(driverObject, deviceExtensionSize, &deviceName, FILE_DEVICE_AKEN, deviceCharacteristics, exlusive, &deviceObject); if(!NT_SUCCESS(ntStatus)) { KdPrint(("DriverEntry: IoCreateDevice failed\n")); return ntStatus; } } // set entry points driverObject->MajorFunction[IRP_MJ_CREATE] = AkenCreate; driverObject->MajorFunction[IRP_MJ_CLOSE] = AkenClose; driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AkenDeviceControl; driverObject->DriverUnload = AkenUnload; // symlink NT device name to Win32 namespace { UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME); NTSTATUS ntStatus = IoCreateSymbolicLink(&win32Name, &deviceName); if(!NT_SUCCESS(ntStatus)) { KdPrint(("DriverEntry: IoCreateSymbolicLink failed\n")); IoDeleteDevice(deviceObject); return ntStatus; } } return STATUS_SUCCESS; } Index: ps/trunk/source/lib/sysdep/os/unix/x/x.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/x/x.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/unix/x/x.cpp (revision 19899) @@ -1,496 +1,496 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ // X Window System-specific code #include "precompiled.h" #if OS_LINUX || OS_BSD # define HAVE_X 1 #else # define HAVE_X 0 #endif #if HAVE_X #include "lib/debug.h" #include "lib/utf8.h" #include "lib/sysdep/gfx.h" #include "lib/sysdep/cursor.h" #include "ps/VideoMode.h" #define Cursor X__Cursor #include #include #include #include #include "SDL.h" #include "SDL_syswm.h" #include #undef Cursor #undef Status static Display *g_SDL_Display; static Window g_SDL_Window; static wchar_t *selection_data=NULL; static size_t selection_size=0; namespace gfx { Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq) { Display* disp = XOpenDisplay(0); if(!disp) WARN_RETURN(ERR::FAIL); int screen = XDefaultScreen(disp); /* 2004-07-13 NOTE: The XDisplayWidth/Height functions don't actually return the current display mode - they return the size of the root window. This means that users with "Virtual Desktops" bigger than what their monitors/graphics card can handle will have to set their 0AD screen resolution manually. There's supposed to be an X extension that can give you the actual display mode, probably including refresh rate info etc, but it's not worth researching and implementing that at this stage. */ if(xres) *xres = XDisplayWidth(disp, screen); if(yres) *yres = XDisplayHeight(disp, screen); if(bpp) *bpp = XDefaultDepth(disp, screen); if(freq) *freq = 0; XCloseDisplay(disp); return INFO::OK; } Status GetMonitorSize(int& width_mm, int& height_mm) { Display* disp = XOpenDisplay(0); if(!disp) WARN_RETURN(ERR::FAIL); int screen = XDefaultScreen(disp); width_mm = XDisplayWidthMM(disp, screen); height_mm = XDisplayHeightMM(disp, screen); XCloseDisplay(disp); return INFO::OK; } } // namespace gfx static bool get_wminfo(SDL_SysWMinfo& wminfo) { SDL_VERSION(&wminfo.version); const int ret = SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &wminfo); if(ret == 1) return true; if(ret == -1) { debug_printf("SDL_GetWMInfo failed\n"); return false; } if(ret == 0) { debug_printf("SDL_GetWMInfo is not implemented on this platform\n"); return false; } debug_printf("SDL_GetWMInfo returned an unknown value: %d\n", ret); return false; } /* Oh, boy, this is heavy stuff... http://www.freedesktop.org/standards/clipboards-spec/clipboards.txt http://www.mail-archive.com/xfree86@xfree86.org/msg15594.html http://michael.toren.net/mirrors/doc/X-copy+paste.txt http://devdocs.wesnoth.org/clipboard_8cpp-source.html http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html http://www.jwz.org/doc/x-cut-and-paste.html The basic run-down on X Selection Handling: * One window owns the "current selection" at any one time * Accessing the Selection (i.e. "paste"), Step-by-step * Ask the X server for the current selection owner * Ask the selection owner window to convert the selection into a format we can understand (XA_STRING - Latin-1 string - for now) * The converted result is stored as a property of the *selection owner* window. It is possible to specify the current application window as the target - but that'd require some X message handling... easier to skip that.. * The final step is to acquire the property value of the selection owner window Notes: An "Atom" is a server-side object that represents a string by an index into some kind of table or something. Pretty much like a handle that refers to one unique string. Atoms are used here to refer to property names and value formats. Expansions: * Implement UTF-8 format support (should be interresting for international users) */ wchar_t *sys_clipboard_get() { Display *disp=XOpenDisplay(NULL); if(!disp) return NULL; // We use CLIPBOARD as the default, since the CLIPBOARD semantics are much // closer to windows semantics. Atom selSource=XInternAtom(disp, "CLIPBOARD", False); Window selOwner=XGetSelectionOwner(disp, selSource); if(selOwner == None) { // However, since many apps don't use CLIPBOARD, but use PRIMARY instead // we use XA_PRIMARY as a fallback clipboard. This is true for xterm, // for example. selSource=XA_PRIMARY; selOwner=XGetSelectionOwner(disp, selSource); } if(selOwner != None) { Atom pty=XInternAtom(disp, "SelectionPropertyTemp", False); XConvertSelection(disp, selSource, XA_STRING, pty, selOwner, CurrentTime); XFlush(disp); Atom type; int format=0, result=0; unsigned long len=0, bytes_left=0, dummy=0; u8 *data=NULL; // Get the length of the property and some attributes // bytes_left will contain the length of the selection result = XGetWindowProperty (disp, selOwner, pty, 0, 0, // offset - len 0, // Delete 0==FALSE AnyPropertyType,//flag &type, // return type &format, // return format &len, &bytes_left, &data); if(result != Success) debug_printf("clipboard_get: result: %d type:%lu len:%lu format:%d bytes_left:%lu\n", result, type, len, format, bytes_left); if(result == Success && bytes_left > 0) { result = XGetWindowProperty (disp, selOwner, pty, 0, bytes_left, 0, AnyPropertyType, &type, &format, &len, &dummy, &data); if(result == Success) { debug_printf("clipboard_get: XGetWindowProperty succeeded, returning data\n"); debug_printf("clipboard_get: data was: \"%s\", type was %lu, XA_STRING atom is %lu\n", (char *)data, type, XA_STRING); if(type == XA_STRING) //Latin-1: Just copy into low byte of wchar_t { wchar_t *ret=(wchar_t *)malloc((bytes_left+1)*sizeof(wchar_t)); std::copy(data, data+bytes_left, ret); ret[bytes_left]=0; return ret; } // TODO: Handle UTF8 strings } else { debug_printf("clipboard_get: XGetWindowProperty failed!\n"); return NULL; } } } return NULL; } Status sys_clipboard_free(wchar_t *clip_buf) { free(clip_buf); return INFO::OK; } /** * An SDL Event filter that intercepts other applications' requests for the * X selection buffer. * * @see x11_clipboard_init * @see sys_clipboard_set */ int clipboard_filter(void* UNUSED(userdata), SDL_Event* event) { /* Pass on all non-window manager specific events immediately */ /* And do nothing if we don't actually have a clip-out to send out */ if(event->type != SDL_SYSWMEVENT || !selection_data) return 1; /* Handle window-manager specific clipboard events */ /* (Note: libsdl must be compiled with X11 support (SDL_VIDEO_DRIVER_X11 in SDL_config.h) - else you'll get errors like "'struct SDL_SysWMmsg' has no member named 'xevent'") */ XEvent* xevent = &event->syswm.msg->msg.x11.event; switch(xevent->type) { /* Copy the selection from our buffer to the requested property, and convert to the requested target format */ case SelectionRequest: { XSelectionRequestEvent *req; XEvent sevent; req = &xevent->xselectionrequest; sevent.xselection.type = SelectionNotify; sevent.xselection.display = req->display; sevent.xselection.selection = req->selection; sevent.xselection.target = req->target; sevent.xselection.property = None; sevent.xselection.requestor = req->requestor; sevent.xselection.time = req->time; // Simply strip all non-Latin1 characters and replace with '?' // We should support XA_UTF8 if(req->target == XA_STRING) { size_t size = wcslen(selection_data); u8* buf = (u8*)alloca(size); for(size_t i = 0; i < size; i++) { buf[i] = selection_data[i] < 0x100 ? selection_data[i] : '?'; } XChangeProperty(g_SDL_Display, req->requestor, req->property, sevent.xselection.target, 8, PropModeReplace, buf, size); sevent.xselection.property = req->property; } // TODO Add more target formats XSendEvent(g_SDL_Display, req->requestor, False, 0, &sevent); XSync(g_SDL_Display, False); } break; } return 1; } /** * Initialization for X clipboard handling, called on-demand by * sys_clipboard_set. */ Status x11_clipboard_init() { SDL_SysWMinfo info; if(get_wminfo(info)) { /* Save the information for later use */ if(info.subsystem == SDL_SYSWM_X11) { g_SDL_Display = info.info.x11.display; g_SDL_Window = info.info.x11.window; /* Enable the special window hook events */ SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); SDL_SetEventFilter(clipboard_filter, NULL); return INFO::OK; } else { return ERR::FAIL; } } return INFO::OK; } /** * Set the Selection (i.e. "copy") * * Step-by-step (X11) *
    *
  • Store the selection text in a local buffer *
  • Tell the X server that we want to own the selection *
  • Listen for Selection events and respond to them as appropriate *
*/ Status sys_clipboard_set(const wchar_t *str) { ONCE(x11_clipboard_init()); debug_printf("sys_clipboard_set: %s\n", utf8_from_wstring(str).c_str()); if(selection_data) { free(selection_data); selection_data = NULL; } selection_size = (wcslen(str)+1)*sizeof(wchar_t); selection_data = (wchar_t *)malloc(selection_size); wcscpy(selection_data, str); // Like for the clipboard_get code above, we rather use CLIPBOARD than // PRIMARY - more windows'y behaviour there. Atom clipboard_atom = XInternAtom(g_SDL_Display, "CLIPBOARD", False); XSetSelectionOwner(g_SDL_Display, clipboard_atom, g_SDL_Window, CurrentTime); XSetSelectionOwner(g_SDL_Display, XA_PRIMARY, g_SDL_Window, CurrentTime); // SDL2 doesn't have a lockable event thread, so it just uses // XSync directly instead of lock_func/unlock_func XSync(g_SDL_Display, False); return INFO::OK; } struct sys_cursor_impl { XcursorImage* image; X__Cursor cursor; }; static XcursorPixel cursor_pixel_to_x11_format(const XcursorPixel& bgra_pixel) { BOOST_STATIC_ASSERT(sizeof(XcursorPixel) == 4 * sizeof(u8)); XcursorPixel ret; u8* dst = reinterpret_cast(&ret); const u8* b = reinterpret_cast(&bgra_pixel); const u8 a = b[3]; for(size_t i = 0; i < 3; ++i) *dst++ = (b[i]) * a / 255; *dst = a; return ret; } Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor) { debug_printf("sys_cursor_create: using Xcursor to create %d x %d cursor\n", w, h); XcursorImage* image = XcursorImageCreate(w, h); if(!image) WARN_RETURN(ERR::FAIL); const XcursorPixel* bgra_img_begin = reinterpret_cast(bgra_img); std::transform(bgra_img_begin, bgra_img_begin + (w*h), image->pixels, cursor_pixel_to_x11_format); image->xhot = hx; image->yhot = hy; SDL_SysWMinfo wminfo; if(!get_wminfo(wminfo)) WARN_RETURN(ERR::FAIL); sys_cursor_impl* impl = new sys_cursor_impl; impl->image = image; impl->cursor = XcursorImageLoadCursor(wminfo.info.x11.display, image); if(impl->cursor == None) WARN_RETURN(ERR::FAIL); *cursor = static_cast(impl); return INFO::OK; } // returns a dummy value representing an empty cursor Status sys_cursor_create_empty(sys_cursor* cursor) { static u8 transparent_bgra[] = { 0x0, 0x0, 0x0, 0x0 }; return sys_cursor_create(1, 1, static_cast(transparent_bgra), 0, 0, cursor); } // replaces the current system cursor with the one indicated. need only be // called once per cursor; pass 0 to restore the default. Status sys_cursor_set(sys_cursor cursor) { if(!cursor) // restore default cursor SDL_ShowCursor(SDL_DISABLE); else { SDL_SysWMinfo wminfo; if(!get_wminfo(wminfo)) WARN_RETURN(ERR::FAIL); if(wminfo.subsystem != SDL_SYSWM_X11) WARN_RETURN(ERR::FAIL); SDL_ShowCursor(SDL_ENABLE); Window window; if(wminfo.info.x11.window) window = wminfo.info.x11.window; else WARN_RETURN(ERR::FAIL); XDefineCursor(wminfo.info.x11.display, window, static_cast(cursor)->cursor); // SDL2 doesn't have a lockable event thread, so it just uses // XSync directly instead of lock_func/unlock_func XSync(wminfo.info.x11.display, False); } return INFO::OK;} // destroys the indicated cursor and frees its resources. if it is // currently the system cursor, the default cursor is restored first. Status sys_cursor_free(sys_cursor cursor) { // bail now to prevent potential confusion below; there's nothing to do. if(!cursor) return INFO::OK; sys_cursor_set(0); // restore default cursor sys_cursor_impl* impl = static_cast(cursor); XcursorImageDestroy(impl->image); SDL_SysWMinfo wminfo; if(!get_wminfo(wminfo)) return ERR::FAIL; XFreeCursor(wminfo.info.x11.display, impl->cursor); delete impl; return INFO::OK; } Status sys_cursor_reset() { return INFO::OK; } #endif // #if HAVE_X Index: ps/trunk/source/lib/sysdep/os/win/error_dialog.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/error_dialog.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/error_dialog.h (revision 19899) @@ -1,45 +1,45 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by error_dialog.rc // #define IDD_DIALOG1 101 #define IDC_EDIT1 1001 #define IDC_COPY 1002 #define IDC_CONTINUE 2000 #define IDC_SUPPRESS 2001 #define IDC_BREAK 2002 #define IDC_EXIT 2003 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1003 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif Index: ps/trunk/source/lib/sysdep/os/win/tests/test_ia32.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/tests/test_ia32.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/tests/test_ia32.h (revision 19899) @@ -1,50 +1,50 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" // note: ia32_i??_from_*, ia32_rint*, ia32_fm??f are all tested within // sysdep to avoid test duplication (both the ia32 versions and // the portable fallback must behave the same). class TestIA32: public CxxTest::TestSuite { public: void test_rdtsc() { // must increase monotonously const u64 c1 = x86_x64::rdtsc(); const u64 c2 = x86_x64::rdtsc(); const u64 c3 = x86_x64::rdtsc(); TS_ASSERT(c1 < c2 && c2 < c3); } void test_ia32_cap() { // make sure the really common/basic caps end up reported as true TS_ASSERT(x86_x64::Cap(x86_x64::CAP_FPU)); TS_ASSERT(x86_x64::Cap(x86_x64::CAP_TSC)); TS_ASSERT(x86_x64::Cap(x86_x64::CAP_MMX)); } }; Index: ps/trunk/source/lib/sysdep/os/win/wcpu.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wcpu.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wcpu.h (revision 19899) @@ -1,43 +1,43 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Windows backend of os_cpu */ #ifndef INCLUDED_WCPU #define INCLUDED_WCPU #include "lib/sysdep/os/win/win.h" extern Status wcpu_ReadFrequencyFromRegistry(u32& freqMhz); // "affinity" and "processorNumber" are what Windows sees. // "processorMask" and "processor" are the idealized representation we expose // to users. the latter insulates them from process affinity restrictions by // defining IDs as indices of the nonzero bits within the process affinity. // these routines are provided for the benefit of wnuma. extern DWORD_PTR wcpu_AffinityFromProcessorMask(DWORD_PTR processAffinity, uintptr_t processorMask); extern uintptr_t wcpu_ProcessorMaskFromAffinity(DWORD_PTR processAffinity, DWORD_PTR affinity); #endif // #ifndef INCLUDED_WCPU Index: ps/trunk/source/lib/sysdep/os/win/wdbg_heap.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg_heap.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdbg_heap.cpp (revision 19899) @@ -1,971 +1,971 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/os/win/wdbg_heap.h" #include "lib/sysdep/os/win/win.h" #include #include #include "lib/external_libraries/dbghelp.h" #include "lib/sysdep/cpu.h" // cpu_AtomicAdd #include "lib/sysdep/os/win/winit.h" #include "lib/sysdep/os/win/wdbg.h" // wdbg_printf #include "lib/sysdep/os/win/wdbg_sym.h" // wdbg_sym_WalkStack WINIT_REGISTER_EARLY_INIT2(wdbg_heap_Init); // wutil -> wdbg_heap WINIT_REGISTER_LATE_SHUTDOWN2(wdbg_heap_Shutdown); // last - no leaks are detected after this void wdbg_heap_Enable(bool enable) { #ifdef _DEBUG // (avoid "expression has no effect" warning in release builds) int flags = 0; if(enable) { flags |= _CRTDBG_ALLOC_MEM_DF; // enable checks at deallocation time flags |= _CRTDBG_LEAK_CHECK_DF; // report leaks at exit #if 0 flags |= _CRTDBG_CHECK_ALWAYS_DF; // check during every heap operation (too slow to be practical) flags |= _CRTDBG_DELAY_FREE_MEM_DF; // memory is never actually freed #endif } _CrtSetDbgFlag(flags); // Send output to stdout as well as the debug window, so it works during // the normal build process as well as when debugging the test .exe _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT); #else UNUSED2(enable); #endif } void wdbg_heap_Validate() { int ok = TRUE; __try { // NB: this is a no-op if !_CRTDBG_ALLOC_MEM_DF. // we could call _heapchk but that would catch fewer errors. ok = _CrtCheckMemory(); } __except(EXCEPTION_EXECUTE_HANDLER) { ok = FALSE; } wdbg_assert(ok == TRUE); // else: heap is corrupt! } //----------------------------------------------------------------------------- // improved leak detection //----------------------------------------------------------------------------- // (this relies on the debug CRT; not compiling it at all in release builds // avoids unreferenced local function warnings) // (this has only been tested on IA32 and seems to have trouble with larger // pointers and is horribly expensive, so it's disabled for now.) #if !defined(NDEBUG) && ARCH_IA32 && 0 # define ENABLE_LEAK_INSTRUMENTATION 1 #else # define ENABLE_LEAK_INSTRUMENTATION 0 #endif #if ENABLE_LEAK_INSTRUMENTATION // leak detectors often rely on macro redirection to determine the file and // line of allocation owners (see _CRTDBG_MAP_ALLOC). unfortunately this // breaks code that uses placement new or functions called free() etc. // // we avoid this problem by using stack traces. this implementation differs // from other approaches, e.g. Visual Leak Detector (the safer variant // before DLL hooking was used) in that no auxiliary storage is needed. // instead, the trace is stashed within the memory block header. // // to avoid duplication of effort, the CRT's leak detection code is not // modified; we only need an allocation and report hook. the latter // mixes the improved file/line information into the normal report. //----------------------------------------------------------------------------- // memory block header // the one disadvantage of our approach is that it requires knowledge of // the internal memory block header structure. it is hoped that IsValid will // uncover any changes. the following definition was adapted from dbgint.h: struct _CrtMemBlockHeader { struct _CrtMemBlockHeader* next; struct _CrtMemBlockHeader* prev; char* file; int line; // fields reversed on Win64 to ensure size % 16 == 0 #if OS_WIN64 int blockType; size_t userDataSize; #else size_t userDataSize; int blockType; #endif long allocationNumber; u8 gap[4]; bool IsValid() const { __try { if(prev && prev->next != this) return false; if(next && next->prev != this) return false; if((unsigned)blockType > 4) return false; if(userDataSize > 1*GiB) return false; if(allocationNumber == 0) return false; for(int i = 0; i < 4; i++) { if(gap[i] != 0xFD) return false; } // this is a false alarm if there is exactly one extant allocation, // but also a valuable indication of a block that has been removed // from the list (i.e. freed). if(prev == next) return false; } __except(EXCEPTION_EXECUTE_HANDLER) { return false; } return true; } }; static _CrtMemBlockHeader* HeaderFromData(void* userData) { _CrtMemBlockHeader* const header = ((_CrtMemBlockHeader*)userData)-1; wdbg_assert(header->IsValid()); return header; } /** * update our idea of the head of the linked list of heap blocks. * called from the allocation hook (see explanation there) * * @return the current head (most recent allocation). * @param operation the current heap operation * @param userData allocation address (if reallocating or deallocating) * @param hasChanged a convenient indication of whether the return value is * different than that of the last call. **/ static _CrtMemBlockHeader* GetHeapListHead(int operation, void* userData, bool& hasChanged) { static _CrtMemBlockHeader* s_heapListHead; // first call: get the heap block list head // notes: // - there is no O(1) accessor for this, so we maintain a copy. // - must be done here instead of in an initializer to guarantee // consistency, since we are now under the _HEAP_LOCK. if(!s_heapListHead) { _CrtMemState state = {0}; _CrtMemCheckpoint(&state); // O(N) s_heapListHead = state.pBlockHeader; wdbg_assert(s_heapListHead->IsValid()); } // the last operation was an allocation or expanding reallocation; // exactly one block has been prepended to the list. if(s_heapListHead->prev) { s_heapListHead = s_heapListHead->prev; // set to new head of list wdbg_assert(s_heapListHead->IsValid()); wdbg_assert(s_heapListHead->prev == 0); hasChanged = true; } // the list head remained unchanged, so the last operation was a // non-expanding reallocation or free. else hasChanged = false; // special case: handle invalidation of the list head // note: even shrinking reallocations cause deallocation. if(operation != _HOOK_ALLOC && userData == s_heapListHead+1) { s_heapListHead = s_heapListHead->next; wdbg_assert(s_heapListHead->IsValid()); hasChanged = false; // (head is now the same as last time) } return s_heapListHead; } //----------------------------------------------------------------------------- // call stack filter // we need to make the most out of the limited amount of frames. to that end, // only user functions are stored; we skip known library and helper functions. // these are determined by recording frames encountered in a backtrace. /** * extents of a module in memory; used to ignore callers that lie within * the C runtime library. **/ class ModuleExtents { public: ModuleExtents() : m_address(0), m_length(0) { } ModuleExtents(const wchar_t* dllName) { HMODULE hModule = GetModuleHandleW(dllName); PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((u8*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew); m_address = (uintptr_t)hModule + ntHeaders->OptionalHeader.BaseOfCode; MEMORY_BASIC_INFORMATION mbi = {0}; VirtualQuery((void*)m_address, &mbi, sizeof(mbi)); m_length = mbi.RegionSize; } uintptr_t Address() const { return m_address; } uintptr_t Length() const { return m_length; } bool Contains(uintptr_t address) const { return (address - m_address) < m_length; } private: uintptr_t m_address; size_t m_length; }; /** * set data structure that avoids dynamic allocations because they would * cause the allocation hook to be reentered (bad). **/ template class ArraySet { public: ArraySet() { m_arrayEnd = m_array; } void Add(T t) { if(m_arrayEnd == m_array+maxItems) { RemoveDuplicates(); wdbg_assert(m_arrayEnd < m_array+maxItems); } *m_arrayEnd++ = t; } bool Find(T t) const { return std::find(m_array, const_cast(m_arrayEnd), t) != m_arrayEnd; } void RemoveDuplicates() { std::sort(m_array, m_arrayEnd); m_arrayEnd = std::unique(m_array, m_arrayEnd); } private: T m_array[maxItems]; T* m_arrayEnd; }; class CallerFilter { public: CallerFilter() { AddRuntimeLibraryToIgnoreList(); m_isRecordingKnownCallers = true; CallHeapFunctions(); m_isRecordingKnownCallers = false; m_knownCallers.RemoveDuplicates(); } Status NotifyOfCaller(uintptr_t pc) { if(!m_isRecordingKnownCallers) return INFO::SKIPPED; // last 'known' function has been reached if(pc == (uintptr_t)&CallerFilter::CallHeapFunctions) return INFO::ALL_COMPLETE; // pc is a 'known' function on the allocation hook's back-trace // (e.g. _malloc_dbg and other helper functions) m_knownCallers.Add(pc); return INFO::OK; } bool IsKnownCaller(uintptr_t pc) const { for(size_t i = 0; i < numModules; i++) { if(m_moduleIgnoreList[i].Contains(pc)) return true; } return m_knownCallers.Find(pc); } private: static const size_t numModules = 2; void AddRuntimeLibraryToIgnoreList() { #if MSC_VERSION && _DLL // DLL runtime library #ifdef NDEBUG static const wchar_t* dllNameFormat = L"msvc%c%d" L".dll"; #else static const wchar_t* dllNameFormat = L"msvc%c%d" L"d" L".dll"; #endif const int dllVersion = (MSC_VERSION-600)/10; // VC2005: 1400 => 80 wdbg_assert(0 < dllVersion && dllVersion <= 999); for(int i = 0; i < numModules; i++) { static const char modules[numModules] = { 'r', 'p' }; // C and C++ runtime libraries wchar_t dllName[20]; swprintf_s(dllName, ARRAY_SIZE(dllName), dllNameFormat, modules[i], dllVersion); m_moduleIgnoreList[i] = ModuleExtents(dllName); } #endif } static void CallHeapFunctions() { { void* p1 = malloc(1); void* p2 = realloc(p1, 111); if(p2) free(p2); else free(p1); } { u8* p = new u8; delete p; } { u8* p = new u8[2]; delete[] p; } } ModuleExtents m_moduleIgnoreList[numModules]; // note: this mechanism cannot hope to exclude every single STL helper // function, which is why we need the module ignore list. // however, it is still useful when compiling against the static CRT. bool m_isRecordingKnownCallers; ArraySet m_knownCallers; }; //----------------------------------------------------------------------------- // stash (part of) a stack trace within _CrtMemBlockHeader // this avoids the need for a mapping between allocation number and the // caller information, which is slow, requires locking and consumes memory. // // callers := array of addresses inside functions that constitute the // stack back-trace. static const size_t numQuantizedPcBits = sizeof(uintptr_t)*CHAR_BIT - 2; static uintptr_t Quantize(uintptr_t pc) { // postcondition: the return value lies within the same function as // pc but can be stored in fewer bits. this is possible because: // - linkers typically align functions to at least four bytes // - pc is a return address and thus preceded by a call instruction and // function prolog, which requires at least four bytes. return pc/4; } static uintptr_t Expand(uintptr_t pc) { return pc*4; } static const size_t numEncodedLengthBits = 2; static const size_t maxCallers = (sizeof(char*)+sizeof(int))*CHAR_BIT / (2+14); static size_t NumBitsForEncodedLength(size_t encodedLength) { static const size_t numBitsForEncodedLength[1u << numEncodedLengthBits] = { 8, // 1K 14, // 64K 20, // 4M numQuantizedPcBits // a full pointer }; return numBitsForEncodedLength[encodedLength]; } static size_t EncodedLength(uintptr_t quantizedOffset) { for(size_t encodedLength = 0; encodedLength < 1u << numEncodedLengthBits; encodedLength++) { const size_t numBits = NumBitsForEncodedLength(encodedLength); const uintptr_t maxValue = (1u << numBits)-1; if(quantizedOffset <= maxValue) return encodedLength; } wdbg_assert(0); // unreachable return 0; } static uintptr_t codeSegmentAddress; static uintptr_t quantizedCodeSegmentAddress; static uintptr_t quantizedCodeSegmentLength; static void FindCodeSegment() { const wchar_t* dllName = 0; // current module ModuleExtents extents(dllName); codeSegmentAddress = extents.Address(); quantizedCodeSegmentAddress = Quantize(codeSegmentAddress); quantizedCodeSegmentLength = Quantize(extents.Length()); } class BitStream { public: BitStream(u8* storage, size_t storageSize) : m_remainderBits(0), m_numRemainderBits(0) , m_pos(storage), m_bitsLeft((size_t)storageSize*8) { } size_t BitsLeft() const { return m_bitsLeft; } void Write(const size_t numOutputBits, uintptr_t outputValue) { wdbg_assert(numOutputBits <= m_bitsLeft); wdbg_assert(outputValue < ((uintptr_t)1u << numOutputBits)); size_t outputBitsLeft = numOutputBits; while(outputBitsLeft > 0) { const size_t numBits = std::min(outputBitsLeft, size_t(8)); m_bitsLeft -= numBits; // (NB: there is no need to extract exactly numBits because // outputValue's MSBs were verified to be zero) const uintptr_t outputByte = outputValue & 0xFF; outputValue >>= 8; outputBitsLeft -= numBits; m_remainderBits |= outputByte << m_numRemainderBits; m_numRemainderBits += numBits; if(m_numRemainderBits >= 8) { const u8 remainderByte = (m_remainderBits & 0xFF); m_remainderBits >>= 8; m_numRemainderBits -= 8; *m_pos++ = remainderByte; } } } void Finish() { const size_t partialBits = m_numRemainderBits % 8; if(partialBits) { m_bitsLeft -= 8-partialBits; m_numRemainderBits += 8-partialBits; } while(m_numRemainderBits) { const u8 remainderByte = (m_remainderBits & 0xFF); *m_pos++ = remainderByte; m_remainderBits >>= 8; m_numRemainderBits -= 8; } wdbg_assert(m_bitsLeft % 8 == 0); while(m_bitsLeft) { *m_pos++ = 0; m_bitsLeft -= 8; } } uintptr_t Read(const size_t numInputBits) { wdbg_assert(numInputBits <= m_bitsLeft); uintptr_t inputValue = 0; size_t inputBitsLeft = numInputBits; while(inputBitsLeft > 0) { const size_t numBits = std::min(inputBitsLeft, size_t(8)); m_bitsLeft -= numBits; if(m_numRemainderBits < numBits) { const size_t inputByte = *m_pos++; m_remainderBits |= inputByte << m_numRemainderBits; m_numRemainderBits += 8; } const uintptr_t remainderByte = (m_remainderBits & ((1u << numBits)-1)); m_remainderBits >>= numBits; m_numRemainderBits -= numBits; inputValue |= remainderByte << (numInputBits-inputBitsLeft); inputBitsLeft -= numBits; } return inputValue; } private: uintptr_t m_remainderBits; size_t m_numRemainderBits; u8* m_pos; size_t m_bitsLeft; }; static void StashCallers(_CrtMemBlockHeader* header, const uintptr_t* callers, size_t numCallers) { // transform an array of callers into a (sorted and unique) set. uintptr_t quantizedPcSet[maxCallers]; std::transform(callers, callers+numCallers, quantizedPcSet, Quantize); std::sort(quantizedPcSet, quantizedPcSet+numCallers); uintptr_t* const end = std::unique(quantizedPcSet, quantizedPcSet+numCallers); const size_t quantizedPcSetSize = end-quantizedPcSet; // transform the set into a sequence of quantized offsets. uintptr_t quantizedOffsets[maxCallers]; if(quantizedPcSet[0] >= quantizedCodeSegmentAddress) quantizedOffsets[0] = quantizedPcSet[0] - quantizedCodeSegmentAddress; else { quantizedOffsets[0] = quantizedPcSet[0]; // make sure RetrieveCallers can differentiate between pointers and code-segment-offsets wdbg_assert(quantizedOffsets[0] >= quantizedCodeSegmentLength); } for(size_t i = 1; i < numCallers; i++) quantizedOffsets[i] = quantizedPcSet[i] - quantizedPcSet[i-1]; // write quantized offsets to stream BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line)); for(size_t i = 0; i < quantizedPcSetSize; i++) { const uintptr_t quantizedOffset = quantizedOffsets[i]; const size_t encodedLength = EncodedLength(quantizedOffset); const size_t numBits = NumBitsForEncodedLength(encodedLength); if(bitStream.BitsLeft() < numEncodedLengthBits+numBits) break; bitStream.Write(numEncodedLengthBits, encodedLength); bitStream.Write(numBits, quantizedOffset); } bitStream.Finish(); } static void RetrieveCallers(_CrtMemBlockHeader* header, uintptr_t* callers, size_t& numCallers) { // read quantized offsets from stream uintptr_t quantizedOffsets[maxCallers]; numCallers = 0; BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line)); for(;;) { if(bitStream.BitsLeft() < numEncodedLengthBits) break; const size_t encodedLength = bitStream.Read(numEncodedLengthBits); const size_t numBits = NumBitsForEncodedLength(encodedLength); if(bitStream.BitsLeft() < numBits) break; const uintptr_t quantizedOffset = bitStream.Read(numBits); if(!quantizedOffset) break; quantizedOffsets[numCallers++] = quantizedOffset; } if(!numCallers) return; // expand offsets into a set of callers if(quantizedOffsets[0] <= quantizedCodeSegmentLength) callers[0] = Expand(quantizedOffsets[0] + quantizedCodeSegmentAddress); else callers[0] = Expand(quantizedOffsets[0]); for(size_t i = 1; i < numCallers; i++) callers[i] = callers[i-1] + Expand(quantizedOffsets[i]); } //----------------------------------------------------------------------------- // find out who called an allocation function /** * gather and store a (filtered) list of callers. **/ class CallStack { public: void Gather() { m_numCallers = 0; CONTEXT context; (void)debug_CaptureContext(&context); (void)wdbg_sym_WalkStack(OnFrame_Trampoline, (uintptr_t)this, context); std::fill(m_callers+m_numCallers, m_callers+maxCallers, 0); } const uintptr_t* Callers() const { return m_callers; } size_t NumCallers() const { return m_numCallers; } private: Status OnFrame(const STACKFRAME64* frame) { const uintptr_t pc = frame->AddrPC.Offset; // skip invalid frames if(pc == 0) return INFO::OK; Status ret = m_filter.NotifyOfCaller(pc); // (CallerFilter provokes stack traces of heap functions; if that is // what happened, then we must not continue) if(ret != INFO::SKIPPED) return ret; // stop the stack walk if frame storage is full if(m_numCallers >= maxCallers) return INFO::ALL_COMPLETE; if(!m_filter.IsKnownCaller(pc)) m_callers[m_numCallers++] = pc; return INFO::OK; } static Status OnFrame_Trampoline(const STACKFRAME64* frame, uintptr_t cbData) { CallStack* this_ = (CallStack*)cbData; return this_->OnFrame(frame); } CallerFilter m_filter; uintptr_t m_callers[maxCallers]; size_t m_numCallers; }; //----------------------------------------------------------------------------- // RAII wrapper for installing a CRT allocation hook class AllocationHook { public: AllocationHook() { wdbg_assert(s_instance == 0 && s_previousHook == 0); s_instance = this; s_previousHook = _CrtSetAllocHook(Hook); } ~AllocationHook() { _CRT_ALLOC_HOOK removedHook = _CrtSetAllocHook(s_previousHook); wdbg_assert(removedHook == Hook); // warn if we removed someone else's hook s_instance = 0; s_previousHook = 0; } /** * @param operation either _HOOK_ALLOC, _HOOK_REALLOC or _HOOK_FREE * @param userData is only valid (nonzero) for realloc and free because * we are called BEFORE the actual heap operation. **/ virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber) = 0; private: static int __cdecl Hook(int operation, void* userData, size_t size, int blockType, long allocationNumber, const unsigned char* file, int line) { static bool busy = false; wdbg_assert(!busy); busy = true; s_instance->OnHeapOperation(operation, userData, size, allocationNumber); busy = false; if(s_previousHook) return s_previousHook(operation, userData, size, blockType, allocationNumber, file, line); return 1; // continue as if the hook had never been called } // unfortunately static because we can't pass our `this' pointer through // the allocation hook. static AllocationHook* s_instance; static _CRT_ALLOC_HOOK s_previousHook; }; AllocationHook* AllocationHook::s_instance; _CRT_ALLOC_HOOK AllocationHook::s_previousHook; //----------------------------------------------------------------------------- // our allocation hook // ideally we would just stash the callers in the newly created header. // unfortunately we are called BEFORE it (and the allocation) are actually // created, so we need to keep the information around until the next call to // AllocHook; only then can it be stored. // // unfortunately the CRT does not provide an O(1) means of getting at the // most recent block header. instead, we do so once and then keep it // up-to-date in the allocation hook. this is safe because we run under // the _HEAP_LOCK and ensure the allocation numbers match. static intptr_t s_numAllocations; intptr_t wdbg_heap_NumberOfAllocations() { return s_numAllocations; } class AllocationTracker : public AllocationHook { public: AllocationTracker() : m_pendingAllocationNumber(0) { } virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber) { UNUSED2(size); if(operation == _HOOK_ALLOC || operation == _HOOK_REALLOC) cpu_AtomicAdd(&s_numAllocations, 1); bool hasChanged; _CrtMemBlockHeader* head = GetHeapListHead(operation, userData, hasChanged); // if the head changed, the last operation was a (re)allocation and // we now have its header; stash the pending call stack there. if(hasChanged) { wdbg_assert(head->allocationNumber == m_pendingAllocationNumber); // note: overwrite existing file/line info (even if valid) to avoid // special cases in the report hook. StashCallers(head, m_pendingCallStack.Callers(), m_pendingCallStack.NumCallers()); } // remember the current caller for next time m_pendingCallStack.Gather(); // NB: called for each operation, as required by the filter recording step m_pendingAllocationNumber = allocationNumber; } private: long m_pendingAllocationNumber; CallStack m_pendingCallStack; }; //----------------------------------------------------------------------------- static void PrintCallStack(const uintptr_t* callers, size_t numCallers) { if(!numCallers || callers[0] == 0) { wdbg_printf(L"\n call stack not available.\n"); return; } wdbg_printf(L"\n partial, unordered call stack:\n"); for(size_t i = 0; i < numCallers; i++) { wchar_t name[DEBUG_SYMBOL_CHARS] = {'\0'}; wchar_t file[DEBUG_FILE_CHARS] = {'\0'}; int line = -1; Status err = debug_ResolveSymbol((void*)callers[i], name, file, &line); wdbg_printf(L" "); if(err != INFO::OK) wdbg_printf(L"(error %d resolving PC=%p) ", err, callers[i]); if(file[0] != '\0') wdbg_printf(L"%ls(%d) : ", file, line); wdbg_printf(L"%ls\n", name); } } static int __cdecl ReportHook(int reportType, wchar_t* message, int* out) { UNUSED2(reportType); // set up return values to reduce the chance of mistakes below *out = 0; // alternatives are failure (-1) and breakIntoDebugger (1) const int ret = 0; // not "handled", continue calling other hooks // note: this hook is transparent in that it never affects the CRT. // we can't suppress parts of a leak report because that causes the // rest of it to be skipped. static enum { WaitingForDump, WaitingForBlock, IsBlock } state = WaitingForDump; switch(state) { case WaitingForDump: if(!wcscmp(message, L"Dumping objects ->\n")) state = WaitingForBlock; return ret; case IsBlock: { // common case: "normal block at 0xPPPPPPPP, N bytes long". const wchar_t* addressString = wcsstr(message, L"0x"); if(addressString) { const uintptr_t address = wcstoul(addressString, 0, 0); _CrtMemBlockHeader* header = HeaderFromData((void*)address); uintptr_t callers[maxCallers]; size_t numCallers; RetrieveCallers(header, callers, numCallers); PrintCallStack(callers, numCallers); state = WaitingForBlock; return ret; } // else: for reasons unknown, there's apparently no information // about the block; fall through to the previous state. } case WaitingForBlock: if(message[0] == '{') state = IsBlock; // suppress messages containing "file" and "line" since the normal // interpretation of those header fields is invalid. else if(wcschr(message, '(')) message[0] = '\0'; return ret; default: wdbg_assert(0); // unreachable } wdbg_assert(0); // unreachable return 0; } #else intptr_t wdbg_heap_NumberOfAllocations() { return 0; } #endif //----------------------------------------------------------------------------- #if ENABLE_LEAK_INSTRUMENTATION static AllocationTracker* s_tracker; #endif static Status wdbg_heap_Init() { #if ENABLE_LEAK_INSTRUMENTATION FindCodeSegment(); // load symbol information now (fails if it happens during shutdown) wchar_t name[DEBUG_SYMBOL_CHARS]; wchar_t file[DEBUG_FILE_CHARS]; int line; (void)debug_ResolveSymbol(wdbg_heap_Init, name, file, &line); int ret = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, ReportHook); if(ret == -1) abort(); s_tracker = new AllocationTracker; #endif wdbg_heap_Enable(true); return INFO::OK; } static Status wdbg_heap_Shutdown() { #if ENABLE_LEAK_INSTRUMENTATION SAFE_DELETE(s_tracker); #endif return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wdir_watch.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdir_watch.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdir_watch.cpp (revision 19899) @@ -1,383 +1,383 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Win32 directory change notification */ #include "precompiled.h" #include "lib/sysdep/dir_watch.h" #include "lib/allocators/shared_ptr.h" #include "lib/path.h" // path_is_subpath #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/winit.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/wiocp.h" WINIT_REGISTER_MAIN_INIT(wdir_watch_Init); WINIT_REGISTER_MAIN_SHUTDOWN(wdir_watch_Shutdown); //----------------------------------------------------------------------------- // DirHandle class DirHandle { public: DirHandle(const OsPath& path) { WinScopedPreserveLastError s; // CreateFile const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; const DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED; m_hDir = CreateFileW(OsString(path).c_str(), FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0); } ~DirHandle() { // contrary to MSDN, the canceled IOs do not issue a completion notification. // (receiving packets after (unsuccessful) cancellation would be dangerous) BOOL ok = CancelIo(m_hDir); WARN_IF_FALSE(ok); CloseHandle(m_hDir); m_hDir = INVALID_HANDLE_VALUE; } // == INVALID_HANDLE_VALUE if path doesn't exist operator HANDLE() const { return m_hDir; } private: HANDLE m_hDir; }; //----------------------------------------------------------------------------- // DirWatchRequest class DirWatchRequest { NONCOPYABLE(DirWatchRequest); public: DirWatchRequest(const OsPath& path) : m_path(path), m_dirHandle(path), m_data(new u8[dataSize]) { m_ovl = (OVERLAPPED*)calloc(1, sizeof(OVERLAPPED)); // rationale for dynamic alloc: see decl ENSURE(m_ovl); // (hEvent is needed for the wait after CancelIo below) const BOOL manualReset = TRUE; const BOOL initialState = FALSE; m_ovl->hEvent = CreateEvent(0, manualReset, initialState, 0); } ~DirWatchRequest() { // we need to free m_data here, so the pending IO had better // not write to that memory in future. therefore: WARN_IF_FALSE(CancelIo(m_dirHandle)); // however, this is not synchronized with the DPC (?) that apparently // delivers the data - m_data is filled anyway. // we need to ensure that either the IO has happened or that it // was successfully canceled before freeing m_data and m_ovl, so wait: { WinScopedPreserveLastError s; // (GetOverlappedResult without a valid hEvent hangs on Vista; // we'll abort after a timeout to be safe.) const DWORD ret = WaitForSingleObject(m_ovl->hEvent, 1000); WARN_IF_FALSE(CloseHandle(m_ovl->hEvent)); if(ret == WAIT_OBJECT_0 || GetLastError() == ERROR_OPERATION_ABORTED) { SetLastError(0); delete[] m_data; free(m_ovl); } else { // (this could conceivably happen if a kernel debugger // hangs the system during the wait duration.) debug_printf("WARNING: IO may still be pending; to avoid memory corruption, we won't free the buffer.\n"); DEBUG_WARN_ERR(ERR::TIMED_OUT); // intentionally leak m_data and m_ovl! } } } const OsPath& Path() const { return m_path; } void AttachTo(HANDLE& hIOCP) const { AttachToCompletionPort(m_dirHandle, hIOCP, (uintptr_t)this); } // (called again after each notification, so it mustn't AttachToCompletionPort) Status Issue() { if(m_dirHandle == INVALID_HANDLE_VALUE) WARN_RETURN(ERR::PATH_NOT_FOUND); const BOOL watchSubtree = TRUE; // (see IntrusiveLink comments) const DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION; // not set: FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_LAST_ACCESS, FILE_NOTIFY_CHANGE_SECURITY DWORD undefined = 0; // (non-NULL pointer avoids BoundsChecker warning) m_ovl->Internal = 0; WARN_IF_FALSE(ReadDirectoryChangesW(m_dirHandle, m_data, dataSize, watchSubtree, filter, &undefined, m_ovl, 0)); return INFO::OK; } /** * (call when completion port indicates data is available) **/ void RetrieveNotifications(DirWatchNotifications& notifications) const { const FILE_NOTIFY_INFORMATION* fni = (const FILE_NOTIFY_INFORMATION*)m_data; for(;;) { // convert (non-zero-terminated) BSTR to Path::String cassert(sizeof(wchar_t) == sizeof(WCHAR)); const size_t length = fni->FileNameLength / sizeof(WCHAR); Path::String name(fni->FileName, length); // (NB: name is actually a relative path since we watch entire subtrees) const OsPath pathname = m_path / name; const DirWatchNotification::EType type = TypeFromAction(fni->Action); notifications.push_back(DirWatchNotification(pathname, type)); if(!fni->NextEntryOffset) // this was the last entry. break; fni = (const FILE_NOTIFY_INFORMATION*)(uintptr_t(fni) + fni->NextEntryOffset); } } private: static DirWatchNotification::EType TypeFromAction(const DWORD action) { switch(action) { case FILE_ACTION_ADDED: case FILE_ACTION_RENAMED_NEW_NAME: return DirWatchNotification::Created; case FILE_ACTION_REMOVED: case FILE_ACTION_RENAMED_OLD_NAME: return DirWatchNotification::Deleted; case FILE_ACTION_MODIFIED: return DirWatchNotification::Changed; default: DEBUG_WARN_ERR(ERR::LOGIC); return DirWatchNotification::Changed; } } OsPath m_path; DirHandle m_dirHandle; // rationale: // - if too small, notifications may be lost! (the CSD-poll application // may be confronted with hundreds of new files in a short time frame) // - requests larger than 64 KiB fail on SMB due to packet restrictions. static const size_t dataSize = 64*KiB; // rationale: // - each instance needs their own buffer. (we can't share a central // copy because the watches are independent and may be triggered // 'simultaneously' before the next poll.) // - lifetime must be managed manually (see dtor) u8* m_data; // rationale: // - ReadDirectoryChangesW's asynchronous mode is triggered by passing // a valid OVERLAPPED parameter; notification proceeds via // completion ports, but we still need hEvent - see above. // - this must remain valid while the IO is pending. if the wait // were to fail, we must not free this memory, either. OVERLAPPED* m_ovl; }; typedef shared_ptr PDirWatchRequest; //----------------------------------------------------------------------------- // IntrusiveLink // using watches of entire subtrees to satisfy single-directory requests // requires a list of existing watches. an intrusive, doubly-linked list // is convenient because removal must occur within the DirWatch destructor. // since boost::intrusive doesn't automatically remove objects from their // containers when they are destroyed, we implement a simple circular list // via sentinel. note that DirWatchManager iterates over DirWatch, not their // embedded links. we map from link to the parent object via offsetof // (slightly less complex than storing back pointers to the parents, and // avoids 'this-pointer used during initialization list' warnings). class IntrusiveLink { public: IntrusiveLink() { m_prev = m_next = this; // sentinel } IntrusiveLink(IntrusiveLink* sentinel) { // insert after sentinel m_prev = sentinel; m_next = sentinel->m_next; m_next->m_prev = this; sentinel->m_next = this; } ~IntrusiveLink() { // remove from list m_prev->m_next = m_next; m_next->m_prev = m_prev; } IntrusiveLink* Next() const { return m_next; } private: IntrusiveLink* m_prev; IntrusiveLink* m_next; }; //----------------------------------------------------------------------------- // DirWatch struct DirWatch { DirWatch(IntrusiveLink* sentinel, const PDirWatchRequest& request) : link(sentinel), request(request) { } IntrusiveLink link; PDirWatchRequest request; }; //----------------------------------------------------------------------------- // DirWatchManager class DirWatchManager { public: DirWatchManager() : hIOCP(0) // Win32 requires 0-init; created in the first call to AttachTo { } ~DirWatchManager() { CloseHandle(hIOCP); } Status Add(const OsPath& path, PDirWatch& dirWatch) { ENSURE(path.IsDirectory()); // check if this is a subdirectory of a tree that's already being // watched (this is much faster than issuing a new watch; it also // prevents accidentally watching the same directory twice). for(IntrusiveLink* link = m_sentinel.Next(); link != &m_sentinel; link = link->Next()) { DirWatch* const existingDirWatch = (DirWatch*)(uintptr_t(link) - offsetof(DirWatch, link)); if(path_is_subpath(OsString(path).c_str(), OsString(existingDirWatch->request->Path()).c_str())) { dirWatch.reset(new DirWatch(&m_sentinel, existingDirWatch->request)); return INFO::OK; } } PDirWatchRequest request(new DirWatchRequest(path)); request->AttachTo(hIOCP); RETURN_STATUS_IF_ERR(request->Issue()); dirWatch.reset(new DirWatch(&m_sentinel, request)); return INFO::OK; } Status Poll(DirWatchNotifications& notifications) { POLL_AGAIN: DWORD bytesTransferred; ULONG_PTR key; OVERLAPPED* ovl; const Status ret = PollCompletionPort(hIOCP, 0, bytesTransferred, key, ovl); if(ret == ERR::ABORTED) // watch was canceled goto POLL_AGAIN; RETURN_STATUS_IF_ERR(ret); DirWatchRequest* request = (DirWatchRequest*)key; request->RetrieveNotifications(notifications); RETURN_STATUS_IF_ERR(request->Issue()); // re-issue return INFO::OK; } private: IntrusiveLink m_sentinel; HANDLE hIOCP; }; static DirWatchManager* s_dirWatchManager; //----------------------------------------------------------------------------- Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch) { WinScopedLock lock(WDIR_WATCH_CS); return s_dirWatchManager->Add(path, dirWatch); } Status dir_watch_Poll(DirWatchNotifications& notifications) { WinScopedLock lock(WDIR_WATCH_CS); return s_dirWatchManager->Poll(notifications); } //----------------------------------------------------------------------------- static Status wdir_watch_Init() { s_dirWatchManager = new DirWatchManager; return INFO::OK; } static Status wdir_watch_Shutdown() { SAFE_DELETE(s_dirWatchManager); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wdll_ver.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdll_ver.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdll_ver.cpp (revision 19899) @@ -1,118 +1,118 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * return DLL version information. */ #include "precompiled.h" #include "lib/sysdep/os/win/wdll_ver.h" #include #include #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/allocators/shared_ptr.h" #if MSC_VERSION #pragma comment(lib, "version.lib") // DLL version #endif //----------------------------------------------------------------------------- static Status ReadVersionString(const OsPath& modulePathname, wchar_t* out_ver, size_t out_ver_len) { WinScopedPreserveLastError s; // GetFileVersion*, Ver* // determine size of and allocate memory for version information. DWORD unused; const DWORD ver_size = GetFileVersionInfoSizeW(OsString(modulePathname).c_str(), &unused); // [bytes] if(!ver_size) { // check if the failure is due to not finding modulePathname // (necessary since GetFileVersionInfoSize doesn't SetLastError) HMODULE hModule = LoadLibraryExW(OsString(modulePathname).c_str(), 0, LOAD_LIBRARY_AS_DATAFILE); if(!hModule) return ERR::FAIL; // NOWARN (file not found - due to FS redirection?) FreeLibrary(hModule); return ERR::NOT_SUPPORTED; // NOWARN (module apparently lacks version information) } shared_ptr mem = Allocate(ver_size); if(!GetFileVersionInfoW(OsString(modulePathname).c_str(), 0, ver_size, mem.get())) WARN_RETURN(ERR::_3); u16* lang; // -> 16 bit language ID, 16 bit codepage UINT lang_len; const BOOL ok = VerQueryValueW(mem.get(), L"\\VarFileInfo\\Translation", (void**)&lang, &lang_len); if(!ok || !lang || lang_len != 4) WARN_RETURN(ERR::_4); wchar_t subblock[64]; swprintf_s(subblock, ARRAY_SIZE(subblock), L"\\StringFileInfo\\%04X%04X\\FileVersion", lang[0], lang[1]); const wchar_t* in_ver; UINT in_ver_len; if(!VerQueryValueW(mem.get(), subblock, (void**)&in_ver, &in_ver_len)) WARN_RETURN(ERR::_5); wcscpy_s(out_ver, out_ver_len, in_ver); return INFO::OK; } void wdll_ver_Append(const OsPath& pathname, VersionList& list) { if(pathname.empty()) return; // avoid error in ReadVersionString // pathname may not have an extension (e.g. driver names from the // registry). note that always appending ".dll" would be incorrect // since some have ".sys" extension. OsPath modulePathname(pathname); if(modulePathname.Extension() == "") modulePathname = modulePathname.ChangeExtension(L".dll"); const OsPath moduleName(modulePathname.Filename()); // read file version. try this with and without FS redirection since // pathname might assume both. wchar_t versionString[500]; // enclosed in () below if(ReadVersionString(modulePathname, versionString, ARRAY_SIZE(versionString)) != INFO::OK) { WinScopedDisableWow64Redirection s; // still failed; avoid polluting list with DLLs that don't exist // (requiring callers to check for their existence beforehand would be // onerous and unreliable) if(ReadVersionString(modulePathname, versionString, ARRAY_SIZE(versionString)) != INFO::OK) return; } if(!list.empty()) list += L", "; list += moduleName.Filename().string(); list += L" ("; list += versionString; list += L")"; } Index: ps/trunk/source/lib/sysdep/os/win/wgfx.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wgfx.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wgfx.cpp (revision 19899) @@ -1,187 +1,187 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * graphics card detection on Windows. */ #include "precompiled.h" #include "lib/sysdep/os/win/wgfx.h" #include "lib/sysdep/gfx.h" #include "lib/sysdep/os/win/wdll_ver.h" #include "lib/sysdep/os/win/wutil.h" #if MSC_VERSION #pragma comment(lib, "advapi32.lib") // registry #endif // note: this implementation doesn't require OpenGL to be initialized. static Status AppendDriverVersionsFromRegistry(VersionList& versionList) { // rationale: // - we could easily determine the 2d driver via EnumDisplaySettings, // but we want to query the actual OpenGL driver. see // http://www.opengl.org/discussion_boards/ubb/Forum3/HTML/009679.html ; // in short, we need the exact OpenGL driver version because some // driver packs (e.g. Omega) mix and match DLLs. // - an alternative implementation would be to enumerate all // DLLs loaded into the process, and check for a glBegin export. // that requires toolhelp/PSAPI (a bit complicated) and telling // ICD/opengl32.dll apart (not future-proof). // - therefore, we stick with the OpenGLDrivers approach. since there is // no good way to determine which of the subkeys (e.g. nvoglnt) is // active (several may exist due to previously removed drivers), // we just display all of them. it is obvious from looking at // gfx_card which one is correct; we thus avoid driver-specific // name checks and reporting incorrectly. wchar_t dllName[MAX_PATH+1]; HKEY hkDrivers; const wchar_t* key = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\OpenGLDrivers"; // (we've received word of this failing on one WinXP system, but // AppendDriverVersionsFromKnownFiles might still work.) if(RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hkDrivers) != 0) return ERR::FAIL; // NOWARN (see above) // for each subkey (i.e. installed OpenGL driver): for(DWORD i = 0; ; i++) { wchar_t driverName[32]; DWORD driverNameLength = ARRAY_SIZE(driverName); const LONG err = RegEnumKeyExW(hkDrivers, i, driverName, &driverNameLength, 0, 0, 0, 0); if(err == ERROR_NO_MORE_ITEMS) { if(i == 0) { RegCloseKey(hkDrivers); return ERR::NOT_SUPPORTED; // NOWARN (ATI and NVidia don't create sub-keys on Windows 7) } break; } ENSURE(err == ERROR_SUCCESS); HKEY hkDriver; if(RegOpenKeyExW(hkDrivers, driverName, 0, KEY_QUERY_VALUE, &hkDriver) == 0) { DWORD dllNameLength = ARRAY_SIZE(dllName)-5; // for ".dll" if(RegQueryValueExW(hkDriver, L"Dll", 0, 0, (LPBYTE)dllName, &dllNameLength) == 0) wdll_ver_Append(dllName, versionList); RegCloseKey(hkDriver); } } // for each value: // (some old drivers, e.g. S3 Super Savage, store their ICD name in a // single REG_SZ value. we therefore include those as well.) for(DWORD i = 0; ; i++) { wchar_t name[100]; // we don't need this, but RegEnumValue fails otherwise. DWORD nameLength = ARRAY_SIZE(name); DWORD type; DWORD dllNameLength = ARRAY_SIZE(dllName)-5; // for ".dll" const DWORD err = RegEnumValueW(hkDrivers, i, name, &nameLength, 0, &type, (LPBYTE)dllName, &dllNameLength); if(err == ERROR_NO_MORE_ITEMS) break; ENSURE(err == ERROR_SUCCESS); if(type == REG_SZ) wdll_ver_Append(dllName, versionList); } RegCloseKey(hkDrivers); return INFO::OK; } static void AppendDriverVersionsFromKnownFiles(VersionList& versionList) { // (check all known file names regardless of gfx_card, which may change and // defeat our parsing. this takes about 5..10 ms) // NVidia wdll_ver_Append(L"nvoglv64.dll", versionList); wdll_ver_Append(L"nvoglv32.dll", versionList); wdll_ver_Append(L"nvoglnt.dll", versionList); // ATI wdll_ver_Append(L"atioglxx.dll", versionList); // Intel wdll_ver_Append(L"ig4icd32.dll", versionList); wdll_ver_Append(L"ig4icd64.dll", versionList); wdll_ver_Append(L"iglicd32.dll", versionList); } std::wstring wgfx_DriverInfo() { VersionList versionList; if(AppendDriverVersionsFromRegistry(versionList) != INFO::OK) // (fails on Windows 7) AppendDriverVersionsFromKnownFiles(versionList); return versionList; } //----------------------------------------------------------------------------- // direct implementations of some gfx functions namespace gfx { Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq) { DEVMODE dm = { sizeof(dm) }; if(!EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm)) WARN_RETURN(ERR::FAIL); // EnumDisplaySettings is documented to set the values of the following: const DWORD expectedFlags = DM_PELSWIDTH|DM_PELSHEIGHT|DM_BITSPERPEL|DM_DISPLAYFREQUENCY|DM_DISPLAYFLAGS; ENSURE((dm.dmFields & expectedFlags) == expectedFlags); if(xres) *xres = (int)dm.dmPelsWidth; if(yres) *yres = (int)dm.dmPelsHeight; if(bpp) *bpp = (int)dm.dmBitsPerPel; if(freq) *freq = (int)dm.dmDisplayFrequency; return INFO::OK; } Status GetMonitorSize(int& width_mm, int& height_mm) { // (DC for the primary monitor's entire screen) const HDC hDC = GetDC(0); width_mm = GetDeviceCaps(hDC, HORZSIZE); height_mm = GetDeviceCaps(hDC, VERTSIZE); ReleaseDC(0, hDC); return INFO::OK; } } // namespace gfx Index: ps/trunk/source/lib/sysdep/os/win/whrt/counter.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/counter.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/counter.h (revision 19899) @@ -1,91 +1,91 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Interface for counter implementations */ #ifndef INCLUDED_COUNTER #define INCLUDED_COUNTER // derived implementations must be called CounterIMPL, // where IMPL matches the WHRT_IMPL identifier. (see CREATE) class ICounter { public: // (compiled-generated) ctor only sets up the vptr virtual ~ICounter() {} virtual const char* Name() const = 0; // Activate with an error return value is much cleaner+safer than // throwing exceptions in the ctor. virtual Status Activate() = 0; virtual void Shutdown() = 0; virtual bool IsSafe() const = 0; /** * @return the current value of the counter (all but the lower * CounterBits() bits must be zero) **/ virtual u64 Counter() const = 0; // note: implementations need not cache the following; that's taken // care of by WHRT. /** * @return the bit width of the counter (<= 64) * WHRT uses this to ensure the counter (running at nominal frequency) * doesn't overflow more than once during CALIBRATION_INTERVAL_MS. **/ virtual size_t CounterBits() const = 0; /** * initial measurement of the tick rate. not necessarily correct * (e.g. when using TSC: os_cpu_ClockFrequency isn't exact). **/ virtual double NominalFrequency() const = 0; /** * actual resolution [s]. differs from 1/NominalFrequency if the * timer adjustment is greater than 1 tick. **/ virtual double Resolution() const = 0; }; /** * @return a newly created ICounter of type \ or 0 iff the ID is invalid. * @param id integer ID (0..N-1) * * there can only be one active counter at a time; the previous one must * have been destroyed before creating another! **/ extern ICounter* CreateCounter(size_t id); /** * shut down the counter, free its resources and zero its pointer. **/ extern void DestroyCounter(ICounter*& counter); #endif // #ifndef INCLUDED_COUNTER Index: ps/trunk/source/lib/sysdep/os/win/whrt/pmt.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/pmt.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/pmt.cpp (revision 19899) @@ -1,115 +1,115 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using ACPI PM timer */ #include "precompiled.h" #include "lib/sysdep/os/win/whrt/pmt.h" #include "lib/sysdep/os/win/whrt/counter.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/acpi.h" #include "lib/sysdep/os/win/mahaf.h" #include "lib/bits.h" static const u32 TMR_VAL_EXT = Bit(8); // FADT flags //----------------------------------------------------------------------------- class CounterPMT : public ICounter { public: CounterPMT() : m_portAddress(0xFFFF) { } virtual const char* Name() const { return "PMT"; } Status Activate() { // mahaf is needed for port I/O. RETURN_STATUS_IF_ERR(mahaf_Init()); // (fails without Administrator privileges) // (note: it's called FADT, but the signature is "FACP") const FADT* fadt = (const FADT*)acpi_GetTable("FACP"); if(!fadt) return ERR::NOT_SUPPORTED; // NOWARN (ACPI tables might not be available) m_portAddress = u16_from_larger(fadt->pmTimerPortAddress); return INFO::OK; } void Shutdown() { } bool IsSafe() const { // the PMT has one issue: "Performance counter value may unexpectedly // leap forward" (Q274323). This happens on some buggy Pentium-era // systems under heavy PCI bus load. We are clever and observe that // the TSC implementation would be used on such systems (because it // has higher precedence and is safe on P5 CPUs), so the PMT is fine // in general. return true; } u64 Counter() const { return mahaf_ReadPort32(m_portAddress); } size_t CounterBits() const { // (see previous acpi_GetTable call) const FADT* fadt = (const FADT*)acpi_GetTable("FACP"); ENSURE(fadt); // Activate made sure FADT is available const size_t counterBits = (fadt->flags & TMR_VAL_EXT)? 32 : 24; return counterBits; } double NominalFrequency() const { return (double)PMT_FREQ; } double Resolution() const { return 1.0 / PMT_FREQ; } private: u16 m_portAddress; }; ICounter* CreateCounterPMT(void* address, size_t size) { ENSURE(sizeof(CounterPMT) <= size); return new(address) CounterPMT(); } Index: ps/trunk/source/lib/sysdep/os/win/whrt/tgt.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/tgt.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/tgt.cpp (revision 19899) @@ -1,106 +1,106 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using timeGetTime */ // note: WinMM is delay-loaded to avoid dragging it in when this timer // implementation isn't used. (this is relevant because its startup is // fairly slow) #include "precompiled.h" #include "lib/sysdep/os/win/whrt/tgt.h" #include "lib/sysdep/os/win/whrt/counter.h" #include "lib/sysdep/os/win/win.h" #include #if MSC_VERSION #pragma comment(lib, "winmm.lib") #endif // "Guidelines For Providing Multimedia Timer Support" claims that // speeding the timer up to 2 ms has little impact, while 1 ms // causes significant slowdown. static const UINT PERIOD_MS = 2; class CounterTGT : public ICounter { public: virtual const char* Name() const { return "TGT"; } Status Activate() { // note: timeGetTime is always available and cannot fail. MMRESULT ret = timeBeginPeriod(PERIOD_MS); ENSURE(ret == TIMERR_NOERROR); return INFO::OK; } void Shutdown() { timeEndPeriod(PERIOD_MS); } bool IsSafe() const { // the only point of criticism is the possibility of falling behind // due to lost interrupts. this can happen to any interrupt-based timer // and some systems may lack a counter-based timer, so consider TGT // 'safe'. note that it is still only chosen when all other timers fail. return true; } u64 Counter() const { return timeGetTime(); } size_t CounterBits() const { return 32; } double NominalFrequency() const { return 1000.0; } double Resolution() const { return PERIOD_MS*1e-3; } }; ICounter* CreateCounterTGT(void* address, size_t size) { ENSURE(sizeof(CounterTGT) <= size); return new(address) CounterTGT(); } Index: ps/trunk/source/lib/sysdep/os/win/whrt/whrt.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/whrt.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/whrt.cpp (revision 19899) @@ -1,317 +1,317 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Windows High Resolution Timer */ #include "precompiled.h" #include "lib/sysdep/os/win/whrt/whrt.h" #include // _beginthreadex #include "lib/sysdep/cpu.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/winit.h" #include "lib/sysdep/acpi.h" #include "lib/bits.h" #include "lib/sysdep/os/win/whrt/counter.h" WINIT_REGISTER_EARLY_INIT2(whrt_Init); // wutil -> whrt -> wtime WINIT_REGISTER_LATE_SHUTDOWN(whrt_Shutdown); namespace ERR { const Status WHRT_COUNTER_UNSAFE = 140000; } //----------------------------------------------------------------------------- // choose best available safe counter // (moved into a separate function to simplify error handling) static inline Status ActivateCounter(ICounter* counter) { RETURN_STATUS_IF_ERR(counter->Activate()); if(!counter->IsSafe()) return ERR::WHRT_COUNTER_UNSAFE; // NOWARN (happens often) return INFO::OK; } /** * @return the newly created and unique instance of the next best counter * that is deemed safe, or 0 if all have already been created. **/ static ICounter* GetNextBestSafeCounter() { for(;;) { static size_t nextCounterId = 0; ICounter* counter = CreateCounter(nextCounterId++); if(!counter) return 0; // tried all, none were safe Status err = ActivateCounter(counter); if(err == INFO::OK) { debug_printf("HRT: using name=%s freq=%f\n", counter->Name(), counter->NominalFrequency()); return counter; // found a safe counter } else { wchar_t buf[100]; debug_printf("HRT: activating %s failed: %s\n", counter->Name(), utf8_from_wstring(StatusDescription(err, buf, ARRAY_SIZE(buf))).c_str()); DestroyCounter(counter); } } } //----------------------------------------------------------------------------- // counter that drives the timer static ICounter* counter; // (these counter properties are cached for efficiency and convenience:) static double nominalFrequency; static double resolution; static size_t counterBits; static u64 counterMask; static void InitCounter() { // we used to support switching counters at runtime, but that's // unnecessarily complex. it need and should only be done once. ENSURE(counter == 0); counter = GetNextBestSafeCounter(); ENSURE(counter != 0); nominalFrequency = counter->NominalFrequency(); resolution = counter->Resolution(); counterBits = counter->CounterBits(); debug_printf("HRT: counter=%s freq=%g res=%g bits=%d\n", counter->Name(), nominalFrequency, resolution, counterBits); // sanity checks ENSURE(nominalFrequency >= 500.0-DBL_EPSILON); ENSURE(resolution <= 2e-3); ENSURE(8 <= counterBits && counterBits <= 64); counterMask = bit_mask(counterBits); } static void ShutdownCounter() { DestroyCounter(counter); } static inline u64 Counter() { return counter->Counter(); } /** * @return difference [ticks], taking rollover into account. * (time-critical, so it's not called through ICounter.) **/ static inline u64 CounterDelta(u64 oldCounter, u64 newCounter) { return (newCounter - oldCounter) & counterMask; } double whrt_Resolution() { ENSURE(resolution != 0.0); return resolution; } //----------------------------------------------------------------------------- // timer state // we're not going to bother calibrating the counter (i.e. measuring its // current frequency by means of a second timer). rationale: // - all counters except the TSC are stable and run at fixed frequencies; // - it's not clear that any other HRT or the tick count would be useful // as a stable time reference (if it were, we should be using it instead); // - calibration would complicate the code (we'd have to make sure the // secondary counter is safe and can co-exist with the primary). /** * stores all timer state shared between readers and the update thread. * (must be POD because it's used before static ctors run.) **/ struct TimerState { // value of the counter at last update. u64 counter; // total elapsed time [seconds] since first update. // converted from tick deltas with the *then current* frequency // (this enables calibration, which is currently not implemented, // but leaving open the possibility costs nothing) double time; u8 padding[48]; }; // how do we detect when the old TimerState is no longer in use and can be // freed? we use two static instances (avoids dynamic allocation headaches) // and swap between them ('double-buffering'). it is assumed that all // entered critical sections (the latching of TimerState fields) will have // been exited before the next update comes around; if not, TimerState.time // changes, the critical section notices and re-reads the new values. static __declspec(align(64)) TimerState timerStates[2]; // note: exchanging pointers is easier than XORing an index. static volatile TimerState* volatile ts = &timerStates[0]; static volatile TimerState* volatile ts2 = &timerStates[1]; static void UpdateTimerState() { // how can we synchronize readers and the update thread? locks are // preferably avoided since they're dangerous and can be slow. what we // need to ensure is that TimerState doesn't change while another thread is // accessing it. the first step is to linearize the update, i.e. have it // appear to happen in an instant (done by building a new TimerState and // having it go live by switching pointers). all that remains is to make // reads of the state variables consistent, done by latching them all and // retrying if an update came in the middle of this. const u64 counter = Counter(); const u64 deltaTicks = CounterDelta(ts->counter, counter); ts2->counter = counter; ts2->time = ts->time + deltaTicks/nominalFrequency; ts = (volatile TimerState*)InterlockedExchangePointer((volatile PVOID*)&ts2, (PVOID)ts); } double whrt_Time() { // latch timer state (counter and time must be from the same update) const volatile TimerState* state = ts; const double time = state->time; const u64 counter = state->counter; const u64 deltaTicks = CounterDelta(counter, Counter()); return (time + deltaTicks/nominalFrequency); } //----------------------------------------------------------------------------- // update thread // note: we used to discipline the HRT timestamp to the system time, so it // was advantageous to trigger updates via WinMM event (thus reducing // instances where we're called in the middle of a scheduler tick). // since that's no longer relevant, we prefer using a thread, because that // avoids the dependency on WinMM and its lengthy startup time. // rationale: (+ and - are reasons for longer and shorter lengths) // + minimize CPU usage // + ensure all threads currently using TimerState return from those // functions before the next interval // - avoid more than 1 counter rollover per interval (InitUpdateThread makes // sure our interval is shorter than the current counter's rollover rate) static const DWORD UPDATE_INTERVAL_MS = 1000; static HANDLE hExitEvent; static HANDLE hUpdateThread; static unsigned __stdcall UpdateThread(void* UNUSED(data)) { debug_SetThreadName("whrt_UpdateThread"); for(;;) { const DWORD ret = WaitForSingleObject(hExitEvent, UPDATE_INTERVAL_MS); // owner terminated or wait failed or exit event signaled - exit thread if(ret != WAIT_TIMEOUT) break; UpdateTimerState(); } return 0; } static inline Status InitUpdateThread() { WinScopedPreserveLastError s; // CreateEvent // make sure our interval isn't too long // (counterBits can be 64 => Bit() would overflow => calculate period/2) const double period_2 = Bit(counterBits-1) / nominalFrequency; const size_t rolloversPerInterval = size_t(UPDATE_INTERVAL_MS / i64(period_2*2.0*1000.0)); ENSURE(rolloversPerInterval <= 1); hExitEvent = CreateEvent(0, TRUE, FALSE, 0); // manual reset, initially false if(hExitEvent == INVALID_HANDLE_VALUE) WARN_RETURN(ERR::LIMIT); hUpdateThread = (HANDLE)_beginthreadex(0, 0, UpdateThread, 0, 0, 0); if(!hUpdateThread) WARN_RETURN(ERR::LIMIT); return INFO::OK; } static inline void ShutdownUpdateThread() { // signal thread BOOL ok = SetEvent(hExitEvent); WARN_IF_FALSE(ok); // the nice way is to wait for it to exit if(WaitForSingleObject(hUpdateThread, 100) != WAIT_OBJECT_0) TerminateThread(hUpdateThread, 0); // forcibly exit (dangerous) CloseHandle(hExitEvent); CloseHandle(hUpdateThread); } //----------------------------------------------------------------------------- static Status whrt_Init() { InitCounter(); // latch initial counter value so that timer starts at 0 ts->counter = Counter(); // must come before UpdateTimerState UpdateTimerState(); // must come before InitUpdateThread to avoid race RETURN_STATUS_IF_ERR(InitUpdateThread()); return INFO::OK; } static Status whrt_Shutdown() { ShutdownUpdateThread(); ShutdownCounter(); acpi_Shutdown(); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/manifest.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/manifest.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/manifest.cpp (revision 19899) @@ -1,51 +1,51 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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" /* To use XP-style themed controls, we need to use the manifest to specify the desired version. (This must be set in the game's .exe in order to affect Atlas.) For VC7.1, we use manifest.rc to include a complete manifest file. For VC8.0, which already generates its own manifest, we use the line below to add the necessary parts to that generated manifest. ICC 10.1 IPO considers this string to be an input file, hence this is currently disabled there. */ #if MSC_VERSION >= 1400 && !ICC_VERSION && defined(LIB_STATIC_LINK) # if ARCH_IA32 # pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df'\"") # elif ARCH_AMD64 # pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df'\"") # endif /* NOTE: vcbuild.exe (as used by the autobuilder) seems to ignore these linker comments, so we have to duplicate these commands into premake.lua too. Remember to keep them in sync with this file. (Duplicate entries appear to get omitted from the .manifest file so there should be no harmful effects.) */ #endif Index: ps/trunk/source/lib/sysdep/os/win/wcpu.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wcpu.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wcpu.cpp (revision 19899) @@ -1,280 +1,280 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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. */ /* * Windows implementation of sysdep/cpu */ #include "precompiled.h" #include "lib/sysdep/os/win/wcpu.h" #include "lib/sysdep/os_cpu.h" #include "lib/bits.h" #include "lib/alignment.h" #include "lib/module_init.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" uintptr_t os_cpu_ProcessorMask() { static uintptr_t processorMask; if(!processorMask) { const HANDLE hProcess = GetCurrentProcess(); DWORD_PTR processAffinity, systemAffinity; const BOOL ok = GetProcessAffinityMask(hProcess, &processAffinity, &systemAffinity); ENSURE(ok); ENSURE(processAffinity != 0); processorMask = processAffinity; } return processorMask; } size_t os_cpu_NumProcessors() { static size_t numProcessors; if(!numProcessors) { numProcessors = PopulationCount(os_cpu_ProcessorMask()); // sanity check SYSTEM_INFO si; GetSystemInfo(&si); // guaranteed to succeed ENSURE(numProcessors <= (size_t)si.dwNumberOfProcessors); ENSURE(numProcessors >= 1); } return numProcessors; } //----------------------------------------------------------------------------- Status wcpu_ReadFrequencyFromRegistry(u32& freqMhz) { HKEY hKey; if(RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS) return ERR::NOT_SUPPORTED; DWORD size = sizeof(freqMhz); LONG ret = RegQueryValueExW(hKey, L"~MHz", 0, 0, (LPBYTE)&freqMhz, &size); RegCloseKey(hKey); if(ret != ERROR_SUCCESS) WARN_RETURN(ERR::FAIL); return INFO::OK; } size_t os_cpu_PageSize() { static size_t systemPageSize; if(!systemPageSize) { SYSTEM_INFO si; GetSystemInfo(&si); // guaranteed to succeed systemPageSize = (size_t)si.dwPageSize; } return systemPageSize; } size_t os_cpu_LargePageSize() { static size_t largePageSize = ~(size_t)0; // "0" has special significance if(largePageSize == ~(size_t)0) { WUTIL_FUNC(pGetLargePageMinimum, SIZE_T, (void)); WUTIL_IMPORT_KERNEL32(GetLargePageMinimum, pGetLargePageMinimum); if(pGetLargePageMinimum) { largePageSize = pGetLargePageMinimum(); // Note: checks disabled due to failing on Vista SP2 with old Xeon CPU // see http://trac.wildfiregames.com/ticket/2346 //ENSURE(largePageSize != 0); // IA-32 and AMD64 definitely support large pages //ENSURE(largePageSize > os_cpu_PageSize()); } // no OS support for large pages else largePageSize = 0; } return largePageSize; } static void GetMemoryStatus(MEMORYSTATUSEX& mse) { // note: we no longer bother dynamically importing GlobalMemoryStatusEx - // it's available on Win2k and above. this function safely handles // systems with > 4 GB of memory. mse.dwLength = sizeof(mse); const BOOL ok = GlobalMemoryStatusEx(&mse); WARN_IF_FALSE(ok); } size_t os_cpu_QueryMemorySize() { MEMORYSTATUSEX mse; GetMemoryStatus(mse); DWORDLONG memorySize = mse.ullTotalPhys; // Richter, "Programming Applications for Windows": the reported // value doesn't include non-paged pool reserved during boot; // it's not considered available to the kernel. (the amount is // 528 KiB on a 512 MiB WinXP/Win2k machine). we'll round up // to the nearest megabyte to fix this. memorySize = round_up(memorySize, DWORDLONG(1*MiB)); // (Align<> cannot compute DWORDLONG) return size_t(memorySize / MiB); } size_t os_cpu_MemoryAvailable() { MEMORYSTATUSEX mse; GetMemoryStatus(mse); const size_t memoryAvailableMiB = size_t(mse.ullAvailPhys / MiB); return memoryAvailableMiB; } //----------------------------------------------------------------------------- DWORD_PTR wcpu_AffinityFromProcessorMask(DWORD_PTR processAffinity, uintptr_t processorMask) { DWORD_PTR affinity = 0; size_t processor = (size_t)-1; for(DWORD processorNumber = 0; processorNumber < (DWORD)os_cpu_MaxProcessors; processorNumber++) { if(IsBitSet(processAffinity, processorNumber)) { ++processor; // index among the affinity's set bits if(IsBitSet(processorMask, processor)) affinity |= DWORD_PTR(1) << processorNumber; } } return affinity; } uintptr_t wcpu_ProcessorMaskFromAffinity(DWORD_PTR processAffinity, DWORD_PTR affinity) { uintptr_t processorMask = 0; size_t processor = (size_t)-1; for(DWORD processorNumber = 0; processorNumber < (DWORD)os_cpu_MaxProcessors; processorNumber++) { if(IsBitSet(processAffinity, processorNumber)) { ++processor; // now corresponds to processorNumber if(IsBitSet(affinity, processorNumber)) processorMask |= uintptr_t(1) << processor; } } return processorMask; } //----------------------------------------------------------------------------- static void VerifyRunningOnCorrectProcessors(DWORD_PTR affinity) { DWORD currentProcessor; // note: NtGetCurrentProcessorNumber and RtlGetCurrentProcessorNumber aren't // implemented on WinXP SP2. WUTIL_FUNC(pGetCurrentProcessorNumber, DWORD, (void)); WUTIL_IMPORT_KERNEL32(GetCurrentProcessorNumber, pGetCurrentProcessorNumber); if(pGetCurrentProcessorNumber) currentProcessor = pGetCurrentProcessorNumber(); else { // note: searching for the current APIC ID or IDT address in a // table won't work because initializing the table also requires // this function. LSL only works on Vista (which already // has GetCurrentProcessorNumber). return; } ENSURE(IsBitSet(affinity, currentProcessor)); } uintptr_t os_cpu_SetThreadAffinityMask(uintptr_t processorMask) { const size_t numProcessors = os_cpu_NumProcessors(); // (avoid undefined result when right shift count >= number of bits) ENSURE(numProcessors == sizeof(processorMask)*CHAR_BIT || (processorMask >> numProcessors) == 0); DWORD_PTR processAffinity, systemAffinity; const BOOL ok = GetProcessAffinityMask(GetCurrentProcess(), &processAffinity, &systemAffinity); WARN_IF_FALSE(ok); const DWORD_PTR affinity = wcpu_AffinityFromProcessorMask(processAffinity, processorMask); const DWORD_PTR previousAffinity = SetThreadAffinityMask(GetCurrentThread(), affinity); ENSURE(previousAffinity != 0); // ensure function didn't fail // (MSDN says SetThreadAffinityMask takes care of rescheduling) VerifyRunningOnCorrectProcessors(affinity); const uintptr_t previousProcessorMask = wcpu_ProcessorMaskFromAffinity(processAffinity, previousAffinity); return previousProcessorMask; } Status os_cpu_CallByEachCPU(OsCpuCallback cb, uintptr_t cbData) { // abort if we can't run on all system processors DWORD_PTR processAffinity, systemAffinity; { const BOOL ok = GetProcessAffinityMask(GetCurrentProcess(), &processAffinity, &systemAffinity); WARN_IF_FALSE(ok); if(processAffinity != systemAffinity) return ERR::OS_CPU_RESTRICTED_AFFINITY; // NOWARN } const uintptr_t previousAffinity = os_cpu_SetThreadAffinityMask(os_cpu_ProcessorMask()); for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { const uintptr_t processorMask = uintptr_t(1) << processor; os_cpu_SetThreadAffinityMask(processorMask); cb(processor, cbData); } (void)os_cpu_SetThreadAffinityMask(previousAffinity); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wdbg.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdbg.h (revision 19899) @@ -1,45 +1,45 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Win32 debug support code. */ #ifndef INCLUDED_WDBG #define INCLUDED_WDBG /** * same as debug_printf except that some type conversions aren't supported * (in particular, no floating point) and output is limited to 1024+1 characters. * * this function does not allocate memory from the CRT heap, which makes it * safe to use from an allocation hook. **/ LIB_API void wdbg_printf(const wchar_t* fmt, ...); /** * similar to ENSURE but safe to use during critical init or * while under the heap or dbghelp locks. **/ #define wdbg_assert(expr) STMT(if(!(expr)) debug_break();) #endif // #ifndef INCLUDED_WDBG Index: ps/trunk/source/lib/sysdep/os/win/wdbg_sym.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg_sym.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdbg_sym.h (revision 19899) @@ -1,62 +1,62 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Win32 stack trace and symbol engine. */ #ifndef INCLUDED_WDBG_SYM #define INCLUDED_WDBG_SYM #include "lib/sysdep/os/win/win.h" // CONTEXT, EXCEPTION_POINTERS struct _tagSTACKFRAME64; /** * called for each stack frame found by wdbg_sym_WalkStack. * * @param frame the dbghelp stack frame (we can't just pass the * instruction-pointer because dump_frame_cb needs the frame pointer to * locate frame-relative variables) * @param cbData the user-specified value that was passed to wdbg_sym_WalkStack * @return Status (see RETURN_STATUS_FROM_CALLBACK). **/ typedef Status (*StackFrameCallback)(const _tagSTACKFRAME64* frame, uintptr_t cbData); /** * Iterate over a call stack, invoking a callback for each frame encountered. * * @param cb * @param cbData * @param context Processor context from which to start (taken from * an exception record or debug_CaptureContext). * @param lastFuncToSkip * * @note It is safe to use ENSURE/debug_warn/WARN_RETURN_STATUS_IF_ERR even during a * stack trace (which is triggered by ENSURE et al. in app code) because * nested stack traces are ignored and only the error is displayed. **/ LIB_API Status wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData, CONTEXT& context, const wchar_t* lastFuncToSkip = 0); LIB_API void wdbg_sym_WriteMinidump(EXCEPTION_POINTERS* ep); #endif // #ifndef INCLUDED_WDBG_SYM Index: ps/trunk/source/lib/sysdep/os/win/wdll_main.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdll_main.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdll_main.h (revision 19899) @@ -1,37 +1,37 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ // for each project that builds a shared-library, include this "header" in // one source file that is on the linker command line. // (avoids the ICC remark "Main entry point was not seen") #if OS_WIN #include "lib/sysdep/os/win/win.h" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD UNUSED(reason), LPVOID UNUSED(reserved)) { // avoid unnecessary DLL_THREAD_ATTACH/DETACH calls // (ignore failure - happens if the DLL uses TLS) (void)DisableThreadLibraryCalls(hInstance); return TRUE; // success (ignored unless reason == DLL_PROCESS_ATTACH) } #endif Index: ps/trunk/source/lib/sysdep/os/win/wfirmware.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wfirmware.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wfirmware.h (revision 19899) @@ -1,41 +1,41 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ #ifndef INCLUDED_WFIRMWARE #define INCLUDED_WFIRMWARE namespace wfirmware { typedef u32 Provider; typedef u32 TableId; typedef std::vector TableIds; extern TableIds GetTableIDs(Provider provider); typedef std::vector Table; extern Table GetTable(Provider provider, TableId tableId); } // namespace wfirmware #endif // #ifndef INCLUDED_WFIRMWARE Index: ps/trunk/source/lib/sysdep/os/win/whrt/counter.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/counter.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/counter.cpp (revision 19899) @@ -1,117 +1,117 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Interface for counter implementations */ #include "precompiled.h" #include "lib/sysdep/os/win/whrt/counter.h" #include "lib/alignment.h" #include "lib/sysdep/cpu.h" // cpu_CAS #include "lib/sysdep/os/win/whrt/tsc.h" #include "lib/sysdep/os/win/whrt/hpet.h" #include "lib/sysdep/os/win/whrt/pmt.h" #include "lib/sysdep/os/win/whrt/qpc.h" #include "lib/sysdep/os/win/whrt/tgt.h" // to add a new counter type, simply include its header here and // insert a case in ConstructCounterAt's switch statement. //----------------------------------------------------------------------------- // create/destroy counters /** * @param id * @param address * @param size Maximum allowable size [bytes] of the subclass instance * @return pointer to a newly constructed ICounter subclass of type \ at * the given address, or 0 iff the ID is invalid. **/ static ICounter* ConstructCounterAt(size_t id, void* address, size_t size) { // rationale for placement new: see call site. // counters are chosen according to the following order. rationale: // - TSC must come before QPC and PMT to make sure a bug in the latter on // Pentium systems doesn't come up. // - PMT works, but is inexplicably slower than QPC on a PIII Mobile. // - TGT really isn't as safe as the others, so it should be last. // - low-overhead and high-resolution counters are preferred. switch(id) { case 0: return CreateCounterHPET(address, size); case 1: return CreateCounterTSC(address, size); case 2: return CreateCounterQPC(address, size); case 3: return CreateCounterPMT(address, size); case 4: return CreateCounterTGT(address, size); default: return 0; } } static volatile intptr_t isCounterAllocated; ICounter* CreateCounter(size_t id) { // we placement-new the Counter classes in a static buffer. // this is dangerous, but we are careful to ensure alignment. it is // unusual and thus bad, but there's also one advantage: we avoid // using global operator new before the CRT is initialized (risky). // // alternatives: // - defining as static doesn't work because the ctors (necessary for // vptr initialization) run during _cinit, which comes after our // first use of them. // - using static_calloc isn't possible because we don't know the // size until after the alloc / placement new. if(!cpu_CAS(&isCounterAllocated, 0, 1)) DEBUG_WARN_ERR(ERR::LOGIC); // static counter memory is already in use! static const size_t memSize = 200; static u8 mem[memSize]; u8* alignedMem = (u8*)Align<16>((uintptr_t)mem); const size_t bytesLeft = mem+memSize - alignedMem; ICounter* counter = ConstructCounterAt(id, alignedMem, bytesLeft); return counter; } void DestroyCounter(ICounter*& counter) { ENSURE(counter); counter->Shutdown(); counter->~ICounter(); // must be called due to placement new counter = 0; isCounterAllocated = 0; } Index: ps/trunk/source/lib/sysdep/os/win/whrt/pit.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/pit.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/pit.h (revision 19899) @@ -1,41 +1,41 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using 82C53/4 PIT */ #ifndef INCLUDED_PIT #define INCLUDED_PIT // note: we don't access the PIT for two reasons: // - it rolls over every 55 ms (1.193 MHz, 16 bit) and would have to be // read at least that often, which would require setting high thread // priority (dangerous). // - reading it is slow and cannot be done by two independent users // (the second being QPC) since the counter value must be latched. // // there are enough other counters anway. static const i64 PIT_FREQ = 1193182; // (= master oscillator frequency/12) #endif // #ifndef INCLUDED_PIT Index: ps/trunk/source/lib/sysdep/os/win/whrt/qpc.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/qpc.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/qpc.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using QueryPerformanceCounter */ #ifndef INCLUDED_QPC #define INCLUDED_QPC class ICounter; extern ICounter* CreateCounterQPC(void* address, size_t size); #endif // #ifndef INCLUDED_QPC Index: ps/trunk/source/lib/sysdep/os/win/whrt/tsc.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/tsc.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/tsc.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using RDTSC */ #ifndef INCLUDED_TSC #define INCLUDED_TSC class ICounter; extern ICounter* CreateCounterTSC(void* address, size_t size); #endif // #ifndef INCLUDED_TSC Index: ps/trunk/source/lib/sysdep/os/win/mahaf.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/mahaf.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/mahaf.h (revision 19899) @@ -1,64 +1,64 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * user-mode interface to Aken driver */ // Mahaf - ferryman in Egyptian mythology that wakes up Aken, // and the interface to the Aken driver. #ifndef INCLUDED_MAHAF #define INCLUDED_MAHAF /** * @return whether mapping physical memory is known to be dangerous * on this platform. * * callable before or after mahaf_Init. * * note: mahaf_MapPhysicalMemory will complain if it * is called despite this function having returned true. **/ LIB_API bool mahaf_IsPhysicalMappingDangerous(); LIB_API Status mahaf_Init(); LIB_API void mahaf_Shutdown(); LIB_API u8 mahaf_ReadPort8 (u16 port); LIB_API u16 mahaf_ReadPort16(u16 port); LIB_API u32 mahaf_ReadPort32(u16 port); LIB_API void mahaf_WritePort8 (u16 port, u8 value); LIB_API void mahaf_WritePort16(u16 port, u16 value); LIB_API void mahaf_WritePort32(u16 port, u32 value); LIB_API volatile void* mahaf_MapPhysicalMemory(uintptr_t physicalAddress, size_t numBytes); LIB_API void mahaf_UnmapPhysicalMemory(volatile void* virtualAddress); LIB_API u64 mahaf_ReadModelSpecificRegister(u64 reg); LIB_API void mahaf_WriteModelSpecificRegister(u64 reg, u64 value); // must be done in the driver because Windows clears CR4.PCE[8] LIB_API u64 mahaf_ReadPerformanceMonitoringCounter(u64 reg); #endif // INCLUDED_MAHAF Index: ps/trunk/source/lib/sysdep/os/win/wclipboard.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wclipboard.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wclipboard.cpp (revision 19899) @@ -1,123 +1,123 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/clipboard.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" // caller is responsible for freeing hMem. static Status SetClipboardText(const wchar_t* text, HGLOBAL& hMem) { const size_t numChars = wcslen(text); hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, (numChars + 1) * sizeof(wchar_t)); if(!hMem) WARN_RETURN(ERR::NO_MEM); wchar_t* lockedText = (wchar_t*)GlobalLock(hMem); if(!lockedText) WARN_RETURN(ERR::NO_MEM); wcscpy_s(lockedText, numChars+1, text); GlobalUnlock(hMem); HANDLE hData = SetClipboardData(CF_UNICODETEXT, hMem); if(!hData) // failed WARN_RETURN(ERR::FAIL); return INFO::OK; } // @return INFO::OK iff text has been assigned a pointer (which the // caller must free via sys_clipboard_free) to the clipboard text. static Status GetClipboardText(wchar_t*& text) { // NB: Windows NT/2000+ auto convert CF_UNICODETEXT <-> CF_TEXT. if(!IsClipboardFormatAvailable(CF_UNICODETEXT)) return INFO::CANNOT_HANDLE; HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT); if(!hMem) WARN_RETURN(ERR::FAIL); const wchar_t* lockedText = (const wchar_t*)GlobalLock(hMem); if(!lockedText) WARN_RETURN(ERR::NO_MEM); const size_t size = GlobalSize(hMem); text = (wchar_t*)malloc(size); if(!text) WARN_RETURN(ERR::NO_MEM); wcscpy_s(text, size/sizeof(wchar_t), lockedText); (void)GlobalUnlock(hMem); return INFO::OK; } // OpenClipboard parameter. // NB: using wutil_AppWindow() causes GlobalLock to fail. static const HWND hWndNewOwner = 0; // MSDN: associate with "current task" Status sys_clipboard_set(const wchar_t* text) { if(!OpenClipboard(hWndNewOwner)) WARN_RETURN(ERR::FAIL); WARN_IF_FALSE(EmptyClipboard()); // NB: to enable copy/pasting something other than text, add // message handlers for WM_RENDERFORMAT and WM_RENDERALLFORMATS. HGLOBAL hMem; Status ret = SetClipboardText(text, hMem); WARN_IF_FALSE(CloseClipboard()); // must happen before GlobalFree ENSURE(GlobalFree(hMem) == 0); // (0 indicates success) return ret; } wchar_t* sys_clipboard_get() { if(!OpenClipboard(hWndNewOwner)) return 0; wchar_t* text; Status ret = GetClipboardText(text); WARN_IF_FALSE(CloseClipboard()); return (ret == INFO::OK)? text : 0; } Status sys_clipboard_free(wchar_t* text) { free(text); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wdbg.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdbg.cpp (revision 19899) @@ -1,144 +1,144 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Win32 debug support code. */ #include "precompiled.h" #include "lib/debug.h" #include "lib/bits.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" // return 1 if the pointer appears to be totally bogus, otherwise 0. // 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. int debug_IsPointerBogus(const void* p) { if(p < (void*)0x10000) return true; #if ARCH_AMD64 if(p == (const void*)(uintptr_t)0xCCCCCCCCCCCCCCCCull) return true; #elif ARCH_IA32 if(p == (const void*)(uintptr_t)0xCCCCCCCCul) return true; #endif // notes: // - we don't check alignment because nothing can be assumed about a // string pointer and we mustn't reject any actually valid pointers. // - nor do we bother checking the address against known stack/heap areas // because that doesn't cover everything (e.g. DLLs, VirtualAlloc). // - cannot use IsBadReadPtr because it accesses the mem // (false alarm for reserved address space). return false; } bool debug_IsCodePointer(void* p) { uintptr_t addr = (uintptr_t)p; // totally invalid pointer if(debug_IsPointerBogus(p)) return false; // comes before load address static const HMODULE base = GetModuleHandle(0); if(addr < (uintptr_t)base) return false; return true; } bool debug_IsStackPointer(void* p) { uintptr_t addr = (uintptr_t)p; // totally invalid pointer if(debug_IsPointerBogus(p)) return false; // not aligned if(addr % sizeof(void*)) return false; // out of bounds (note: IA-32 stack grows downwards) NT_TIB* tib = (NT_TIB*)NtCurrentTeb(); if(!(tib->StackLimit < p && p < tib->StackBase)) return false; return true; } void debug_puts(const char* text) { OutputDebugStringW(wstring_from_utf8(text).c_str()); } void wdbg_printf(const wchar_t* fmt, ...) { wchar_t buf[1024+1]; // wvsprintfW will truncate to this size va_list ap; va_start(ap, fmt); wvsprintfW(buf, fmt, ap); // (return value doesn't indicate whether truncation occurred) va_end(ap); OutputDebugStringW(buf); } // inform the debugger of the current thread's description, which it then // displays instead of just the thread handle. // // see "Setting a Thread Name (Unmanaged)": http://msdn2.microsoft.com/en-us/library/xcb2z8hs(vs.71).aspx void debug_SetThreadName(const char* name) { // we pass information to the debugger via a special exception it // swallows. if not running under one, bail now to avoid // "first chance exception" warnings. if(!IsDebuggerPresent()) return; // presented by Jay Bazuzi (from the VC debugger team) at TechEd 1999. const struct ThreadNameInfo { DWORD type; const char* name; DWORD thread_id; // any valid ID or -1 for current thread DWORD flags; } info = { 0x1000, name, (DWORD)-1, 0 }; __try { RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info); } __except(EXCEPTION_EXECUTE_HANDLER) { // if we get here, the debugger didn't handle the exception. // this happens if profiling with Dependency Walker; ENSURE // must not be called because we may be in critical init. } } Index: ps/trunk/source/lib/sysdep/os/win/wdbg_sym.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg_sym.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdbg_sym.cpp (revision 19899) @@ -1,1787 +1,1787 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ /* * Win32 stack trace and symbol engine. */ #include "precompiled.h" #include "lib/sysdep/os/win/wdbg_sym.h" #include #include #include #include "lib/byte_order.h" // movzx_le64 #include "lib/module_init.h" #include "lib/sysdep/cpu.h" #include "lib/debug_stl.h" #include "lib/app_hooks.h" #include "lib/external_libraries/dbghelp.h" #include "lib/sysdep/os/win/wdbg.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/winit.h" WINIT_REGISTER_CRITICAL_INIT(wdbg_sym_Init); static WUTIL_FUNC(pRtlCaptureContext, VOID, (PCONTEXT)); static Status wdbg_sym_Init() { WUTIL_IMPORT_KERNEL32(RtlCaptureContext, pRtlCaptureContext); return INFO::OK; } //---------------------------------------------------------------------------- // dbghelp //---------------------------------------------------------------------------- // global for convenience (we only support a single process) static HANDLE hProcess; // for StackWalk64; taken from PE header by InitDbghelp. static WORD machine; static Status InitDbghelp() { hProcess = GetCurrentProcess(); dbghelp_ImportFunctions(); // set options // notes: // - can be done before SymInitialize; we do so in case // any of the options affect it. // - do not set directly - that would zero any existing flags. DWORD opts = pSymGetOptions(); //opts |= SYMOPT_DEBUG; // lots of debug spew in output window opts |= SYMOPT_DEFERRED_LOADS; // the "fastest, most efficient way" opts |= SYMOPT_LOAD_LINES; opts |= SYMOPT_UNDNAME; pSymSetOptions(opts); // initialize dbghelp. // .. request symbols from all currently active modules be loaded. const BOOL fInvadeProcess = TRUE; // .. use default *symbol* search path. we don't use this to locate // our PDB file because its absolute path is stored inside the EXE. const PWSTR UserSearchPath = 0; WinScopedPreserveLastError s; // SymInitializeW const BOOL ok = pSymInitializeW(hProcess, UserSearchPath, fInvadeProcess); WARN_IF_FALSE(ok); HMODULE hModule = GetModuleHandle(0); IMAGE_NT_HEADERS* const header = pImageNtHeader(hModule); machine = header->FileHeader.Machine; return INFO::OK; } // ensure dbghelp is initialized exactly once. // call every time before dbghelp functions are used. // (on-demand initialization allows handling exceptions raised before // winit.cpp init functions are called) // // NB: this may take SECONDS if OS symbols are installed and // symserv wants to access the internet. static void sym_init() { static ModuleInitState initState; ModuleInit(&initState, InitDbghelp); } static STACKFRAME64 PopulateStackFrame(CONTEXT& context) { STACKFRAME64 sf; memset(&sf, 0, sizeof(sf)); sf.AddrPC.Mode = AddrModeFlat; sf.AddrFrame.Mode = AddrModeFlat; sf.AddrStack.Mode = AddrModeFlat; #if ARCH_AMD64 sf.AddrPC.Offset = context.Rip; sf.AddrFrame.Offset = context.Rbp; sf.AddrStack.Offset = context.Rsp; #else sf.AddrPC.Offset = context.Eip; sf.AddrFrame.Offset = context.Ebp; sf.AddrStack.Offset = context.Esp; #endif return sf; } static IMAGEHLP_STACK_FRAME PopulateImageStackFrame(const STACKFRAME64& sf) { IMAGEHLP_STACK_FRAME isf; memset(&isf, 0, sizeof(isf)); // apparently only PC, FP and SP are necessary, but // we copy everything to be safe. isf.InstructionOffset = sf.AddrPC.Offset; isf.ReturnOffset = sf.AddrReturn.Offset; isf.FrameOffset = sf.AddrFrame.Offset; isf.StackOffset = sf.AddrStack.Offset; isf.BackingStoreOffset = sf.AddrBStore.Offset; isf.FuncTableEntry = (ULONG64)sf.FuncTableEntry; // (note: array of different types, can't copy directly) for(int i = 0; i < 4; i++) isf.Params[i] = sf.Params[i]; // isf.Reserved - already zeroed isf.Virtual = sf.Virtual; // isf.Reserved2 - already zeroed return isf; } struct SYMBOL_INFO_PACKAGEW2 : public SYMBOL_INFO_PACKAGEW { SYMBOL_INFO_PACKAGEW2() { si.SizeOfStruct = sizeof(si); si.MaxNameLen = MAX_SYM_NAME; } }; #pragma pack(push, 1) // note: we can't derive from TI_FINDCHILDREN_PARAMS because its members // aren't guaranteed to precede ours (although they do in practice). struct TI_FINDCHILDREN_PARAMS2 { TI_FINDCHILDREN_PARAMS2(DWORD numChildren) { p.Start = 0; p.Count = std::min(numChildren, maxChildren); } static const DWORD maxChildren = 300; TI_FINDCHILDREN_PARAMS p; DWORD childrenStorage[maxChildren-1]; }; #pragma pack(pop) // actual implementation; made available so that functions already under // the lock don't have to unlock (slow) to avoid recursive locking. static Status ResolveSymbol_lk(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line) { sym_init(); const DWORD64 addr = (DWORD64)ptr_of_interest; size_t successes = 0; WinScopedPreserveLastError s; // SymFromAddrW, SymGetLineFromAddrW64 // get symbol name (if requested) if(sym_name) { sym_name[0] = '\0'; SYMBOL_INFO_PACKAGEW2 sp; SYMBOL_INFOW* sym = &sp.si; if(pSymFromAddrW(hProcess, addr, 0, sym)) { wcscpy_s(sym_name, DEBUG_SYMBOL_CHARS, sym->Name); successes++; } } // get source file and/or line number (if requested) if(file || line) { if (file) file[0] = '\0'; if (line) *line = 0; IMAGEHLP_LINEW64 line_info = { sizeof(IMAGEHLP_LINEW64) }; DWORD displacement; // unused but required by pSymGetLineFromAddr64! if(pSymGetLineFromAddrW64(hProcess, addr, &displacement, &line_info)) { if(file) { // strip full path down to base name only. // this loses information, but that isn't expected to be a // problem and is balanced by not having to do this from every // call site (full path is too long to display nicely). const wchar_t* basename = path_name_only(line_info.FileName); wcscpy_s(file, DEBUG_FILE_CHARS, basename); successes++; } if(line) { *line = line_info.LineNumber; successes++; } } } if(addr == 0 && GetLastError() == ERROR_MOD_NOT_FOUND) SetLastError(0); if(GetLastError() == ERROR_INVALID_ADDRESS) SetLastError(0); return (successes != 0)? INFO::OK : ERR::FAIL; } // file is the base name only, not path (see rationale in wdbg_sym). // the PDB implementation is rather slow (~500us). Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line) { WinScopedLock lock(WDBG_SYM_CS); return ResolveSymbol_lk(ptr_of_interest, sym_name, file, line); } //---------------------------------------------------------------------------- // stack walk //---------------------------------------------------------------------------- Status debug_CaptureContext(void* pcontext) { // there are 4 ways to do so, in order of preference: // - RtlCaptureContext (only available on WinXP or above) // - assembly language subroutine (complicates the build system) // - intentionally raise an SEH exception and capture its context // (causes annoying "first chance exception" messages and // can't co-exist with WinScopedLock's destructor) // - GetThreadContext while suspended (a bit tricky + slow). // note: it used to be common practice to query the current thread // context, but WinXP SP2 and above require it be suspended. if(!pRtlCaptureContext) return ERR::NOT_SUPPORTED; // NOWARN CONTEXT* context = (CONTEXT*)pcontext; cassert(sizeof(CONTEXT) <= DEBUG_CONTEXT_SIZE); memset(context, 0, sizeof(CONTEXT)); context->ContextFlags = CONTEXT_FULL; pRtlCaptureContext(context); return INFO::OK; } static Status CallStackWalk(STACKFRAME64& sf, CONTEXT& context) { WinScopedLock lock(WDBG_SYM_CS); SetLastError(0); // StackWalk64 doesn't always SetLastError const HANDLE hThread = GetCurrentThread(); if(!pStackWalk64(machine, hProcess, hThread, &sf, &context, 0, pSymFunctionTableAccess64, pSymGetModuleBase64, 0)) return ERR::FAIL; // NOWARN (no stack frames left) // (the frame pointer can be zero despite StackWalk64 returning TRUE.) if(sf.AddrFrame.Offset == 0) return ERR::FAIL; // NOWARN (no stack frames left) // huge WTF in x64 debug builds (dbghelp 6.12.0002.633): // AddrFrame.Offset doesn't match the correct RBP value. // StackWalk64 updates the context [http://bit.ly/lo1aqZ] and // its Rbp is correct, so we'll use that. #if ARCH_AMD64 sf.AddrFrame.Offset = context.Rbp; #endif return INFO::OK; } // NB: CaptureStackBackTrace may be faster (http://msinilo.pl/blog/?p=40), // but wasn't known during development. Status wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData, CONTEXT& context, const wchar_t* lastFuncToSkip) { sym_init(); STACKFRAME64 sf = PopulateStackFrame(context); wchar_t func[DEBUG_SYMBOL_CHARS]; Status ret = ERR::SYM_NO_STACK_FRAMES_FOUND; for(;;) // each stack frame: { if(CallStackWalk(sf, context) != INFO::OK) return ret; if(lastFuncToSkip) { void* const pc = (void*)(uintptr_t)sf.AddrPC.Offset; if(debug_ResolveSymbol(pc, func, 0, 0) == INFO::OK) { if(wcsstr(func, lastFuncToSkip)) // this was the last one to skip lastFuncToSkip = 0; continue; } } ret = cb(&sf, cbData); RETURN_STATUS_FROM_CALLBACK(ret); } } void* debug_GetCaller(void* pcontext, const wchar_t* lastFuncToSkip) { struct StoreAddress { static Status Func(const STACKFRAME64* sf, uintptr_t cbData) { const uintptr_t funcAddress = sf->AddrPC.Offset; // store funcAddress in our `output parameter' memcpy((void*)cbData, &funcAddress, sizeof(funcAddress)); return INFO::OK; } }; void* func; wdbg_assert(pcontext != 0); Status ret = wdbg_sym_WalkStack(&StoreAddress::Func, (uintptr_t)&func, *(CONTEXT*)pcontext, lastFuncToSkip); return (ret == INFO::OK)? func : 0; } //----------------------------------------------------------------------------- // helper routines for symbol value dump //----------------------------------------------------------------------------- // infinite recursion has never happened, but we check for it anyway. static const size_t maxIndirection = 255; static const size_t maxLevel = 255; struct DumpState { size_t level; size_t indirection; uintptr_t moduleBase; LPSTACKFRAME64 stackFrame; DumpState(uintptr_t moduleBase, LPSTACKFRAME64 stackFrame) : level(0), indirection(0), moduleBase(moduleBase), stackFrame(stackFrame) { } }; //---------------------------------------------------------------------------- static size_t out_chars_left; static wchar_t* out_pos; // (only warn once until next out_init to avoid flood of messages.) static bool out_have_warned_of_overflow; // some top-level (*) symbols cause tons of output - so much that they may // single-handedly overflow the buffer (e.g. pointer to a tree of huge UDTs). // we can't have that, so there is a limit in place as to how much a // single top-level symbol can output. after that is reached, dumping is // aborted for that symbol but continues for the subsequent top-level symbols. // // this is implemented as follows: dump_sym_cb latches the current output // position; each dump_sym (through which all symbols go) checks if the // new position exceeds the limit and aborts if so. // slight wrinkle: since we don't want each level of UDTs to successively // realize the limit has been hit and display the error message, we // return ERR::SYM_SINGLE_SYMBOL_LIMIT once and thereafter INFO::SYM_SUPPRESS_OUTPUT. // // * example: local variables, as opposed to child symbols in a UDT. static wchar_t* out_latched_pos; static bool out_have_warned_of_limit; static void out_init(wchar_t* buf, size_t max_chars) { out_pos = buf; out_chars_left = max_chars; out_have_warned_of_overflow = false; out_have_warned_of_limit = false; } static void out(const wchar_t* fmt, ...) { va_list args; va_start(args, fmt); // use vswprintf, not vswprintf_s, because we want to gracefully // handle buffer overflows int len = vswprintf(out_pos, out_chars_left, fmt, args); va_end(args); // success if(len >= 0) { out_pos += len; // make sure out_chars_left remains nonnegative if((size_t)len > out_chars_left) { DEBUG_WARN_ERR(ERR::LOGIC); // apparently wrote more than out_chars_left len = (int)out_chars_left; } out_chars_left -= len; } // no more room left else { // the buffer really is full yet out_chars_left may not be 0 // (since it isn't updated if vswprintf returns -1). // must be set so subsequent calls don't try to squeeze stuff in. out_chars_left = 0; // write a warning into the output buffer (once) so it isn't // abruptly cut off (which looks like an error) if(!out_have_warned_of_overflow) { out_have_warned_of_overflow = true; // with the current out_pos / out_chars_left variables, there's // no way of knowing where the buffer actually ends. no matter; // we'll just put the warning before out_pos and eat into the // second newest text. const wchar_t text[] = L"(no more room in buffer)"; wcscpy_s(out_pos-ARRAY_SIZE(text), ARRAY_SIZE(text), text); // safe } } } static void out_erase(size_t num_chars) { // don't do anything if end of buffer was hit (prevents repeatedly // scribbling over the last few bytes). if(out_have_warned_of_overflow) return; out_chars_left += (ssize_t)num_chars; out_pos -= num_chars; *out_pos = '\0'; // make sure it's 0-terminated in case there is no further output. } // (see above) static void out_latch_pos() { out_have_warned_of_limit = false; out_latched_pos = out_pos; } // (see above) static Status out_check_limit() { if(out_have_warned_of_limit) return INFO::SYM_SUPPRESS_OUTPUT; if(out_pos - out_latched_pos > 3000) // ~30 lines { out_have_warned_of_limit = true; return ERR::SYM_SINGLE_SYMBOL_LIMIT; // NOWARN } // no limit hit, proceed normally return INFO::OK; } //---------------------------------------------------------------------------- #define INDENT STMT(for(size_t i__ = 0; i__ <= state.level; i__++) out(L" ");) #define UNINDENT STMT(out_erase((state.level+1)*4);) // does it look like an ASCII string is located at ? // set to 2 to search for WCS-2 strings (of western characters!). // called by dump_sequence for its string special-case. // // algorithm: scan the "string" and count # text chars vs. garbage. static bool is_string(const u8* p, size_t stride) { // note: access violations are caught by dump_sym; output is "?". int score = 0; for(;;) { // current character is: const int c = *p & 0xff; // prevent sign extension p += stride; // .. text if(isalnum(c)) score += 5; // .. end of string else if(!c) break; // .. garbage else if(!isprint(c)) score -= 4; // got enough information either way => done. // (we don't want to unnecessarily scan huge binary arrays) if(abs(score) >= 10) break; } return (score > 0); } // forward decl; called by dump_sequence and some of dump_sym_*. static Status dump_sym(DWORD id, const u8* p, DumpState& state); // from cvconst.h // // rationale: we don't provide a get_register routine, since only the // value of FP is known to dump_frame_cb (via STACKFRAME64). // displaying variables stored in registers is out of the question; // all we can do is display FP-relative variables. enum CV_HREG_e { CV_REG_EBP = 22, CV_AMD64_RBP = 334 }; static void dump_error(Status err) { switch(err) { case 0: // no error => no output break; case ERR::SYM_SINGLE_SYMBOL_LIMIT: out(L"(too much output; skipping to next top-level symbol)"); break; case ERR::SYM_UNRETRIEVABLE_STATIC: out(L"(unavailable - located in another module)"); break; case ERR::SYM_UNRETRIEVABLE: out(L"(unavailable)"); break; case ERR::SYM_TYPE_INFO_UNAVAILABLE: out(L"(unavailable - type info request failed (GLE=%d))", GetLastError()); break; case ERR::SYM_INTERNAL_ERROR: out(L"(unavailable - internal error)\r\n"); break; case INFO::SYM_SUPPRESS_OUTPUT: // not an error; do not output anything. handled by caller. break; default: out(L"(unavailable - unspecified error 0x%X (%d))", err, err); break; } } // moved out of dump_sequence. static Status dump_string(const u8* p, size_t el_size) { // not char or wchar_t string if(el_size != sizeof(char) && el_size != sizeof(wchar_t)) return INFO::CANNOT_HANDLE; // not text if(!is_string(p, el_size)) return INFO::CANNOT_HANDLE; wchar_t buf[512]; if(el_size == sizeof(wchar_t)) { wcsncpy(buf, (const wchar_t*)p, ARRAY_SIZE(buf)); // can't use wcscpy_s because p might be too long wcscpy_s(buf+ARRAY_SIZE(buf)-4, 4, L"..."); // ensure null-termination } // convert to wchar_t else { size_t i; for(i = 0; i < ARRAY_SIZE(buf)-1; i++) { buf[i] = (wchar_t)p[i]; if(buf[i] == '\0') break; } buf[i] = '\0'; } out(L"\"%ls\"", buf); return INFO::OK; } // moved out of dump_sequence. static void seq_determine_formatting(size_t el_size, size_t el_count, bool* fits_on_one_line, size_t* num_elements_to_show) { if(el_size == sizeof(char)) { *fits_on_one_line = el_count <= 16; *num_elements_to_show = std::min((size_t)16u, el_count); } else if(el_size <= sizeof(int)) { *fits_on_one_line = el_count <= 8; *num_elements_to_show = std::min((size_t)12u, el_count); } else { *fits_on_one_line = false; *num_elements_to_show = std::min((size_t)8u, el_count); } // make sure empty containers are displayed with [0] {}, otherwise // the lack of output looks like an error. if(!el_count) *fits_on_one_line = true; } static Status dump_sequence(DebugStlIterator el_iterator, void* internal, size_t el_count, DWORD el_type_id, size_t el_size, DumpState& state) { const u8* el_p = 0; // avoid "uninitialized" warning // special case: display as a string if the sequence looks to be text. // do this only if container isn't empty because the otherwise the // iterator may crash. if(el_count) { el_p = el_iterator(internal, el_size); Status ret = dump_string(el_p, el_size); if(ret == INFO::OK) return ret; } // choose formatting based on element size and count bool fits_on_one_line; size_t num_elements_to_show; seq_determine_formatting(el_size, el_count, &fits_on_one_line, &num_elements_to_show); out(L"[%d] ", el_count); state.level++; out(fits_on_one_line? L"{ " : L"\r\n"); for(size_t i = 0; i < num_elements_to_show; i++) { if(!fits_on_one_line) INDENT; Status err = dump_sym(el_type_id, el_p, state); el_p = el_iterator(internal, el_size); // there was no output for this child; undo its indentation (if any), // skip everything below and proceed with the next child. if(err == INFO::SYM_SUPPRESS_OUTPUT) { if(!fits_on_one_line) UNINDENT; continue; } dump_error(err); // nop if err == INFO::OK // add separator unless this is the last element (can't just // erase below due to additional "..."). if(i != num_elements_to_show-1) out(fits_on_one_line? L", " : L"\r\n"); if(err == ERR::SYM_SINGLE_SYMBOL_LIMIT) break; } // for each child // indicate some elements were skipped if(el_count != num_elements_to_show) out(L" ..."); state.level--; if(fits_on_one_line) out(L" }"); return INFO::OK; } static const u8* array_iterator(void* internal, size_t el_size) { const u8*& pos = *(const u8**)internal; const u8* cur_pos = pos; pos += el_size; return cur_pos; } static Status dump_array(const u8* p, size_t el_count, DWORD el_type_id, size_t el_size, DumpState& state) { const u8* iterator_internal_pos = p; return dump_sequence(array_iterator, &iterator_internal_pos, el_count, el_type_id, el_size, state); } static Status CanHandleDataKind(DWORD dataKind) { switch(dataKind) { case DataIsMember: // address is already correct (udt_dump_normal retrieved the offset; // we do it that way so we can check it against the total // UDT size for safety) and SymFromIndex would fail return INFO::SKIPPED; case DataIsUnknown: WARN_RETURN(ERR::FAIL); case DataIsStaticMember: // this symbol is defined as static in another module => // there's nothing we can do. return ERR::SYM_UNRETRIEVABLE_STATIC; // NOWARN case DataIsLocal: case DataIsStaticLocal: case DataIsParam: case DataIsObjectPtr: case DataIsFileStatic: case DataIsGlobal: case DataIsConstant: // ok, can handle return INFO::OK; } WARN_RETURN(ERR::LOGIC); // UNREACHABLE } static bool IsRelativeToFramePointer(DWORD flags, DWORD reg) { if(flags & SYMFLAG_FRAMEREL) // note: this is apparently obsolete return true; if((flags & SYMFLAG_REGREL) == 0) return false; if(reg == CV_REG_EBP || reg == CV_AMD64_RBP) return true; return false; } static bool IsUnretrievable(DWORD flags) { // note: it is unlikely that the crashdump register context // contains the correct values for this scope, so symbols // stored in or relative to a general register are unavailable. if(flags & SYMFLAG_REGISTER) return true; // note: IsRelativeToFramePointer is called first, so if we still // see this flag, the base register is not the frame pointer. // since we most probably don't know its value in the current // scope (see above), the symbol is inaccessible. if(flags & SYMFLAG_REGREL) return true; return false; } static Status DetermineSymbolAddress(DWORD id, const SYMBOL_INFOW* sym, const DumpState& state, const u8** pp) { DWORD dataKind; if(!pSymGetTypeInfo(hProcess, state.moduleBase, id, TI_GET_DATAKIND, &dataKind)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); Status ret = CanHandleDataKind(dataKind); RETURN_STATUS_IF_ERR(ret); if(ret == INFO::SKIPPED) return INFO::OK; // pp is already correct // note: we have not yet observed a non-zero TI_GET_ADDRESSOFFSET or // TI_GET_ADDRESS, and TI_GET_OFFSET is apparently equal to sym->Address. // get address uintptr_t addr = (uintptr_t)sym->Address; if(IsRelativeToFramePointer(sym->Flags, sym->Register)) addr += (uintptr_t)state.stackFrame->AddrFrame.Offset; else if(IsUnretrievable(sym->Flags)) return ERR::SYM_UNRETRIEVABLE; // NOWARN *pp = (const u8*)(uintptr_t)addr; debug_printf("SYM| %s at %p flags=%X dk=%d sym->addr=%I64X fp=%I64x\n", utf8_from_wstring(sym->Name).c_str(), *pp, sym->Flags, dataKind, sym->Address, state.stackFrame->AddrFrame.Offset); return INFO::OK; } //----------------------------------------------------------------------------- // dump routines for each dbghelp symbol type //----------------------------------------------------------------------------- // these functions return != 0 if they're not able to produce any // reasonable output at all; the caller (dump_sym_data, dump_sequence, etc.) // will display the appropriate error message via dump_error. // called by dump_sym; lock is held. static Status dump_sym_array(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // get element count and size DWORD el_type_id = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &el_type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); // .. workaround: TI_GET_COUNT returns total struct size for // arrays-of-struct. therefore, calculate as size / el_size. ULONG64 el_size_; if(!pSymGetTypeInfo(hProcess, state.moduleBase, el_type_id, TI_GET_LENGTH, &el_size_)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t el_size = (size_t)el_size_; ENSURE(el_size != 0); const size_t num_elements = size/el_size; ENSURE(num_elements != 0); return dump_array(p, num_elements, el_type_id, el_size, state); } //----------------------------------------------------------------------------- // if the current value is a printable character, display in that form. // this isn't only done in btChar because characters are sometimes stored // in integers. static void AppendCharacterIfPrintable(u64 data) { if(data < 0x100) { int c = (int)data; if(isprint(c)) out(L" ('%hc')", c); } } static Status dump_sym_base_type(DWORD type_id, const u8* p, DumpState& state) { DWORD base_type; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_BASETYPE, &base_type)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // single out() call. note: we pass a single u64 for all sizes, // which will only work on little-endian systems. // must be declared before goto to avoid W4 warning. const wchar_t* fmt = L""; u64 data = movzx_le64(p, size); // if value is 0xCC..CC (uninitialized mem), we display as hex. // the output would otherwise be garbage; this makes it obvious. // note: be very careful to correctly handle size=0 (e.g. void*). for(size_t i = 0; i < size; i++) { if(p[i] != 0xCC) break; if(i == size-1) { out(L"(uninitialized)"); return INFO::OK; } } switch(base_type) { // floating-point case btFloat: if(size == sizeof(float)) { // NB: the C calling convention calls for float arguments to be // converted to double. passing `data' wouldn't work because it's // merely a zero-extended 32-bit representation of the float. float value; memcpy(&value, p, sizeof(value)); out(L"%f (0x%08I64X)", value, data); } else if(size == sizeof(double)) out(L"%g (0x%016I64X)", data, data); else DEBUG_WARN_ERR(ERR::LOGIC); // invalid float size break; // boolean case btBool: ENSURE(size == sizeof(bool)); if(data == 0 || data == 1) out(L"%ls", data? L"true " : L"false"); else out(L"(bool)0x%02I64X", data); break; // integers (displayed as decimal and hex) // note: 0x00000000 can get annoying (0 would be nicer), // but it indicates the variable size and makes for consistently // formatted structs/arrays. (0x1234 0 0x5678 is ugly) case btInt: case btLong: case btUInt: case btULong: if(size == 1) { // _TUCHAR if(state.indirection) { state.indirection = 0; return dump_array(p, 8, type_id, size, state); } fmt = L"%I64d (0x%02I64X)"; } else if(size == 2) fmt = L"%I64d (0x%04I64X)"; else if(size == 4) fmt = L"%I64d (0x%08I64X)"; else if(size == 8) fmt = L"%I64d (0x%016I64X)"; else DEBUG_WARN_ERR(ERR::LOGIC); // invalid size for integers out(fmt, data, data); break; // character case btChar: case btWChar: ENSURE(size == sizeof(char) || size == sizeof(wchar_t)); // char*, wchar_t* if(state.indirection) { state.indirection = 0; return dump_array(p, 8, type_id, size, state); } out(L"%d", data); AppendCharacterIfPrintable(data); break; // note: void* is sometimes indicated as (pointer, btNoType). case btVoid: case btNoType: // void* - cannot display what it's pointing to (type unknown). if(state.indirection) { out_erase(4); // " -> " fmt = L""; } else DEBUG_WARN_ERR(ERR::LOGIC); // non-pointer btVoid or btNoType break; default: DEBUG_WARN_ERR(ERR::LOGIC); // unknown type break; // unsupported complex types case btBCD: case btCurrency: case btDate: case btVariant: case btComplex: case btBit: case btBSTR: case btHresult: return ERR::SYM_UNSUPPORTED; // NOWARN } return INFO::OK; } //----------------------------------------------------------------------------- static Status dump_sym_base_class(DWORD type_id, const u8* p, DumpState& state) { DWORD base_class_type_id; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &base_class_type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); // this is a virtual base class. we can't display those because it'd // require reading the VTbl, which is difficult given lack of documentation // and just not worth it. DWORD vptr_ofs; if(pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_VIRTUALBASEPOINTEROFFSET, &vptr_ofs)) return ERR::SYM_UNSUPPORTED; // NOWARN return dump_sym(base_class_type_id, p, state); } //----------------------------------------------------------------------------- static Status dump_sym_data(DWORD id, const u8* p, DumpState& state) { SYMBOL_INFO_PACKAGEW2 sp; SYMBOL_INFOW* sym = &sp.si; if(!pSymFromIndexW(hProcess, state.moduleBase, id, sym)) RETURN_STATUS_IF_ERR(ERR::SYM_TYPE_INFO_UNAVAILABLE); out(L"%ls = ", sym->Name); __try { RETURN_STATUS_IF_ERR(DetermineSymbolAddress(id, sym, state, &p)); // display value recursively return dump_sym(sym->TypeIndex, p, state); } __except(EXCEPTION_EXECUTE_HANDLER) { return ERR::SYM_INTERNAL_ERROR; // NOWARN } } //----------------------------------------------------------------------------- static Status dump_sym_enum(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; const i64 enum_value = movsx_le64(p, size); // get array of child symbols (enumerants). DWORD numChildren; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_CHILDRENCOUNT, &numChildren)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); TI_FINDCHILDREN_PARAMS2 fcp(numChildren); if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_FINDCHILDREN, &fcp)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); numChildren = fcp.p.Count; // was truncated to maxChildren const DWORD* children = fcp.p.ChildId; // for each child (enumerant): for(size_t i = 0; i < numChildren; i++) { DWORD child_data_id = children[i]; // get this enumerant's value. we can't make any assumptions about // the variant's type or size - no restriction is documented. // rationale: VariantChangeType is much less tedious than doing // it manually and guarantees we cover everything. the OLE DLL is // already pulled in by e.g. OpenGL anyway. VARIANT v; if(!pSymGetTypeInfo(hProcess, state.moduleBase, child_data_id, TI_GET_VALUE, &v)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); if(VariantChangeType(&v, &v, 0, VT_I8) != S_OK) continue; // it's the one we want - output its name. if(enum_value == v.llVal) { const wchar_t* name; if(!pSymGetTypeInfo(hProcess, state.moduleBase, child_data_id, TI_GET_SYMNAME, &name)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); out(L"%ls", name); LocalFree((HLOCAL)name); return INFO::OK; } } // we weren't able to retrieve a matching enum value, but can still // produce reasonable output (the numeric value). // note: could goto here after a SGTI fails, but we fail instead // to make sure those errors are noticed. out(L"%I64d", enum_value); return INFO::OK; } //----------------------------------------------------------------------------- static Status dump_sym_function(DWORD UNUSED(type_id), const u8* UNUSED(p), DumpState& UNUSED(state)) { return INFO::SYM_SUPPRESS_OUTPUT; } //----------------------------------------------------------------------------- static Status dump_sym_function_type(DWORD UNUSED(type_id), const u8* p, DumpState& state) { // this symbol gives class parent, return type, and parameter count. // unfortunately the one thing we care about, its name, // isn't exposed via TI_GET_SYMNAME, so we resolve it ourselves. wchar_t name[DEBUG_SYMBOL_CHARS]; Status err = ResolveSymbol_lk((void*)p, name, 0, 0); if(state.indirection == 0) out(L"0x%p ", p); if(err == INFO::OK) out(L"(%ls)", name); return INFO::OK; } //----------------------------------------------------------------------------- // do not follow pointers that we have already displayed. this reduces // clutter a bit and prevents infinite recursion for cyclical references // (e.g. via struct S { S* p; } s; s.p = &s;) // note: allocating memory dynamically would cause trouble if dumping // the stack from within memory-related code (the allocation hook would // be reentered, which is not permissible). static const size_t maxVisited = 1000; static const u8* visited[maxVisited]; static size_t numVisited; static void ptr_reset_visited() { numVisited = 0; } static bool ptr_already_visited(const u8* p) { for(size_t i = 0; i < numVisited; i++) { if(visited[i] == p) return true; } if(numVisited < maxVisited) { visited[numVisited] = p; numVisited++; } // capacity exceeded else { // warn user - but only once (we can't use the regular // debug_DisplayError and wdbg_assert doesn't have a // suppress mechanism) static bool haveComplained; if(!haveComplained) { debug_printf("WARNING: ptr_already_visited: capacity exceeded, increase maxVisited\n"); debug_break(); haveComplained = true; } } return false; } static Status dump_sym_pointer(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // read+output pointer's value. p = (const u8*)(uintptr_t)movzx_le64(p, size); out(L"0x%p", p); // bail if it's obvious the pointer is bogus // (=> can't display what it's pointing to) if(debug_IsPointerBogus(p)) return INFO::OK; // avoid duplicates and circular references if(ptr_already_visited(p)) { out(L" (see above)"); return INFO::OK; } // display what the pointer is pointing to. // if the pointer is invalid (despite "bogus" check above), // dump_data_sym recovers via SEH and prints an error message. // if the pointed-to value turns out to uninteresting (e.g. void*), // the responsible dump_sym* will erase "->", leaving only address. out(L" -> "); if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); // prevent infinite recursion just to be safe (shouldn't happen) if(state.indirection >= maxIndirection) WARN_RETURN(ERR::SYM_NESTING_LIMIT); state.indirection++; return dump_sym(type_id, p, state); } //----------------------------------------------------------------------------- static Status dump_sym_typedef(DWORD type_id, const u8* p, DumpState& state) { if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_TYPEID, &type_id)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); return dump_sym(type_id, p, state); } //----------------------------------------------------------------------------- // determine type and size of the given child in a UDT. // useful for UDTs that contain typedefs describing their contents, // e.g. value_type in STL containers. static Status udt_get_child_type(const wchar_t* child_name, ULONG numChildren, const DWORD* children, const DumpState& state, DWORD* el_type_id, size_t* el_size) { const DWORD lastError = GetLastError(); *el_type_id = 0; *el_size = 0; for(ULONG i = 0; i < numChildren; i++) { const DWORD child_id = children[i]; SYMBOL_INFO_PACKAGEW2 sp; SYMBOL_INFOW* sym = &sp.si; if(!pSymFromIndexW(hProcess, state.moduleBase, child_id, sym)) { // this happens for several UDTs; cause is unknown. ENSURE(GetLastError() == ERROR_NOT_FOUND); continue; } if(!wcscmp(sym->Name, child_name)) { *el_type_id = sym->TypeIndex; *el_size = (size_t)sym->Size; return INFO::OK; } } SetLastError(lastError); // (happens if called for containers that are treated as STL but are not) return ERR::SYM_CHILD_NOT_FOUND; // NOWARN } static Status udt_dump_std(const wchar_t* type_name, const u8* p, size_t size, DumpState& state, ULONG numChildren, const DWORD* children) { Status err; // not a C++ standard library object; can't handle it. if(wcsncmp(type_name, L"std::", 5) != 0) return INFO::CANNOT_HANDLE; // check for C++ objects that should be displayed via udt_dump_normal. // C++03 containers are special-cased and the rest (apart from those here) // are ignored, because for the most part they are spew. if(!wcsncmp(type_name, L"std::pair", 9) || !wcsncmp(type_name, L"std::tr1::", 10)) return INFO::CANNOT_HANDLE; // display contents of STL containers // .. get element type DWORD el_type_id; size_t el_size; err = udt_get_child_type(L"value_type", numChildren, children, state, &el_type_id, &el_size); if(err != INFO::OK) goto not_valid_container; // .. get iterator and # elements size_t el_count; DebugStlIterator el_iterator; u8 it_mem[DEBUG_STL_MAX_ITERATOR_SIZE]; err = debug_stl_get_container_info(type_name, p, size, el_size, &el_count, &el_iterator, it_mem); if(err != INFO::OK) goto not_valid_container; return dump_sequence(el_iterator, it_mem, el_count, el_type_id, el_size, state); not_valid_container: // build and display detailed "error" message. wchar_t buf[100]; const wchar_t* text; // .. object named std::* but doesn't include a "value_type" child => // it's a non-STL C++ stdlib object. wasn't handled by the // special case above, so we just display its simplified type name // (the contents are usually spew). if(err == ERR::SYM_CHILD_NOT_FOUND) text = L""; // .. not one of the containers we can analyse. else if(err == ERR::STL_CNT_UNKNOWN) text = L"unknown "; else if(err == ERR::STL_CNT_UNSUPPORTED) text = L"unsupported "; // .. container of a known type but contents are invalid. else if(err == ERR::STL_CNT_INVALID) text = L"uninitialized/invalid "; // .. some other error encountered else { wchar_t description[200]; (void)StatusDescription(err, description, ARRAY_SIZE(description)); swprintf_s(buf, ARRAY_SIZE(buf), L"error \"%ls\" while analyzing ", description); text = buf; } // (debug_stl modifies its input string in-place; type_name is // a const string returned by dbghelp) wchar_t type_name_buf[DEBUG_SYMBOL_CHARS]; wcscpy_s(type_name_buf, ARRAY_SIZE(type_name_buf), type_name); out(L"(%ls%ls)", text, debug_stl_simplify_name(type_name_buf)); return INFO::OK; } static bool udt_should_suppress(const wchar_t* type_name) { // specialized HANDLEs are defined as pointers to structs by // DECLARE_HANDLE. we only want the numerical value (pointer address), // so prevent these structs from being displayed. // note: no need to check for indirection; these are only found in // HANDLEs (which are pointers). // removed obsolete defs: HEVENT, HFILE, HUMPD if(type_name[0] != 'H') goto not_handle; #define SUPPRESS_HANDLE(name) if(!wcscmp(type_name, L#name L"__")) return true; SUPPRESS_HANDLE(HACCEL); SUPPRESS_HANDLE(HBITMAP); SUPPRESS_HANDLE(HBRUSH); SUPPRESS_HANDLE(HCOLORSPACE); SUPPRESS_HANDLE(HCURSOR); SUPPRESS_HANDLE(HDC); SUPPRESS_HANDLE(HENHMETAFILE); SUPPRESS_HANDLE(HFONT); SUPPRESS_HANDLE(HGDIOBJ); SUPPRESS_HANDLE(HGLOBAL); SUPPRESS_HANDLE(HGLRC); SUPPRESS_HANDLE(HHOOK); SUPPRESS_HANDLE(HICON); SUPPRESS_HANDLE(HIMAGELIST); SUPPRESS_HANDLE(HIMC); SUPPRESS_HANDLE(HINSTANCE); SUPPRESS_HANDLE(HKEY); SUPPRESS_HANDLE(HKL); SUPPRESS_HANDLE(HKLOCAL); SUPPRESS_HANDLE(HMENU); SUPPRESS_HANDLE(HMETAFILE); SUPPRESS_HANDLE(HMODULE); SUPPRESS_HANDLE(HMONITOR); SUPPRESS_HANDLE(HPALETTE); SUPPRESS_HANDLE(HPEN); SUPPRESS_HANDLE(HRGN); SUPPRESS_HANDLE(HRSRC); SUPPRESS_HANDLE(HSTR); SUPPRESS_HANDLE(HTASK); SUPPRESS_HANDLE(HWINEVENTHOOK); SUPPRESS_HANDLE(HWINSTA); SUPPRESS_HANDLE(HWND); not_handle: return false; } static Status udt_dump_suppressed(const wchar_t* type_name, const u8* UNUSED(p), size_t UNUSED(size), DumpState state, ULONG UNUSED(numChildren), const DWORD* UNUSED(children)) { if(!udt_should_suppress(type_name)) return INFO::CANNOT_HANDLE; // the data symbol is pointer-to-UDT. since we won't display its // contents, leave only the pointer's value. if(state.indirection) out_erase(4); // " -> " // indicate something was deliberately left out // (otherwise, lack of output may be taken for an error) out(L" (..)"); return INFO::OK; } // (by now) non-trivial heuristic to determine if a UDT should be // displayed on one line or several. split out of udt_dump_normal. static bool udt_fits_on_one_line(const wchar_t* type_name, size_t child_count, size_t total_size) { // special case: always put CStr* on one line // (std::*string are displayed directly, but these go through // udt_dump_normal. we want to avoid the ensuing 3-line output) if(!wcscmp(type_name, L"CStr") || !wcscmp(type_name, L"CStr8") || !wcscmp(type_name, L"CStrW")) return true; // try to get actual number of relevant children // (typedefs etc. are never displayed, but are included in child_count. // we have to balance that vs. tons of static members, which aren't // reflected in total_size). // .. prevent division by 0. if(child_count == 0) child_count = 1; // special-case a few types that would otherwise be classified incorrectly // (due to having more or less than expected relevant children) if(!wcsncmp(type_name, L"std::pair", 9)) child_count = 2; const size_t avg_size = total_size / child_count; // (if 0, no worries - child_count will probably be large and // we return false, which is a safe default) // small UDT with a few (small) members: fits on one line. if(child_count <= 3 && avg_size <= sizeof(int)) return true; return false; } static Status udt_dump_normal(const wchar_t* type_name, const u8* p, size_t size, DumpState state, ULONG numChildren, const DWORD* children) { // special case: boost::unordered types are complex and may cause a stack overflow // see http://trac.wildfiregames.com/ticket/1813 // TODO: at least give some info about them if(!wcsncmp(type_name, L"boost::unordered", 16)) return INFO::CANNOT_HANDLE; const bool fits_on_one_line = udt_fits_on_one_line(type_name, numChildren, size); // prevent infinite recursion just to be safe (shouldn't happen) if(state.level >= maxLevel) WARN_RETURN(ERR::SYM_NESTING_LIMIT); state.level++; out(fits_on_one_line? L"{ " : L"\r\n"); bool displayed_anything = false; for(ULONG i = 0; i < numChildren; i++) { const DWORD child_id = children[i]; // get offset. if not available, skip this child // (we only display data here, not e.g. typedefs) DWORD ofs = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, child_id, TI_GET_OFFSET, &ofs)) continue; if(ofs >= size) { debug_printf("INVALID_UDT %s %d %d\n", utf8_from_wstring(type_name).c_str(), ofs, size); } //ENSURE(ofs < size); if(!fits_on_one_line) INDENT; const u8* el_p = p+ofs; Status err = dump_sym(child_id, el_p, state); // there was no output for this child; undo its indentation (if any), // skip everything below and proceed with the next child. if(err == INFO::SYM_SUPPRESS_OUTPUT) { if(!fits_on_one_line) UNINDENT; continue; } displayed_anything = true; dump_error(err); // nop if err == INFO::OK out(fits_on_one_line? L", " : L"\r\n"); if(err == ERR::SYM_SINGLE_SYMBOL_LIMIT) break; } // for each child state.level--; if(!displayed_anything) { out_erase(2); // "{ " or "\r\n" out(L"(%ls)", type_name); return INFO::OK; } // remove trailing comma separator // note: we can't avoid writing it by checking if i == numChildren-1: // each child might be the last valid data member. if(fits_on_one_line) { out_erase(2); // ", " out(L" }"); } return INFO::OK; } static Status dump_sym_udt(DWORD type_id, const u8* p, DumpState& state) { ULONG64 size64 = 0; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_LENGTH, &size64)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const size_t size = (size_t)size64; // get array of child symbols (members/functions/base classes). DWORD numChildren; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_CHILDRENCOUNT, &numChildren)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); TI_FINDCHILDREN_PARAMS2 fcp(numChildren); if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_FINDCHILDREN, &fcp)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); numChildren = fcp.p.Count; // was truncated to maxChildren const DWORD* children = fcp.p.ChildId; const wchar_t* type_name; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_SYMNAME, &type_name)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); Status ret; // note: order is important (e.g. STL special-case must come before // suppressing UDTs, which tosses out most other C++ stdlib classes) ret = udt_dump_std (type_name, p, size, state, numChildren, children); if(ret != INFO::CANNOT_HANDLE) goto done; ret = udt_dump_suppressed(type_name, p, size, state, numChildren, children); if(ret != INFO::CANNOT_HANDLE) goto done; ret = udt_dump_normal (type_name, p, size, state, numChildren, children); if(ret != INFO::CANNOT_HANDLE) goto done; done: LocalFree((HLOCAL)type_name); return ret; } //----------------------------------------------------------------------------- static Status dump_sym_vtable(DWORD UNUSED(type_id), const u8* UNUSED(p), DumpState& UNUSED(state)) { // unsupported (vtable internals are undocumented; too much work). return INFO::SYM_SUPPRESS_OUTPUT; } //----------------------------------------------------------------------------- static Status dump_sym_unknown(DWORD type_id, const u8* UNUSED(p), DumpState& state) { // redundant (already done in dump_sym), but this is rare. DWORD type_tag; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_SYMTAG, &type_tag)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); debug_printf("SYM: unknown tag: %d\n", type_tag); out(L"(unknown symbol type)"); return INFO::OK; } //----------------------------------------------------------------------------- typedef Status (*DumpFunc)(DWORD typeId, const u8* p, DumpState& state); static DumpFunc DumpFuncFromTypeTag(DWORD typeTag) { switch(typeTag) { case SymTagArrayType: return dump_sym_array; case SymTagBaseType: return dump_sym_base_type; case SymTagBaseClass: return dump_sym_base_class; case SymTagData: return dump_sym_data; case SymTagEnum: return dump_sym_enum; case SymTagFunction: return dump_sym_function; case SymTagFunctionType: return dump_sym_function_type; case SymTagPointerType: return dump_sym_pointer; case SymTagTypedef: return dump_sym_typedef; case SymTagUDT: return dump_sym_udt; case SymTagVTable: return dump_sym_vtable; default: return dump_sym_unknown; } } // write name and value of the symbol to the output buffer. // delegates to dump_sym_* depending on the symbol's tag. static Status dump_sym(DWORD type_id, const u8* p, DumpState& state) { RETURN_STATUS_IF_ERR(out_check_limit()); DWORD typeTag; if(!pSymGetTypeInfo(hProcess, state.moduleBase, type_id, TI_GET_SYMTAG, &typeTag)) WARN_RETURN(ERR::SYM_TYPE_INFO_UNAVAILABLE); const DumpFunc dumpFunc = DumpFuncFromTypeTag(typeTag); return dumpFunc(type_id, p, state); } //----------------------------------------------------------------------------- // stack trace //----------------------------------------------------------------------------- static bool ShouldSkipSymbol(const wchar_t* name) { if(!wcscmp(name, L"suppress__")) return true; if(!wcscmp(name, L"__profile")) return true; return false; } // output the symbol's name and value via dump_sym*. // called from dump_frame_cb for each local symbol; lock is held. static BOOL CALLBACK dump_sym_cb(SYMBOL_INFOW* sym, ULONG UNUSED(size), PVOID userContext) { if(ShouldSkipSymbol(sym->Name)) return TRUE; // continue out_latch_pos(); // see decl const u8* p = (const u8*)(uintptr_t)sym->Address; DumpState state((uintptr_t)sym->ModBase, (LPSTACKFRAME64)userContext); INDENT; Status err = dump_sym(sym->Index, p, state); dump_error(err); if(err == INFO::SYM_SUPPRESS_OUTPUT) UNINDENT; else out(L"\r\n"); return TRUE; // continue } // called by wdbg_sym_WalkStack for each stack frame static Status dump_frame_cb(const STACKFRAME64* sf, uintptr_t UNUSED(userContext)) { void* func = (void*)(uintptr_t)sf->AddrPC.Offset; wchar_t func_name[DEBUG_SYMBOL_CHARS]; wchar_t file[DEBUG_FILE_CHARS]; int line; Status ret = ResolveSymbol_lk(func, func_name, file, &line); if(ret == INFO::OK) { // don't trace back further than the app's entry point // (no one wants to see this frame). checking for the // function name isn't future-proof, but not stopping is no big deal. // an alternative would be to check if module=kernel32, but // that would cut off callbacks as well. // note: the stdcall mangled name includes parameter size, which is // different in 64-bit, so only check the first characters. if(!wcsncmp(func_name, L"_BaseProcessStart", 17) || !wcscmp(func_name, L"BaseThreadInitThunk")) return INFO::OK; // skip any mainCRTStartup frames if(!wcscmp(func_name, L"__tmainCRTStartup")) return INFO::OK; if(!wcscmp(func_name, L"mainCRTStartup")) return INFO::OK; out(L"%ls (%ls:%d)\r\n", func_name, file, line); } else out(L"%p\r\n", func); WinScopedPreserveLastError s; // SymSetContext // only enumerate symbols for this stack frame // (i.e. its locals and parameters) // problem: debug info is scope-aware, so we won't see any variables // declared in sub-blocks. we'd have to pass an address in that block, // which isn't worth the trouble. IMAGEHLP_STACK_FRAME isf = PopulateImageStackFrame(*sf); const PIMAGEHLP_CONTEXT ic = 0; // ignored // NB: this sometimes fails for reasons unknown in a static // member function, possibly because the return address is in kernel32 (void)pSymSetContext(hProcess, &isf, ic); const ULONG64 base = 0; const wchar_t* const mask = 0; // use scope set by pSymSetContext pSymEnumSymbolsW(hProcess, base, mask, dump_sym_cb, (PVOID)sf); if(GetLastError() == ERROR_NOT_SUPPORTED) // no debug info present? SetLastError(0); out(L"\r\n"); return INFO::OK; } Status debug_DumpStack(wchar_t* buf, size_t maxChars, void* pcontext, const wchar_t* lastFuncToSkip) { static intptr_t busy; if(!cpu_CAS(&busy, 0, 1)) return ERR::REENTERED; // NOWARN out_init(buf, maxChars); ptr_reset_visited(); wdbg_assert(pcontext != 0); Status ret = wdbg_sym_WalkStack(dump_frame_cb, 0, *(CONTEXT*)pcontext, lastFuncToSkip); COMPILER_FENCE; busy = 0; return ret; } //----------------------------------------------------------------------------- // write out a "minidump" containing register and stack state; this enables // examining the crash in a debugger. called by wdbg_exception_filter. // heavily modified from http://www.codeproject.com/debug/XCrashReportPt3.asp // lock must be held. void wdbg_sym_WriteMinidump(EXCEPTION_POINTERS* exception_pointers) { sym_init(); WinScopedLock lock(WDBG_SYM_CS); OsPath path = ah_get_log_dir()/"crashlog.dmp"; HANDLE hFile = CreateFileW(OsString(path).c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { DEBUG_DISPLAY_ERROR(L"wdbg_sym_WriteMinidump: unable to create crashlog.dmp."); return; } MINIDUMP_EXCEPTION_INFORMATION mei; mei.ThreadId = GetCurrentThreadId(); mei.ExceptionPointers = exception_pointers; mei.ClientPointers = FALSE; // exception_pointers is not in our address space. // note: we don't store other crashlog info within the dump file // (UserStreamParam), since we will need to generate a plain text file on // non-Windows platforms. users will just have to send us both files. HANDLE hProcess = GetCurrentProcess(); DWORD pid = GetCurrentProcessId(); if(!pMiniDumpWriteDump || !pMiniDumpWriteDump(hProcess, pid, hFile, MiniDumpNormal, &mei, 0, 0)) DEBUG_DISPLAY_ERROR(L"wdbg_sym_WriteMinidump: unable to generate minidump."); CloseHandle(hFile); } Index: ps/trunk/source/lib/sysdep/os/win/wdll_delay_load.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdll_delay_load.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdll_delay_load.h (revision 19899) @@ -1,48 +1,48 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * DLL delay loading and notification */ #ifndef INCLUDED_WDLL_DELAY_LOAD #define INCLUDED_WDLL_DELAY_LOAD // must be POD because it is used before static ctors run. struct WdllLoadNotify { const char* dll_name; Status (*func)(); WdllLoadNotify* next; }; extern void wdll_add_notify(WdllLoadNotify*); // request that func be called if and when dll_name is ever delay-loaded. // must be invoked at function scope. #define WDLL_ADD_NOTIFY(dll_name, func)\ STMT(\ static WdllLoadNotify UID__ = { dll_name, func };\ wdll_add_notify(&UID__);\ ) #endif // #ifndef INCLUDED_WDLL_DELAY_LOAD Index: ps/trunk/source/lib/sysdep/os/win/wfirmware.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wfirmware.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wfirmware.cpp (revision 19899) @@ -1,70 +1,70 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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 "lib/sysdep/os/win/wfirmware.h" #include "lib/sysdep/os/win/wutil.h" namespace wfirmware { TableIds GetTableIDs(Provider provider) { WUTIL_FUNC(pEnumSystemFirmwareTables, UINT, (DWORD, PVOID, DWORD)); WUTIL_IMPORT_KERNEL32(EnumSystemFirmwareTables, pEnumSystemFirmwareTables); if(!pEnumSystemFirmwareTables) return TableIds(); const size_t tableIdsSize = pEnumSystemFirmwareTables(provider, 0, 0); ENSURE(tableIdsSize != 0); ENSURE(tableIdsSize % sizeof(TableId) == 0); TableIds tableIDs(DivideRoundUp(tableIdsSize, sizeof(TableId)), 0); const size_t bytesWritten = pEnumSystemFirmwareTables(provider, &tableIDs[0], (DWORD)tableIdsSize); ENSURE(bytesWritten == tableIdsSize); return tableIDs; } Table GetTable(Provider provider, TableId tableId) { WUTIL_FUNC(pGetSystemFirmwareTable, UINT, (DWORD, DWORD, PVOID, DWORD)); WUTIL_IMPORT_KERNEL32(GetSystemFirmwareTable, pGetSystemFirmwareTable); if(!pGetSystemFirmwareTable) return Table(); const size_t tableSize = pGetSystemFirmwareTable(provider, tableId, 0, 0); if(tableSize == 0) { DEBUG_WARN_ERR(ERR::LOGIC); return Table(); } Table table(tableSize, 0); const size_t bytesWritten = pGetSystemFirmwareTable(provider, tableId, &table[0], (DWORD)tableSize); ENSURE(bytesWritten == tableSize); return table; } } // namespace wfirmware Index: ps/trunk/source/lib/sysdep/os/win/wgl.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wgl.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wgl.h (revision 19899) @@ -1,92 +1,92 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Windows definitions required for GL/gl.h */ // RAGE! Win32 OpenGL headers are full of crap we have to emulate // (must not include windows.h) #ifndef WGL_HEADER_NEEDED #error "wgl.h: why is this included from anywhere but ogl.h?" #endif #ifndef WINGDIAPI #define WINGDIAPI __declspec(dllimport) #endif #ifndef CALLBACK #define CALLBACK __stdcall #endif #ifndef APIENTRY #define APIENTRY __stdcall #endif #ifndef WINAPI #define WINAPI __stdcall #endif #ifndef DECLARE_HANDLE typedef void VOID; typedef void* LPVOID; typedef int BOOL; typedef unsigned short USHORT; typedef int INT; typedef unsigned int UINT; typedef long LONG; typedef unsigned long DWORD; typedef int INT32; typedef __int64 INT64; typedef float FLOAT; typedef char CHAR; typedef const char* LPCSTR; typedef void* HANDLE; typedef int (*PROC)(); struct RECT { LONG left; LONG top; LONG right; LONG bottom; }; #define DECLARE_HANDLE(name) typedef HANDLE name DECLARE_HANDLE(HDC); DECLARE_HANDLE(HGLRC); #endif // VC6 doesn't define wchar_t as built-in type #ifndef _WCHAR_T_DEFINED typedef unsigned short wchar_t; // for glu.h #define _WCHAR_T_DEFINED #endif WINGDIAPI BOOL WINAPI wglCopyContext(HGLRC, HGLRC, UINT); WINGDIAPI HGLRC WINAPI wglCreateContext(HDC); WINGDIAPI HGLRC WINAPI wglCreateLayerContext(HDC, int); WINGDIAPI BOOL WINAPI wglDeleteContext(HGLRC); WINGDIAPI HGLRC WINAPI wglGetCurrentContext(); WINGDIAPI HDC WINAPI wglGetCurrentDC(); WINGDIAPI PROC WINAPI wglGetProcAddress(LPCSTR); WINGDIAPI BOOL WINAPI wglMakeCurrent(HDC, HGLRC); WINGDIAPI BOOL WINAPI wglShareLists(HGLRC, HGLRC); WINGDIAPI BOOL WINAPI wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); WINGDIAPI BOOL WINAPI wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); Index: ps/trunk/source/lib/sysdep/os/win/whrt/hpet.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/hpet.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/hpet.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using High Precision Event Timer */ #ifndef INCLUDED_HPET #define INCLUDED_HPET class ICounter; extern ICounter* CreateCounterHPET(void* address, size_t size); #endif // #ifndef INCLUDED_HPET Index: ps/trunk/source/lib/sysdep/os/win/whrt/qpc.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/qpc.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/qpc.cpp (revision 19899) @@ -1,157 +1,157 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using QueryPerformanceCounter */ #include "precompiled.h" #include "lib/sysdep/os/win/whrt/qpc.h" #include "lib/sysdep/os/win/whrt/counter.h" #include "lib/sysdep/os_cpu.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" // wutil_argv #include "lib/sysdep/os/win/whrt/pit.h" // PIT_FREQ #include "lib/sysdep/os/win/whrt/pmt.h" // PMT_FREQ class CounterQPC : public ICounter { public: CounterQPC() : m_frequency(-1) { } virtual const char* Name() const { return "QPC"; } Status Activate() { // note: QPC is observed to be universally supported, but the API // provides for failure, so play it safe. LARGE_INTEGER qpcFreq, qpcValue; const BOOL ok1 = QueryPerformanceFrequency(&qpcFreq); const BOOL ok2 = QueryPerformanceCounter(&qpcValue); if(!ok1 || !ok2) WARN_RETURN(ERR::FAIL); if(!qpcFreq.QuadPart || !qpcValue.QuadPart) WARN_RETURN(ERR::FAIL); m_frequency = (i64)qpcFreq.QuadPart; return INFO::OK; } void Shutdown() { } bool IsSafe() const { // note: we have separate modules that directly access some of the // counters potentially used by QPC. disabling the redundant counters // would be ugly (increased coupling). instead, we'll make sure our // implementations could (if necessary) coexist with QPC, but it // shouldn't come to that since only one counter is needed/used. // the PIT is entirely safe (even if annoyingly slow to read) if(m_frequency == PIT_FREQ) return true; // the PMT is generally safe (see discussion in CounterPmt::IsSafe), // but older QPC implementations had problems with 24-bit rollover. // "System clock problem can inflate benchmark scores" // (http://www.lionbridge.com/bi/cont2000/200012/perfcnt.asp ; no longer // online, nor findable in Google Cache / archive.org) tells of // incorrect values every 4.6 seconds (i.e. 24 bits @ 3.57 MHz) unless // the timer is polled in the meantime. fortunately, this is guaranteed // by our periodic updates (which come at least that often). if(m_frequency == PMT_FREQ) return true; // the TSC has been known to be buggy (even mentioned in MSDN). it is // used on MP HAL systems and can be detected by comparing QPF with the // CPU clock. we consider it unsafe unless the user promises (via // command line) that it's patched and thus reliable on their system. bool usesTsc = IsSimilarMagnitude((double)m_frequency, os_cpu_ClockFrequency()); // unconfirmed reports indicate QPC sometimes uses 1/3 of the // CPU clock frequency, so check that as well. usesTsc |= IsSimilarMagnitude((double)m_frequency, os_cpu_ClockFrequency()/3); if(usesTsc) { const bool isTscSafe = wutil_HasCommandLineArgument(L"-wQpcTscSafe"); return isTscSafe; } // the HPET is reliable and used on Vista. it can't easily be recognized // since its frequency is variable (the spec says > 10 MHz; the master // 14.318 MHz oscillator is often used). considering frequencies in // [10, 100 MHz) to be a HPET would be dangerous because it may actually // be faster or RDTSC slower. we have to exclude all other cases and // assume it's a HPET - and thus safe - if we get here. return true; } u64 Counter() const { // fairly time-critical here, don't check the return value // (IsSupported made sure it succeeded initially) LARGE_INTEGER qpc_value; (void)QueryPerformanceCounter(&qpc_value); return qpc_value.QuadPart; } size_t CounterBits() const { // there are reports of incorrect rollover handling in the PMT // implementation of QPC (see CounterPMT::IsSafe). however, other // counters would be used on those systems, so it's irrelevant. // we'll report the full 64 bits. return 64; } double NominalFrequency() const { return (double)m_frequency; } double Resolution() const { return 1.0 / m_frequency; } private: // used in several places and QPF is a bit slow+cumbersome. // (i64 allows easier conversion to double) i64 m_frequency; }; ICounter* CreateCounterQPC(void* address, size_t size) { ENSURE(sizeof(CounterQPC) <= size); return new(address) CounterQPC(); } Index: ps/trunk/source/lib/sysdep/os/win/whrt/tsc.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/tsc.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/tsc.cpp (revision 19899) @@ -1,259 +1,259 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using RDTSC */ #include "precompiled.h" #include "lib/sysdep/os/win/whrt/tsc.h" #include "lib/sysdep/os/win/whrt/counter.h" #include "lib/bits.h" #include "lib/sysdep/acpi.h" #include "lib/sysdep/os_cpu.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" #if ARCH_X86_X64 # include "lib/sysdep/arch/x86_x64/x86_x64.h" // x86_x64::rdtsc # include "lib/sysdep/arch/x86_x64/topology.h" # include "lib/sysdep/arch/x86_x64/msr.h" #endif //----------------------------------------------------------------------------- static bool IsUniprocessor() { if(topology::NumPackages() != 1) return false; if(topology::CoresPerPackage() != 1) return false; return true; } static bool IsInvariantTSC() { #if ARCH_X86_X64 // (we no longer need to check x86_x64::Vendor - Intel and AMD // agreed on the definition of this feature check) x86_x64::CpuidRegs regs = { 0 }; regs.eax = 0x80000007; if(x86_x64::cpuid(®s)) { // TSC is invariant across P-state, C-state, turbo, and // stop grant transitions (e.g. STPCLK) if(regs.edx & BIT(8)) return true; } #endif return false; } static bool IsThrottlingPossible() { #if ARCH_X86_X64 x86_x64::CpuidRegs regs = { 0 }; switch(x86_x64::Vendor()) { case x86_x64::VENDOR_INTEL: if(x86_x64::Cap(x86_x64::CAP_TM_SCC) || x86_x64::Cap(x86_x64::CAP_EST)) return true; break; case x86_x64::VENDOR_AMD: regs.eax = 0x80000007; if(x86_x64::cpuid(®s)) { enum AmdPowerNowFlags { PN_FREQ_ID_CTRL = BIT(1), PN_HW_THERMAL_CTRL = BIT(4), PN_SW_THERMAL_CTRL = BIT(5) }; if(regs.edx & (PN_FREQ_ID_CTRL|PN_HW_THERMAL_CTRL|PN_SW_THERMAL_CTRL)) return true; } break; default: break; } #endif return false; } static bool IsSandyBridge() { if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL) return false; if(x86_x64::Model() == x86_x64::MODEL_SANDY_BRIDGE) return true; if(x86_x64::Model() == x86_x64::MODEL_SANDY_BRIDGE_2) return true; return false; } //----------------------------------------------------------------------------- class CounterTSC : public ICounter { public: virtual const char* Name() const { return "TSC"; } Status Activate() { #if ARCH_X86_X64 if(!x86_x64::Cap(x86_x64::CAP_TSC)) return ERR::NOT_SUPPORTED; // NOWARN (CPU doesn't support RDTSC) #endif return INFO::OK; } void Shutdown() { } bool IsSafe() const { // using the TSC for timing is subject to a litany of // potential problems, discussed below: if(IsInvariantTSC()) return true; // SMP or multi-core => counters are unsynchronized. both offset and // drift could be solved by maintaining separate per-core // counter states, but that requires atomic reads of the TSC and // the current processor number. // // (otherwise, we have a subtle race condition: if preempted while // reading the time and rescheduled on a different core, incorrect // results may be returned, which would be unacceptable.) // // unfortunately this isn't possible without OS support or the // as yet unavailable RDTSCP instruction => unsafe. // // (note: if the TSC is invariant, drift is no longer a concern. // we could synchronize the TSC MSRs during initialization and avoid // per-core counter state and the race condition mentioned above. // however, we won't bother, since such platforms aren't yet widespread // and would surely support the nice and safe HPET, anyway) if(!IsUniprocessor()) return false; const FADT* fadt = (const FADT*)acpi_GetTable("FACP"); if(fadt) { ENSURE(fadt->header.size >= sizeof(FADT)); // TSC isn't incremented in deep-sleep states => unsafe. if(fadt->IsC3Supported()) return false; // frequency throttling possible => unsafe. if(fadt->IsDutyCycleSupported()) return false; } #if ARCH_X86_X64 // recent CPU: //if(x86_x64::Generation() >= 7) { // note: 8th generation CPUs support C1-clock ramping, which causes // drift on multi-core systems, but those were excluded above. // in addition to frequency changes due to P-state transitions, // we're also subject to STPCLK throttling. this happens when // the chipset thinks the system is dangerously overheated; the // OS isn't even notified. this may be rare, but could cause // incorrect results => unsafe. //return false; } #endif // we're dealing with a single older CPU; the only problem there is // throttling, i.e. changes to the TSC frequency. we don't want to // disable this because it may be important for cooling. the OS // initiates changes but doesn't notify us; jumps are too frequent // and drastic to detect and account for => unsafe. if(IsThrottlingPossible()) return false; return true; } u64 Counter() const { return x86_x64::rdtsc(); } size_t CounterBits() const { return 64; } double NominalFrequency() const { // WARNING: do not call x86_x64::ClockFrequency because it uses the // HRT, which we're currently in the process of initializing. // instead query CPU clock frequency via OS. // // note: even here, initial accuracy isn't critical because the // clock is subject to thermal drift and would require continual // recalibration anyway. #if ARCH_X86_X64 if(MSR::IsAccessible() && MSR::HasPlatformInfo()) { const i64 busFrequency = IsSandyBridge()? 100000000 : 133333333; const u64 platformInfo = MSR::Read(MSR::PLATFORM_INFO); const u8 maxNonTurboRatio = bits(platformInfo, 8, 15); return double(maxNonTurboRatio) * busFrequency; } else #endif return os_cpu_ClockFrequency(); } double Resolution() const { return 1.0 / NominalFrequency(); } }; ICounter* CreateCounterTSC(void* address, size_t size) { ENSURE(sizeof(CounterTSC) <= size); return new(address) CounterTSC(); } Index: ps/trunk/source/lib/sysdep/os/win/mahaf.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/mahaf.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/mahaf.cpp (revision 19899) @@ -1,398 +1,398 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * user-mode interface to Aken driver */ #include "precompiled.h" #include "lib/sysdep/os/win/mahaf.h" #include "lib/config2.h" #include "lib/module_init.h" #include "lib/sysdep/os/win/wutil.h" #include #include "lib/sysdep/os/win/aken/aken.h" #include "lib/sysdep/os/win/wversion.h" static HANDLE hAken = INVALID_HANDLE_VALUE; // handle to Aken driver //----------------------------------------------------------------------------- // ioctl wrappers //----------------------------------------------------------------------------- static u32 ReadPort(u16 port, u8 numBytes) { AkenReadPortIn in; in.port = (USHORT)port; in.numBytes = (UCHAR)numBytes; AkenReadPortOut out; DWORD bytesReturned; LPOVERLAPPED ovl = 0; // synchronous const BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_READ_PORT, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl); WARN_RETURN_0_IF_FALSE(ok); ENSURE(bytesReturned == sizeof(out)); return out.value; } u8 mahaf_ReadPort8(u16 port) { const u32 value = ReadPort(port, 1); ENSURE(value <= 0xFF); return (u8)(value & 0xFF); } u16 mahaf_ReadPort16(u16 port) { const u32 value = ReadPort(port, 2); ENSURE(value <= 0xFFFF); return (u16)(value & 0xFFFF); } u32 mahaf_ReadPort32(u16 port) { const u32 value = ReadPort(port, 4); return value; } static void WritePort(u16 port, u32 value, u8 numBytes) { AkenWritePortIn in; in.value = (DWORD32)value; in.port = (USHORT)port; in.numBytes = (UCHAR)numBytes; DWORD bytesReturned; // unused but must be passed to DeviceIoControl LPOVERLAPPED ovl = 0; // synchronous BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_WRITE_PORT, &in, sizeof(in), 0, 0u, &bytesReturned, ovl); WARN_IF_FALSE(ok); } void mahaf_WritePort8(u16 port, u8 value) { WritePort(port, (u32)value, 1); } void mahaf_WritePort16(u16 port, u16 value) { WritePort(port, (u32)value, 2); } void mahaf_WritePort32(u16 port, u32 value) { WritePort(port, value, 4); } bool mahaf_IsPhysicalMappingDangerous() { // pre-XP versions don't prevent re-mapping pages with incompatible // attributes, which may lead to disaster due to TLB corruption. if(wversion_Number() < WVERSION_XP) return true; return false; } volatile void* mahaf_MapPhysicalMemory(uintptr_t physicalAddress, size_t numBytes) { ENSURE(!mahaf_IsPhysicalMappingDangerous()); AkenMapIn in; in.physicalAddress = (DWORD64)physicalAddress; in.numBytes = (DWORD64)numBytes; AkenMapOut out; DWORD bytesReturned; LPOVERLAPPED ovl = 0; // synchronous const BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_MAP, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl); WARN_RETURN_0_IF_FALSE(ok); ENSURE(bytesReturned == sizeof(out)); volatile void* virtualAddress = (volatile void*)(uintptr_t)out.virtualAddress; return virtualAddress; } void mahaf_UnmapPhysicalMemory(volatile void* virtualAddress) { ENSURE(!mahaf_IsPhysicalMappingDangerous()); AkenUnmapIn in; in.virtualAddress = (DWORD64)virtualAddress; DWORD bytesReturned; // unused but must be passed to DeviceIoControl LPOVERLAPPED ovl = 0; // synchronous BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_UNMAP, &in, sizeof(in), 0, 0u, &bytesReturned, ovl); WARN_IF_FALSE(ok); } static u64 ReadRegister(DWORD ioctl, u64 reg) { AkenReadRegisterIn in; in.reg = reg; AkenReadRegisterOut out; DWORD bytesReturned; LPOVERLAPPED ovl = 0; // synchronous const BOOL ok = DeviceIoControl(hAken, ioctl, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl); WARN_RETURN_0_IF_FALSE(ok); ENSURE(bytesReturned == sizeof(out)); return out.value; } u64 mahaf_ReadModelSpecificRegister(u64 reg) { return ReadRegister((DWORD)IOCTL_AKEN_READ_MSR, reg); } u64 mahaf_ReadPerformanceMonitoringCounter(u64 reg) { return ReadRegister((DWORD)IOCTL_AKEN_READ_PMC, reg); } void mahaf_WriteModelSpecificRegister(u64 reg, u64 value) { AkenWriteRegisterIn in; in.reg = reg; in.value = value; DWORD bytesReturned; // unused but must be passed to DeviceIoControl LPOVERLAPPED ovl = 0; // synchronous BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_WRITE_MSR, &in, sizeof(in), 0, 0u, &bytesReturned, ovl); WARN_IF_FALSE(ok); } //----------------------------------------------------------------------------- // driver installation //----------------------------------------------------------------------------- // @param access need not include SC_MANAGER_CONNECT ("implicitly specified") static SC_HANDLE OpenServiceControlManager(DWORD access) { LPCWSTR machineName = 0; // local LPCWSTR databaseName = 0; // default SC_HANDLE hSCM = OpenSCManagerW(machineName, databaseName, access); if(!hSCM) { // ensure no other problems arose ENSURE(GetLastError() == ERROR_ACCESS_DENIED); // administrator privileges are required for SC_MANAGER_CREATE_SERVICE. // this is a problem on Vista / Win7, so users will have to use the // separate aken_install.bat return 0; } return hSCM; // success } static void UninstallDriver() { SC_HANDLE hSCM = OpenServiceControlManager(SC_MANAGER_ENUMERATE_SERVICE); if(!hSCM) return; SC_HANDLE hService = OpenServiceW(hSCM, AKEN_NAME, SERVICE_STOP|SERVICE_INTERROGATE); if(!hService) return; // stop service SERVICE_STATUS serviceStatus; if(!ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus)) { // if the problem wasn't that the service is already stopped, // something actually went wrong. const DWORD err = GetLastError(); ENSURE(err == ERROR_SERVICE_NOT_ACTIVE || err == ERROR_SERVICE_CANNOT_ACCEPT_CTRL); } // delete service BOOL ok; ok = DeleteService(hService); WARN_IF_FALSE(ok); ok = CloseServiceHandle(hService); WARN_IF_FALSE(ok); ok = CloseServiceHandle(hSCM); WARN_IF_FALSE(ok); } #if CONFIG2_MAHAF_ATTEMPT_DRIVER_START static void StartDriver(const OsPath& driverPathname) { const SC_HANDLE hSCM = OpenServiceControlManager(SC_MANAGER_CREATE_SERVICE); if(!hSCM) { ENSURE(GetLastError() == ERROR_ACCESS_DENIED); SetLastError(0); return; } SC_HANDLE hService = OpenServiceW(hSCM, AKEN_NAME, SERVICE_START); // during development, we want to ensure the newest build is used, so // unload and re-create the service if it's running/installed. // as of 2008-03-24 no further changes to Aken are pending, so this is // disabled (thus also avoiding trouble when running multiple instances) #if 0 if(hService) { BOOL ok = CloseServiceHandle(hService); WARN_IF_FALSE(ok); hService = 0; UninstallDriver(); } #endif // create service (note: this just enters the service into SCM's DB; // no error is raised if the driver binary doesn't exist etc.) if(!hService) { LPCWSTR startName = 0; // LocalSystem // NB: Windows 7 seems to insist upon backslashes (i.e. external_file_string) hService = CreateServiceW(hSCM, AKEN_NAME, AKEN_NAME, SERVICE_START, SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, OsString(driverPathname).c_str(), 0, 0, 0, startName, 0); ENSURE(hService != 0); } { DWORD numArgs = 0; BOOL ok = StartService(hService, numArgs, 0); if(!ok) { switch(GetLastError()) { case ERROR_SERVICE_ALREADY_RUNNING: // ok, no action needed break; case ERROR_ACCESS_DENIED: // Win7, can't start service; must use aken_install.bat break; case ERROR_INVALID_IMAGE_HASH: // Win7 x86 rejects our code signing certificate; must enable // "test signing" mode via aken_install.bat break; default: // unexpected problem DEBUG_WARN_ERR(ERR::LOGIC); break; } } } CloseServiceHandle(hService); CloseServiceHandle(hSCM); } static bool Is64BitOs() { #if OS_WIN64 return true; #else return wutil_IsWow64(); #endif } static OsPath DriverPathname() { const char* const bits = Is64BitOs()? "64" : ""; #ifdef NDEBUG const char* const debug = ""; #else const char* const debug = "d"; #endif char filename[PATH_MAX]; sprintf_s(filename, ARRAY_SIZE(filename), "aken%s%s.sys", bits, debug); return wutil_ExecutablePath() / filename; } #endif // CONFIG2_MAHAF_ATTEMPT_DRIVER_START //----------------------------------------------------------------------------- static Status Init() { WinScopedPreserveLastError s; if(wutil_HasCommandLineArgument(L"-wNoMahaf")) return ERR::NOT_SUPPORTED; // NOWARN #if CONFIG2_MAHAF_ATTEMPT_DRIVER_START { const OsPath driverPathname = DriverPathname(); StartDriver(driverPathname); } #endif { const DWORD shareMode = 0; hAken = CreateFileW(L"\\\\.\\Aken", GENERIC_READ, shareMode, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if(hAken == INVALID_HANDLE_VALUE) { // GetLastError() is ERROR_FILE_NOT_FOUND if the driver isn't running, // but this is also reached when a handle has already been opened // (e.g. by a second instance of the same program) - in which case we must // indicate failure so that clients won't engage in unsynchronized ring 0 operations. SetLastError(0); return ERR::INVALID_HANDLE; // NOWARN (happens often due to security restrictions) } } return INFO::OK; } static void Shutdown() { CloseHandle(hAken); hAken = INVALID_HANDLE_VALUE; UninstallDriver(); } static ModuleInitState initState; Status mahaf_Init() { return ModuleInit(&initState, Init); } void mahaf_Shutdown() { ModuleShutdown(&initState, Shutdown); } Index: ps/trunk/source/lib/sysdep/os/win/tests/test_wdbg_sym.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/tests/test_wdbg_sym.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/tests/test_wdbg_sym.h (revision 19899) @@ -1,322 +1,322 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // note: this is more of an on-demand display of the stack trace than // self-test of it. // TODO: compare against known-good result? // problem: results may differ by compiler (e.g. due to differing STL) #include "lib/self_test.h" #include #include #include #include #include #include "lib/bits.h" #include "lib/sysdep/os/win/win.h" // HWND #include "lib/sysdep/sysdep.h" #include "lib/sysdep/os/win/wdbg_sym.h" #include "lib/external_libraries/dbghelp.h" static void* callers[100]; static size_t numCallers; static Status OnFrame(const _tagSTACKFRAME64* frame, uintptr_t UNUSED(cbData)) { callers[numCallers++] = (void*)frame->AddrPC.Offset; return INFO::OK; } #pragma optimize("", off) #pragma warning(disable:4748) // /GS can not protect [..] from local buffer overrun because optimizations are disabled // (these must be outside of TestWdbgSym so that we can simply // search for the function's name as a substring within the ILT // decorated name (which omits the :: scope resolution operator, // while debug_ResolveSymbol for the function does not) __declspec(noinline) static void Func1() { CONTEXT context; (void)debug_CaptureContext(&context); wdbg_sym_WalkStack(OnFrame, 0, context); } __declspec(noinline) static void Func2() { Func1(); } __declspec(noinline) static void Func3() { Func2(); } class TestWdbgSym : public CxxTest::TestSuite { static void m_test_array() { struct Small { int i1; int i2; }; struct Large { double d1; double d2; double d3; double d4; }; Large large_array_of_large_structs[8] = { { 0.0,0.0,0.0,0.0 } }; Large small_array_of_large_structs[2] = { { 0.0,0.0,0.0,0.0 } }; Small large_array_of_small_structs[8] = { { 1,2 } }; Small small_array_of_small_structs[2] = { { 1,2 } }; int ints[] = { 1,2,3,4,5 }; wchar_t chars[] = { 'w','c','h','a','r','s',0 }; wchar_t many_wchars[1024]; memset(many_wchars, 'a', sizeof(many_wchars)); debug_printf("\n(dumping stack frames may result in access violations...)\n"); debug_printf(" locals: %.0d %.0c %.0c %.0g %.0g %.0d %.0d\n", ints[0], chars[0], many_wchars[0], large_array_of_large_structs[0].d1, small_array_of_large_structs[0].d1, large_array_of_small_structs[0].i1, small_array_of_small_structs[0].i1); // note: we don't want any kind of dialog to be raised, because // this test now always runs. therefore, just make sure a decent // amount of text (not just "(failed)" error messages) was produced. ErrorMessageMem emm = {0}; CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE]; (void)debug_CaptureContext(context); const wchar_t* text = debug_BuildErrorMessage(L"dummy", 0,0,0, context, L"m_test_array", &emm); TS_ASSERT(wcslen(text) > 500); #if 0 { std::wofstream s(L"d:\\out.txt"); s << text; } #endif debug_FreeErrorMessage(&emm); debug_printf("(done dumping stack frames)\n"); } // also used by test_stl as an element type struct Nested { int nested_member; struct Nested* self_ptr; }; static void m_test_udt() { Nested nested = { 123 }; nested.self_ptr = &nested; typedef struct { u8 s1; u8 s2; char s3; } Small; Small small__ = { 0x55, 0xaa, -1 }; UNUSED2(small__); struct Large { u8 large_member_u8; std::string large_member_string; double large_member_double; } large = { 0xff, "large struct string", 123456.0 }; UNUSED2(large); class Base { int base_int; std::wstring base_wstring; public: Base() : base_int(123), base_wstring(L"base wstring") { } }; class Derived : private Base { double derived_double; public: Derived() : derived_double(-1.0) { } } derived; m_test_array(); } // STL containers and their contents static void m_test_stl() { std::vector v_wstring; v_wstring.push_back(L"ws1"); v_wstring.push_back(L"ws2"); std::deque d_int; d_int.push_back(1); d_int.push_back(2); d_int.push_back(3); std::deque d_string; d_string.push_back("a"); d_string.push_back("b"); d_string.push_back("c"); std::list l_float; l_float.push_back(0.1f); l_float.push_back(0.2f); l_float.push_back(0.3f); l_float.push_back(0.4f); std::map m_string_int; m_string_int.insert(std::make_pair("s5", 5)); m_string_int.insert(std::make_pair("s6", 6)); m_string_int.insert(std::make_pair("s7", 7)); std::map m_int_string; m_int_string.insert(std::make_pair(1, "s1")); m_int_string.insert(std::make_pair(2, "s2")); m_int_string.insert(std::make_pair(3, "s3")); std::map m_int_int; m_int_int.insert(std::make_pair(1, 1)); m_int_int.insert(std::make_pair(2, 2)); m_int_int.insert(std::make_pair(3, 3)); std::set s_uintptr; s_uintptr.insert(0x123); s_uintptr.insert(0x456); // empty std::deque d_u8_empty; std::list l_nested_empty; std::map m_double_empty; std::multimap mm_int_empty; std::set s_uint_empty; std::multiset ms_char_empty; std::vector v_double_empty; std::queue q_double_empty; std::stack st_double_empty; std::string str_empty; std::wstring wstr_empty; m_test_udt(); // uninitialized std::deque d_u8_uninit; std::list l_nested_uninit; std::map m_double_uninit; std::multimap mm_int_uninit; std::set s_uint_uninit; std::multiset ms_char_uninit; std::vector v_double_uninit; std::queue q_double_uninit; std::stack st_double_uninit; std::string str_uninit; std::wstring wstr_uninit; } // also exercises all basic types because we need to display some values // anyway (to see at a glance whether symbol engine addrs are correct) static void m_test_addrs(int p_int, double p_double, char* p_pchar, uintptr_t p_uintptr) { size_t l_uint = 0x1234; bool l_bool = true; UNUSED2(l_bool); wchar_t l_wchars[] = L"wchar string"; enum TestEnum { VAL1=1, VAL2=2 } l_enum = VAL1; u8 l_u8s[] = { 1,2,3,4 }; void (*l_funcptr)(void) = m_test_stl; static double s_double = -2.718; static char s_chars[] = {'c','h','a','r','s',0}; static void (*s_funcptr)(int, double, char*, uintptr_t) = m_test_addrs; static void* s_ptr = (void*)(uintptr_t)0x87654321; static HDC s_hdc = (HDC)0xff0; #if 0 // output only needed when debugging debug_printf("\nTEST_ADDRS\n"); debug_printf("p_int addr=%p val=%d\n", &p_int, p_int); debug_printf("p_double addr=%p val=%g\n", &p_double, p_double); debug_printf("p_pchar addr=%p val=%s\n", &p_pchar, p_pchar); debug_printf("p_uintptr addr=%p val=%lu\n", &p_uintptr, p_uintptr); debug_printf("l_uint addr=%p val=%u\n", &l_uint, l_uint); debug_printf("l_wchars addr=%p val=%s\n", &l_wchars, utf8_from_wstring(l_wchars)); debug_printf("l_enum addr=%p val=%d\n", &l_enum, l_enum); debug_printf("l_u8s addr=%p val=%d\n", &l_u8s, l_u8s); debug_printf("l_funcptr addr=%p val=%p\n", &l_funcptr, l_funcptr); #else UNUSED2(p_uintptr); UNUSED2(p_pchar); UNUSED2(p_double); UNUSED2(p_int); UNUSED2(l_funcptr); UNUSED2(l_enum); UNUSED2(l_uint); (void)l_u8s; (void)l_wchars; #endif m_test_stl(); int uninit_int; UNUSED2(uninit_int); float uninit_float; UNUSED2(uninit_float); double uninit_double; UNUSED2(uninit_double); bool uninit_bool; UNUSED2(uninit_bool); HWND uninit_hwnd; UNUSED2(uninit_hwnd); } #pragma optimize("", on) public: void test_stack_trace() { m_test_addrs(123, 3.1415926535897932384626, "pchar string", 0xf00d); } void test_stack_walk() { Status ret; Func3(); TS_ASSERT(numCallers >= 3); size_t foundFunctionBits = 0; void* funcAddresses[3] = { (void*)&Func1, (void*)&Func2, (void*)&Func3 }; for(size_t idxCaller = 0; idxCaller < numCallers; idxCaller++) { wchar_t callerName[DEBUG_SYMBOL_CHARS]; ret = debug_ResolveSymbol(callers[idxCaller], callerName, 0, 0); TS_ASSERT_OK(ret); wchar_t funcName[DEBUG_SYMBOL_CHARS]; for(size_t idxFunc = 0; idxFunc < ARRAY_SIZE(funcAddresses); idxFunc++) { ret = debug_ResolveSymbol(funcAddresses[idxFunc], funcName, 0, 0); TS_ASSERT_OK(ret); if(wcsstr(funcName, callerName)) foundFunctionBits |= BIT(idxFunc); } } TS_ASSERT(foundFunctionBits == bit_mask(ARRAY_SIZE(funcAddresses))); } }; Index: ps/trunk/source/lib/sysdep/os/win/wcursor.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wcursor.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wcursor.cpp (revision 19899) @@ -1,142 +1,142 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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 "lib/sysdep/cursor.h" #include "lib/sysdep/gfx.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" static sys_cursor cursor_from_HICON(HICON hIcon) { return (sys_cursor)(uintptr_t)hIcon; } static sys_cursor cursor_from_HCURSOR(HCURSOR hCursor) { return (sys_cursor)(uintptr_t)hCursor; } static HICON HICON_from_cursor(sys_cursor cursor) { return (HICON)(uintptr_t)cursor; } static HCURSOR HCURSOR_from_cursor(sys_cursor cursor) { return (HCURSOR)(uintptr_t)cursor; } static Status sys_cursor_create_common(int w, int h, void* bgra_img, void* mask_img, int hx, int hy, sys_cursor* cursor) { *cursor = 0; // MSDN says selecting this HBITMAP into a DC is slower since we use // CreateBitmap; bpp/format must be checked against those of the DC. // this is the simplest way and we don't care about slight performance // differences because this is typically only called once. HBITMAP hbmColor = CreateBitmap(w, h, 1, 32, bgra_img); // CreateIconIndirect doesn't access this; we just need to pass // an empty bitmap. HBITMAP hbmMask = CreateBitmap(w, h, 1, 1, mask_img); // create the cursor (really an icon; they differ only in // fIcon and the hotspot definitions). ICONINFO ii; ii.fIcon = FALSE; // cursor ii.xHotspot = (DWORD)hx; ii.yHotspot = (DWORD)hy; ii.hbmMask = hbmMask; ii.hbmColor = hbmColor; HICON hIcon = CreateIconIndirect(&ii); // CreateIconIndirect makes copies, so we no longer need these. DeleteObject(hbmMask); DeleteObject(hbmColor); if(!wutil_IsValidHandle(hIcon)) WARN_RETURN(ERR::FAIL); *cursor = cursor_from_HICON(hIcon); return INFO::OK; } Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor) { // alpha-blended cursors do not work on a 16-bit display // (they get drawn as a black square), so refuse to load the // cursor in that case int bpp = 0; RETURN_STATUS_IF_ERR(gfx::GetVideoMode(NULL, NULL, &bpp, NULL)); if (bpp <= 16) return ERR::FAIL; return sys_cursor_create_common(w, h, bgra_img, NULL, hx, hy, cursor); } Status sys_cursor_create_empty(sys_cursor* cursor) { // the mask gets ignored on 32-bit displays, but is used on 16-bit displays; // setting it to 0xFF makes the cursor invisible (though I'm not quite // sure why it's that way round) u8 bgra_img[] = {0, 0, 0, 0}; u8 mask_img[] = {0xFF}; return sys_cursor_create_common(1, 1, bgra_img, mask_img, 0, 0, cursor); } Status sys_cursor_set(sys_cursor cursor) { // restore default cursor. if(!cursor) cursor = cursor_from_HCURSOR(LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW))); (void)SetCursor(HCURSOR_from_cursor(cursor)); // return value (previous cursor) is useless. return INFO::OK; } Status 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 arrow // (just for safety). if(cursor_from_HCURSOR(GetCursor()) == cursor) WARN_IF_ERR(sys_cursor_set(0)); if(!DestroyIcon(HICON_from_cursor(cursor))) WARN_RETURN(StatusFromWin()); return INFO::OK; } Status sys_cursor_reset() { return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wdbg_heap.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg_heap.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdbg_heap.h (revision 19899) @@ -1,55 +1,55 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * improved debug heap using MS CRT */ #ifndef INCLUDED_WDBG_HEAP #define INCLUDED_WDBG_HEAP // this module provides a more convenient interface to the MS CRT's // debug heap checks. it also hooks into allocations to record the // caller/owner information without requiring macros (which break code // using placement new or member functions called free). /** * enable or disable manual and automatic heap validity checking. * (enabled by default during critical_init.) **/ LIB_API void wdbg_heap_Enable(bool); /** * check heap integrity. * errors are reported by the CRT or via debug_DisplayError. * no effect if called between wdbg_heap_Enable(false) and the next * wdbg_heap_Enable(true). **/ LIB_API void wdbg_heap_Validate(); /** * @return the total number of alloc and realloc operations thus far. * used by the in-game profiler. **/ LIB_API intptr_t wdbg_heap_NumberOfAllocations(); #endif // #ifndef INCLUDED_WDBG_HEAP Index: ps/trunk/source/lib/sysdep/os/win/wdll_delay_load.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdll_delay_load.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdll_delay_load.cpp (revision 19899) @@ -1,476 +1,476 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * DLL delay loading and notification */ #include "precompiled.h" #include "lib/sysdep/os/win/wdll_delay_load.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/winit.h" WINIT_REGISTER_LATE_SHUTDOWN2(wdll_Shutdown); // last - DLLs are unloaded here //----------------------------------------------------------------------------- // delay loading (modified from VC7 DelayHlp.cpp and DelayImp.h) #if MSC_VERSION && MSC_VERSION >= 1700 // FACILITY_VISUALCPP is already defined in winerror.h in VC2012 # undef FACILITY_VISUALCPP #endif #define FACILITY_VISUALCPP ((LONG)0x6d) #define VcppException(sev,status) ((sev) | (FACILITY_VISUALCPP<<16) | status) typedef IMAGE_THUNK_DATA * PImgThunkData; typedef const IMAGE_THUNK_DATA * PCImgThunkData; typedef DWORD RVA; typedef struct ImgDelayDescr { DWORD grAttrs; // attributes RVA rvaDLLName; // RVA to dll name RVA rvaHmod; // RVA of module handle RVA rvaIAT; // RVA of the IAT RVA rvaINT; // RVA of the INT RVA rvaBoundIAT; // RVA of the optional bound IAT RVA rvaUnloadIAT; // RVA of optional copy of original IAT DWORD dwTimeStamp; // 0 if not bound, // O.W. date/time stamp of DLL bound to (Old BIND) } ImgDelayDescr, * PImgDelayDescr; typedef const ImgDelayDescr * PCImgDelayDescr; enum DLAttr { // Delay Load Attributes dlattrRva = 0x1 // RVAs are used instead of pointers // Having this set indicates a VC7.0 // and above delay load descriptor. }; enum { dliStartProcessing, // used to bypass or note helper only dliNoteStartProcessing = dliStartProcessing, dliNotePreLoadLibrary, // called just before LoadLibrary, can // override w/ new HMODULE return val dliNotePreGetProcAddress, // called just before GetProcAddress, can // override w/ new FARPROC return value dliFailLoadLib, // failed to load library, fix it by // returning a valid HMODULE dliFailGetProc, // failed to get proc address, fix it by // returning a valid FARPROC dliNoteEndProcessing // called after all processing is done, no // no bypass possible at this point except // by longjmp()/throw()/RaiseException. }; typedef struct DelayLoadProc { BOOL fImportByName; union { LPCSTR szProcName; DWORD dwOrdinal; }; } DelayLoadProc; typedef struct DelayLoadInfo { DWORD cb; // size of structure PCImgDelayDescr pidd; // raw form of data (everything is there) FARPROC * ppfn; // points to address of function to load LPCSTR szDll; // name of dll DelayLoadProc dlp; // name or ordinal of procedure HMODULE hmodCur; // the hInstance of the library we have loaded FARPROC pfnCur; // the actual function that will be called DWORD dwLastError;// error received (if an error notification) } DelayLoadInfo, * PDelayLoadInfo; typedef FARPROC (WINAPI *PfnDliHook)(unsigned dliNotify, PDelayLoadInfo pdli); //----------------------------------------------------------------------------- // load notification static WdllLoadNotify* notify_list; void wdll_add_notify(WdllLoadNotify* notify) { notify->next = notify_list; notify_list = notify; } static FARPROC WINAPI notify_hook(unsigned dliNotify, PDelayLoadInfo pdli) { if(dliNotify != dliNoteEndProcessing) return 0; for(WdllLoadNotify* n = notify_list; n; n = n->next) if(strncasecmp(pdli->szDll, n->dll_name, strlen(n->dll_name)) == 0) n->func(); return 0; } //----------------------------------------------------------------------------- // hook // The "notify hook" gets called for every call to the // delay load helper. This allows a user to hook every call and // skip the delay load helper entirely. // // dliNotify == { // dliStartProcessing | // dliNotePreLoadLibrary | // dliNotePreGetProc | // dliNoteEndProcessing} // on this call. // EXTERN_C PfnDliHook __pfnDliNotifyHook2 = notify_hook; // This is the failure hook, dliNotify = {dliFailLoadLib|dliFailGetProc} EXTERN_C PfnDliHook __pfnDliFailureHook2 = 0; #if !ICC_VERSION #pragma intrinsic(strlen,memcmp,memcpy) #endif // utility function for calculating the index of the current import // for all the tables (INT, BIAT, UIAT, and IAT). inline unsigned IndexFromPImgThunkData(PCImgThunkData pitdCur, PCImgThunkData pitdBase) { return (unsigned) (pitdCur - pitdBase); } // C++ template utility function for converting RVAs to pointers // extern "C" const IMAGE_DOS_HEADER __ImageBase; template X PFromRva(RVA rva) { return X(PBYTE(&__ImageBase) + rva); } // structure definitions for the list of unload records typedef struct UnloadInfo * PUnloadInfo; typedef struct UnloadInfo { PUnloadInfo puiNext; PCImgDelayDescr pidd; } UnloadInfo; // utility function for calculating the count of imports given the base // of the IAT. NB: this only works on a valid IAT! inline unsigned CountOfImports(PCImgThunkData pitdBase) { unsigned cRet = 0; PCImgThunkData pitd = pitdBase; while (pitd->u1.Function) { pitd++; cRet++; } return cRet; } extern "C" PUnloadInfo __puiHead = 0; struct ULI : public UnloadInfo { ULI(PCImgDelayDescr pidd_) { pidd = pidd_; Link(); } ~ULI() { Unlink(); } void* operator new(size_t cb) { return ::LocalAlloc(LPTR, cb); } void operator delete(void* pv) { ::LocalFree(pv); } void Unlink() { PUnloadInfo* ppui = &__puiHead; while (*ppui && *ppui != this) ppui = &((*ppui)->puiNext); if (*ppui == this) *ppui = puiNext; } void Link() { puiNext = __puiHead; __puiHead = this; } }; // For our own internal use, we convert to the old // format for convenience. // struct InternalImgDelayDescr { DWORD grAttrs; // attributes LPCSTR szName; // pointer to dll name HMODULE * phmod; // address of module handle PImgThunkData pIAT; // address of the IAT PCImgThunkData pINT; // address of the INT PCImgThunkData pBoundIAT; // address of the optional bound IAT PCImgThunkData pUnloadIAT; // address of optional copy of original IAT DWORD dwTimeStamp; // 0 if not bound, // O.W. date/time stamp of DLL bound to (Old BIND) }; typedef InternalImgDelayDescr * PIIDD; typedef const InternalImgDelayDescr * PCIIDD; static inline PIMAGE_NT_HEADERS WINAPI PinhFromImageBase(HMODULE hmod) { return PIMAGE_NT_HEADERS(PBYTE(hmod) + PIMAGE_DOS_HEADER(hmod)->e_lfanew); } static inline void WINAPI OverlayIAT(PImgThunkData pitdDst, PCImgThunkData pitdSrc) { memcpy(pitdDst, pitdSrc, CountOfImports(pitdDst) * sizeof IMAGE_THUNK_DATA); } static inline DWORD WINAPI TimeStampOfImage(PIMAGE_NT_HEADERS pinh) { return pinh->FileHeader.TimeDateStamp; } static inline bool WINAPI FLoadedAtPreferredAddress(PIMAGE_NT_HEADERS pinh, HMODULE hmod) { return UINT_PTR(hmod) == pinh->OptionalHeader.ImageBase; } extern "C" FARPROC WINAPI __delayLoadHelper2(PCImgDelayDescr pidd, FARPROC* ppfnIATEntry) { // Set up some data we use for the hook procs but also useful for // our own use // InternalImgDelayDescr idd = { pidd->grAttrs, PFromRva(pidd->rvaDLLName), PFromRva(pidd->rvaHmod), PFromRva(pidd->rvaIAT), PFromRva(pidd->rvaINT), PFromRva(pidd->rvaBoundIAT), PFromRva(pidd->rvaUnloadIAT), pidd->dwTimeStamp }; DelayLoadInfo dli = { sizeof(DelayLoadInfo), pidd, ppfnIATEntry, idd.szName, { 0 }, 0, 0, 0 }; if (!(idd.grAttrs & dlattrRva)) { PDelayLoadInfo rgpdli[1] = { &dli }; RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_INVALID_PARAMETER), 0, 1, PULONG_PTR(rgpdli)); return 0; } HMODULE hmod = *idd.phmod; // Calculate the index for the IAT entry in the import address table // N.B. The INT entries are ordered the same as the IAT entries so // the calculation can be done on the IAT side. // const unsigned iIAT = IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry), idd.pIAT); const unsigned iINT = iIAT; PCImgThunkData pitd = &(idd.pINT[iINT]); dli.dlp.fImportByName = !IMAGE_SNAP_BY_ORDINAL(pitd->u1.Ordinal); if (dli.dlp.fImportByName) dli.dlp.szProcName = LPCSTR(PFromRva(RVA(UINT_PTR(pitd->u1.AddressOfData)))->Name); else dli.dlp.dwOrdinal = DWORD(IMAGE_ORDINAL(pitd->u1.Ordinal)); // Call the initial hook. If it exists and returns a function pointer, // abort the rest of the processing and just return it for the call. // FARPROC pfnRet = NULL; if (__pfnDliNotifyHook2) { pfnRet = ((*__pfnDliNotifyHook2)(dliStartProcessing, &dli)); if (pfnRet != NULL) goto HookBypass; } // Check to see if we need to try to load the library. // if (hmod == 0) { if (__pfnDliNotifyHook2) { hmod = HMODULE(((*__pfnDliNotifyHook2)(dliNotePreLoadLibrary, &dli))); } if (hmod == 0) { hmod = ::LoadLibraryA(dli.szDll); } if (hmod == 0) { dli.dwLastError = ::GetLastError(); if (__pfnDliFailureHook2) { // when the hook is called on LoadLibrary failure, it will // return 0 for failure and an hmod for the lib if it fixed // the problem. // hmod = HMODULE((*__pfnDliFailureHook2)(dliFailLoadLib, &dli)); } if (hmod == 0) { PDelayLoadInfo rgpdli[1] = { &dli }; RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND), 0, 1, PULONG_PTR(rgpdli)); // If we get to here, we blindly assume that the handler of the exception // has magically fixed everything up and left the function pointer in // dli.pfnCur. // return dli.pfnCur; } } // Store the library handle. If it is already there, we infer // that another thread got there first, and we need to do a // FreeLibrary() to reduce the refcount // HMODULE hmodT = HMODULE(InterlockedExchangePointer((PVOID *) idd.phmod, PVOID(hmod))); if (hmodT != hmod) { // add lib to unload list if we have unload data if (pidd->rvaUnloadIAT) { new ULI(pidd); } } else { ::FreeLibrary(hmod); } } // Go for the procedure now. // dli.hmodCur = hmod; if (__pfnDliNotifyHook2) { pfnRet = (*__pfnDliNotifyHook2)(dliNotePreGetProcAddress, &dli); } if (pfnRet == 0) { if (pidd->rvaBoundIAT && pidd->dwTimeStamp) { // bound imports exist...check the timestamp from the target image // PIMAGE_NT_HEADERS pinh(PinhFromImageBase(hmod)); if (pinh->Signature == IMAGE_NT_SIGNATURE && TimeStampOfImage(pinh) == idd.dwTimeStamp && FLoadedAtPreferredAddress(pinh, hmod)) { // Everything is good to go, if we have a decent address // in the bound IAT! // pfnRet = FARPROC(UINT_PTR(idd.pBoundIAT[iIAT].u1.Function)); if (pfnRet != 0) { goto SetEntryHookBypass; } } } pfnRet = ::GetProcAddress(hmod, dli.dlp.szProcName); } if (pfnRet == 0) { dli.dwLastError = ::GetLastError(); if (__pfnDliFailureHook2) { // when the hook is called on GetProcAddress failure, it will // return 0 on failure and a valid proc address on success // pfnRet = (*__pfnDliFailureHook2)(dliFailGetProc, &dli); } if (pfnRet == 0) { PDelayLoadInfo rgpdli[1] = { &dli }; RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND), 0, 1, PULONG_PTR(rgpdli)); // If we get to here, we blindly assume that the handler of the exception // has magically fixed everything up and left the function pointer in // dli.pfnCur. // pfnRet = dli.pfnCur; } } SetEntryHookBypass: *ppfnIATEntry = pfnRet; HookBypass: if (__pfnDliNotifyHook2) { dli.dwLastError = 0; dli.hmodCur = hmod; dli.pfnCur = pfnRet; (*__pfnDliNotifyHook2)(dliNoteEndProcessing, &dli); } return pfnRet; } static void UnloadAllDlls() { PUnloadInfo pui; // free all DLLs (avoid BoundsChecker warning) while((pui = __puiHead) != 0) if(pui->pidd->rvaUnloadIAT) { PCImgDelayDescr pidd = pui->pidd; HMODULE* phmod = PFromRva(pidd->rvaHmod); HMODULE hmod = *phmod; OverlayIAT( PFromRva(pidd->rvaIAT), PFromRva(pidd->rvaUnloadIAT) ); ::FreeLibrary(hmod); *phmod = NULL; delete (ULI*)pui; // changes __puiHead! } } //----------------------------------------------------------------------------- static Status wdll_Shutdown() { UnloadAllDlls(); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wdll_ver.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdll_ver.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wdll_ver.h (revision 19899) @@ -1,46 +1,46 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * return DLL version information */ #ifndef INCLUDED_WDLL_VER #define INCLUDED_WDLL_VER #include "lib/os_path.h" typedef std::wstring VersionList; /** * Read DLL version information and append it to a string. * * @param pathname of DLL (preferably the complete path, so that we don't * inadvertently load another one on the library search path.) * If no extension is given, .dll will be appended. * * The text output includes the module name. * On failure, the version is given as "unknown". **/ extern void wdll_ver_Append(const OsPath& pathname, VersionList& list); #endif // #ifndef INCLUDED_WDLL_VER Index: ps/trunk/source/lib/sysdep/os/win/wgfx.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wgfx.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wgfx.h (revision 19899) @@ -1,28 +1,28 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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. */ #ifndef INCLUDED_WGFX #define INCLUDED_WGFX extern std::wstring wgfx_DriverInfo(); #endif // #ifndef INCLUDED_WGFX Index: ps/trunk/source/lib/sysdep/os/win/whrt/hpet.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/hpet.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/hpet.cpp (revision 19899) @@ -1,233 +1,233 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using High Precision Event Timer */ #include "precompiled.h" #include "lib/sysdep/os/win/whrt/hpet.h" // for atomic 64-bit read/write: #define HAVE_X64_MOVD ARCH_AMD64 && (ICC_VERSION || MSC_VERSION >= 1500) #if HAVE_X64_MOVD # include #else # include #endif #include "lib/sysdep/os/win/whrt/counter.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/mahaf.h" #include "lib/sysdep/acpi.h" #include "lib/bits.h" class CounterHPET : public ICounter { public: CounterHPET() : m_hpetRegisters(0) { } virtual const char* Name() const { return "HPET"; } Status Activate() { RETURN_STATUS_IF_ERR(MapRegisters(m_hpetRegisters)); RETURN_STATUS_IF_ERR(VerifyCapabilities(m_frequency, m_counterBits)); // start the counter (if not already running) Write64(CONFIG, Read64(CONFIG)|1); // note: to avoid interfering with any other users of the timer // (e.g. Vista QPC), we don't reset the counter value to 0. return INFO::OK; } void Shutdown() { if(m_hpetRegisters) { mahaf_UnmapPhysicalMemory((void*)m_hpetRegisters); m_hpetRegisters = 0; } } bool IsSafe() const { // the HPET having been created to address other timers' problems, // it has no issues of its own. return true; } u64 Counter() const { // notes: // - Read64 is atomic and avoids race conditions. // - 32-bit counters (m_counterBits == 32) still allow // reading the whole register (the upper bits are zero). return Read64(COUNTER_VALUE); } size_t CounterBits() const { return m_counterBits; } double NominalFrequency() const { return m_frequency; } double Resolution() const { return 1.0 / m_frequency; } private: #pragma pack(push, 1) struct HpetDescriptionTable { AcpiTable header; u32 eventTimerBlockId; AcpiGenericAddress baseAddress; u8 sequenceNumber; u16 minimumPeriodicTicks; u8 attributes; }; #pragma pack(pop) enum RegisterOffsets { CAPS_AND_ID = 0x00, CONFIG = 0x10, COUNTER_VALUE = 0xF0, MAX_OFFSET = 0x3FF }; static Status MapRegisters(volatile void*& registers) { if(mahaf_IsPhysicalMappingDangerous()) return ERR::FAIL; // NOWARN (happens on Win2k) RETURN_STATUS_IF_ERR(mahaf_Init()); // (fails without Administrator privileges) const HpetDescriptionTable* hpet = (const HpetDescriptionTable*)acpi_GetTable("HPET"); if(!hpet) return ERR::NOT_SUPPORTED; // NOWARN (HPET not reported by BIOS) if(hpet->baseAddress.addressSpaceId != ACPI_AS_MEMORY) return ERR::NOT_SUPPORTED; // NOWARN (happens on some BIOSes) // hpet->baseAddress.accessSize is reserved const uintptr_t address = uintptr_t(hpet->baseAddress.address); ENSURE(address % 8 == 0); // "registers are generally aligned on 64-bit boundaries" registers = mahaf_MapPhysicalMemory(address, MAX_OFFSET+1); if(!registers) WARN_RETURN(ERR::NO_MEM); return INFO::OK; } // note: this is atomic even on 32-bit CPUs (Pentium MMX and // above have a 64-bit data bus and MOVQ instruction) u64 Read64(size_t offset) const { ENSURE(offset <= MAX_OFFSET); ENSURE(offset % 8 == 0); const uintptr_t address = uintptr_t(m_hpetRegisters)+offset; const __m128i value128 = _mm_loadl_epi64((__m128i*)address); #if HAVE_X64_MOVD return _mm_cvtsi128_si64x(value128); #else __declspec(align(16)) u32 values[4]; _mm_store_si128((__m128i*)values, value128); return u64_from_u32(values[1], values[0]); #endif } void Write64(size_t offset, u64 value) const { ENSURE(offset <= MAX_OFFSET); ENSURE(offset % 8 == 0); ENSURE(offset != CAPS_AND_ID); // can't write to read-only registers const uintptr_t address = uintptr_t(m_hpetRegisters)+offset; #if HAVE_X64_MOVD const __m128i value128 = _mm_cvtsi64x_si128(value); #else const __m128i value128 = _mm_set_epi32(0, 0, int(value >> 32), int(value & 0xFFFFFFFF)); #endif _mm_storel_epi64((__m128i*)address, value128); } Status VerifyCapabilities(double& frequency, u32& counterBits) const { // AMD document 43366 indicates the clock generator that drives the // HPET is "spread-capable". Wikipedia's frequency hopping article // explains that this reduces electromagnetic interference. // The AMD document recommends BIOS writers add SMM hooks for // reporting the resulting slightly different frequency. // This apparently requires calibration triggered when the HPET is // accessed, during which the config register is -1. We'll wait // about 1 ms (MMIO is expected to take at least 1 us) and // then ensure the HPET timer period is within reasonable bounds. u64 caps_and_id = Read64(CAPS_AND_ID); for(size_t reps = 0; reps < 1000; reps++) { if(caps_and_id != ~u64(0)) // register seems valid break; caps_and_id = Read64(CAPS_AND_ID); } const u8 revision = (u8)bits(caps_and_id, 0, 7); ENSURE(revision != 0); // "the value must NOT be 00h" counterBits = (caps_and_id & Bit(13))? 64 : 32; const u16 vendorID = (u16)bits(caps_and_id, 16, 31); const u32 period_fs = (u32)bits(caps_and_id, 32, 63); ENSURE(period_fs != 0); // "a value of 0 in this field is not permitted" frequency = 1e15 / period_fs; debug_printf("HPET: rev=%X vendor=%X bits=%d period=%08X freq=%g\n", revision, vendorID, counterBits, period_fs, frequency); if(period_fs > 0x05F5E100) // 100 ns (spec guarantees >= 10 MHz) return ERR::CORRUPTED; // avoid using HPET (e.g. if calibration was still in progress) return INFO::OK; } volatile void* m_hpetRegisters; double m_frequency; u32 m_counterBits; }; ICounter* CreateCounterHPET(void* address, size_t size) { ENSURE(sizeof(CounterHPET) <= size); return new(address) CounterHPET(); } Index: ps/trunk/source/lib/sysdep/os/win/whrt/pmt.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/pmt.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/pmt.h (revision 19899) @@ -1,35 +1,35 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using ACPI PM timer */ #ifndef INCLUDED_PMT #define INCLUDED_PMT static const i64 PMT_FREQ = 3579545; // (= master oscillator frequency/4) class ICounter; extern ICounter* CreateCounterPMT(void* address, size_t size); #endif // #ifndef INCLUDED_PMT Index: ps/trunk/source/lib/sysdep/os/win/whrt/tgt.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/tgt.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/tgt.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Timer implementation using timeGetTime */ #ifndef INCLUDED_TGT #define INCLUDED_TGT class ICounter; extern ICounter* CreateCounterTGT(void* address, size_t size); #endif // #ifndef INCLUDED_TGT Index: ps/trunk/source/lib/sysdep/os/win/winit.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/winit.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/winit.h (revision 19899) @@ -1,172 +1,172 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * windows-specific module init and shutdown mechanism */ #ifndef INCLUDED_WINIT #define INCLUDED_WINIT /* Overview -------- This facility allows registering init and shutdown functions with only one line of code and zero runtime overhead. It provides for dependencies between modules, allowing groups of functions to run before others. Details ------- Participating modules store function pointer(s) to their init and/or shutdown function in a specific COFF section. The sections are grouped according to the desired notification and the order in which functions are to be called (useful if one module depends on another). They are then gathered by the linker and arranged in alphabetical order. Placeholder variables in the sections indicate where the series of functions begins and ends for a given notification time. At runtime, all of the function pointers between the markers are invoked. Example ------- (at file scope:) WINIT_REGISTER_MAIN_INIT(InitCallback); Rationale --------- Several methods of module init are possible: (see Large Scale C++ Design) - on-demand initialization: each exported function would have to check if init already happened. that would be brittle and hard to verify. - singleton: variant of the above, but not applicable to a procedural interface (and quite ugly to boot). - registration: static constructors call a central notification function. module dependencies would be quite difficult to express - this would require a graph or separate lists for each priority (clunky). worse, a fatal flaw is that other C++ constructors may depend on the modules we are initializing and already have run. there is no way to influence ctor call order between separate source files, so this is out of the question. - linker-based registration: same as above, but the linker takes care of assembling various functions into one sorted table. the list of init functions is available before C++ ctors have run. incidentally, zero runtime overhead is incurred. unfortunately, this approach is MSVC-specific. however, the MS CRT uses a similar method for its init, so this is expected to remain supported. */ //----------------------------------------------------------------------------- // section declarations // section names are of the format ".WINIT${type}{group}". // {type} is I for initialization- or S for shutdown functions. // {group} is [0, 9] - see below. // note: __declspec(allocate) requires declaring segments in advance via // #pragma section. #pragma section(".WINIT$I$", read) #pragma section(".WINIT$I0", read) #pragma section(".WINIT$I1", read) #pragma section(".WINIT$I2", read) #pragma section(".WINIT$I6", read) #pragma section(".WINIT$I7", read) #pragma section(".WINIT$IZ", read) #pragma section(".WINIT$S$", read) #pragma section(".WINIT$S0", read) #pragma section(".WINIT$S1", read) #pragma section(".WINIT$S6", read) #pragma section(".WINIT$S7", read) #pragma section(".WINIT$S8", read) #pragma section(".WINIT$SZ", read) #pragma comment(linker, "/merge:.WINIT=.rdata") //----------------------------------------------------------------------------- // Function groups // to allow correct ordering of module init in the face of dependencies, // we introduce 'groups'. all functions in one are called before those in // the next higher group, but order within the group is undefined. // (this is because the linker sorts sections alphabetically but doesn't // specify the order in which object files are processed.) // these macros register a function to be called at the given time. // usage: invoke at file scope, passing a function identifier/symbol. // rationale: // - __declspec(allocate) requires section declarations, but allows users to // write only one line (instead of needing an additional #pragma data_seg) // - fixed groups instead of passing a group number are more clear and // encourage thinking about init order. (__declspec(allocate) requires // a single string literal anyway and doesn't support string merging) // - why EXTERN_C and __pragma? VC8's link-stage optimizer believes // the static function pointers defined by WINIT_REGISTER_* to be unused; // unless action is taken, they would be removed. to prevent this, we // forcibly include the function pointer symbols. this means the variable // must be extern, not static. the linker needs to know the decorated // symbol name, so we disable mangling via EXTERN_C. // very early init; must not fail, since error handling code *crashes* // if called before these have completed. #define WINIT_REGISTER_CRITICAL_INIT(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$I0")) Status (*p##func)(void) = func // meant for modules with dependents but whose init is complicated and may // raise error/warning messages (=> can't go in WINIT_REGISTER_CRITICAL_INIT) #define WINIT_REGISTER_EARLY_INIT(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$I1")) Status (*p##func)(void) = func // available for dependents of WINIT_REGISTER_EARLY_INIT-modules that // must still come before WINIT_REGISTER_MAIN_INIT. #define WINIT_REGISTER_EARLY_INIT2(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$I2")) Status (*p##func)(void) = func // most modules will go here unless they are often used or // have many dependents. #define WINIT_REGISTER_MAIN_INIT(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$I6")) Status (*p##func)(void) = func // available for any modules that may need to come after // WINIT_REGISTER_MAIN_INIT (unlikely) #define WINIT_REGISTER_LATE_INIT(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$I7")) Status (*p##func)(void) = func #define WINIT_REGISTER_EARLY_SHUTDOWN(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$S0")) Status (*p##func)(void) = func #define WINIT_REGISTER_EARLY_SHUTDOWN2(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$S1")) Status (*p##func)(void) = func #define WINIT_REGISTER_MAIN_SHUTDOWN(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$S6")) Status (*p##func)(void) = func #define WINIT_REGISTER_LATE_SHUTDOWN(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$S7")) Status (*p##func)(void) = func #define WINIT_REGISTER_LATE_SHUTDOWN2(func) __pragma(comment(linker, "/include:" STRINGIZE(DECORATED_NAME(p##func)))) static Status func(); EXTERN_C __declspec(allocate(".WINIT$S8")) Status (*p##func)(void) = func //----------------------------------------------------------------------------- /** * call each registered function. * * if this is called before CRT initialization, callbacks must not use any * non-stateless CRT functions such as atexit. see wstartup.h for the * current status on this issue. **/ extern void winit_CallInitFunctions(); extern void winit_CallShutdownFunctions(); #endif // #ifndef INCLUDED_WINIT Index: ps/trunk/source/lib/sysdep/os/win/wposix/crt_posix.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/crt_posix.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/crt_posix.h (revision 19899) @@ -1,50 +1,50 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /** * see rationale in wposix.h. * we need to have CRT functions (e.g. _open) declared correctly * but must prevent POSIX functions (e.g. open) from being declared - * they would conflict with our wrapper function. * * since the headers only declare POSIX stuff \#if !__STDC__, a solution * would be to set this flag temporarily. note that MSDN says that * such predefined macros cannot be redefined nor may they change during * compilation, but this works and seems to be a fairly common practice. **/ // undefine include guards set by no_crt_posix #if defined(_INC_IO) && defined(WPOSIX_DEFINED_IO_INCLUDE_GUARD) # undef _INC_IO #endif #if defined(_INC_DIRECT) && defined(WPOSIX_DEFINED_DIRECT_INCLUDE_GUARD) # undef _INC_DIRECT #endif #define __STDC__ 1 #include // _open etc. #include // _rmdir #undef __STDC__ #define __STDC__ 0 Index: ps/trunk/source/lib/sysdep/os/win/wposix/wdlfcn.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wdlfcn.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wdlfcn.cpp (revision 19899) @@ -1,80 +1,80 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/os/win/wposix/wdlfcn.h" #include "lib/sysdep/os/win/wposix/wposix_internal.h" static HMODULE HMODULE_from_void(void* handle) { return (HMODULE)handle; } static void* void_from_HMODULE(HMODULE hModule) { return (void*)hModule; } int dlclose(void* handle) { WARN_IF_FALSE(FreeLibrary(HMODULE_from_void(handle))); return 0; } char* dlerror() { // the obvious implementation involves sys_StatusDescription, but we // don't want to return a pointer to a static array because that's not // thread-safe. we therefore check GetLastError directly. switch(GetLastError()) { case ERROR_MOD_NOT_FOUND: return "module not found"; case ERROR_PROC_NOT_FOUND: return "symbol not found"; default: return "unknown"; } } void* dlopen(const char* so_name, int flags) { ENSURE(!(flags & RTLD_GLOBAL)); OsPath pathname = Path(so_name).ChangeExtension(L".dll"); HMODULE hModule = LoadLibraryW(OsString(pathname).c_str()); return void_from_HMODULE(hModule); } void* dlsym(void* handle, const char* sym_name) { HMODULE hModule = HMODULE_from_void(handle); void* sym = GetProcAddress(hModule, sym_name); ENSURE(sym); return sym; } Index: ps/trunk/source/lib/sysdep/os/win/wposix/wfilesystem.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wfilesystem.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wfilesystem.h (revision 19899) @@ -1,64 +1,64 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_WFILESYSTEM #define INCLUDED_WFILESYSTEM // // sys/stat.h // #include // for S_IFREG etc. #if MSC_VERSION typedef unsigned int mode_t; // defined by MinGW but not VC #define stat _stat64 // we need 64-bit st_size and time_t #endif // permission masks when creating files (_wsopen_s doesn't distinguish // between owner/user/group) #define S_IRUSR _S_IREAD #define S_IRGRP _S_IREAD #define S_IROTH _S_IREAD #define S_IWUSR _S_IWRITE #define S_IWGRP _S_IWRITE #define S_IWOTH _S_IWRITE #define S_IXUSR 0 #define S_IXGRP 0 #define S_IXOTH 0 #define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) #define S_IRWXG (S_IRGRP|S_IWGRP|S_IXGRP) #define S_IRWXO (S_IROTH|S_IWOTH|S_IXOTH) #define S_ISDIR(m) (m & S_IFDIR) #define S_ISREG(m) (m & S_IFREG) // // // extern int read (int fd, void* buf, size_t nbytes); // thunk extern int write(int fd, void* buf, size_t nbytes); // thunk extern off_t lseek(int fd, off_t ofs, int whence); // thunk #endif // #ifndef INCLUDED_WFILESYSTEM Index: ps/trunk/source/lib/sysdep/os/win/wposix/wposix.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wposix.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wposix.h (revision 19899) @@ -1,76 +1,76 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * emulate a subset of POSIX on Win32. */ #ifndef INCLUDED_WPOSIX #define INCLUDED_WPOSIX /** * rationale: the Windows headers declare many POSIX functions (e.g. read). * unfortunately, these are often slightly incorrect (size_t vs. size_t). * to avert trouble in user code caused by these differences, we declare * all functions ourselves according to SUSv3 and do not use the headers. * * however, it does not end there. some other libraries (e.g. wxWidgets) * will want to pull in these headers, which would conflict with our * declarations. also, our implementation uses the actual CRT code, * so we want those functions (e.g. _read) to be declared correctly even * if switching compiler/CRT version. * * how can these conflicting requirements be reconciled? our headers \#include * "no_crt_posix.h" to \#define the CRT headers' include guards and thus * prevent them from declaring anything. the implementation files \#include * "crt_posix.h", which pulls in the CRT headers (even if "no_crt_posix.h" * was previously included, e.g. in the PCH). note that the CRT headers * would still cause conflicts with the POSIX function declarations, * but we are able to prevent this via __STDC__. **/ // // // LIB_API int setenv(const char* envname, const char* envval, int overwrite); // // // // (missing on MSVC) #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 #endif #ifndef INFINITY #define INFINITY (std::numeric_limits::infinity()) #endif #ifndef NAN #define NAN (std::numeric_limits::quiet_NaN()) #endif #endif // #ifndef INCLUDED_WPOSIX Index: ps/trunk/source/lib/sysdep/os/win/wposix/wpthread.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wpthread.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wpthread.h (revision 19899) @@ -1,135 +1,135 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * emulate pthreads on Windows. */ #ifndef INCLUDED_WPTHREAD #define INCLUDED_WPTHREAD // // // struct sched_param { int sched_priority; }; enum { SCHED_RR, SCHED_FIFO, SCHED_OTHER }; // changing will break pthread_setschedparam: #define sched_get_priority_max(policy) +2 #define sched_get_priority_min(policy) -2 // // // // one-time init typedef intptr_t pthread_once_t; // required for cpu_CAS #define PTHREAD_ONCE_INIT 0 // static pthread_once_t x = PTHREAD_ONCE_INIT; LIB_API int pthread_once(pthread_once_t*, void (*init_routine)()); // thread typedef uintptr_t pthread_t; LIB_API int pthread_equal(pthread_t t1, pthread_t t2); LIB_API pthread_t pthread_self(); LIB_API int pthread_getschedparam(pthread_t thread, int* policy, struct sched_param* param); LIB_API int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param* param); LIB_API int pthread_create(pthread_t* thread, const void* attr, void* (*func)(void*), void* arg); LIB_API int pthread_cancel(pthread_t thread); LIB_API int pthread_join(pthread_t thread, void** value_ptr); // mutex typedef void* pthread_mutexattr_t; LIB_API int pthread_mutexattr_init(pthread_mutexattr_t* attr); LIB_API int pthread_mutexattr_destroy(pthread_mutexattr_t* attr); enum { PTHREAD_MUTEX_RECURSIVE }; // the only one we support LIB_API int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type); LIB_API int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type); typedef void* pthread_mutex_t; // pointer to critical section LIB_API pthread_mutex_t pthread_mutex_initializer(); #define PTHREAD_MUTEX_INITIALIZER pthread_mutex_initializer() LIB_API int pthread_mutex_init(pthread_mutex_t*, const pthread_mutexattr_t*); LIB_API int pthread_mutex_destroy(pthread_mutex_t*); LIB_API int pthread_mutex_lock(pthread_mutex_t*); LIB_API int pthread_mutex_trylock(pthread_mutex_t*); LIB_API int pthread_mutex_unlock(pthread_mutex_t*); LIB_API int pthread_mutex_timedlock(pthread_mutex_t*, const struct timespec*); // thread-local storage typedef unsigned int pthread_key_t; LIB_API int pthread_key_create(pthread_key_t*, void (*dtor)(void*)); LIB_API int pthread_key_delete(pthread_key_t); LIB_API void* pthread_getspecific(pthread_key_t); LIB_API int pthread_setspecific(pthread_key_t, const void* value); // // // typedef uintptr_t sem_t; #define SEM_FAILED 0 LIB_API sem_t* sem_open(const char* name, int oflag, ...); LIB_API int sem_close(sem_t* sem); LIB_API int sem_unlink(const char* name); LIB_API int sem_init(sem_t*, int pshared, unsigned value); LIB_API int sem_destroy(sem_t*); LIB_API int sem_post(sem_t*); LIB_API int sem_wait(sem_t*); LIB_API int sem_timedwait(sem_t*, const struct timespec*); // wait until semaphore is locked or a message arrives. non-portable. // // background: on Win32, UI threads must periodically pump messages, or // else deadlock may result (see WaitForSingleObject docs). that entails // avoiding any blocking functions. when event waiting is needed, // one cheap workaround would be to time out periodically and pump messages. // that would work, but either wastes CPU time waiting, or introduces // message latency. to avoid this, we provide an API similar to sem_wait and // sem_timedwait that gives MsgWaitForMultipleObjects functionality. // // return value: 0 if the semaphore has been locked (SUS terminology), // -1 otherwise. errno differentiates what happened: ETIMEDOUT if a // message arrived (this is to ease switching between message waiting and // periodic timeout), or an error indication. LIB_API int sem_msgwait_np(sem_t* sem); #endif // #ifndef INCLUDED_WPTHREAD Index: ps/trunk/source/lib/sysdep/os/win/wposix/wutsname.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wutsname.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wutsname.cpp (revision 19899) @@ -1,65 +1,65 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/os/win/wposix/wutsname.h" #include "lib/sysdep/os/win/wutil.h" // WinScopedPreserveLastError #include "lib/sysdep/os/win/wversion.h" // wversion_Family int uname(struct utsname* un) { OSVERSIONINFOW vi = { sizeof(OSVERSIONINFOW) }; GetVersionExW(&vi); // OS implementation name sprintf_s(un->sysname, ARRAY_SIZE(un->sysname), "%ls", wversion_Family()); // release info const wchar_t* vs = vi.szCSDVersion; int sp; if(swscanf_s(vs, L"Service Pack %d", &sp) == 1) sprintf_s(un->release, ARRAY_SIZE(un->release), "SP %d", sp); else un->release[0] = '\0'; // version sprintf_s(un->version, ARRAY_SIZE(un->version), "%ls.%lu", wversion_String(), vi.dwBuildNumber & 0xFFFF); // node name { WinScopedPreserveLastError s; // GetComputerName DWORD bufSize = sizeof(un->nodename); WARN_IF_FALSE(GetComputerNameA(un->nodename, &bufSize)); } // hardware type SYSTEM_INFO si; GetSystemInfo(&si); if(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) strcpy_s(un->machine, ARRAY_SIZE(un->machine), "x64"); else strcpy_s(un->machine, ARRAY_SIZE(un->machine), "x86"); return 0; } Index: ps/trunk/source/lib/sysdep/os/win/wseh.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wseh.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wseh.h (revision 19899) @@ -1,35 +1,35 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Win32 debug support code and exception handler. */ #ifndef INCLUDED_WSEH #define INCLUDED_WSEH struct _EXCEPTION_POINTERS; extern long __stdcall wseh_ExceptionFilter(_EXCEPTION_POINTERS* ep); EXTERN_C int wseh_EntryPoint(); #endif // #ifndef INCLUDED_WSEH Index: ps/trunk/source/lib/sysdep/os/win/winit.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/winit.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/winit.cpp (revision 19899) @@ -1,95 +1,95 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * windows-specific module init and shutdown mechanism */ #include "precompiled.h" #include "lib/sysdep/os/win/winit.h" #include "lib/sysdep/os/win/win.h" // GetTickCount for quick'n dirty timing // see http://blogs.msdn.com/larryosterman/archive/2004/09/27/234840.aspx // for discussion of a similar mechanism. // // note: this module is kept distinct from the CRT's init/shutdown mechanism // to insulate against changes there. another advantage is that callbacks // can return Status instead of int. // currently (2008-02-17) the init groups are populated as follows: // critical : wposix // early : wutil // early2 : whrt, wdbg_heap // main : waio, wsock, wtime, wdir_watch // late : wsdl typedef Status (*PfnLibError)(); // pointers to start and end of function tables. // notes: // - COFF tosses out empty segments, so we have to put in one value // (zero, because CallFunctionPointers has to ignore entries =0 anyway). // - ASCII '$' and 'Z' come before resp. after '0'..'9', so use that to // bound the section names. __declspec(allocate(".WINIT$I$")) PfnLibError initBegin = 0; __declspec(allocate(".WINIT$IZ")) PfnLibError initEnd = 0; __declspec(allocate(".WINIT$S$")) PfnLibError shutdownBegin = 0; __declspec(allocate(".WINIT$SZ")) PfnLibError shutdownEnd = 0; // note: #pragma comment(linker, "/include") is not necessary since // these are referenced below. /** * call into a range of function pointers. * @param [begin, end): STL-style range * * note: pointers = 0 are ignored. this is because the above placeholders * are initialized to 0 and because the range may be larger than * expected due to COFF section padding (with zeroes). **/ static void CallFunctionPointers(PfnLibError* begin, PfnLibError* end) { const DWORD t0 = GetTickCount(); for(PfnLibError* ppfunc = begin; ppfunc < end; ppfunc++) { if(*ppfunc) { (*ppfunc)(); } } const DWORD t1 = GetTickCount(); debug_printf("WINIT| total elapsed time in callbacks %d ms (+-10)\n", t1-t0); } void winit_CallInitFunctions() { CallFunctionPointers(&initBegin, &initEnd); } void winit_CallShutdownFunctions() { CallFunctionPointers(&shutdownBegin, &shutdownEnd); } Index: ps/trunk/source/lib/sysdep/os/win/wnuma.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wnuma.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wnuma.cpp (revision 19899) @@ -1,515 +1,515 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/numa.h" #include "lib/bits.h" // PopulationCount #include "lib/alignment.h" #include "lib/timer.h" #include "lib/module_init.h" #include "lib/sysdep/vm.h" #include "lib/sysdep/acpi.h" #include "lib/sysdep/os_cpu.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/wcpu.h" #include #if ARCH_X86_X64 #include "lib/sysdep/arch/x86_x64/apic.h" // ProcessorFromApicId #endif //----------------------------------------------------------------------------- // nodes struct Node // POD { // (Windows doesn't guarantee node numbers are contiguous, so // we associate them with contiguous indices in nodes[]) UCHAR nodeNumber; u32 proximityDomainNumber; uintptr_t processorMask; }; static Node nodes[os_cpu_MaxProcessors]; static size_t numNodes; static Node* AddNode() { ENSURE(numNodes < ARRAY_SIZE(nodes)); return &nodes[numNodes++]; } static Node* FindNodeWithProcessorMask(uintptr_t processorMask) { for(size_t node = 0; node < numNodes; node++) { if(nodes[node].processorMask == processorMask) return &nodes[node]; } return 0; } static Node* FindNodeWithProcessor(size_t processor) { for(size_t node = 0; node < numNodes; node++) { if(IsBitSet(nodes[node].processorMask, processor)) return &nodes[node]; } return 0; } //----------------------------------------------------------------------------- // Windows topology static UCHAR HighestNodeNumber() { WUTIL_FUNC(pGetNumaHighestNodeNumber, BOOL, (PULONG)); WUTIL_IMPORT_KERNEL32(GetNumaHighestNodeNumber, pGetNumaHighestNodeNumber); if(!pGetNumaHighestNodeNumber) return 0; // NUMA not supported => only one node ULONG highestNodeNumber; const BOOL ok = pGetNumaHighestNodeNumber(&highestNodeNumber); WARN_IF_FALSE(ok); return (UCHAR)highestNodeNumber; } static void PopulateNodes() { WUTIL_FUNC(pGetNumaNodeProcessorMask, BOOL, (UCHAR, PULONGLONG)); WUTIL_IMPORT_KERNEL32(GetNumaNodeProcessorMask, pGetNumaNodeProcessorMask); if(!pGetNumaNodeProcessorMask) return; DWORD_PTR processAffinity, systemAffinity; { const BOOL ok = GetProcessAffinityMask(GetCurrentProcess(), &processAffinity, &systemAffinity); WARN_IF_FALSE(ok); } ENSURE(PopulationCount(processAffinity) <= PopulationCount(systemAffinity)); for(UCHAR nodeNumber = 0; nodeNumber <= HighestNodeNumber(); nodeNumber++) { ULONGLONG affinity; { const BOOL ok = pGetNumaNodeProcessorMask(nodeNumber, &affinity); WARN_IF_FALSE(ok); } if(!affinity) continue; // empty node, skip Node* node = AddNode(); node->nodeNumber = nodeNumber; node->processorMask = wcpu_ProcessorMaskFromAffinity(processAffinity, (DWORD_PTR)affinity); } } //----------------------------------------------------------------------------- // ACPI SRAT topology #if ARCH_X86_X64 #pragma pack(push, 1) // fields common to Affinity* structures struct AffinityHeader { u8 type; u8 length; // size [bytes], including this header }; struct AffinityAPIC { static const u8 type = 0; AffinityHeader header; u8 proximityDomainNumber0; u8 apicId; u32 flags; u8 sapicId; u8 proximityDomainNumber123[3]; u32 clockDomain; u32 ProximityDomainNumber() const { // (this is the apparent result of backwards compatibility, ugh.) u32 proximityDomainNumber; memcpy(&proximityDomainNumber, &proximityDomainNumber123[0]-1, sizeof(proximityDomainNumber)); proximityDomainNumber &= ~0xFF; proximityDomainNumber |= proximityDomainNumber0; return proximityDomainNumber; } }; struct AffinityMemory { static const u8 type = 1; AffinityHeader header; u32 proximityDomainNumber; u16 reserved1; u64 baseAddress; u64 length; u32 reserved2; u32 flags; u64 reserved3; }; // AffinityX2APIC omitted, since the APIC ID is sufficient for our purposes // Static Resource Affinity Table struct SRAT { AcpiTable header; u32 reserved1; u8 reserved2[8]; AffinityHeader affinities[1]; }; #pragma pack(pop) template static const Affinity* DynamicCastFromHeader(const AffinityHeader* header) { if(header->type != Affinity::type) return 0; // sanity check: ensure no padding was inserted ENSURE(header->length == sizeof(Affinity)); const Affinity* affinity = (const Affinity*)header; if(!IsBitSet(affinity->flags, 0)) // not enabled return 0; return affinity; } struct ProximityDomain { uintptr_t processorMask; // (AffinityMemory's fields are not currently needed) }; typedef std::map ProximityDomains; static ProximityDomains ExtractProximityDomainsFromSRAT(const SRAT* srat) { ProximityDomains proximityDomains; for(const AffinityHeader* header = srat->affinities; header < (const AffinityHeader*)(uintptr_t(srat)+srat->header.size); header = (const AffinityHeader*)(uintptr_t(header) + header->length)) { const AffinityAPIC* affinityAPIC = DynamicCastFromHeader(header); if(affinityAPIC) { const size_t processor = ProcessorFromApicId(affinityAPIC->apicId); const u32 proximityDomainNumber = affinityAPIC->ProximityDomainNumber(); ProximityDomain& proximityDomain = proximityDomains[proximityDomainNumber]; proximityDomain.processorMask |= Bit(processor); } } return proximityDomains; } static void PopulateNodesFromProximityDomains(const ProximityDomains& proximityDomains) { for(ProximityDomains::const_iterator it = proximityDomains.begin(); it != proximityDomains.end(); ++it) { const u32 proximityDomainNumber = it->first; const ProximityDomain& proximityDomain = it->second; Node* node = FindNodeWithProcessorMask(proximityDomain.processorMask); if(!node) node = AddNode(); // (we don't know Windows' nodeNumber; it has hopefully already been set) node->proximityDomainNumber = proximityDomainNumber; node->processorMask = proximityDomain.processorMask; } } #endif // #if ARCH_X86_X64 //----------------------------------------------------------------------------- static ModuleInitState initState; static Status InitTopology() { PopulateNodes(); #if ARCH_X86_X64 const SRAT* srat = (const SRAT*)acpi_GetTable("SRAT"); if(srat && AreApicIdsReliable()) { const ProximityDomains proximityDomains = ExtractProximityDomainsFromSRAT(srat); PopulateNodesFromProximityDomains(proximityDomains); } #endif // neither OS nor ACPI information is available if(numNodes == 0) { // add dummy node that contains all system processors Node* node = AddNode(); node->nodeNumber = 0; node->proximityDomainNumber = 0; node->processorMask = os_cpu_ProcessorMask(); } return INFO::OK; } size_t numa_NumNodes() { (void)ModuleInit(&initState, InitTopology); return numNodes; } size_t numa_NodeFromProcessor(size_t processor) { (void)ModuleInit(&initState, InitTopology); ENSURE(processor < os_cpu_NumProcessors()); Node* node = FindNodeWithProcessor(processor); ENSURE(node); return nodes-node; } uintptr_t numa_ProcessorMaskFromNode(size_t node) { (void)ModuleInit(&initState, InitTopology); ENSURE(node < numNodes); return nodes[node].processorMask; } static UCHAR NodeNumberFromNode(size_t node) { (void)ModuleInit(&initState, InitTopology); ENSURE(node < numa_NumNodes()); return nodes[node].nodeNumber; } //----------------------------------------------------------------------------- // memory info size_t numa_AvailableMemory(size_t node) { // note: it is said that GetNumaAvailableMemoryNode sometimes incorrectly // reports zero bytes. the actual cause may however be unexpected // RAM configuration, e.g. not all slots filled. WUTIL_FUNC(pGetNumaAvailableMemoryNode, BOOL, (UCHAR, PULONGLONG)); WUTIL_IMPORT_KERNEL32(GetNumaAvailableMemoryNode, pGetNumaAvailableMemoryNode); if(pGetNumaAvailableMemoryNode) { const UCHAR nodeNumber = NodeNumberFromNode(node); ULONGLONG availableBytes; const BOOL ok = pGetNumaAvailableMemoryNode(nodeNumber, &availableBytes); WARN_IF_FALSE(ok); const size_t availableMiB = size_t(availableBytes / MiB); return availableMiB; } // NUMA not supported - return available system memory else return os_cpu_MemoryAvailable(); } #pragma pack(push, 1) // ACPI System Locality Information Table // (System Locality == Proximity Domain) struct SLIT { AcpiTable header; u64 numSystemLocalities; u8 entries[1]; // numSystemLocalities*numSystemLocalities entries }; #pragma pack(pop) static double ReadRelativeDistanceFromSLIT(const SLIT* slit) { const size_t n = slit->numSystemLocalities; ENSURE(slit->header.size == sizeof(SLIT)-sizeof(slit->entries)+n*n); // diagonals are specified to be 10 for(size_t i = 0; i < n; i++) ENSURE(slit->entries[i*n+i] == 10); // entries = relativeDistance * 10 return *std::max_element(slit->entries, slit->entries+n*n) / 10.0; } // @return ratio between max/min time required to access one node's // memory from each processor. static double MeasureRelativeDistance() { const size_t size = 32*MiB; void* mem = vm::Allocate(size); ASSUME_ALIGNED(mem, pageSize); const uintptr_t previousProcessorMask = os_cpu_SetThreadAffinityMask(os_cpu_ProcessorMask()); double minTime = 1e10, maxTime = 0.0; for(size_t node = 0; node < numa_NumNodes(); node++) { const uintptr_t processorMask = numa_ProcessorMaskFromNode(node); os_cpu_SetThreadAffinityMask(processorMask); const double startTime = timer_Time(); memset(mem, 0, size); const double elapsedTime = timer_Time() - startTime; minTime = std::min(minTime, elapsedTime); maxTime = std::max(maxTime, elapsedTime); } (void)os_cpu_SetThreadAffinityMask(previousProcessorMask); vm::Free(mem, size); return maxTime / minTime; } static double relativeDistance; static Status InitRelativeDistance() { // early-out for non-NUMA systems (saves some time) if(numa_NumNodes() == 1) { relativeDistance = 1.0; return INFO::OK; } // trust values reported by the BIOS, if available const SLIT* slit = (const SLIT*)acpi_GetTable("SLIT"); if(slit) relativeDistance = ReadRelativeDistanceFromSLIT(slit); else relativeDistance = MeasureRelativeDistance(); ENSURE(relativeDistance >= 1.0); ENSURE(relativeDistance <= 4.0); return INFO::OK; } double numa_Factor() { static ModuleInitState initState; (void)ModuleInit(&initState, InitRelativeDistance); return relativeDistance; } static bool IsMemoryInterleaved() { if(numa_NumNodes() == 1) return false; if(!acpi_GetTable("FACP")) // no ACPI tables available return false; // indeterminate, assume not interleaved if(acpi_GetTable("SRAT")) // present iff not interleaved return false; return true; } static bool isMemoryInterleaved; static Status InitMemoryInterleaved() { isMemoryInterleaved = IsMemoryInterleaved(); return INFO::OK; } bool numa_IsMemoryInterleaved() { static ModuleInitState initState; (void)ModuleInit(&initState, InitMemoryInterleaved); return isMemoryInterleaved; } //----------------------------------------------------------------------------- #if 0 static bool VerifyPages(void* mem, size_t size, size_t pageSize, size_t node) { WUTIL_FUNC(pQueryWorkingSetEx, BOOL, (HANDLE, PVOID, DWORD)); WUTIL_IMPORT_KERNEL32(QueryWorkingSetEx, pQueryWorkingSetEx); if(!pQueryWorkingSetEx) return true; // can't do anything #if WINVER >= 0x600 size_t largePageSize = os_cpu_LargePageSize(); ENSURE(largePageSize != 0); // this value is needed for later // retrieve attributes of all pages constituting mem const size_t numPages = (size + pageSize-1) / pageSize; PSAPI_WORKING_SET_EX_INFORMATION* wsi = new PSAPI_WORKING_SET_EX_INFORMATION[numPages]; for(size_t i = 0; i < numPages; i++) wsi[i].VirtualAddress = (u8*)mem + i*pageSize; pQueryWorkingSetEx(GetCurrentProcess(), wsi, DWORD(sizeof(PSAPI_WORKING_SET_EX_INFORMATION)*numPages)); // ensure each is valid and allocated on the correct node for(size_t i = 0; i < numPages; i++) { const PSAPI_WORKING_SET_EX_BLOCK& attributes = wsi[i].VirtualAttributes; if(!attributes.Valid) return false; if((attributes.LargePage != 0) != (pageSize == largePageSize)) { debug_printf("NUMA: is not a large page\n"); return false; } if(attributes.Node != node) { debug_printf("NUMA: allocated from remote node\n"); return false; } } delete[] wsi; #else UNUSED2(mem); UNUSED2(size); UNUSED2(pageSize); UNUSED2(node); #endif return true; } #endif Index: ps/trunk/source/lib/sysdep/os/win/wposix/waio.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/waio.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/waio.h (revision 19899) @@ -1,143 +1,143 @@ -/* Copyright (c) 2011 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 * "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. */ /* * emulate POSIX asynchronous I/O on Windows. */ #ifndef INCLUDED_WAIO #define INCLUDED_WAIO #include "lib/status.h" #include "lib/os_path.h" #include "lib/posix/posix_time.h" // timespec #include "lib/sysdep/os/win/wposix/wposix_types.h" // Note: transfer buffers, offsets, and lengths must be sector-aligned // (we don't bother copying to an align buffer because our block cache // already requires splitting IOs into naturally-aligned blocks) // // // union sigval // unused { int sival_int; // Integer signal value. void* sival_ptr; // Pointer signal value. }; struct sigevent // unused { int sigev_notify; // notification mode int sigev_signo; // signal number union sigval sigev_value; // signal value void (*sigev_notify_function)(union sigval); }; // // // struct aiocb { int aio_fildes; // File descriptor. off_t aio_offset; // File offset. volatile void* aio_buf; // Location of buffer. size_t aio_nbytes; // Length of transfer. int aio_reqprio; // Request priority offset. (unused) struct sigevent aio_sigevent; // Signal number and value. (unused) int aio_lio_opcode; // Operation to be performed. // internal use only; must be zero-initialized before // calling the first aio_read/aio_write/lio_listio // (aio_return resets it to 0) void* ovl; }; enum { // aio_cancel return AIO_ALLDONE, // None of the requested operations could be canceled since they are already complete. AIO_CANCELED, // All requested operations have been canceled. AIO_NOTCANCELED, // Some of the requested operations could not be canceled since they are in progress. // lio_listio mode LIO_WAIT, // wait until all I/O is complete LIO_NOWAIT, // lio_listio ops LIO_NOP, LIO_READ, LIO_WRITE }; extern int aio_read(struct aiocb*); extern int aio_write(struct aiocb*); extern int lio_listio(int, struct aiocb* const[], int, struct sigevent*); // (if never called, IOCP notifications will pile up.) extern int aio_suspend(const struct aiocb* const[], int, const struct timespec*); // @return status of transfer (0 or an errno) extern int aio_error(const struct aiocb*); // @return bytes transferred or -1 on error. // frees internal storage related to the request and MUST be called // exactly once for each aiocb after aio_error != EINPROGRESS. extern ssize_t aio_return(struct aiocb*); extern int aio_cancel(int, struct aiocb*); extern int aio_fsync(int, struct aiocb*); // open the file for aio (not possible via _wsopen_s since it cannot // set FILE_FLAG_NO_BUFFERING). // // @return the smallest available file descriptor. NB: these numbers // are not 0-based to avoid confusion with lowio descriptors and // must only be used with waio functions. extern Status waio_open(const OsPath& pathname, int oflag, ...); extern Status waio_close(int fd); // call this before writing a large file to preallocate clusters, thus // reducing fragmentation. // // @param fd file descriptor from _wsopen_s OR waio_open // @param size is rounded up to a multiple of maxSectorSize (required by // SetEndOfFile; this could be avoided by using the undocumented // NtSetInformationFile or SetFileInformationByHandle on Vista and later). // use wtruncate after I/O is complete to chop off the excess padding. // // NB: writes that extend a file (i.e. ALL WRITES when creating new files) // are synchronous, which prevents overlapping I/O and other work. // (http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B156932) // if Windows XP and the SE_MANAGE_VOLUME_NAME privileges are available, // this function sets the valid data length to avoid the synchronous zero-fill. // to avoid exposing the previous disk contents until the application // successfully writes to the file, deny sharing when opening the file. LIB_API Status waio_Preallocate(int fd, off_t size); #endif // #ifndef INCLUDED_WAIO Index: ps/trunk/source/lib/sysdep/os/win/wposix/wfilesystem.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wfilesystem.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wfilesystem.cpp (revision 19899) @@ -1,355 +1,355 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/filesystem.h" #include "lib/sysdep/cpu.h" // cpu_CAS #include "lib/sysdep/os/win/wutil.h" // StatusFromWin #include "lib/sysdep/os/win/wposix/waio.h" // waio_reopen #include "lib/sysdep/os/win/wposix/wtime_internal.h" // wtime_utc_filetime_to_time_t #include "lib/sysdep/os/win/wposix/crt_posix.h" // _close, _lseeki64 etc. //----------------------------------------------------------------------------- // WDIR suballocator //----------------------------------------------------------------------------- // most applications only need a single WDIR at a time. we avoid expensive // heap allocations by reusing a single static instance. if it is already // in use, we allocate further instances dynamically. // NB: this is thread-safe due to CAS. struct WDIR // POD { HANDLE hFind; WIN32_FIND_DATAW findData; // indeterminate if hFind == INVALID_HANDLE_VALUE // wreaddir will return the address of this member. // (must be stored in WDIR to allow multiple independent // wopendir/wreaddir sequences). struct wdirent ent; // used by wreaddir to skip the first FindNextFileW. (a counter is // easy to test/update and also provides useful information.) size_t numCalls; }; static WDIR wdir_storage; static volatile intptr_t wdir_in_use; static inline WDIR* wdir_alloc() { if(cpu_CAS(&wdir_in_use, 0, 1)) // gained ownership return &wdir_storage; // already in use (rare) - allocate from heap return new WDIR; } static inline void wdir_free(WDIR* d) { if(d == &wdir_storage) { const bool ok = cpu_CAS(&wdir_in_use, 1, 0); // relinquish ownership ENSURE(ok); // ensure it wasn't double-freed } else // allocated from heap delete d; } //----------------------------------------------------------------------------- // dirent.h //----------------------------------------------------------------------------- static bool IsValidDirectory(const OsPath& path) { const DWORD fileAttributes = GetFileAttributesW(OsString(path).c_str()); // path not found if(fileAttributes == INVALID_FILE_ATTRIBUTES) return false; // not a directory if((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) return false; // NB: no longer reject hidden or system attributes since // wsnd's add_oal_dlls_in_dir opens the Windows system directory, // which sometimes has these attributes set. return true; } WDIR* wopendir(const OsPath& path) { WinScopedPreserveLastError s; if(!IsValidDirectory(path)) { errno = ENOENT; return 0; } WDIR* d = wdir_alloc(); d->numCalls = 0; // NB: "c:\\path" only returns information about that directory; // trailing slashes aren't allowed. append "\\*" to retrieve its entries. OsPath searchPath = path/"*"; // (we don't defer FindFirstFileW until wreaddir because callers // expect us to return 0 if directory reading will/did fail.) d->hFind = FindFirstFileW(OsString(searchPath).c_str(), &d->findData); if(d->hFind != INVALID_HANDLE_VALUE) return d; // success if(GetLastError() == ERROR_NO_MORE_FILES) return d; // success, but directory is empty Status status = StatusFromWin(); // release the WDIR allocated above (this is preferable to // always copying the large WDIR or findData from a temporary) wdir_free(d); WARN_IF_ERR(status); errno = ErrnoFromStatus(status); return 0; } struct wdirent* wreaddir(WDIR* d) { // directory is empty and d->findData is indeterminate if(d->hFind == INVALID_HANDLE_VALUE) return 0; WinScopedPreserveLastError s; // until end of directory or a valid entry was found: for(;;) { if(d->numCalls++ != 0) // (skip first call to FindNextFileW - see wopendir) { if(!FindNextFileW(d->hFind, &d->findData)) { if(GetLastError() == ERROR_NO_MORE_FILES) SetLastError(0); else // unexpected error DEBUG_WARN_ERR(StatusFromWin()); return 0; // end of directory or error } } // only accept non-hidden and non-system entries - otherwise, // callers might encounter errors when attempting to open them. if((d->findData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) == 0) { d->ent.d_name = d->findData.cFileName; // (NB: d_name is a pointer) return &d->ent; } } } int wreaddir_stat_np(WDIR* d, struct stat* s) { // NTFS stores UTC but FAT stores local times, which are incorrectly // translated to UTC based on the _current_ DST settings. we no longer // bother checking the filesystem, since that's either unreliable or // expensive. timestamps may therefore be off after a DST transition, // which means our cached files would be regenerated. FILETIME* filetime = &d->findData.ftLastWriteTime; memset(s, 0, sizeof(*s)); s->st_size = (off_t)u64_from_u32(d->findData.nFileSizeHigh, d->findData.nFileSizeLow); s->st_mode = (unsigned short)((d->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)? S_IFDIR : S_IFREG); s->st_mtime = wtime_utc_filetime_to_time_t(filetime); return 0; } int wclosedir(WDIR* d) { FindClose(d->hFind); wdir_free(d); return 0; } //----------------------------------------------------------------------------- // fcntl.h //----------------------------------------------------------------------------- int wopen(const OsPath& pathname, int oflag) { ENSURE(!(oflag & O_CREAT)); // must specify mode_arg if O_CREAT return wopen(OsString(pathname).c_str(), oflag, _S_IREAD|_S_IWRITE); } int wopen(const OsPath& pathname, int oflag, mode_t mode) { if(oflag & O_DIRECT) { Status ret = waio_open(pathname, oflag); if(ret < 0) { errno = ErrnoFromStatus(ret); return -1; } return (int)ret; // file descriptor } else { WinScopedPreserveLastError s; // _wsopen_s's CreateFileW int fd; oflag |= _O_BINARY; if(oflag & O_WRONLY) oflag |= O_CREAT|O_TRUNC; // NB: _wsopen_s ignores mode unless oflag & O_CREAT errno_t ret = _wsopen_s(&fd, OsString(pathname).c_str(), oflag, _SH_DENYRD, mode); if(ret != 0) { errno = ret; return -1; // NOWARN } return fd; } } int wclose(int fd) { ENSURE(fd >= 3); // not invalid nor stdin/out/err if(waio_close(fd) != 0) return _close(fd); return 0; } //----------------------------------------------------------------------------- // unistd.h //----------------------------------------------------------------------------- // we don't want to #define read to _read, since that's a fairly common // identifier. therefore, translate from MS CRT names via thunk functions. // efficiency is less important, and the overhead could be optimized away. int read(int fd, void* buf, size_t nbytes) { return _read(fd, buf, (int)nbytes); } int write(int fd, void* buf, size_t nbytes) { return _write(fd, buf, (int)nbytes); } off_t lseek(int fd, off_t ofs, int whence) { return _lseeki64(fd, ofs, whence); } int wtruncate(const OsPath& pathname, off_t length) { // (re-open the file to avoid the FILE_FLAG_NO_BUFFERING // sector-alignment restriction) HANDLE hFile = CreateFileW(OsString(pathname).c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); ENSURE(hFile != INVALID_HANDLE_VALUE); LARGE_INTEGER ofs; ofs.QuadPart = length; WARN_IF_FALSE(SetFilePointerEx(hFile, ofs, 0, FILE_BEGIN)); WARN_IF_FALSE(SetEndOfFile(hFile)); WARN_IF_FALSE(CloseHandle(hFile)); return 0; } int wunlink(const OsPath& pathname) { return _wunlink(OsString(pathname).c_str()); } int wrmdir(const OsPath& path) { return _wrmdir(OsString(path).c_str()); } int wrename(const OsPath& pathnameOld, const OsPath& pathnameNew) { return _wrename(OsString(pathnameOld).c_str(), OsString(pathnameNew).c_str()); } OsPath wrealpath(const OsPath& pathname) { wchar_t resolved[PATH_MAX]; if(!GetFullPathNameW(OsString(pathname).c_str(), PATH_MAX, resolved, 0)) return OsPath(); return resolved; } static int ErrnoFromCreateDirectory() { switch(GetLastError()) { case ERROR_ALREADY_EXISTS: return EEXIST; case ERROR_PATH_NOT_FOUND: return ENOENT; case ERROR_ACCESS_DENIED: return EACCES; case ERROR_WRITE_PROTECT: return EROFS; case ERROR_DIRECTORY: return ENOTDIR; default: return 0; } } int wmkdir(const OsPath& path, mode_t UNUSED(mode)) { if(!CreateDirectoryW(OsString(path).c_str(), (LPSECURITY_ATTRIBUTES)NULL)) { errno = ErrnoFromCreateDirectory(); return -1; } return 0; } int wstat(const OsPath& pathname, struct stat* buf) { return _wstat64(OsString(pathname).c_str(), buf); } Index: ps/trunk/source/lib/sysdep/os/win/wposix/wposix.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wposix.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wposix.cpp (revision 19899) @@ -1,46 +1,46 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * emulate a subset of POSIX on Win32. */ #include "precompiled.h" #include "lib/sysdep/os/win/wposix/wposix.h" #include "lib/sysdep/os/win/wposix/wposix_internal.h" #include "lib/bits.h" int setenv(const char* envname, const char* envval, int overwrite) { if(!envname || envname[0] == '\0' || strchr(envname, '=')) { errno = EINVAL; return -1; } if(overwrite || !getenv(envname)) SetEnvironmentVariableA(envname, envval); return 0; } Index: ps/trunk/source/lib/sysdep/os/win/wposix/wpthread.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wpthread.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wpthread.cpp (revision 19899) @@ -1,706 +1,706 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * emulate pthreads on Windows. */ #include "precompiled.h" #include "lib/sysdep/os/win/wposix/wpthread.h" #include #include #include "lib/sysdep/cpu.h" // cpu_CAS #include "lib/posix/posix_filesystem.h" // O_CREAT #include "lib/sysdep/os/win/wposix/wposix_internal.h" #include "lib/sysdep/os/win/wposix/wtime.h" // timespec #include "lib/sysdep/os/win/wseh.h" // wseh_ExceptionFilter #include "lib/sysdep/os/win/winit.h" WINIT_REGISTER_CRITICAL_INIT(wpthread_Init); static HANDLE HANDLE_from_pthread(pthread_t p) { return (HANDLE)((char*)0 + p); } static pthread_t pthread_from_HANDLE(HANDLE h) { return (pthread_t)(uintptr_t)h; } //----------------------------------------------------------------------------- // misc //----------------------------------------------------------------------------- // non-pseudo handle so that pthread_self value is unique for each thread static __declspec(thread) HANDLE hCurrentThread; static void NotifyCurrentThread() { // (we leave it to the OS to clean these up at process exit - threads are not created often) WARN_IF_FALSE(DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hCurrentThread, 0, FALSE, DUPLICATE_SAME_ACCESS)); } int pthread_equal(pthread_t t1, pthread_t t2) { return t1 == t2; } pthread_t pthread_self() { return pthread_from_HANDLE(hCurrentThread); } int pthread_once(pthread_once_t* once, void (*init_routine)()) { if(cpu_CAS((volatile intptr_t*)once, 0, 1)) init_routine(); return 0; } int pthread_getschedparam(pthread_t thread, int* policy, struct sched_param* param) { if(policy) { DWORD pc = GetPriorityClass(GetCurrentProcess()); *policy = (pc >= HIGH_PRIORITY_CLASS)? SCHED_FIFO : SCHED_RR; } if(param) { const HANDLE hThread = HANDLE_from_pthread(thread); param->sched_priority = GetThreadPriority(hThread); } return 0; } int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param* param) { const int pri = param->sched_priority; // additional boost for policy == SCHED_FIFO DWORD pri_class = NORMAL_PRIORITY_CLASS; if(policy == SCHED_FIFO) { pri_class = HIGH_PRIORITY_CLASS; if(pri == 2) pri_class = REALTIME_PRIORITY_CLASS; } SetPriorityClass(GetCurrentProcess(), pri_class); // choose fixed Windows values from pri const HANDLE hThread = HANDLE_from_pthread(thread); SetThreadPriority(hThread, pri); return 0; } //----------------------------------------------------------------------------- // thread-local storage //----------------------------------------------------------------------------- // minimum amount of TLS slots every Windows version provides; // used to validate indices. static const size_t TLS_LIMIT = 64; // rationale: don't use an array of dtors for every possible TLS slot. // other DLLs may allocate any number of them in their DllMain, so the // array would have to be quite large. instead, store both key and dtor - // we are thus limited only by pthread_key_create calls (which we control). static const size_t MAX_DTORS = 4; static struct { pthread_key_t key; void (*dtor)(void*); } dtors[MAX_DTORS]; int pthread_key_create(pthread_key_t* key, void (*dtor)(void*)) { DWORD idx = TlsAlloc(); if(idx == TLS_OUT_OF_INDEXES) return -ENOMEM; ENSURE(idx < TLS_LIMIT); *key = (pthread_key_t)idx; // acquire a free dtor slot size_t i; for(i = 0; i < MAX_DTORS; i++) { if(cpu_CAS((volatile intptr_t*)&dtors[i].dtor, (intptr_t)0, (intptr_t)dtor)) goto have_slot; } // not enough slots; we have a valid key, but its dtor won't be called. WARN_IF_ERR(ERR::LIMIT); return -1; have_slot: dtors[i].key = *key; return 0; } int pthread_key_delete(pthread_key_t key) { DWORD idx = (DWORD)key; ENSURE(idx < TLS_LIMIT); BOOL ret = TlsFree(idx); ENSURE(ret != 0); return 0; } void* pthread_getspecific(pthread_key_t key) { DWORD idx = (DWORD)key; ENSURE(idx < TLS_LIMIT); // TlsGetValue sets last error to 0 on success (boo). // we don't want this to hide previous errors, so it's restored below. DWORD last_err = GetLastError(); void* data = TlsGetValue(idx); // no error if(GetLastError() == 0) { // we care about performance here. SetLastError is low overhead, // but last error = 0 is expected. if(last_err != 0) SetLastError(last_err); } else WARN_IF_ERR(ERR::FAIL); return data; } int pthread_setspecific(pthread_key_t key, const void* value) { DWORD idx = (DWORD)key; ENSURE(idx < TLS_LIMIT); BOOL ret = TlsSetValue(idx, (void*)value); ENSURE(ret != 0); return 0; } static void call_tls_dtors() { again: bool had_valid_tls = false; // for each registered dtor: (call order unspecified by SUSv3) for(size_t i = 0; i < MAX_DTORS; i++) { // is slot #i in use? void (*dtor)(void*) = dtors[i].dtor; if(!dtor) continue; // clear slot and call dtor with its previous value. const pthread_key_t key = dtors[i].key; void* tls = pthread_getspecific(key); if(tls) { WARN_IF_ERR(pthread_setspecific(key, 0)); dtor(tls); had_valid_tls = true; } } // rationale: SUSv3 says we're allowed to loop infinitely. we do so to // expose any dtor bugs - this shouldn't normally happen. if(had_valid_tls) goto again; } //----------------------------------------------------------------------------- // mutex //----------------------------------------------------------------------------- // rationale: CRITICAL_SECTIONS have less overhead than Win32 Mutex. // disadvantage is that pthread_mutex_timedlock isn't supported, but // the user can switch to semaphores if this facility is important. // DeleteCriticalSection currently doesn't complain if we double-free // (e.g. user calls destroy() and static initializer atexit runs), // and dox are ambiguous. // note: pthread_mutex_t must not be an opaque struct, because the // initializer returns pthread_mutex_t directly and CRITICAL_SECTIONS // shouldn't be copied. // // note: we use wutil_Allocate instead of new because the (no longer extant) // memory manager used a pthread_mutex. int pthread_mutexattr_init(pthread_mutexattr_t* UNUSED(attr)) { return 0; } int pthread_mutexattr_destroy(pthread_mutexattr_t* UNUSED(attr)) { return 0; } int pthread_mutexattr_gettype(const pthread_mutexattr_t* UNUSED(attr), int* type) { *type = PTHREAD_MUTEX_RECURSIVE; return 0; } int pthread_mutexattr_settype(pthread_mutexattr_t* UNUSED(attr), int type) { return (type == PTHREAD_MUTEX_RECURSIVE)? 0 : -ENOSYS; } static CRITICAL_SECTION* CRITICAL_SECTION_from_pthread_mutex_t(pthread_mutex_t* m) { if(!m) { DEBUG_WARN_ERR(ERR::LOGIC); return 0; } return (CRITICAL_SECTION*)*m; } pthread_mutex_t pthread_mutex_initializer() { CRITICAL_SECTION* cs = (CRITICAL_SECTION*)wutil_Allocate(sizeof(CRITICAL_SECTION)); InitializeCriticalSection(cs); return (pthread_mutex_t)cs; } int pthread_mutex_destroy(pthread_mutex_t* m) { CRITICAL_SECTION* cs = CRITICAL_SECTION_from_pthread_mutex_t(m); if(!cs) return -1; DeleteCriticalSection(cs); wutil_Free(cs); *m = 0; // cause double-frees to be noticed return 0; } int pthread_mutex_init(pthread_mutex_t* m, const pthread_mutexattr_t*) { *m = pthread_mutex_initializer(); return 0; } int pthread_mutex_lock(pthread_mutex_t* m) { CRITICAL_SECTION* cs = CRITICAL_SECTION_from_pthread_mutex_t(m); if(!cs) return -1; EnterCriticalSection(cs); return 0; } int pthread_mutex_trylock(pthread_mutex_t* m) { CRITICAL_SECTION* cs = CRITICAL_SECTION_from_pthread_mutex_t(m); if(!cs) return -1; const BOOL successfullyEnteredOrAlreadyOwns = TryEnterCriticalSection(cs); return successfullyEnteredOrAlreadyOwns? 0 : -1; } int pthread_mutex_unlock(pthread_mutex_t* m) { CRITICAL_SECTION* cs = CRITICAL_SECTION_from_pthread_mutex_t(m); if(!cs) return -1; LeaveCriticalSection(cs); return 0; } // not implemented - pthread_mutex is based on CRITICAL_SECTION, // which doesn't support timeouts. use sem_timedwait instead. int pthread_mutex_timedlock(pthread_mutex_t* UNUSED(m), const struct timespec* UNUSED(abs_timeout)) { return -ENOSYS; } //----------------------------------------------------------------------------- // semaphore //----------------------------------------------------------------------------- static HANDLE HANDLE_from_sem_t(sem_t* sem) { return (HANDLE)*sem; } sem_t* sem_open(const char* name, int oflag, ...) { WinScopedPreserveLastError s; const bool create = (oflag & O_CREAT) != 0; const bool exclusive = (oflag & O_EXCL) != 0; // SUSv3 parameter requirements: ENSURE(name[0] == '/'); ENSURE((oflag & ~(O_CREAT|O_EXCL)) == 0); // no other bits ENSURE(!exclusive || create); // excl implies creat // if creating, get additional parameters unsigned initialValue = 0; if(create) { va_list args; va_start(args, oflag); const mode_t mode = va_arg(args, mode_t); initialValue = va_arg(args, unsigned); va_end(args); ENSURE(mode == 0700 && "this implementation ignores mode_t"); } // create or open SetLastError(0); const LONG maxValue = 0x7fffffff; const HANDLE hSemaphore = CreateSemaphore(0, (LONG)initialValue, maxValue, 0); if(hSemaphore == 0) return SEM_FAILED; const bool existed = (GetLastError() == ERROR_ALREADY_EXISTS); // caller insisted on creating anew, but it already existed if(exclusive && existed) { CloseHandle(hSemaphore); errno = EEXIST; return SEM_FAILED; } // caller wanted to open semaphore, but it didn't exist if(!create && !existed) { CloseHandle(hSemaphore); errno = ENOENT; return SEM_FAILED; } // we have to return a pointer to sem_t, and the sem_init interface // requires sem_t to be usable as the handle, so we'll have to // allocate memory here (ugh). sem_t* sem = new sem_t; *sem = (sem_t)hSemaphore; return sem; } int sem_close(sem_t* sem) { // jw: not sure if this is correct according to SUSv3, but it's // certainly enough for MessagePasserImpl's needs. sem_destroy(sem); delete sem; return 0; // success } int sem_unlink(const char* UNUSED(name)) { // see sem_close return 0; // success } int sem_init(sem_t* sem, int pshared, unsigned value) { SECURITY_ATTRIBUTES sec = { sizeof(SECURITY_ATTRIBUTES) }; sec.bInheritHandle = (BOOL)pshared; HANDLE h = CreateSemaphore(&sec, (LONG)value, 0x7fffffff, 0); WARN_IF_FALSE(h); *sem = (sem_t)h; return 0; } int sem_destroy(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); WARN_IF_FALSE(CloseHandle(h)); return 0; } int sem_post(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); WARN_IF_FALSE(ReleaseSemaphore(h, 1, 0)); return 0; } int sem_wait(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); DWORD ret = WaitForSingleObject(h, INFINITE); ENSURE(ret == WAIT_OBJECT_0); return 0; } // helper function for sem_timedwait - multiple return is convenient. // converts an absolute timeout deadline into a relative length for use with // WaitForSingleObject with the following peculiarity: if the semaphore // could be locked immediately, abs_timeout must be ignored (see SUS). // to that end, we return a timeout of 0 and pass back = false if // abs_timeout is invalid. static DWORD calc_timeout_length_ms(const struct timespec* abs_timeout, bool& timeout_is_valid) { timeout_is_valid = false; if(!abs_timeout) return 0; // SUS requires we fail if not normalized if(abs_timeout->tv_nsec >= 1000000000) return 0; struct timespec cur_time; if(clock_gettime(CLOCK_REALTIME, &cur_time) != 0) return 0; timeout_is_valid = true; // convert absolute deadline to relative length, rounding up to [ms]. // note: use i64 to avoid overflow in multiply. const i64 ds = abs_timeout->tv_sec - cur_time.tv_sec; const long dn = abs_timeout->tv_nsec - cur_time.tv_nsec; i64 length_ms = ds*1000 + (dn+500000)/1000000; // .. deadline already reached; we'll still attempt to lock once if(length_ms < 0) return 0; // .. length > 49 days => result won't fit in 32 bits. most likely bogus. // note: we're careful to avoid returning exactly -1 since // that's the Win32 INFINITE value. if(length_ms >= 0xFFFFFFFF) { WARN_IF_ERR(ERR::LIMIT); length_ms = 0xfffffffe; } return (DWORD)(length_ms & 0xFFFFFFFF); } int sem_timedwait(sem_t* sem, const struct timespec* abs_timeout) { bool timeout_is_valid; DWORD timeout_ms = calc_timeout_length_ms(abs_timeout, timeout_is_valid); HANDLE h = HANDLE_from_sem_t(sem); DWORD ret = WaitForSingleObject(h, timeout_ms); // successfully decremented semaphore; bail. if(ret == WAIT_OBJECT_0) return 0; // we're going to return -1. decide what happened: // .. abs_timeout was invalid (must not check this before trying to lock) if(!timeout_is_valid) errno = EINVAL; // .. timeout reached (not a failure) else if(ret == WAIT_TIMEOUT) errno = ETIMEDOUT; return -1; } // wait until semaphore is locked or a message arrives. non-portable. // // background: on Win32, UI threads must periodically pump messages, or // else deadlock may result (see WaitForSingleObject docs). that entails // avoiding any blocking functions. when event waiting is needed, // one cheap workaround would be to time out periodically and pump messages. // that would work, but either wastes CPU time waiting, or introduces // message latency. to avoid this, we provide an API similar to sem_wait and // sem_timedwait that gives MsgWaitForMultipleObjects functionality. // // return value: 0 if the semaphore has been locked (SUS terminology), // -1 otherwise. errno differentiates what happened: ETIMEDOUT if a // message arrived (this is to ease switching between message waiting and // periodic timeout), or an error indication. int sem_msgwait_np(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); DWORD ret = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLEVENTS); // semaphore is signalled if(ret == WAIT_OBJECT_0) return 0; // something else: // .. message came up if(ret == WAIT_OBJECT_0+1) errno = ETIMEDOUT; // .. error else { errno = EINVAL; WARN_IF_ERR(ERR::FAIL); } return -1; } //----------------------------------------------------------------------------- // threads //----------------------------------------------------------------------------- // _beginthreadex cannot call the user's thread function directly due to // differences in calling convention; we need to pass its address and // the user-specified data pointer to our trampoline. // // rationale: // - a local variable in pthread_create isn't safe because the // new thread might not start before pthread_create returns. // - using one static FuncAndArg protected by critical section doesn't // work. wutil_Lock allows recursive locking, so if creating 2 threads, // the parent thread may create both without being stopped and thus // stomp on the first thread's func_and_arg. // - blocking pthread_create until the trampoline has latched func_and_arg // would work. this is a bit easier to understand than nonrecursive CS. // deadlock is impossible because thread_start allows the parent to // continue before doing anything dangerous. however, this requires // initializing a semaphore, which leads to init order problems. // - stashing func and arg in TLS would work, but it is a very limited // resource and __declspec(thread) is documented as possibly // interfering with delay loading. // - heap allocations are the obvious safe solution. we'd like to // minimize them, but it's the least painful way. struct FuncAndArg { void* (*func)(void*); void* arg; }; // bridge calling conventions required by _beginthreadex and POSIX. static unsigned __stdcall thread_start(void* param) { const FuncAndArg* func_and_arg = (const FuncAndArg*)param; void* (*func)(void*) = func_and_arg->func; void* arg = func_and_arg->arg; wutil_Free(param); NotifyCurrentThread(); void* ret = 0; __try { ret = func(arg); call_tls_dtors(); } __except(wseh_ExceptionFilter(GetExceptionInformation())) { ret = 0; } return (unsigned)(uintptr_t)ret; } int pthread_create(pthread_t* thread_id, const void* UNUSED(attr), void* (*func)(void*), void* arg) { // notes: // - use wutil_Allocate instead of the normal heap because we /might/ // potentially be called before _cinit. // - placement new is more trouble than it's worth // (see REDEFINED_NEW), so we don't bother with a ctor. FuncAndArg* func_and_arg = (FuncAndArg*)wutil_Allocate(sizeof(FuncAndArg)); if(!func_and_arg) { WARN_IF_ERR(ERR::NO_MEM); return -1; } func_and_arg->func = func; func_and_arg->arg = arg; // _beginthreadex has more overhead and no value added vs. // CreateThread, but it avoids small memory leaks in // ExitThread when using the statically-linked CRT (-> MSDN). HANDLE hThread = (HANDLE)_beginthreadex(0, 0, thread_start, func_and_arg, 0, 0); if(!hThread) { WARN_IF_ERR(ERR::FAIL); return -1; } // SUSv3 doesn't specify whether this is optional - go the safe route. if(thread_id) *thread_id = pthread_from_HANDLE(hThread); return 0; } int pthread_cancel(pthread_t thread) { HANDLE hThread = HANDLE_from_pthread(thread); TerminateThread(hThread, 0); debug_printf("WARNING: pthread_cancel is unsafe\n"); return 0; } int pthread_join(pthread_t thread, void** value_ptr) { HANDLE hThread = HANDLE_from_pthread(thread); // note: pthread_join doesn't call for a timeout. if this wait // locks up the process, at least it'll be easy to see why. DWORD ret = WaitForSingleObject(hThread, INFINITE); if(ret != WAIT_OBJECT_0) { WARN_IF_ERR(ERR::FAIL); return -1; } // pass back the code that was passed to pthread_exit. // SUS says <*value_ptr> need only be set on success! if(value_ptr) GetExitCodeThread(hThread, (LPDWORD)value_ptr); CloseHandle(hThread); return 0; } static Status wpthread_Init() { NotifyCurrentThread(); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wposix/wtime_internal.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wtime_internal.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wtime_internal.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_WTIME_INTERNAL #define INCLUDED_WTIME_INTERNAL // convert UTC FILETIME to seconds-since-1970 UTC. // used by wfilesystem. #ifndef _FILETIME_ // prevent ICE on VC7 struct _FILETIME; #endif extern time_t wtime_utc_filetime_to_time_t(_FILETIME* ft); #endif // #ifndef INCLUDED_WTIME_INTERNAL Index: ps/trunk/source/lib/sysdep/os/win/wseh.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wseh.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wseh.cpp (revision 19899) @@ -1,393 +1,393 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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. */ /* * Structured Exception Handling support */ #include "precompiled.h" #include "lib/sysdep/os/win/wseh.h" #include "lib/byte_order.h" // FOURCC #include "lib/utf8.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/wdbg_sym.h" // wdbg_sym_WriteMinidump #include // __security_init_cookie #define NEED_COOKIE_INIT // note: several excellent references are pointed to by comments below. //----------------------------------------------------------------------------- // analyze an exception (determine description and locus) // VC++ exception handling internals. // see http://www.codeproject.com/cpp/exceptionhandler.asp struct XTypeInfo { DWORD _; const std::type_info* ti; // .. }; struct XTypeInfoArray { DWORD count; const XTypeInfo* types[1]; }; struct XInfo { DWORD _[3]; const XTypeInfoArray* array; }; // does the given SEH exception look like a C++ exception? // (compiler-specific). static bool IsCppException(const EXCEPTION_RECORD* er) { #if MSC_VERSION // notes: // - value of multibyte character constants (e.g. 'msc') aren't // specified by C++, so use FOURCC instead. // - "MS C" compiler is the only interpretation of this magic value that // makes sense, so it is apparently stored in big-endian format. if(er->ExceptionCode != FOURCC_BE(0xe0, 'm','s','c')) return false; // exception info = (magic, &thrown_Cpp_object, &XInfo) if(er->NumberParameters != 3) return false; // MAGIC_NUMBER1 from exsup.inc if(er->ExceptionInformation[0] != 0x19930520) return false; return true; #else # error "port" #endif } /** * @param er An exception record for which IsCppException returned true. * @param description * @param maxChars **/ static const wchar_t* GetCppExceptionDescription(const EXCEPTION_RECORD* er, wchar_t* description, size_t maxChars) { // see above for interpretation const ULONG_PTR* const ei = er->ExceptionInformation; // note: we can't share a __try below - the failure of // one attempt must not abort the others. // get std::type_info char type_buf[100] = {'\0'}; const char* type_name = type_buf; __try { const XInfo* xi = (XInfo*)ei[2]; const XTypeInfoArray* xta = xi->array; const XTypeInfo* xti = xta->types[0]; const std::type_info* ti = xti->ti; // strip "class " from start of string (clutter) strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name()); if(!strncmp(type_buf, "class ", 6)) type_name += 6; } __except(EXCEPTION_EXECUTE_HANDLER) { } // std::exception.what() char what[160] = {'\0'}; __try { std::exception* e = (std::exception*)ei[1]; strcpy_s(what, ARRAY_SIZE(what), e->what()); } __except(EXCEPTION_EXECUTE_HANDLER) { } // format the info we got (if both are empty, then something is seriously // wrong; it's better to show empty strings than returning 0 to have our // caller display the SEH info) swprintf_s(description, maxChars, L"%hs(\"%hs\")", type_name, what); return description; } static const wchar_t* GetSehExceptionDescription(const EXCEPTION_RECORD* er, wchar_t* description, size_t maxChars) { const DWORD code = er->ExceptionCode; const ULONG_PTR* ei = er->ExceptionInformation; // rationale: we don't use FormatMessage because it is unclear whether // NTDLL's symbol table will always include English-language strings // (we don't want to receive crashlogs in foreign gobbledygook). // it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .). switch(code) { case EXCEPTION_ACCESS_VIOLATION: { // special case: display type and address. const wchar_t* accessType = (ei[0])? L"writing" : L"reading"; const ULONG_PTR address = ei[1]; swprintf_s(description, maxChars, L"Access violation %ls 0x%08X", accessType, address); return description; } case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment"; case EXCEPTION_BREAKPOINT: return L"Breakpoint"; case EXCEPTION_SINGLE_STEP: return L"Single step"; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded"; case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand"; case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero"; case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result"; case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation"; case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow"; case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check"; case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow"; case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero"; case EXCEPTION_INT_OVERFLOW: return L"Integer overflow"; case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction"; case EXCEPTION_IN_PAGE_ERROR: return L"In page error"; case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction"; case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception"; case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow"; case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition"; case EXCEPTION_GUARD_PAGE: return L"Guard page"; case EXCEPTION_INVALID_HANDLE: return L"Invalid handle"; } // anything else => unknown; display its exception code. // we don't punt to GetExceptionDescription because anything // we get called for will actually be a SEH exception. swprintf_s(description, maxChars, L"Unknown (0x%08X)", code); return description; } /** * @return a description of the exception type and cause (in English). **/ static const wchar_t* GetExceptionDescription(const EXCEPTION_POINTERS* ep, wchar_t* description, size_t maxChars) { const EXCEPTION_RECORD* const er = ep->ExceptionRecord; if(IsCppException(er)) return GetCppExceptionDescription(er, description, maxChars); else return GetSehExceptionDescription(er, description, maxChars); } // return location at which the exception occurred. // params: see debug_ResolveSymbol. static void GetExceptionLocus(EXCEPTION_POINTERS* ep, wchar_t* file, int* line, wchar_t* func) { // HACK: provides no useful information - ExceptionAddress always // points to kernel32!RaiseException. we use debug_GetCaller to // determine the real location. const wchar_t* const lastFuncToSkip = L"RaiseException"; void* func_addr = debug_GetCaller(ep->ContextRecord, lastFuncToSkip); (void)debug_ResolveSymbol(func_addr, func, file, line); } //----------------------------------------------------------------------------- // exception filter // called when an exception is detected (see below); provides detailed // debugging information and exits. // // note: keep memory allocs and locking to an absolute minimum, because // they may deadlock the process! long __stdcall wseh_ExceptionFilter(struct _EXCEPTION_POINTERS* ep) { // OutputDebugString raises an exception, which OUGHT to be swallowed // by WaitForDebugEvent but sometimes isn't. if we see it, ignore it. if(ep->ExceptionRecord->ExceptionCode == 0x40010006) // DBG_PRINTEXCEPTION_C return EXCEPTION_CONTINUE_EXECUTION; // if run in a debugger, let it handle exceptions (tends to be more // convenient since it can bring up the crash location) if(IsDebuggerPresent()) return EXCEPTION_CONTINUE_SEARCH; // make sure we don't recurse infinitely if this function raises an // SEH exception. (we may only have the guard page's 4 KB worth of // stack space if the exception is EXCEPTION_STACK_OVERFLOW) static intptr_t nestingLevel = 0; cpu_AtomicAdd(&nestingLevel, 1); if(nestingLevel >= 3) return EXCEPTION_CONTINUE_SEARCH; // someone is already holding the dbghelp lock - this is bad. // we'll report this problem first and then try to display the // exception info regardless (maybe dbghelp won't blow up). if(wutil_IsLocked(WDBG_SYM_CS) == 1) DEBUG_DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock.."); // a dump file is essential for debugging, so write it before // anything else goes wrong (especially before showing the error // dialog because the user could choose to exit immediately) wdbg_sym_WriteMinidump(ep); // extract details from ExceptionRecord. wchar_t descriptionBuf[150]; const wchar_t* description = GetExceptionDescription(ep, descriptionBuf, ARRAY_SIZE(descriptionBuf)); wchar_t file[DEBUG_FILE_CHARS] = {0}; int line = 0; wchar_t func[DEBUG_SYMBOL_CHARS] = {0}; GetExceptionLocus(ep, file, &line, func); wchar_t message[500]; const wchar_t* messageFormat = L"Much to our regret we must report the program has encountered an error.\r\n" L"\r\n" L"Please let us know at http://trac.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n" L"\r\n" L"Details: unhandled exception (%ls)\r\n"; swprintf_s(message, ARRAY_SIZE(message), messageFormat, description); size_t flags = 0; if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) flags = DE_NO_CONTINUE; const wchar_t* const lastFuncToSkip = WIDEN(STRINGIZE(DECORATED_NAME(wseh_ExceptionFilter))); ErrorReaction er = debug_DisplayError(message, flags, ep->ContextRecord, lastFuncToSkip, file,line,utf8_from_wstring(func).c_str(), 0); ENSURE(er == ER_CONTINUE); // nothing else possible // invoke the Win32 default handler - it calls ExitProcess for // most exception types. return EXCEPTION_CONTINUE_SEARCH; } //----------------------------------------------------------------------------- // install SEH exception handler /* rationale: we want to replace the OS "program error" dialog box because it is not all too helpful in debugging. to that end, there are 5 ways to make sure unhandled SEH exceptions are caught: - via WaitForDebugEvent; the app is run from a separate debugger process. the exception is in another address space, so we'd have to deal with that and basically implement a full-featured debugger - overkill. - by wrapping all threads (their handler chains are in TLS) in __try. this can be done with the cooperation of wpthread, but threads not under our control aren't covered. - with a vectored exception handler. this works across threads, but it's only available on WinXP (unacceptable). since it is called before __try blocks, we would receive expected/legitimate exceptions. (see http://msdn.microsoft.com/msdnmag/issues/01/09/hood/default.aspx) - by setting the per-process unhandled exception filter. as above, this works across threads and is at least portable across Win32. unfortunately, some Win32 DLLs appear to register their own handlers, so this isn't reliable. - by hooking the exception dispatcher. this isn't future-proof. note that the vectored and unhandled-exception filters aren't called when the process is being debugged (messing with the PEB flag doesn't help; root cause is the Win32 KiUserExceptionDispatcher implementation). however, this is fine since the IDE's debugger is more helpful than our dialog (it is able to jump directly to the offending code). wrapping all threads in a __try appears to be the best choice. unfortunately, we cannot retroactively install an SEH handler: the OS ensures SEH chain nodes are on the thread's stack (as defined by NT_TIB) in ascending order. (see http://www.microsoft.com/msj/0197/exception/exception.aspx) the handler would also have to be marked via .safeseh, but that is doable (see http://blogs.msdn.com/greggm/archive/2004/07/22/191544.aspx) consequently, we'll have to run within a __try; if the init code is to be covered, this must happen within the program entry point. note: since C++ exceptions are implemented via SEH, we can also catch those here; it's nicer than a global try{} and avoids duplicating this code. we can still get at the C++ information (std::exception.what()) by examining the internal exception data structures. these are compiler-specific, but haven't changed from VC5-VC7.1. alternatively, _set_se_translator could to translate all SEH exceptions to C++ classes. this way is more reliable/documented, but has several drawbacks: - it wouldn't work at all in C programs, - a new fat exception class would have to be created to hold the SEH exception information (e.g. CONTEXT for a stack trace), and - this information would not be available for C++ exceptions. (see http://blogs.msdn.com/cbrumme/archive/2003/10/01/51524.aspx) */ #ifdef LIB_STATIC_LINK #include "lib/utf8.h" EXTERN_C int wmainCRTStartup(); static int CallStartupWithinTryBlock() { int ret; __try { ret = wmainCRTStartup(); } __except(wseh_ExceptionFilter(GetExceptionInformation())) { ret = -1; } return ret; } EXTERN_C int wseh_EntryPoint() { #ifdef NEED_COOKIE_INIT // 2006-02-16 workaround for R6035 on VC8: // // SEH code compiled with /GS pushes a "security cookie" onto the // stack. since we're called before CRT init, the cookie won't have // been initialized yet, which would cause the CRT to FatalAppExit. // to solve this, we must call __security_init_cookie before any // hidden compiler-generated SEH registration code runs, // which means the __try block must be moved into a helper function. // // NB: wseh_EntryPoint() must not contain local string buffers, // either - /GS would install a cookie here as well (same problem). // // see http://msdn2.microsoft.com/en-US/library/ms235603.aspx __security_init_cookie(); #endif return CallStartupWithinTryBlock(); } #endif Index: ps/trunk/source/lib/sysdep/os/win/win.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/win.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/win.h (revision 19899) @@ -1,103 +1,103 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * bring in with compatibility fixes */ #ifndef INCLUDED_WIN #define INCLUDED_WIN #if !OS_WIN #error "win.h: do not include if not compiling for Windows" #endif // Win32 socket declarations aren't portable (e.g. problems with socklen_t) // => skip winsock.h; use curl or enet library instead. #define _WINSOCKAPI_ #define WIN32_LEAN_AND_MEAN #define VC_EXTRALEAN // other headers may have defined 's include guard to prevent // external libraries from pulling it in (which would cause conflicts). #undef _WINDOWS_ // set version; needed for EnumDisplayDevices #ifndef NTDDI_VERSION # define NTDDI_VERSION NTDDI_LONGHORN #endif #ifndef _WIN32_WINNT # define _WIN32_WINNT 0x600 #endif #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ //#define NOVIRTUALKEYCODES // VK_* //#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* //#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* //#define NOSYSMETRICS // SM_* #define NOMENUS // MF_* //#define NOICONS // IDI_* #define NOKEYSTATES // MK_* //#define NOSYSCOMMANDS // SC_* #define NORASTEROPS // Binary and Tertiary raster ops //#define NOSHOWWINDOW // SW_* #define OEMRESOURCE // OEM Resource values #define NOATOM // Atom Manager routines //#define NOCLIPBOARD // Clipboard routines //#define NOCOLOR // Screen colors //#define NOCTLMGR // Control and Dialog routines #define NODRAWTEXT // DrawText() and DT_* //#define NOGDI // All GDI defines and routines //#define NOKERNEL // All KERNEL defines and routines //#define NOUSER // All USER defines and routines //#define NONLS // All NLS defines and routines //#define NOMB // MB_* and MessageBox() #define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines #define NOMETAFILE // typedef METAFILEPICT #define NOMINMAX // Macros min(a,b) and max(a,b) //#define NOMSG // typedef MSG and associated routines #define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* #define NOSCROLL // SB_* and scrolling routines //#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. //#define NOSOUND // Sound driver routines #define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines //#define NOWH // SetWindowsHook and WH_* //#define NOWINOFFSETS // GWL_*, GCL_*, associated routines //#define NOCOMM // COMM driver routines #define NOKANJI // Kanji support stuff. #define NOHELP // Help engine interface. #define NOPROFILER // Profiler interface. #define NODEFERWINDOWPOS // DeferWindowPos routines #define NOMCX // Modem Configuration Extensions #include #include #if ARCH_IA32 // the official version causes pointer truncation warnings. # undef InterlockedExchangePointer # define InterlockedExchangePointer(Target, Value) (PVOID)(uintptr_t)InterlockedExchange((PLONG)(Target), (LONG)(uintptr_t)(Value)) #endif #endif // #ifndef INCLUDED_WIN Index: ps/trunk/source/lib/sysdep/os/win/wiocp.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wiocp.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wiocp.h (revision 19899) @@ -1,50 +1,50 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * I/O completion port */ #ifndef INCLUDED_WIOCP #define INCLUDED_WIOCP #include "lib/sysdep/os/win/win.h" // this appears to be the best solution for IO notification. // there are three alternatives: // - multiple threads with blocking I/O. this is rather inefficient when // many directories (e.g. mods) are being watched. // - normal overlapped I/O: build a contiguous array of the hEvents // in all OVERLAPPED structures, and WaitForMultipleObjects. // it would be cumbersome to update this array when adding/removing watches. // - callback notification: a notification function is called when the thread // that initiated the I/O (ReadDirectoryChangesW) enters an alertable // wait state. it is desirable for notifications to arrive at a single // known point - see dir_watch_Poll. however, other APIs might also // trigger APC delivery. // @param hIOCP 0 to create a new port extern void AttachToCompletionPort(HANDLE hFile, HANDLE& hIOCP, ULONG_PTR key, DWORD numConcurrentThreads = 0); extern Status PollCompletionPort(HANDLE hIOCP, DWORD timeout, DWORD& bytesTransferred, ULONG_PTR& key, OVERLAPPED*& ovl); #endif // #ifndef INCLUDED_WIOCP Index: ps/trunk/source/lib/sysdep/os/win/wposix/waio.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/waio.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/waio.cpp (revision 19899) @@ -1,692 +1,692 @@ -/* Copyright (c) 2011 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 * "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. */ /* * emulate POSIX asynchronous I/O on Windows. */ // NB: this module is significantly faster than Intel's aio library, // which also returns ERROR_INVALID_PARAMETER from aio_error if the // file is opened with FILE_FLAG_OVERLAPPED. (it looks like they are // using threaded blocking IO) #include "precompiled.h" #include "lib/sysdep/os/win/wposix/waio.h" #include "lib/bits.h" // round_up #include "lib/alignment.h" // IsAligned #include "lib/module_init.h" #include "lib/sysdep/cpu.h" // cpu_AtomicAdd #include "lib/sysdep/filesystem.h" // O_DIRECT #include "lib/sysdep/os/win/wutil.h" // wutil_SetPrivilege #include "lib/sysdep/os/win/wiocp.h" #include "lib/sysdep/os/win/winit.h" #include "lib/sysdep/os/win/wposix/crt_posix.h" // _get_osfhandle WINIT_REGISTER_MAIN_SHUTDOWN(waio_Shutdown); // (dynamic linking preserves compatibility with previous Windows versions) static WUTIL_FUNC(pSetFileCompletionNotificationModes, BOOL, (HANDLE, UCHAR)); static WUTIL_FUNC(pSetFileIoOverlappedRange, BOOL, (HANDLE, PUCHAR, ULONG)); static WUTIL_FUNC(pSetFileValidData, BOOL, (HANDLE, LONGLONG)); // (there must be one global IOCP because aio_suspend might be called for // requests from different files) static HANDLE hIOCP; //----------------------------------------------------------------------------- // OpenFile static DWORD DesiredAccess(int oflag) { switch(oflag & (O_RDONLY|O_WRONLY|O_RDWR)) { case O_RDONLY: // (WinXP x64 requires FILE_WRITE_ATTRIBUTES for SetFileCompletionNotificationModes) return GENERIC_READ|FILE_WRITE_ATTRIBUTES; case O_WRONLY: return GENERIC_WRITE; case O_RDWR: return GENERIC_READ|GENERIC_WRITE; default: DEBUG_WARN_ERR(ERR::INVALID_PARAM); return 0; } } static DWORD ShareMode(int oflag) { switch(oflag & (O_RDONLY|O_WRONLY|O_RDWR)) { case O_RDONLY: return FILE_SHARE_READ; case O_WRONLY: return FILE_SHARE_WRITE; case O_RDWR: return FILE_SHARE_WRITE; // deny read access (c.f. waio_Preallocate) default: DEBUG_WARN_ERR(ERR::INVALID_PARAM); return 0; } } static DWORD CreationDisposition(int oflag) { if(oflag & O_CREAT) return (oflag & O_EXCL)? CREATE_NEW : CREATE_ALWAYS; if(oflag & O_TRUNC) return TRUNCATE_EXISTING; return OPEN_EXISTING; } static DWORD FlagsAndAttributes() { // - FILE_FLAG_SEQUENTIAL_SCAN is ignored when FILE_FLAG_NO_BUFFERING // is set (c.f. "Windows via C/C++", p. 324) // - FILE_FLAG_WRITE_THROUGH is ~5% slower (diskspd.cpp suggests it // disables hardware caching; the overhead may also be due to the // Windows cache manager) const DWORD flags = FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING; const DWORD attributes = FILE_ATTRIBUTE_NORMAL; return flags|attributes; } static Status OpenFile(const OsPath& pathname, int oflag, HANDLE& hFile) { WinScopedPreserveLastError s; const DWORD access = DesiredAccess(oflag); const DWORD share = ShareMode(oflag); const DWORD create = CreationDisposition(oflag); const DWORD flags = FlagsAndAttributes(); hFile = CreateFileW(OsString(pathname).c_str(), access, share, 0, create, flags, 0); if(hFile == INVALID_HANDLE_VALUE) WARN_RETURN(StatusFromWin()); return INFO::OK; } //----------------------------------------------------------------------------- // OvlAllocator // allocator for OVERLAPPED (enables a special optimization, see Associate) struct OvlAllocator // POD { // freelist entries for (padded) OVERLAPPED from our storage struct Entry { SLIST_ENTRY entry; OVERLAPPED ovl; }; Status Init() { // the allocation must be naturally aligned to ensure it doesn't // overlap another page, which might prevent SetFileIoOverlappedRange // from pinning the pages if one of them is PAGE_NOACCESS. storage = _mm_malloc(storageSize, storageSize); if(!storage) WARN_RETURN(ERR::NO_MEM); memset(storage, 0, storageSize); InitializeSListHead(&freelist); // storageSize provides more than enough OVERLAPPED, so we // pad them to the cache line size to maybe avoid a few RFOs. const size_t size = Align(sizeof(Entry)); for(uintptr_t offset = 0; offset+size <= storageSize; offset += size) { Entry* entry = (Entry*)(uintptr_t(storage) + offset); ENSURE(IsAligned(entry, MEMORY_ALLOCATION_ALIGNMENT)); InterlockedPushEntrySList(&freelist, &entry->entry); } extant = 0; return INFO::OK; } void Shutdown() { if(extant != 0) debug_printf("waio: OvlAllocator::Shutdown with extant=%d\n", extant); InterlockedFlushSList(&freelist); _mm_free(storage); storage = 0; } // irrevocably enable a special optimization for all I/Os requests // concerning this file, ending when the file is closed. has no effect // unless Vista+ and SeLockMemoryPrivilege are available. void Associate(HANDLE hFile) { ENSURE(extant == 0); // pin the page in kernel address space, which means our thread // won't have to be the one to service the I/O, thus avoiding an APC. // ("thread agnostic I/O") if(pSetFileIoOverlappedRange) WARN_IF_FALSE(pSetFileIoOverlappedRange(hFile, (PUCHAR)storage, storageSize)); } // @return OVERLAPPED initialized for I/O starting at offset, // or 0 if all available structures have already been allocated. OVERLAPPED* Allocate(off_t offset) { Entry* entry = (Entry*)InterlockedPopEntrySList(&freelist); if(!entry) return 0; OVERLAPPED& ovl = entry->ovl; ovl.Internal = 0; ovl.InternalHigh = 0; ovl.Offset = u64_lo(offset); ovl.OffsetHigh = u64_hi(offset); ovl.hEvent = 0; // (notification is via IOCP and/or polling) cpu_AtomicAdd(&extant, +1); return &ovl; } void Deallocate(OVERLAPPED* ovl) { cpu_AtomicAdd(&extant, -1); const uintptr_t address = uintptr_t(ovl); ENSURE(uintptr_t(storage) <= address && address < uintptr_t(storage)+storageSize); InterlockedPushEntrySList(&freelist, (PSLIST_ENTRY)(address - offsetof(Entry, ovl))); } // one 4 KiB page is enough for 64 OVERLAPPED per file (i.e. plenty). static const size_t storageSize = pageSize; void* storage; #if MSC_VERSION # pragma warning(push) # pragma warning(disable:4324) // structure was padded due to __declspec(align()) #endif __declspec(align(MEMORY_ALLOCATION_ALIGNMENT)) SLIST_HEADER freelist; #if MSC_VERSION # pragma warning(pop) #endif volatile intptr_t extant; }; //----------------------------------------------------------------------------- // FileControlBlock // information required to start asynchronous I/Os from a file struct FileControlBlock // POD { HANDLE hFile; OvlAllocator ovl; Status Init() { hFile = INVALID_HANDLE_VALUE; return ovl.Init(); } void Shutdown() { ENSURE(hFile == INVALID_HANDLE_VALUE); ovl.Shutdown(); } Status Open(const OsPath& pathname, int oflag) { RETURN_STATUS_IF_ERR(OpenFile(pathname, oflag, hFile)); ovl.Associate(hFile); AttachToCompletionPort(hFile, hIOCP, (ULONG_PTR)this); // minor optimization: avoid posting to IOCP in rare cases // where the I/O completes synchronously if(pSetFileCompletionNotificationModes) { // (for reasons as yet unknown, this fails when the file is // opened for read-only access) (void)pSetFileCompletionNotificationModes(hFile, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS); } return INFO::OK; } Status Close() { const BOOL ok = CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; if(!ok) WARN_RETURN(ERR::INVALID_HANDLE); return INFO::OK; } }; //----------------------------------------------------------------------------- // FileControlBlocks struct FileControlBlocks // POD { // (we never open more than a few files at a time.) static const size_t maxFiles = 8; // (our descriptors exceed _NHANDLE_ (2048) to ensure they are not // confused with lowio descriptors.) static const int firstDescriptor = 4000; FileControlBlock fcbs[maxFiles]; CACHE_ALIGNED(volatile intptr_t) inUse[maxFiles]; void Init() { for(size_t i = 0; i < maxFiles; i++) { inUse[i] = 0; fcbs[i].Init(); } } void Shutdown() { for(size_t i = 0; i < maxFiles; i++) { ENSURE(inUse[i] == 0); fcbs[i].Shutdown(); } } FileControlBlock* Allocate() { for(size_t i = 0; i < maxFiles; i++) { if(cpu_CAS(&inUse[i], 0, 1)) return &fcbs[i]; } return 0; } void Deallocate(FileControlBlock* fcb) { const size_t index = fcb - &fcbs[0]; inUse[index] = 0; } int Descriptor(FileControlBlock* fcb) const { const size_t index = fcb - &fcbs[0]; return firstDescriptor + (int)index; } FileControlBlock* FromDescriptor(int descriptor) { const size_t index = size_t(descriptor - firstDescriptor); if(index >= maxFiles) return 0; if(!inUse[index]) return 0; return &fcbs[index]; } }; static FileControlBlocks fileControlBlocks; //----------------------------------------------------------------------------- // init/shutdown static ModuleInitState waio_initState; // called from waio_Open (avoids overhead if this module is never used) static Status waio_Init() { fileControlBlocks.Init(); WUTIL_IMPORT_KERNEL32(SetFileCompletionNotificationModes, pSetFileCompletionNotificationModes); // NB: using these functions when the privileges are not available would // trigger warnings. since callers have to check the function pointers // anyway, just refrain from setting them in such cases. if(wutil_SetPrivilege(L"SeLockMemoryPrivilege", true) == INFO::OK) WUTIL_IMPORT_KERNEL32(SetFileIoOverlappedRange, pSetFileIoOverlappedRange); if(wutil_SetPrivilege(L"SeManageVolumePrivilege", true) == INFO::OK) WUTIL_IMPORT_KERNEL32(SetFileValidData, pSetFileValidData); return INFO::OK; } static Status waio_Shutdown() { if(waio_initState == 0) // we were never initialized return INFO::OK; fileControlBlocks.Shutdown(); WARN_IF_FALSE(CloseHandle(hIOCP)); return INFO::OK; } //----------------------------------------------------------------------------- // Windows-only APIs Status waio_open(const OsPath& pathname, int oflag, ...) { ASSERT(oflag & O_DIRECT); ENSURE(!(oflag & O_APPEND)); // not supported RETURN_STATUS_IF_ERR(ModuleInit(&waio_initState, waio_Init)); FileControlBlock* fcb = fileControlBlocks.Allocate(); if(!fcb) WARN_RETURN(ERR::LIMIT); RETURN_STATUS_IF_ERR(fcb->Open(pathname, oflag)); return (Status)fileControlBlocks.Descriptor(fcb); } Status waio_close(int fd) { FileControlBlock* fcb = fileControlBlocks.FromDescriptor(fd); if(!fcb) return ERR::INVALID_HANDLE; // NOWARN - fd might be from lowio Status ret = fcb->Close(); fileControlBlocks.Deallocate(fcb); return ret; } Status waio_Preallocate(int fd, off_t size) { WinScopedPreserveLastError s; HANDLE hFile; // from FileControlBlock OR lowio { FileControlBlock* fcb = fileControlBlocks.FromDescriptor(fd); if(fcb) hFile = fcb->hFile; else { hFile = (HANDLE)_get_osfhandle(fd); if(hFile == INVALID_HANDLE_VALUE) WARN_RETURN(ERR::INVALID_HANDLE); } } // Windows requires sector alignment (see discussion in header) const off_t alignedSize = round_up(size, (off_t)maxSectorSize); // (Align<> cannot compute off_t) // allocate all space up front to reduce fragmentation LARGE_INTEGER size64; size64.QuadPart = alignedSize; WARN_IF_FALSE(SetFilePointerEx(hFile, size64, 0, FILE_BEGIN)); if(!SetEndOfFile(hFile)) { if(GetLastError() == ERROR_DISK_FULL) SetLastError(0); else debug_printf("waio_Preallocate(%lld) failed: %d\n", size, GetLastError()); return ERR::FAIL; // NOWARN (either out of disk space, or error was printed) } // avoid synchronous zero-fill (see discussion in header) if(pSetFileValidData) { // this has been reported (#849) to fail with GetLastError() == 0 for // both tiny and medium alignedSize, despite Administrator privileges. // it seems the problem is the underlying FAT filesystem. Nick Ryan: // "FastFat does not process the FILE_VALID_DATA_LENGTH_INFORMATION IOCTL" // (http://us.generation-nt.com/answer/setfilevaliddata-help-25926952.html?page=2) // verifying the filesystem is indeed FAT would be overkill; we'll just // ignore the return code. however, GetLastError can be used to check // whether other problems arose. (void)pSetFileValidData(hFile, alignedSize); ENSURE(GetLastError() == 0); } return INFO::OK; } //----------------------------------------------------------------------------- // helper functions // called by aio_read, aio_write, and lio_listio. // cb->aio_lio_opcode specifies desired operation. // @return -1 on failure (having also set errno) static int Issue(aiocb* cb) { ENSURE(IsAligned(cb->aio_offset, maxSectorSize)); ENSURE(IsAligned(cb->aio_buf, maxSectorSize)); ENSURE(IsAligned(cb->aio_nbytes, maxSectorSize)); FileControlBlock* fcb = fileControlBlocks.FromDescriptor(cb->aio_fildes); if(!fcb) { DEBUG_WARN_ERR(ERR::INVALID_HANDLE); errno = EINVAL; return -1; } ENSURE(!cb->ovl); // SUSv3: aiocb must not be in use cb->ovl = fcb->ovl.Allocate(cb->aio_offset); if(!cb->ovl) { DEBUG_WARN_ERR(ERR::LIMIT); errno = EMFILE; return -1; } WinScopedPreserveLastError s; const HANDLE hFile = fcb->hFile; void* const buf = (void*)cb->aio_buf; // from volatile void* const DWORD size = u64_lo(cb->aio_nbytes); ENSURE(u64_hi(cb->aio_nbytes) == 0); OVERLAPPED* ovl = (OVERLAPPED*)cb->ovl; // (there is no point in using WriteFileGather/ReadFileScatter here // because the IO manager still needs to lock pages and translate them // into an MDL, and we'd just be increasing the number of addresses) const BOOL ok = (cb->aio_lio_opcode == LIO_WRITE)? WriteFile(hFile, buf, size, 0, ovl) : ReadFile(hFile, buf, size, 0, ovl); if(ok || GetLastError() == ERROR_IO_PENDING) return 0; // success const Status status = StatusFromWin(); WARN_IF_ERR(status); errno = ErrnoFromStatus(status); return -1; } static bool AreAnyComplete(const struct aiocb* const cbs[], int n) { for(int i = 0; i < n; i++) { if(!cbs[i]) // SUSv3: must ignore NULL entries continue; if(HasOverlappedIoCompleted((OVERLAPPED*)cbs[i]->ovl)) return true; } return false; } //----------------------------------------------------------------------------- // API int aio_read(struct aiocb* cb) { cb->aio_lio_opcode = LIO_READ; return Issue(cb); } int aio_write(struct aiocb* cb) { cb->aio_lio_opcode = LIO_WRITE; return Issue(cb); } int lio_listio(int mode, struct aiocb* const cbs[], int n, struct sigevent* se) { ENSURE(mode == LIO_WAIT || mode == LIO_NOWAIT); UNUSED2(se); // signaling is not implemented. for(int i = 0; i < n; i++) { if(cbs[i] == 0 || cbs[i]->aio_lio_opcode == LIO_NOP) continue; if(Issue(cbs[i]) == -1) return -1; } if(mode == LIO_WAIT) return aio_suspend(cbs, n, 0); return 0; } int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* timeout) { // consume all pending notifications to prevent them from piling up if // requests are always complete by the time we're called DWORD bytesTransferred; ULONG_PTR key; OVERLAPPED* ovl; while(PollCompletionPort(hIOCP, 0, bytesTransferred, key, ovl) == INFO::OK) {} // avoid blocking if already complete (synchronous requests don't post notifications) if(AreAnyComplete(cbs, n)) return 0; // caller doesn't want to block, and no requests are complete if(timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0) { errno = EAGAIN; return -1; } // reduce CPU usage by blocking until a notification arrives or a // brief timeout elapses (necessary because other threads - or even // the above poll - might have consumed our notification). note that // re-posting notifications that don't concern the respective requests // is not desirable because POSIX doesn't require aio_suspend to be // called, which means notifications might pile up. const DWORD milliseconds = 1; // as short as possible (don't oversleep) const Status ret = PollCompletionPort(hIOCP, milliseconds, bytesTransferred, key, ovl); if(ret != INFO::OK && ret != ERR::AGAIN) // failed { DEBUG_WARN_ERR(ERR::LOGIC); return -1; } // scan again (even if we got a notification, it might not concern THESE requests) if(AreAnyComplete(cbs, n)) return 0; // none completed, must repeat the above steps. provoke being called again by // claiming to have been interrupted by a signal. errno = EINTR; return -1; } int aio_error(const struct aiocb* cb) { const OVERLAPPED* ovl = (const OVERLAPPED*)cb->ovl; if(!ovl) // called after aio_return return EINVAL; if(!HasOverlappedIoCompleted(ovl)) return EINPROGRESS; if(ovl->Internal != ERROR_SUCCESS) return EIO; return 0; } ssize_t aio_return(struct aiocb* cb) { FileControlBlock* fcb = fileControlBlocks.FromDescriptor(cb->aio_fildes); OVERLAPPED* ovl = (OVERLAPPED*)cb->ovl; if(!fcb || !ovl) { errno = EINVAL; return -1; } const ULONG_PTR status = ovl->Internal; const ULONG_PTR bytesTransferred = ovl->InternalHigh; cb->ovl = 0; // prevent further calls to aio_error/aio_return COMPILER_FENCE; fcb->ovl.Deallocate(ovl); return (status == ERROR_SUCCESS)? bytesTransferred : -1; } // Win32 limitation: cancel all I/Os this thread issued for the given file // (CancelIoEx can cancel individual operations, but is only // available starting with Vista) int aio_cancel(int fd, struct aiocb* UNUSED(cb)) { FileControlBlock* fcb = fileControlBlocks.FromDescriptor(fd); if(!fcb) { WARN_IF_ERR(ERR::INVALID_HANDLE); errno = EINVAL; return -1; } WARN_IF_FALSE(CancelIo(fcb->hFile)); return AIO_CANCELED; } int aio_fsync(int, struct aiocb*) { WARN_IF_ERR(ERR::NOT_SUPPORTED); errno = ENOSYS; return -1; } Index: ps/trunk/source/lib/sysdep/os/win/wposix/werrno.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/werrno.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/werrno.h (revision 19899) @@ -1,141 +1,141 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_WERRNO #define INCLUDED_WERRNO // // // #include // this is an exhaustive list of SUSv3 error codes; // see http://www.opengroup.org/onlinepubs/009695399/basedefs/errno.h.html . // defined by VC errno.h and SUSv3 // (commented out so that we use VC errno.h's already correct values) #if 0 #define EPERM 1 // Operation not permitted #define ENOENT 2 // No such file or directory #define ESRCH 3 // No such process #define EINTR 4 // Interrupted system call #define EIO 5 // I/O error #define ENXIO 6 // No such device or address #define E2BIG 7 // Argument list too long #define ENOEXEC 8 // Exec format error #define EBADF 9 // Bad file number #define ECHILD 10 // No child processes #define EAGAIN 11 // Try again #define ENOMEM 12 // Out of memory #define EACCES 13 // Permission denied #define EFAULT 14 // Bad address #define EBUSY 16 // Device or resource busy #define EEXIST 17 // File exists #define ENODEV 19 // No such device #define ENOTDIR 20 // Not a directory #define EISDIR 21 // Is a directory #define EINVAL 22 // Invalid argument #define ENFILE 23 // File table overflow #define EMFILE 24 // Too many open files #define ENOTTY 25 // Not a typewriter #define EFBIG 27 // File too large #define ENOSPC 28 // No space left on device #define ESPIPE 29 // Illegal seek #define EMLINK 31 // Too many links #define EPIPE 32 // Broken pipe #define EDOM 33 // Math argument out of domain of func #define ERANGE 34 // Math result not representable #endif // defined by VC errno.h and also SUSv3 (with different values) // (commented out because we must match the values used by the compiled CRT) #if 0 #define EDEADLK 35 // Resource deadlock would occur #define ENAMETOOLONG 36 // File name too long #define ENOLCK 37 // No record locks available #define ENOSYS 38 // Function not implemented #define ENOTEMPTY 39 // Directory not empty #define EILSEQ 84 // Illegal byte sequence #endif // defined by winsock2 and also Linux (with different values) // (values derived from winsock2 WSA* constants minus WSABASEERR) // update: disabled on newer Boost versions because filesystem drags in boost/cerrno.hpp #if (!defined(BOOST_VERSION) || BOOST_VERSION <= 103401) && (!MSC_VERSION || MSC_VERSION < 1600) #define EWOULDBLOCK 35 #define EINPROGRESS 36 #define EALREADY 37 #define ENOTSOCK 38 #define EDESTADDRREQ 39 #define EMSGSIZE 40 #define EPROTOTYPE 41 #define ENOPROTOOPT 42 #define EPROTONOSUPPORT 43 #define EOPNOTSUPP 45 #define EAFNOSUPPORT 47 #define EADDRINUSE 48 #define EADDRNOTAVAIL 49 #define ENETDOWN 50 #define ENETUNREACH 51 #define ENETRESET 52 #define ECONNABORTED 53 #define ECONNRESET 54 #define ENOBUFS 55 #define EISCONN 56 #define ENOTCONN 57 #define ETIMEDOUT 60 #define ECONNREFUSED 61 #define EHOSTUNREACH 65 #define EDQUOT 69 #define ESTALE 70 #endif // defined by winsock2 but not Linux // (commented out because they're not portable) #if 0 #define ESOCKTNOSUPPORT 44 #define EPFNOSUPPORT 46 #define ESHUTDOWN 58 #define ETOOMANYREFS 59 #define ELOOP 62 #define EHOSTDOWN 64 #define EPROCLIM 67 #define EUSERS 68 #define EREMOTE 71 #endif // defined by Linux but not winsock2 // (commented out because they're not generated on Windows and thus // probably shouldn't be used) #if 0 #define ENOMSG 42 // No message of desired type #define EIDRM 43 // Identifier removed #define ENOLINK 67 // Reserved #define EPROTO 71 // Protocol error #define EMULTIHOP 72 // Reserved #define EBADMSG 74 // Not a data message #define EOVERFLOW 75 // Value too large for defined data type #define ECANCELED 125 // Operation Canceled #endif #endif // #ifndef INCLUDED_WERRNO Index: ps/trunk/source/lib/sysdep/os/win/wposix/wmman.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wmman.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wmman.h (revision 19899) @@ -1,62 +1,62 @@ -/* Copyright (c) 2011 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 * "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. */ #ifndef INCLUDED_WMMAN #define INCLUDED_WMMAN // // // // mmap prot flags #define PROT_NONE 0x00 #define PROT_READ 0x01 #define PROT_WRITE 0x02 #define PROT_EXEC 0x04 // mmap flags #define MAP_SHARED 0x01 // writes change the underlying file #define MAP_PRIVATE 0x02 // writes do not affect the file (copy-on-write) #define MAP_FIXED 0x04 // .. non-portable #define MAP_ANONYMOUS 0x10 // backed by the pagefile; fd should be -1 #define MAP_NORESERVE 0x20 // see below // note: we need a means of only "reserving" virtual address ranges // for the fixed-address expandable array mechanism. the non-portable // MAP_NORESERVE flag says that no space in the page file need be reserved. // the caller can still try to access the entire mapping, but might get // SIGBUS if there isn't enough room to commit a page. Linux currently // doesn't commit mmap-ed regions anyway, but we specify this flag to // make sure of that in the future. #define MAP_FAILED ((void*)intptr_t(-1)) extern void* mmap(void* start, size_t len, int prot, int flags, int fd, off_t offset); extern int munmap(void* start, size_t len); extern int mprotect(void* addr, size_t len, int prot); // convert POSIX PROT_* flags to their Win32 PAGE_* enumeration equivalents. LIB_API unsigned MemoryProtectionFromPosix(int prot); #endif // #ifndef INCLUDED_WMMAN Index: ps/trunk/source/lib/sysdep/os/win/wposix/wposix_types.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wposix_types.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wposix_types.h (revision 19899) @@ -1,112 +1,112 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * type definitions needed for wposix. */ #ifndef INCLUDED_WPOSIX_TYPES #define INCLUDED_WPOSIX_TYPES #include // intptr_t // // // typedef signed char int8_t; typedef short int16_t; // already defined by MinGW #if MSC_VERSION typedef int int32_t; #endif #if MSC_VERSION || ICC_VERSION || LCC_VERSION typedef __int64 int64_t; #else typedef long long int64_t; #endif typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; #if MSC_VERSION || ICC_VERSION || LCC_VERSION typedef unsigned __int64 uint64_t; #else typedef unsigned long long uint64_t; #endif // note: we used to define [u]intptr_t here (if not already done). // however, it's not necessary with VC7 and later, and the compiler's // definition squelches 'pointer-to-int truncation' warnings, so don't. #if MSC_VERSION >= 1600 # include #else // define them ourselves # define INT16_MAX 32767 # define INT16_MIN (-INT16_MAX-1) # define UINT16_MAX 65536u # define UINT16_MIN 0u # define INT32_MAX 2147483647 # define INT32_MIN (-INT32_MAX-1) # define UINT32_MAX 4294967295u # define UINT32_MIN 0u #endif // // // typedef intptr_t ssize_t; // prevent wxWidgets from (incompatibly) redefining it #define HAVE_SSIZE_T // VC9 defines off_t as long, but we need 64-bit file offsets even in // 32-bit builds. to avoid conflicts, we have to define _OFF_T_DEFINED, // which promises _off_t has also been defined. since that's used by // CRT headers, we have to define it too, but must use the original // long type to avoid breaking struct stat et al. typedef __int64 off_t; typedef long _off_t; #define _OFF_T_DEFINED // // // // Win32 MAX_PATH is 260; our number may be a bit more efficient. #define PATH_MAX 256u #if OS_WIN # ifndef SIZE_MAX // VC2005 already defines this in limits.h # define SIZE_MAX 0xFFFFFFFF # endif #else # define SIZE_MAX 0xFFFFFFFFFFFFFFFF #endif #endif // #ifndef INCLUDED_WPOSIX_TYPES Index: ps/trunk/source/lib/sysdep/os/win/wposix/wtime.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wtime.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wtime.h (revision 19899) @@ -1,74 +1,74 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * emulate POSIX high resolution timer on Windows. */ #ifndef INCLUDED_WTIME #define INCLUDED_WTIME // // // typedef unsigned long useconds_t; typedef long suseconds_t; // // // LIB_API unsigned sleep(unsigned sec); LIB_API int usleep(useconds_t us); // // // typedef enum { // in our implementation, these actually do the same thing // (a timer that latches the system time at startup and uses the // monotonic HRT to add elapsed time since then) CLOCK_REALTIME, CLOCK_MONOTONIC } clockid_t; // POSIX realtime clock_* struct timespec { time_t tv_sec; long tv_nsec; }; extern int nanosleep(const struct timespec* rqtp, struct timespec* rmtp); extern int clock_gettime(clockid_t clock, struct timespec* ts); extern int clock_getres(clockid_t clock, struct timespec* res); LIB_API char* strptime(const char* buf, const char* format, struct tm* timeptr); #endif // #ifndef INCLUDED_WTIME Index: ps/trunk/source/lib/sysdep/os/win/wprofiler.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wprofiler.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wprofiler.cpp (revision 19899) @@ -1,242 +1,242 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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" #if 0 // we need a means of measuring performance, since it is hard to predict and // depends on many factors. to cover a wider range of configurations, this // must also be possible on end-user systems lacking specialized developer // tools. therefore, we must ship our own implementation; this complements // Intel VTune et al. // // there are 3 approaches to the problem: // - single-step analysis logs every executed instruction. very thorough, but // intolerably slow (~1000x) and not suitable for performance measurement. // - intrusive measuring tracks execution time of explicitly marked // functions or 'zones'. more complex, requires adding code, and // inaccurate when thread switches are frequent. // - IP sampling records the current instruction pointer at regular // intervals; slow sections of code will over time appear more often. // not exact, but simple and low-overhead. // // we implement IP sampling due to its simplicity. an intrusive approach // might also be added later to account for performance per-module // (helps spot the culprit in case hotspots are called from multiple sites). // on Windows, we retrieve the current IP with GetThreadContext. dox require // this to happen from another thread, and for the target to be suspended // (now enforced by XP SP2). this leads to all sorts of problems: // - if the suspended thread was dispatching an exception in the kernel, // register state may be a mix between the correct values and // those captured from the exception. // - if running on Win9x with real-mode drivers, interrupts may interfere // with GetThreadContext. however, it's not supported anyway due to other // deficiencies (e.g. lack of proper mmap support). // - the suspended thread may be holding locks; we need to be extremely // careful to avoid deadlock! many win api functions acquire locks in // non-obvious ways. static HANDLE prof_target_thread; static pthread_t prof_thread; // delay [ms] between samples. OS sleep timers usually provide only // ms resolution. increasing interval reduces overhead and accuracy. static const int PROFILE_INTERVAL_MS = 1; static uintptr_t get_target_pc() { DWORD ret; HANDLE hThread = prof_target_thread; // convenience ret = SuspendThread(hThread); if(ret == (DWORD)-1) { DEBUG_WARN_ERR(ERR::LOGIC); // get_target_pc: SuspendThread failed return 0; } // note: we don't need to call more than once: this increments a DWORD // 'suspend count'; target is guaranteed to be suspended unless // the function failed. ///////////////////////////////////////////// // be VERY CAREFUL to avoid anything that may acquire a lock until // after ResumeThread! this includes locks taken by the OS, // e.g. malloc -> heap or GetProcAddress -> loader. // reason is, if the target thread was holding a lock we try to // acquire here, a classic deadlock results. uintptr_t pc = 0; // => will return 0 if GetThreadContext fails CONTEXT context; context.ContextFlags = CONTEXT_CONTROL; if(GetThreadContext(hThread, &context)) pc = context.PC_; ///////////////////////////////////////////// ret = ResumeThread(hThread); ENSURE(ret != 0); // don't fail (we have a valid PC), but warn return pc; } static pthread_t thread; static sem_t exit_flag; static void* prof_thread_func(void* UNUSED(data)) { debug_SetThreadName("eip_sampler"); const long _1e6 = 1000000; const long _1e9 = 1000000000; for(;;) { // calculate absolute timeout for sem_timedwait struct timespec abs_timeout; clock_gettime(CLOCK_REALTIME, &abs_timeout); abs_timeout.tv_nsec += PROFILE_INTERVAL_MS * _1e6; // .. handle nanosecond wraparound (must not be > 1000m) if(abs_timeout.tv_nsec >= _1e9) { abs_timeout.tv_nsec -= _1e9; abs_timeout.tv_sec++; } errno = 0; // if we acquire the semaphore, exit was requested. if(sem_timedwait(&exit_flag, &abs_timeout) == 0) break; // actual error: warn if(errno != ETIMEDOUT) DEBUG_WARN_ERR(ERR::LOGIC); // wpcu prof_thread_func: sem_timedwait failed uintptr_t pc = get_target_pc(); UNUSED2(pc); // ADD TO LIST } return 0; } // call from thread that is to be profiled Status prof_start() { // we need a real HANDLE to the target thread for use with // Suspend|ResumeThread and GetThreadContext. // alternative: DuplicateHandle on the current thread pseudo-HANDLE. // this way is a bit more obvious/simple. const DWORD access = THREAD_GET_CONTEXT|THREAD_SUSPEND_RESUME; HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId()); if(hThread == INVALID_HANDLE_VALUE) WARN_RETURN(ERR::FAIL); prof_target_thread = hThread; sem_init(&exit_flag, 0, 0); pthread_create(&thread, 0, prof_thread_func, 0); return INFO::OK; } Status prof_shutdown() { WARN_IF_FALSE(CloseHandle(prof_target_thread)); return INFO::OK; } /* open question: how to store the EIP values returned? some background: the mechanism above churns out an EIP value (may be in our process, but might also be bogus); we need to store it somehow pending analysis. when done with the current run, we'd want to resolve EIP -> function name, source file etc. (rather slow, so don't do it at runtime). so, how to store it in the meantime? 2 possibilities: - simple array/vector of addresses (of course optimized to reduce allocs) - fixed size array of 'bins' (range of addresses; may be as fine as 1 byte); each bin has a counter which is incremented when the bin's corresponding address has been hit. it's a size tradeoff here; for simple runs of < 1 min (60,000 ms), #1 would use 240kb of mem. #2 requires sizeof_whole_program * bytes_per_counter up front, and has problems measuring DLLs (we'd have to explicitly map the DLL address range into a bin - ugh). however, if we ever want to test for say an hour (improves accuracy of profiling due to larger sample size), #1 would guzzle 15mb of memory. hm, another idea would be to write out #1's list of addresses periodically. to make sure the disk I/O doesn't come at a bad time, we could have the main thread call into the profiler and request it write out at that time. this would require extreme caution to avoid the deadlock problem, but looks doable. -------- [2] ---------- realistic profiler runs will take up to an hour. writing out to disk would work: could have main thread call back. that and adding EIP to list would be atomic (locked). BUT: large amount of data, that's bad (loading at 30mb/s => 500ms load time alone) problem with enumerating all symbols at startup: how do we enum all DLLs? hybrid idea: std::map of EIPs. we don't build the map at startup, but add when first seen and subsequently increment counter stored there. problem: uses more memory/slower access than list. would have to make sure EIPs are reused. to help that, could quantize down to 4 byte (or so) bins. accessing debug information at runtime to determine function length is too slow. maybe some weird data structure: one bucket controls say 256 bytes of code bucket is found by stripping off lower 8 bits. then, store only the hit count for that byte. where's the savings over normal count? TODO: what if the thread is sleeping at the time we query EIP? can't detect that - suspend count is only set by SuspendThread do we want to report that point (it's good to know), or try to access other threads? TODO split off target thread / get PC into sysdep; profiler thread is portable! at exit: resolve list to hotspots probably hard; a start would be just the function in which the address is, then hit count ========================================== */ #endif Index: ps/trunk/source/lib/sysdep/os/win/whrt/whrt.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/whrt/whrt.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/whrt/whrt.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * Windows High Resolution Timer */ #ifndef INCLUDED_WHRT #define INCLUDED_WHRT extern double whrt_Resolution(); extern double whrt_Time(); #endif // #ifndef INCLUDED_WHRT Index: ps/trunk/source/lib/sysdep/os/win/wiocp.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wiocp.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wiocp.cpp (revision 19899) @@ -1,56 +1,56 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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 "lib/sysdep/os/win/wiocp.h" #include "lib/file/file.h" // ERR::IO #include "lib/sysdep/os/win/wutil.h" void AttachToCompletionPort(HANDLE hFile, HANDLE& hIOCP, ULONG_PTR key, DWORD numConcurrentThreads) { WinScopedPreserveLastError s; // CreateIoCompletionPort // (when called for the first time, ends up creating hIOCP) hIOCP = CreateIoCompletionPort(hFile, hIOCP, key, numConcurrentThreads); ENSURE(wutil_IsValidHandle(hIOCP)); } Status PollCompletionPort(HANDLE hIOCP, DWORD timeout, DWORD& bytesTransferred, ULONG_PTR& key, OVERLAPPED*& ovl) { if(hIOCP == 0) return ERR::INVALID_HANDLE; // NOWARN (happens if called before the first Attach) WinScopedPreserveLastError s; bytesTransferred = 0; key = 0; ovl = 0; if(GetQueuedCompletionStatus(hIOCP, &bytesTransferred, &key, &ovl, timeout)) return INFO::OK; const Status ret = StatusFromWin(); if(ret == ERR::AGAIN || ret == ERR::ABORTED) // avoid polluting last error SetLastError(0); return ret; // NOWARN (let caller decide what to do) } Index: ps/trunk/source/lib/sysdep/os/win/wposix/no_crt_posix.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/no_crt_posix.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/no_crt_posix.h (revision 19899) @@ -1,39 +1,39 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /** * see rationale in wposix.h. * prevent subsequent includes of CRT headers (e.g. ) from * interfering with previous declarations made by wposix headers (e.g. open). * this is accomplished by \#defining their include guards. * note: \#include "crt_posix.h" can undo the effects of this header and * pull in those headers. **/ #ifndef _INC_IO // include guard # define _INC_IO # define WPOSIX_DEFINED_IO_INCLUDE_GUARD #endif #ifndef _INC_DIRECT // include guard # define _INC_DIRECT # define WPOSIX_DEFINED_DIRECT_INCLUDE_GUARD #endif Index: ps/trunk/source/lib/sysdep/os/win/wposix/wdlfcn.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wdlfcn.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wdlfcn.h (revision 19899) @@ -1,42 +1,42 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_WDLFCN #define INCLUDED_WDLFCN // // // // these have no meaning for the Windows GetProcAddress implementation, // so they are ignored but provided for completeness. #define RTLD_LAZY 0x01 #define RTLD_NOW 0x02 #define RTLD_GLOBAL 0x04 // semantics are unsupported, so complain if set. #define RTLD_LOCAL 0x08 LIB_API int dlclose(void* handle); LIB_API char* dlerror(); LIB_API void* dlopen(const char* so_name, int flags); LIB_API void* dlsym(void* handle, const char* sym_name); #endif // #ifndef INCLUDED_WDLFCN Index: ps/trunk/source/lib/sysdep/os/win/wposix/wmman.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wmman.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wmman.cpp (revision 19899) @@ -1,232 +1,232 @@ -/* Copyright (c) 2011 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 * "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 "lib/sysdep/os/win/wposix/wmman.h" #include "lib/sysdep/os/win/wposix/wposix_internal.h" #include "lib/sysdep/os/win/wposix/crt_posix.h" // _get_osfhandle unsigned MemoryProtectionFromPosix(int prot) { if(prot == PROT_NONE) return PAGE_NOACCESS; // this covers all 8 combinations of read|write|exec switch(prot & (PROT_READ|PROT_WRITE|PROT_EXEC)) { case PROT_READ: return PAGE_READONLY; case PROT_WRITE: // not supported by Win32; POSIX allows us to also grant read access. return PAGE_READWRITE; case PROT_EXEC: return PAGE_EXECUTE; case PROT_READ|PROT_WRITE: return PAGE_READWRITE; case PROT_READ|PROT_EXEC: return PAGE_EXECUTE_READ; case PROT_WRITE|PROT_EXEC: // not supported by Win32; POSIX allows us to also grant read access. return PAGE_EXECUTE_READWRITE; case PROT_READ|PROT_WRITE|PROT_EXEC: return PAGE_EXECUTE_READWRITE; default: // none set DEBUG_WARN_ERR(ERR::INVALID_FLAG); return PAGE_NOACCESS; } // UNREACHABLE } //----------------------------------------------------------------------------- // memory mapping //----------------------------------------------------------------------------- int mprotect(void* addr, size_t len, int prot) { const DWORD newProtect = (DWORD)MemoryProtectionFromPosix(prot); DWORD oldProtect; // required by VirtualProtect const BOOL ok = VirtualProtect(addr, len, newProtect, &oldProtect); WARN_IF_FALSE(ok); return ok? 0 : -1; } // called when flags & MAP_ANONYMOUS static Status mmap_mem(void* start, size_t len, int prot, int flags, int fd, void** pp) { // sanity checks. we don't care about these but enforce them to // ensure callers are compatible with mmap. // .. MAP_ANONYMOUS is documented to require this. ENSURE(fd == -1); // .. if MAP_SHARED, writes are to change "the underlying [mapped] // object", but there is none here (we're backed by the page file). ENSURE(!(flags & MAP_SHARED)); // see explanation at MAP_NORESERVE definition. bool want_commit = (prot != PROT_NONE && !(flags & MAP_NORESERVE)); // decommit a given area (leaves its address space reserved) if(!want_commit && start != 0 && flags & MAP_FIXED) { MEMORY_BASIC_INFORMATION mbi; if(!VirtualQuery(start, &mbi, sizeof(mbi))) WARN_RETURN(StatusFromWin()); if(mbi.State == MEM_COMMIT) { WARN_IF_FALSE(VirtualFree(start, len, MEM_DECOMMIT)); *pp = 0; // make sure *pp won't be misinterpreted as an error cassert(MAP_FAILED); return INFO::OK; } } const DWORD allocationType = want_commit? MEM_COMMIT : MEM_RESERVE; const DWORD protect = (DWORD)MemoryProtectionFromPosix(prot); void* p = VirtualAlloc(start, len, allocationType, protect); if(!p) { debug_printf("wmman: VirtualAlloc(%p, 0x%I64X) failed\n", start, len); WARN_RETURN(ERR::NO_MEM); } *pp = p; return INFO::OK; } // given mmap prot and flags, output protection/access values for use with // CreateFileMapping / MapViewOfFile. they only support read-only, // read/write and copy-on-write, so we dumb it down to that and later // set the correct (and more restrictive) permission via mprotect. static Status DecodeFlags(int prot, int flags, DWORD& protect, DWORD& access) { // ensure exactly one of (MAP_SHARED, MAP_PRIVATE) is specified switch(flags & (MAP_SHARED|MAP_PRIVATE)) { case 0: case MAP_SHARED|MAP_PRIVATE: WARN_RETURN(ERR::INVALID_PARAM); default:; } if(prot & PROT_WRITE) { // determine write behavior: (whether they change the underlying file) if(flags & MAP_SHARED) // writes affect the file { protect = PAGE_READWRITE; access = FILE_MAP_WRITE; // read and write } else // copy on write (file remains unchanged) { protect = PAGE_WRITECOPY; access = FILE_MAP_COPY; } } else { protect = PAGE_READONLY; access = FILE_MAP_READ; } return INFO::OK; } static Status mmap_file(void* start, size_t len, int prot, int flags, int fd, off_t ofs, void** pp) { WinScopedPreserveLastError s; ENSURE(fd != -1); // handled by mmap_mem HANDLE hFile = HANDLE_from_intptr(_get_osfhandle(fd)); if(hFile == INVALID_HANDLE_VALUE) WARN_RETURN(ERR::INVALID_HANDLE); // MapViewOfFileEx will fail if the "suggested" base address is // nonzero but cannot be honored, so wipe out unless MAP_FIXED. if(!(flags & MAP_FIXED)) start = 0; // choose protection and access rights for CreateFileMapping / // MapViewOfFile. these are weaker than what PROT_* allows and // are augmented below by subsequently mprotect-ing. DWORD protect; DWORD access; RETURN_STATUS_IF_ERR(DecodeFlags(prot, flags, protect, access)); const HANDLE hMap = CreateFileMapping(hFile, 0, protect, 0, 0, 0); if(!hMap) WARN_RETURN(ERR::NO_MEM); void* p = MapViewOfFileEx(hMap, access, u64_hi(ofs), u64_lo(ofs), (SIZE_T)len, start); // ensure we got the requested address if MAP_FIXED was passed. ENSURE(!(flags & MAP_FIXED) || (p == start)); // free the mapping object now, so that we don't have to hold on to its // handle until munmap(). it's not actually released yet due to the // reference held by MapViewOfFileEx (if it succeeded). CloseHandle(hMap); // map failed; bail now to avoid "restoring" the last error value. if(!p) WARN_RETURN(ERR::NO_MEM); // enforce the desired (more restrictive) protection. (void)mprotect(p, len, prot); *pp = p; return INFO::OK; } void* mmap(void* start, size_t len, int prot, int flags, int fd, off_t ofs) { ASSERT(len != 0); void* p; Status status; if(flags & MAP_ANONYMOUS) status = mmap_mem(start, len, prot, flags, fd, &p); else status = mmap_file(start, len, prot, flags, fd, ofs, &p); if(status < 0) { errno = ErrnoFromStatus(status); return MAP_FAILED; // NOWARN - already done } return p; } int munmap(void* start, size_t UNUSED(len)) { // UnmapViewOfFile checks if start was returned by MapViewOfFile*; // if not, it will fail. BOOL ok = UnmapViewOfFile(start); if(!ok) // VirtualFree requires dwSize to be 0 (entire region is released). ok = VirtualFree(start, 0, MEM_RELEASE); WARN_IF_FALSE(ok); return ok? 0 : -1; } Index: ps/trunk/source/lib/sysdep/os/win/wposix/wposix_internal.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wposix_internal.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wposix_internal.h (revision 19899) @@ -1,37 +1,37 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_WPOSIX_INTERNAL #define INCLUDED_WPOSIX_INTERNAL #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/winit.h" #include "lib/sysdep/os/win/wutil.h" // cast intptr_t to HANDLE; centralized for easier changing, e.g. avoiding // warnings. i = -1 converts to INVALID_HANDLE_VALUE (same value). inline HANDLE HANDLE_from_intptr(intptr_t i) { return (HANDLE)((char*)0 + i); } #endif // #ifndef INCLUDED_WPOSIX_INTERNAL Index: ps/trunk/source/lib/sysdep/os/win/wposix/wtime.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wtime.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wtime.cpp (revision 19899) @@ -1,622 +1,622 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * emulate POSIX time functionality on Windows. */ // note: clock_gettime et al. have been removed. callers should use the // WHRT directly, rather than needlessly translating s -> ns -> s, // which costs time and accuracy. #include "precompiled.h" #include "lib/sysdep/os/win/wposix/wtime.h" #include "lib/sysdep/os/win/wposix/wposix_internal.h" #include "lib/sysdep/os/win/whrt/whrt.h" WINIT_REGISTER_MAIN_INIT(wtime_Init); // whrt -> wtime // NT system time and FILETIME are hectonanoseconds since Jan. 1, 1601 UTC. // SYSTEMTIME is a struct containing month, year, etc. static const long _1e3 = 1000; static const long _1e6 = 1000000; static const long _1e7 = 10000000; static const i64 _1e9 = 1000000000; // // FILETIME -> time_t routines; used by wposix filetime_to_time_t wrapper. // // hectonanoseconds between Windows and POSIX epoch static const u64 posix_epoch_hns = 0x019DB1DED53E8000; // this function avoids the pitfall of casting FILETIME* to u64*, // which is not safe due to differing alignment guarantees! // on some platforms, that would result in an exception. static u64 u64_from_FILETIME(const FILETIME* ft) { return u64_from_u32(ft->dwHighDateTime, ft->dwLowDateTime); } // convert UTC FILETIME to seconds-since-1970 UTC: // we just have to subtract POSIX epoch and scale down to units of seconds. // // used by wfilesystem. // // note: RtlTimeToSecondsSince1970 isn't officially documented, // so don't use that. time_t wtime_utc_filetime_to_time_t(FILETIME* ft) { u64 hns = u64_from_FILETIME(ft); u64 s = (hns - posix_epoch_hns) / _1e7; return (time_t)(s & 0xFFFFFFFF); } //----------------------------------------------------------------------------- // system clock at startup [nanoseconds since POSIX epoch] // note: the HRT starts at 0; any increase by the time we get here // just makes our notion of the start time more accurate) static i64 stInitial_ns; static void LatchInitialSystemTime() { FILETIME ft; GetSystemTimeAsFileTime(&ft); const u64 hns = u64_from_FILETIME(&ft); stInitial_ns = (hns - posix_epoch_hns) * 100; } // return nanoseconds since POSIX epoch. // algorithm: add current HRT value to the startup system time static i64 CurrentSystemTime_ns() { const i64 ns = stInitial_ns + i64(whrt_Time() * _1e9); return ns; } static timespec TimespecFromNs(i64 ns) { timespec ts; ts.tv_sec = (time_t)((ns / _1e9) & 0xFFFFFFFF); ts.tv_nsec = (long)(ns % _1e9); return ts; } static size_t MsFromTimespec(const timespec& ts) { i64 ms = ts.tv_sec; // avoid overflow ms *= _1e3; ms += ts.tv_nsec / _1e6; return size_t(ms); } //----------------------------------------------------------------------------- int clock_gettime(clockid_t clock, struct timespec* ts) { ENSURE(clock == CLOCK_REALTIME || clock == CLOCK_MONOTONIC); const i64 ns = CurrentSystemTime_ns(); *ts = TimespecFromNs(ns); return 0; } int clock_getres(clockid_t clock, struct timespec* ts) { ENSURE(clock == CLOCK_REALTIME || clock == CLOCK_MONOTONIC); const double resolution = whrt_Resolution(); const i64 ns = i64(resolution * 1e9); *ts = TimespecFromNs(ns); return 0; } int nanosleep(const struct timespec* rqtp, struct timespec* /* rmtp */) { const DWORD ms = (DWORD)MsFromTimespec(*rqtp); if(ms) Sleep(ms); return 0; } unsigned sleep(unsigned sec) { // warn if overflow would result (it would be insane to ask for // such lengthy sleep timeouts, but still) ENSURE(sec < std::numeric_limits::max()/1000); const DWORD ms = sec * 1000; if(ms) Sleep(ms); return sec; } int usleep(useconds_t us) { ENSURE(us < _1e6); const DWORD ms = us/1000; if(ms) Sleep(ms); return 0; } //----------------------------------------------------------------------------- // strptime from NetBSD /* * Copyright (c) 1999 Kungliga Tekniska Hogskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of KTH nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ static const char *abb_weekdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL }; static const char *full_weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", NULL }; static const char *abb_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; static const char *full_month[] = { "January", "February", "Mars", "April", "May", "June", "July", "August", "September", "October", "November", "December", NULL, }; static const char *ampm[] = { "am", "pm", NULL }; /* * Try to match `*buf' to one of the strings in `strs'. Return the * index of the matching string (or -1 if none). Also advance buf. */ static int match_string (const char **buf, const char **strs) { int i = 0; for (i = 0; strs[i] != NULL; ++i) { size_t len = strlen (strs[i]); if (strncasecmp (*buf, strs[i], len) == 0) { *buf += len; return i; } } return -1; } /* * tm_year is relative this year */ const int tm_year_base = 1900; /* * Return TRUE iff `year' was a leap year. */ static int is_leap_year (int year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } /* * Return the weekday [0,6] (0 = Sunday) of the first day of `year' */ static int first_day (int year) { int ret = 4; for (; year > 1970; --year) ret = (ret + 365 + is_leap_year (year) ? 1 : 0) % 7; return ret; } /* * Set `timeptr' given `wnum' (week number [0, 53]) */ static void set_week_number_sun (struct tm *timeptr, int wnum) { int fday = first_day (timeptr->tm_year + tm_year_base); timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday; if (timeptr->tm_yday < 0) { timeptr->tm_wday = fday; timeptr->tm_yday = 0; } } /* * Set `timeptr' given `wnum' (week number [0, 53]) */ static void set_week_number_mon (struct tm *timeptr, int wnum) { int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday; if (timeptr->tm_yday < 0) { timeptr->tm_wday = (fday + 1) % 7; timeptr->tm_yday = 0; } } /* * Set `timeptr' given `wnum' (week number [0, 53]) */ static void set_week_number_mon4 (struct tm *timeptr, int wnum) { int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7; int offset = 0; if (fday < 4) offset += 7; timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday; if (timeptr->tm_yday < 0) { timeptr->tm_wday = fday; timeptr->tm_yday = 0; } } /* * */ char * strptime (const char *buf, const char *format, struct tm *timeptr) { char c; for (; (c = *format) != '\0'; ++format) { char *s; int ret; if (isspace (c)) { while (isspace (*buf)) ++buf; } else if (c == '%' && format[1] != '\0') { c = *++format; if (c == 'E' || c == 'O') c = *++format; switch (c) { case 'A' : ret = match_string (&buf, full_weekdays); if (ret < 0) return NULL; timeptr->tm_wday = ret; break; case 'a' : ret = match_string (&buf, abb_weekdays); if (ret < 0) return NULL; timeptr->tm_wday = ret; break; case 'B' : ret = match_string (&buf, full_month); if (ret < 0) return NULL; timeptr->tm_mon = ret; break; case 'b' : case 'h' : ret = match_string (&buf, abb_month); if (ret < 0) return NULL; timeptr->tm_mon = ret; break; case 'C' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_year = (ret * 100) - tm_year_base; buf = s; break; case 'c' : abort (); case 'D' : /* %m/%d/%y */ s = strptime (buf, "%m/%d/%y", timeptr); if (s == NULL) return NULL; buf = s; break; case 'd' : case 'e' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_mday = ret; buf = s; break; case 'H' : case 'k' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_hour = ret; buf = s; break; case 'I' : case 'l' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; if (ret == 12) timeptr->tm_hour = 0; else timeptr->tm_hour = ret; buf = s; break; case 'j' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_yday = ret - 1; buf = s; break; case 'm' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_mon = ret - 1; buf = s; break; case 'M' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_min = ret; buf = s; break; case 'n' : if (*buf == '\n') ++buf; else return NULL; break; case 'p' : ret = match_string (&buf, ampm); if (ret < 0) return NULL; if (timeptr->tm_hour == 0) { if (ret == 1) timeptr->tm_hour = 12; } else timeptr->tm_hour += 12; break; case 'r' : /* %I:%M:%S %p */ s = strptime (buf, "%I:%M:%S %p", timeptr); if (s == NULL) return NULL; buf = s; break; case 'R' : /* %H:%M */ s = strptime (buf, "%H:%M", timeptr); if (s == NULL) return NULL; buf = s; break; case 'S' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_sec = ret; buf = s; break; case 't' : if (*buf == '\t') ++buf; else return NULL; break; case 'T' : /* %H:%M:%S */ case 'X' : s = strptime (buf, "%H:%M:%S", timeptr); if (s == NULL) return NULL; buf = s; break; case 'u' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_wday = ret - 1; buf = s; break; case 'w' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_wday = ret; buf = s; break; case 'U' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; set_week_number_sun (timeptr, ret); buf = s; break; case 'V' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; set_week_number_mon4 (timeptr, ret); buf = s; break; case 'W' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; set_week_number_mon (timeptr, ret); buf = s; break; case 'x' : s = strptime (buf, "%Y:%m:%d", timeptr); if (s == NULL) return NULL; buf = s; break; case 'y' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; if (ret < 70) timeptr->tm_year = 100 + ret; else timeptr->tm_year = ret; buf = s; break; case 'Y' : ret = strtol (buf, &s, 10); if (s == buf) return NULL; timeptr->tm_year = ret - tm_year_base; buf = s; break; case 'Z' : abort (); case '\0' : --format; /* FALLTHROUGH */ case '%' : if (*buf == '%') ++buf; else return NULL; break; default : if (*buf == '%' || *++buf == c) ++buf; else return NULL; break; } } else { if (*buf == c) ++buf; else return NULL; } } return (char *)buf; } //----------------------------------------------------------------------------- static Status wtime_Init() { LatchInitialSystemTime(); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wposix/wutsname.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wutsname.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wutsname.h (revision 19899) @@ -1,42 +1,42 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_WUTSNAME #define INCLUDED_WUTSNAME // // // struct utsname { char sysname[9]; // Name of this implementation of the operating system. char nodename[16]; // Name of this node within an implementation-defined communications network. char release[9]; // Current release level of this implementation. char version[16]; // Current version level of this release. char machine[9]; // Name of the hardware type on which the system is running. }; LIB_API int uname(struct utsname*); #endif // #ifndef INCLUDED_WUTSNAME Index: ps/trunk/source/lib/sysdep/os/win/wutil.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wutil.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wutil.cpp (revision 19899) @@ -1,580 +1,580 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * various Windows-specific utilities */ #include "precompiled.h" #include "lib/sysdep/os/win/wutil.h" #include #include // __argc #include "lib/file/file.h" #include "lib/posix/posix.h" #include "lib/sysdep/sysdep.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wdbg.h" // wdbg_assert #include "lib/sysdep/os/win/winit.h" #include // SHGetFolderPath WINIT_REGISTER_EARLY_INIT(wutil_Init); WINIT_REGISTER_LATE_SHUTDOWN(wutil_Shutdown); //----------------------------------------------------------------------------- // safe allocator // may be used independently of libc malloc // (in particular, before _cinit and while calling static dtors). // used by wpthread critical section code. void* wutil_Allocate(size_t size) { const DWORD flags = HEAP_ZERO_MEMORY; return HeapAlloc(GetProcessHeap(), flags, size); } void wutil_Free(void* p) { const DWORD flags = 0; HeapFree(GetProcessHeap(), flags, p); } //----------------------------------------------------------------------------- // locks // several init functions are before called before _cinit. // POSIX static mutex init may not have been done by then, // so we need our own lightweight functions. static CRITICAL_SECTION cs[NUM_CS]; static bool cs_valid; void wutil_Lock(WinLockId id) { if(!cs_valid) return; EnterCriticalSection(&cs[id]); } void wutil_Unlock(WinLockId id) { if(!cs_valid) return; LeaveCriticalSection(&cs[id]); } bool wutil_IsLocked(WinLockId id) { if(!cs_valid) return false; const BOOL successfullyEntered = TryEnterCriticalSection(&cs[id]); if(!successfullyEntered) return true; // still locked LeaveCriticalSection(&cs[id]); return false; // probably not locked } static void InitLocks() { for(int i = 0; i < NUM_CS; i++) InitializeCriticalSection(&cs[i]); cs_valid = true; } static void ShutdownLocks() { cs_valid = false; for(int i = 0; i < NUM_CS; i++) DeleteCriticalSection(&cs[i]); memset(cs, 0, sizeof(cs)); } //----------------------------------------------------------------------------- // error codes // only call after a Win32 function indicates failure. Status StatusFromWin() { switch(GetLastError()) { case ERROR_BUSY: case WAIT_TIMEOUT: return ERR::AGAIN; case ERROR_OPERATION_ABORTED: return ERR::ABORTED; case ERROR_INVALID_HANDLE: return ERR::INVALID_HANDLE; case ERROR_INSUFFICIENT_BUFFER: return ERR::INVALID_SIZE; case ERROR_INVALID_PARAMETER: case ERROR_BAD_ARGUMENTS: return ERR::INVALID_PARAM; case ERROR_OUTOFMEMORY: case ERROR_NOT_ENOUGH_MEMORY: return ERR::NO_MEM; case ERROR_NOT_SUPPORTED: case ERROR_CALL_NOT_IMPLEMENTED: case ERROR_PROC_NOT_FOUND: return ERR::NOT_SUPPORTED; case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return ERR::FILE_NOT_FOUND; case ERROR_ACCESS_DENIED: return ERR::FILE_ACCESS; default: return ERR::FAIL; } } //----------------------------------------------------------------------------- // command line // copy of GetCommandLine string. will be tokenized and then referenced by // the argv pointers. static wchar_t* argvContents; int s_argc = 0; wchar_t** s_argv = 0; static void ReadCommandLine() { const wchar_t* commandLine = GetCommandLineW(); // (this changes as quotation marks are removed) size_t numChars = wcslen(commandLine); argvContents = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, (numChars+1)*sizeof(wchar_t)); wcscpy_s(argvContents, numChars+1, commandLine); // first pass: tokenize string and count number of arguments bool ignoreSpace = false; for(size_t i = 0; i < numChars; i++) { switch(argvContents[i]) { case '"': ignoreSpace = !ignoreSpace; // strip the " character memmove(argvContents+i, argvContents+i+1, (numChars-i)*sizeof(wchar_t)); numChars--; i--; break; case ' ': if(!ignoreSpace) { argvContents[i] = '\0'; s_argc++; } break; } } s_argc++; // have argv entries point into the tokenized string s_argv = (wchar_t**)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, s_argc*sizeof(wchar_t*)); wchar_t* nextArg = argvContents; for(int i = 0; i < s_argc; i++) { s_argv[i] = nextArg; nextArg += wcslen(nextArg)+1; } } int wutil_argc() { return s_argc; } wchar_t** wutil_argv() { ENSURE(s_argv); return s_argv; } static void FreeCommandLine() { HeapFree(GetProcessHeap(), 0, s_argv); HeapFree(GetProcessHeap(), 0, argvContents); } bool wutil_HasCommandLineArgument(const wchar_t* arg) { for(int i = 0; i < s_argc; i++) { if(!wcscmp(s_argv[i], arg)) return true; } return false; } //----------------------------------------------------------------------------- // directories // (NB: wutil_Init is called before static ctors => use placement new) static OsPath* systemPath; static OsPath* executablePath; static OsPath* localAppdataPath; static OsPath* roamingAppdataPath; static OsPath* personalPath; const OsPath& wutil_SystemPath() { return *systemPath; } const OsPath& wutil_ExecutablePath() { return *executablePath; } const OsPath& wutil_LocalAppdataPath() { return *localAppdataPath; } const OsPath& wutil_RoamingAppdataPath() { return *roamingAppdataPath; } const OsPath& wutil_PersonalPath() { return *personalPath; } // Helper to avoid duplicating this setup static OsPath* GetFolderPath(int csidl) { HWND hwnd = 0; // ignored unless a dial-up connection is needed to access the folder HANDLE token = 0; wchar_t path[MAX_PATH]; // mandated by SHGetFolderPathW const HRESULT ret = SHGetFolderPathW(hwnd, csidl, token, 0, path); if (!SUCCEEDED(ret)) { debug_printf("SHGetFolderPathW failed with HRESULT = 0x%08lx for csidl = 0x%04x\n", ret, csidl); debug_warn("SHGetFolderPathW failed (see debug output)"); } if(GetLastError() == ERROR_NO_TOKEN) // avoid polluting last error SetLastError(0); return new(wutil_Allocate(sizeof(OsPath))) OsPath(path); } static void GetDirectories() { WinScopedPreserveLastError s; // system directory { const UINT length = GetSystemDirectoryW(0, 0); ENSURE(length != 0); std::wstring path(length, '\0'); const UINT charsWritten = GetSystemDirectoryW(&path[0], length); ENSURE(charsWritten == length-1); systemPath = new(wutil_Allocate(sizeof(OsPath))) OsPath(path); } // executable's directory executablePath = new(wutil_Allocate(sizeof(OsPath))) OsPath(sys_ExecutablePathname().Parent()); // roaming application data roamingAppdataPath = GetFolderPath(CSIDL_APPDATA); // local application data localAppdataPath = GetFolderPath(CSIDL_LOCAL_APPDATA); // my documents personalPath = GetFolderPath(CSIDL_PERSONAL); } static void FreeDirectories() { systemPath->~OsPath(); wutil_Free(systemPath); executablePath->~OsPath(); wutil_Free(executablePath); localAppdataPath->~OsPath(); wutil_Free(localAppdataPath); roamingAppdataPath->~OsPath(); wutil_Free(roamingAppdataPath); personalPath->~OsPath(); wutil_Free(personalPath); } //----------------------------------------------------------------------------- // user32 fix // HACK: make sure a reference to user32 is held, even if someone // decides to delay-load it. this fixes bug #66, which was the // Win32 mouse cursor (set via user32!SetCursor) appearing as a // black 32x32(?) rectangle. the underlying cause was as follows: // powrprof.dll was the first client of user32, causing it to be // loaded. after we were finished with powrprof, we freed it, in turn // causing user32 to unload. later code would then reload user32, // which apparently terminally confused the cursor implementation. // // since we hold a reference here, user32 will never unload. // of course, the benefits of delay-loading are lost for this DLL, // but that is unavoidable. it is safer to force loading it, rather // than documenting the problem and asking it not be delay-loaded. static HMODULE hUser32Dll; static void ForciblyLoadUser32Dll() { hUser32Dll = LoadLibraryW(L"user32.dll"); } // avoids Boundschecker warning static void FreeUser32Dll() { FreeLibrary(hUser32Dll); } //----------------------------------------------------------------------------- // memory static void EnableLowFragmentationHeap() { if(IsDebuggerPresent()) { // and the debug heap isn't explicitly disabled, char* var = getenv("_NO_DEBUG_HEAP"); if(!var || var[0] != '1') return; // we can't enable the LFH } #if WINVER >= 0x0501 WUTIL_FUNC(pHeapSetInformation, BOOL, (HANDLE, HEAP_INFORMATION_CLASS, void*, size_t)); WUTIL_IMPORT_KERNEL32(HeapSetInformation, pHeapSetInformation); if(pHeapSetInformation) { ULONG flags = 2; // enable LFH pHeapSetInformation(GetProcessHeap(), HeapCompatibilityInformation, &flags, sizeof(flags)); } #endif // #if WINVER >= 0x0501 } //----------------------------------------------------------------------------- // Wow64 // Wow64 'helpfully' redirects all 32-bit apps' accesses of // %windir%\\system32\\drivers to %windir%\\system32\\drivers\\SysWOW64. // that's bad, because the actual drivers are not in the subdirectory. to // work around this, provide for temporarily disabling redirection. static WUTIL_FUNC(pIsWow64Process, BOOL, (HANDLE, PBOOL)); static WUTIL_FUNC(pWow64DisableWow64FsRedirection, BOOL, (PVOID*)); static WUTIL_FUNC(pWow64RevertWow64FsRedirection, BOOL, (PVOID)); static bool isWow64; static void ImportWow64Functions() { WUTIL_IMPORT_KERNEL32(IsWow64Process, pIsWow64Process); WUTIL_IMPORT_KERNEL32(Wow64DisableWow64FsRedirection, pWow64DisableWow64FsRedirection); WUTIL_IMPORT_KERNEL32(Wow64RevertWow64FsRedirection, pWow64RevertWow64FsRedirection); } static void DetectWow64() { // function not found => running on 32-bit Windows if(!pIsWow64Process) { isWow64 = false; return; } BOOL isWow64Process = FALSE; const BOOL ok = pIsWow64Process(GetCurrentProcess(), &isWow64Process); WARN_IF_FALSE(ok); isWow64 = (isWow64Process == TRUE); } bool wutil_IsWow64() { return isWow64; } WinScopedDisableWow64Redirection::WinScopedDisableWow64Redirection() { // note: don't just check if the function pointers are valid. 32-bit // Vista includes them but isn't running Wow64, so calling the functions // would fail. since we have to check if actually on Wow64, there's no // more need to verify the pointers (their existence is implied). if(!wutil_IsWow64()) return; const BOOL ok = pWow64DisableWow64FsRedirection(&m_wasRedirectionEnabled); WARN_IF_FALSE(ok); } WinScopedDisableWow64Redirection::~WinScopedDisableWow64Redirection() { if(!wutil_IsWow64()) return; const BOOL ok = pWow64RevertWow64FsRedirection(m_wasRedirectionEnabled); WARN_IF_FALSE(ok); } //----------------------------------------------------------------------------- Status wutil_SetPrivilege(const wchar_t* privilege, bool enable) { WinScopedPreserveLastError s; HANDLE hToken; if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken)) return ERR::_1; TOKEN_PRIVILEGES tp; if (!LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid)) return ERR::_2; tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = enable? SE_PRIVILEGE_ENABLED : 0; SetLastError(0); const BOOL ok = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, 0, 0); if(!ok || GetLastError() != 0) return ERR::_3; WARN_IF_FALSE(CloseHandle(hToken)); return INFO::OK; } //----------------------------------------------------------------------------- // module handle #ifndef LIB_STATIC_LINK #include "lib/sysdep/os/win/wdll_main.h" HMODULE wutil_LibModuleHandle() { HMODULE hModule; const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; const BOOL ok = GetModuleHandleEx(flags, (LPCWSTR)&wutil_LibModuleHandle, &hModule); // (avoid ENSURE etc. because we're called from debug_DisplayError) wdbg_assert(ok); return hModule; } #else HMODULE wutil_LibModuleHandle() { return GetModuleHandle(0); } #endif //----------------------------------------------------------------------------- // find main window // this is required by the error dialog and clipboard code. // note that calling from wutil_Init won't work, because the app will not // have created its window by then. static HWND hAppWindow; static BOOL CALLBACK FindAppWindowByPid(HWND hWnd, LPARAM UNUSED(lParam)) { DWORD pid; DWORD tid = GetWindowThreadProcessId(hWnd, &pid); UNUSED2(tid); if(pid == GetCurrentProcessId()) hAppWindow = hWnd; return TRUE; // keep calling } HWND wutil_AppWindow() { if(!hAppWindow) { WARN_IF_FALSE(EnumWindows(FindAppWindowByPid, 0)); // (hAppWindow may still be 0 if we haven't created a window yet) } return hAppWindow; } //----------------------------------------------------------------------------- static Status wutil_Init() { InitLocks(); ForciblyLoadUser32Dll(); EnableLowFragmentationHeap(); ReadCommandLine(); GetDirectories(); ImportWow64Functions(); DetectWow64(); return INFO::OK; } static Status wutil_Shutdown() { FreeCommandLine(); FreeUser32Dll(); ShutdownLocks(); FreeDirectories(); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wvm.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wvm.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wvm.cpp (revision 19899) @@ -1,547 +1,547 @@ -/* Copyright (c) 2011 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 * "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. */ /* * virtual memory interface. supercedes POSIX mmap; provides support for * large pages, autocommit, and specifying protection flags during allocation. */ #include "precompiled.h" #include "lib/sysdep/vm.h" #include "lib/sysdep/os/win/wutil.h" #include #include "lib/timer.h" #include "lib/bits.h" // round_down #include "lib/alignment.h" // CACHE_ALIGNED #include "lib/module_init.h" #include "lib/sysdep/cpu.h" // cpu_AtomicAdd #include "lib/sysdep/numa.h" #include "lib/sysdep/arch/x86_x64/x86_x64.h" // x86_x64::ApicId #include "lib/sysdep/arch/x86_x64/apic.h" // ProcessorFromApicId #include "lib/sysdep/os/win/wversion.h" #include "lib/sysdep/os/win/winit.h" WINIT_REGISTER_CRITICAL_INIT(wvm_Init); //----------------------------------------------------------------------------- // functions not supported by 32-bit Windows XP static WUTIL_FUNC(pGetCurrentProcessorNumber, DWORD, (VOID)); static WUTIL_FUNC(pGetNumaProcessorNode, BOOL, (UCHAR, PUCHAR)); static WUTIL_FUNC(pVirtualAllocExNuma, LPVOID, (HANDLE, LPVOID, SIZE_T, DWORD, DWORD, DWORD)); static DWORD WINAPI EmulateGetCurrentProcessorNumber(VOID) { const ApicId apicId = GetApicId(); const DWORD processor = (DWORD)ProcessorFromApicId(apicId); ASSERT(processor < os_cpu_MaxProcessors); return processor; } static BOOL WINAPI EmulateGetNumaProcessorNode(UCHAR UNUSED(processor), PUCHAR node) { // given that the system doesn't support GetNumaProcessorNode, // it will also lack VirtualAllocExNuma, so the node value we assign // is ignored by EmulateVirtualAllocExNuma. *node = 0; return TRUE; } static LPVOID WINAPI EmulateVirtualAllocExNuma(HANDLE UNUSED(hProcess), LPVOID p, SIZE_T size, DWORD allocationType, DWORD protect, DWORD UNUSED(node)) { return VirtualAlloc(p, size, allocationType, protect); } static Status wvm_Init() { WUTIL_IMPORT_KERNEL32(GetCurrentProcessorNumber, pGetCurrentProcessorNumber); WUTIL_IMPORT_KERNEL32(GetNumaProcessorNode, pGetNumaProcessorNode); WUTIL_IMPORT_KERNEL32(VirtualAllocExNuma, pVirtualAllocExNuma); if(!pGetCurrentProcessorNumber) pGetCurrentProcessorNumber = &EmulateGetCurrentProcessorNumber; if(!pGetNumaProcessorNode) pGetNumaProcessorNode = &EmulateGetNumaProcessorNode; if(!pVirtualAllocExNuma) pVirtualAllocExNuma = &EmulateVirtualAllocExNuma; return INFO::OK; } namespace vm { //----------------------------------------------------------------------------- // per-processor statistics // (alignment avoids false sharing) CACHE_ALIGNED(struct Statistics) // POD { // thread-safe (required due to concurrent commits) void NotifyLargePageCommit() { cpu_AtomicAdd(&largePageCommits, +1); } void NotifySmallPageCommit() { cpu_AtomicAdd(&smallPageCommits, +1); } intptr_t largePageCommits; intptr_t smallPageCommits; }; static CACHE_ALIGNED(Statistics) statistics[os_cpu_MaxProcessors]; void DumpStatistics() { ENSURE(IsAligned(&statistics[0], cacheLineSize)); ENSURE(IsAligned(&statistics[1], cacheLineSize)); size_t smallPageCommits = 0; size_t largePageCommits = 0; uintptr_t processorsWithNoCommits = 0; for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++) { const Statistics& s = statistics[processor]; if(s.smallPageCommits == 0 && s.largePageCommits == 0) processorsWithNoCommits |= Bit(processor); smallPageCommits += s.smallPageCommits; largePageCommits += s.largePageCommits; } const size_t totalCommits = smallPageCommits+largePageCommits; if(totalCommits == 0) // this module wasn't used => don't print debug output return; const size_t largePageRatio = totalCommits? largePageCommits*100/totalCommits : 0; debug_printf("%d commits (%d, i.e. %d%% of them via large pages)\n", totalCommits, largePageCommits, largePageRatio); if(processorsWithNoCommits != 0) debug_printf(" processors with no commits: %x\n", processorsWithNoCommits); if(numa_NumNodes() > 1) debug_printf("NUMA factor: %.2f\n", numa_Factor()); } //----------------------------------------------------------------------------- // allocator with large-page and NUMA support static bool largePageAllocationTookTooLong = false; static bool ShouldUseLargePages(size_t allocationSize, DWORD allocationType, PageType pageType) { // don't even check for large page support. if(pageType == kSmall) return false; // can't use large pages when reserving - VirtualAlloc would fail with // ERROR_INVALID_PARAMETER. if((allocationType & MEM_COMMIT) == 0) return false; // OS lacks support for large pages. if(os_cpu_LargePageSize() == 0) return false; // large pages are available and application wants them used. if(pageType == kLarge) return true; // default: use a heuristic. { // internal fragmentation would be excessive. if(allocationSize <= largePageSize/2) return false; // a previous attempt already took too long. if(largePageAllocationTookTooLong) return false; // pre-Vista Windows OSes attempt to cope with page fragmentation by // trimming the working set of all processes, thus swapping them out, // and waiting for contiguous regions to appear. this is terribly // slow (multiple seconds), hence the following heuristic: if(wversion_Number() < WVERSION_VISTA) { // if there's not plenty of free memory, then memory is surely // already fragmented. if(os_cpu_MemoryAvailable() < 2000) // 2 GB return false; } } return true; } // used for reserving address space, committing pages, or both. static void* AllocateLargeOrSmallPages(uintptr_t address, size_t size, DWORD allocationType, PageType pageType = kDefault, int prot = PROT_READ|PROT_WRITE) { const HANDLE hProcess = GetCurrentProcess(); const DWORD protect = MemoryProtectionFromPosix(prot); UCHAR node; const DWORD processor = pGetCurrentProcessorNumber(); WARN_IF_FALSE(pGetNumaProcessorNode((UCHAR)processor, &node)); if(ShouldUseLargePages(size, allocationType, pageType)) { // MEM_LARGE_PAGES requires aligned addresses and sizes const size_t largePageSize = os_cpu_LargePageSize(); const uintptr_t alignedAddress = round_down(address, largePageSize); const size_t alignedSize = round_up(size+largePageSize-1, largePageSize); // note: this call can take SECONDS, which is why several checks are // undertaken before we even try. these aren't authoritative, so we // at least prevent future attempts if it takes too long. const double startTime = timer_Time(); COMPILER_FENCE; void* largePages = pVirtualAllocExNuma(hProcess, LPVOID(alignedAddress), alignedSize, allocationType|MEM_LARGE_PAGES, protect, node); const double elapsedTime = timer_Time() - startTime; COMPILER_FENCE; if(elapsedTime > 0.5) largePageAllocationTookTooLong = true; // avoid large pages next time if(largePages) { if((allocationType & MEM_COMMIT) != 0) statistics[processor].NotifyLargePageCommit(); return largePages; } } // try (again) with regular pages void* smallPages = pVirtualAllocExNuma(hProcess, LPVOID(address), size, allocationType, protect, node); if(smallPages) { if((allocationType & MEM_COMMIT) != 0) statistics[processor].NotifySmallPageCommit(); return smallPages; } else { MEMORY_BASIC_INFORMATION mbi = {0}; (void)VirtualQuery(LPCVOID(address), &mbi, sizeof(mbi)); // return value is #bytes written in mbi debug_printf("Allocation failed: base=%p allocBase=%p allocProt=%d size=%d state=%d prot=%d type=%d\n", mbi.BaseAddress, mbi.AllocationBase, mbi.AllocationProtect, mbi.RegionSize, mbi.State, mbi.Protect, mbi.Type); } return 0; } //----------------------------------------------------------------------------- // address space reservation // indicates the extent of a range of address space, // and the parameters for committing large/small pages in it. // // this bookkeeping information increases the safety of on-demand commits, // enables different parameters for separate allocations, and allows // variable alignment because it retains the original base address. // (storing this information within the allocated memory would // require mapping an additional page and may waste an entire // large page if the base address happens to be aligned already.) CACHE_ALIGNED(struct AddressRangeDescriptor) // POD { // attempt to activate this descriptor and reserve address space. // side effect: initializes all fields if successful. // // @param size, commitSize, pageType, prot - see ReserveAddressSpace. // @return INFO::SKIPPED if this descriptor is already in use, // INFO::OK on success, otherwise ERR::NO_MEM (after showing an // error message). Status Allocate(size_t size, size_t commitSize, PageType pageType, int prot) { // if this descriptor wasn't yet in use, mark it as busy // (double-checking is cheaper than cpu_CAS) if(base != 0 || !cpu_CAS(&base, intptr_t(0), intptr_t(this))) return INFO::SKIPPED; ENSURE(size != 0); // probably indicates a bug in caller ENSURE((commitSize % largePageSize) == 0 || pageType == kSmall); ASSERT(pageType == kLarge || pageType == kSmall || pageType == kDefault); ASSERT(prot == PROT_NONE || (prot & ~(PROT_READ|PROT_WRITE|PROT_EXEC)) == 0); this->commitSize = commitSize; this->pageType = pageType; this->prot = prot; alignment = (pageType == kSmall)? pageSize : largePageSize; totalSize = round_up(size+alignment-1, alignment); // NB: it is meaningless to ask for large pages when reserving // (see ShouldUseLargePages). pageType only affects subsequent commits. base = (intptr_t)AllocateLargeOrSmallPages(0, totalSize, MEM_RESERVE); if(!base) { debug_printf("AllocateLargeOrSmallPages of %lld failed\n", (u64)totalSize); DEBUG_DISPLAY_ERROR(ErrorString()); return ERR::NO_MEM; // NOWARN (error string is more helpful) } alignedBase = round_up(uintptr_t(base), alignment); alignedEnd = alignedBase + round_up(size, alignment); return INFO::OK; } void Free() { vm::Free((void*)base, totalSize); alignment = alignedBase = alignedEnd = 0; totalSize = 0; COMPILER_FENCE; base = 0; // release descriptor for subsequent reuse } bool Contains(uintptr_t address) const { // safety check: we should never see pointers in the no-man's-land // between the original and rounded up base addresses. ENSURE(!(uintptr_t(base) <= address && address < alignedBase)); return (alignedBase <= address && address < alignedEnd); } bool Commit(uintptr_t address) { // (safe because Allocate rounded up to alignment) const uintptr_t alignedAddress = round_down(address, alignment); ENSURE(alignedBase <= alignedAddress && alignedAddress+commitSize <= alignedEnd); return vm::Commit(alignedAddress, commitSize, pageType, prot); } // corresponds to the respective page size (Windows requires // naturally aligned addresses and sizes when committing large pages). // note that VirtualAlloc's alignment defaults to 64 KiB. uintptr_t alignment; uintptr_t alignedBase; // multiple of alignment uintptr_t alignedEnd; // " // (actual requested size / allocated address is required by // ReleaseAddressSpace due to variable alignment.) volatile intptr_t base; // (type is dictated by cpu_CAS) size_t totalSize; // parameters to be relayed to vm::Commit size_t commitSize; PageType pageType; int prot; //private: static const wchar_t* ErrorString() { #if ARCH_IA32 return L"Out of address space (64-bit OS may help)"; #elif OS_WIN // because early AMD64 lacked CMPXCHG16B, the Windows lock-free slist // must squeeze the address, ABA tag and list length (a questionable // design decision) into 64 bits. that leaves 39 bits for the // address, plus 4 implied zero bits due to 16-byte alignment. // [http://www.alex-ionescu.com/?p=50] return L"Out of address space (Windows only provides 8 TiB)"; #else return L"Out of address space"; #endif } }; // (array size governs the max. number of extant allocations) static AddressRangeDescriptor ranges[2*os_cpu_MaxProcessors]; static AddressRangeDescriptor* FindDescriptor(uintptr_t address) { for(size_t idxRange = 0; idxRange < ARRAY_SIZE(ranges); idxRange++) { AddressRangeDescriptor& d = ranges[idxRange]; if(d.Contains(address)) return &d; } return 0; // not contained in any allocated ranges } void* ReserveAddressSpace(size_t size, size_t commitSize, PageType pageType, int prot) { for(size_t idxRange = 0; idxRange < ARRAY_SIZE(ranges); idxRange++) { Status ret = ranges[idxRange].Allocate(size, commitSize, pageType, prot); if(ret == INFO::OK) return (void*)ranges[idxRange].alignedBase; if(ret == ERR::NO_MEM) return 0; // else: descriptor already in use, try the next one } // all descriptors are in use; ranges[] was too small DEBUG_WARN_ERR(ERR::LIMIT); return 0; } void ReleaseAddressSpace(void* p, size_t UNUSED(size)) { // it is customary to ignore null pointers if(!p) return; AddressRangeDescriptor* d = FindDescriptor(uintptr_t(p)); if(d) d->Free(); else { debug_printf("No AddressRangeDescriptor contains %P\n", p); ENSURE(0); } } //----------------------------------------------------------------------------- // commit/decommit, allocate/free, protect TIMER_ADD_CLIENT(tc_commit); bool Commit(uintptr_t address, size_t size, PageType pageType, int prot) { TIMER_ACCRUE_ATOMIC(tc_commit); return AllocateLargeOrSmallPages(address, size, MEM_COMMIT, pageType, prot) != 0; } bool Decommit(uintptr_t address, size_t size) { return VirtualFree(LPVOID(address), size, MEM_DECOMMIT) != FALSE; } bool Protect(uintptr_t address, size_t size, int prot) { const DWORD protect = MemoryProtectionFromPosix(prot); DWORD oldProtect; // required by VirtualProtect const BOOL ok = VirtualProtect(LPVOID(address), size, protect, &oldProtect); return ok != FALSE; } void* Allocate(size_t size, PageType pageType, int prot) { return AllocateLargeOrSmallPages(0, size, MEM_RESERVE|MEM_COMMIT, pageType, prot); } void Free(void* p, size_t UNUSED(size)) { if(p) // otherwise, VirtualFree complains { const BOOL ok = VirtualFree(p, 0, MEM_RELEASE); WARN_IF_FALSE(ok); } } //----------------------------------------------------------------------------- // on-demand commit // NB: avoid using debug_printf here because OutputDebugString has been // observed to generate vectored exceptions when running outside the IDE. static LONG CALLBACK VectoredHandler(const PEXCEPTION_POINTERS ep) { const PEXCEPTION_RECORD er = ep->ExceptionRecord; // we only want to handle access violations. (strictly speaking, // unmapped memory causes page faults, but Windows reports them // with EXCEPTION_ACCESS_VIOLATION.) if(er->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) return EXCEPTION_CONTINUE_SEARCH; // NB: read exceptions are legitimate and occur when updating an // accumulator for the first time. // get the source/destination of the read/write operation that // failed. (NB: don't use er->ExceptionAddress - that's the // location of the code that encountered the fault) const uintptr_t address = (uintptr_t)er->ExceptionInformation[1]; // if unknown (e.g. access violation in kernel address space or // violation of alignment requirements), we don't want to handle it. if(address == ~uintptr_t(0)) return EXCEPTION_CONTINUE_SEARCH; // the address space must have been allocated by ReserveAddressSpace // (otherwise we wouldn't know the desired commitSize/pageType/prot). AddressRangeDescriptor* d = FindDescriptor(address); if(!d) return EXCEPTION_CONTINUE_SEARCH; // NB: the first access to a page isn't necessarily at offset 0 // (memcpy isn't guaranteed to copy sequentially). rounding down // is safe and necessary - see AddressRangeDescriptor::alignment. const uintptr_t alignedAddress = round_down(address, d->alignment); bool ok = d->Commit(alignedAddress); if(!ok) { debug_printf("VectoredHandler: Commit(0x%p) failed; address=0x%p\n", alignedAddress, address); ENSURE(0); return EXCEPTION_CONTINUE_SEARCH; } // continue at (i.e. retry) the same instruction. return EXCEPTION_CONTINUE_EXECUTION; } static PVOID handler; static ModuleInitState initState; static volatile intptr_t references = 0; // atomic static Status InitHandler() { ENSURE(handler == 0); handler = AddVectoredExceptionHandler(TRUE, VectoredHandler); ENSURE(handler != 0); return INFO::OK; } static void ShutdownHandler() { ENSURE(handler != 0); const ULONG ret = RemoveVectoredExceptionHandler(handler); ENSURE(ret != 0); handler = 0; } void BeginOnDemandCommits() { ModuleInit(&initState, InitHandler); cpu_AtomicAdd(&references, +1); } void EndOnDemandCommits() { if(cpu_AtomicAdd(&references, -1) == 1) ModuleShutdown(&initState, ShutdownHandler); } } // namespace vm Index: ps/trunk/source/lib/sysdep/rtl/gcc/gcc.cpp =================================================================== --- ps/trunk/source/lib/sysdep/rtl/gcc/gcc.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/rtl/gcc/gcc.cpp (revision 19899) @@ -1,99 +1,99 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/rtl.h" #include "lib/bits.h" // Linux glibc has posix_memalign and (obsolete) memalign // Android libc has only memalign // OS X and BSD probably do not have either. #define HAVE_POSIX_MEMALIGN (OS_LINUX && !OS_ANDROID) #define HAVE_MEMALIGN OS_LINUX #if HAVE_POSIX_MEMALIGN void* rtl_AllocateAligned(size_t size, size_t alignment) { void *ptr; int ret = posix_memalign(&ptr, alignment, size); if (ret) { // TODO: report error? return NULL; } return ptr; } void rtl_FreeAligned(void* alignedPointer) { free(alignedPointer); } #elif HAVE_MEMALIGN void* rtl_AllocateAligned(size_t size, size_t alignment) { return memalign(alignment, size); } void rtl_FreeAligned(void* alignedPointer) { free(alignedPointer); } #else // Fallback aligned allocation using malloc void* rtl_AllocateAligned(size_t size, size_t align) { // This ensures we have enough extra space to store the original pointer, // and produce an aligned buffer, assuming the platform malloc ensures at // least sizeof(void*) alignment. if (align < 2*sizeof(void*)) align = 2*sizeof(void*); void* const malloc_ptr = malloc(size + align); if (!malloc_ptr) return NULL; // Round malloc_ptr up to the next aligned address, leaving some unused // space before the pointer we'll return. The minimum alignment above // ensures we'll have at least sizeof(void*) extra space. void* const aligned_ptr = (void *)(round_down(uintptr_t(malloc_ptr), uintptr_t(align)) + align); // Just make sure we did the right thing with all the alignment hacks above. ENSURE(((void**)aligned_ptr) - 1 >= malloc_ptr); // Store the original pointer which will have to be sent to free(). ((void **)aligned_ptr)[-1] = malloc_ptr; return aligned_ptr; } void rtl_FreeAligned(void* alignedPointer) { if (alignedPointer) free(((void**)alignedPointer)[-1]); } #endif Index: ps/trunk/source/lib/sysdep/os/win/wsysdep.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wsysdep.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wsysdep.cpp (revision 19899) @@ -1,612 +1,612 @@ -/* Copyright (c) 2011 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 * "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. */ /* * Windows backend of the sysdep interface */ #include "precompiled.h" #include "lib/sysdep/sysdep.h" #include "lib/alignment.h" #include "lib/sysdep/os/win/win.h" // includes windows.h; must come before shlobj #include // pick_dir #include // open_url #include #include // message crackers #include #include "lib/sysdep/clipboard.h" #include "lib/sysdep/os/win/error_dialog.h" #include "lib/sysdep/os/win/wutil.h" #if CONFIG_ENABLE_BOOST # include #endif #if MSC_VERSION #pragma comment(lib, "shell32.lib") // for sys_pick_directory SH* calls #pragma comment(lib, "winhttp.lib") #endif bool sys_IsDebuggerPresent() { return (IsDebuggerPresent() != 0); } std::wstring sys_WideFromArgv(const char* argv_i) { // NB: despite http://cbloomrants.blogspot.com/2008/06/06-14-08-1.html, // WinXP x64 EN cmd.exe (chcp reports 437) encodes argv u-umlaut // (entered manually or via auto-complete) via cp1252. the same applies // to WinXP SP2 DE (where chcp reports 850). const UINT cp = CP_ACP; const DWORD flags = MB_PRECOMPOSED|MB_ERR_INVALID_CHARS; const int inputSize = -1; // null-terminated std::vector buf(strlen(argv_i)+1); // (upper bound on number of characters) // NB: avoid mbstowcs because it may specify another locale const int ret = MultiByteToWideChar(cp, flags, argv_i, (int)inputSize, &buf[0], (int)buf.size()); ENSURE(ret != 0); return std::wstring(&buf[0]); } void sys_display_msg(const wchar_t* caption, const wchar_t* msg) { MessageBoxW(0, msg, caption, MB_ICONEXCLAMATION|MB_TASKMODAL|MB_SETFOREGROUND); } //----------------------------------------------------------------------------- // "program error" dialog (triggered by ENSURE and exception) //----------------------------------------------------------------------------- // support for resizing the dialog / its controls (must be done manually) static POINTS dlg_clientOrigin; static POINTS dlg_prevClientSize; static void dlg_OnMove(HWND UNUSED(hDlg), int x, int y) { dlg_clientOrigin.x = (short)x; dlg_clientOrigin.y = (short)y; } static const size_t ANCHOR_LEFT = 0x01; static const size_t ANCHOR_RIGHT = 0x02; static const size_t ANCHOR_TOP = 0x04; static const size_t ANCHOR_BOTTOM = 0x08; static const size_t ANCHOR_ALL = 0x0F; static void dlg_ResizeControl(HWND hDlg, int dlgItem, int dx, int dy, size_t anchors) { HWND hControl = GetDlgItem(hDlg, dlgItem); RECT r; GetWindowRect(hControl, &r); int w = r.right - r.left, h = r.bottom - r.top; int x = r.left - dlg_clientOrigin.x, y = r.top - dlg_clientOrigin.y; if(anchors & ANCHOR_RIGHT) { // right only if(!(anchors & ANCHOR_LEFT)) x += dx; // horizontal (stretch width) else w += dx; } if(anchors & ANCHOR_BOTTOM) { // bottom only if(!(anchors & ANCHOR_TOP)) y += dy; // vertical (stretch height) else h += dy; } SetWindowPos(hControl, 0, x,y, w,h, SWP_NOZORDER); } static void dlg_OnSize(HWND hDlg, UINT state, int clientSizeX, int clientSizeY) { // 'minimize' was clicked. we need to ignore this, otherwise // dx/dy would reduce some control positions to less than 0. // since Windows clips them, we wouldn't later be able to // reconstruct the previous values when 'restoring'. if(state == SIZE_MINIMIZED) return; // NB: origin might legitimately be 0, but we know it is invalid // on the first call to this function, where dlg_prevClientSize is 0. const bool isOriginValid = (dlg_prevClientSize.y != 0); const int dx = clientSizeX - dlg_prevClientSize.x; const int dy = clientSizeY - dlg_prevClientSize.y; dlg_prevClientSize.x = (short)clientSizeX; dlg_prevClientSize.y = (short)clientSizeY; if(!isOriginValid) // must not call dlg_ResizeControl return; dlg_ResizeControl(hDlg, IDC_CONTINUE, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); dlg_ResizeControl(hDlg, IDC_SUPPRESS, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); dlg_ResizeControl(hDlg, IDC_BREAK , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); dlg_ResizeControl(hDlg, IDC_EXIT , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); dlg_ResizeControl(hDlg, IDC_COPY , dx,dy, ANCHOR_RIGHT|ANCHOR_BOTTOM); dlg_ResizeControl(hDlg, IDC_EDIT1 , dx,dy, ANCHOR_ALL); } static void dlg_OnGetMinMaxInfo(HWND UNUSED(hDlg), LPMINMAXINFO mmi) { // we must make sure resize_control will never set negative coords - // Windows would clip them, and its real position would be lost. // restrict to a reasonable and good looking minimum size [pixels]. mmi->ptMinTrackSize.x = 407; mmi->ptMinTrackSize.y = 159; // determined experimentally } struct DialogParams { const wchar_t* text; size_t flags; }; static BOOL dlg_OnInitDialog(HWND hDlg, HWND UNUSED(hWndFocus), LPARAM lParam) { const DialogParams* params = (const DialogParams*)lParam; HWND hWnd; // need to reset for new instance of dialog dlg_clientOrigin.x = dlg_clientOrigin.y = 0; dlg_prevClientSize.x = dlg_prevClientSize.y = 0; if(!(params->flags & DE_ALLOW_SUPPRESS)) { hWnd = GetDlgItem(hDlg, IDC_SUPPRESS); EnableWindow(hWnd, FALSE); } // set fixed font for readability hWnd = GetDlgItem(hDlg, IDC_EDIT1); HGDIOBJ hObj = (HGDIOBJ)GetStockObject(SYSTEM_FIXED_FONT); LPARAM redraw = FALSE; SendMessage(hWnd, WM_SETFONT, (WPARAM)hObj, redraw); SetDlgItemTextW(hDlg, IDC_EDIT1, params->text); return TRUE; // set default keyboard focus } static void dlg_OnCommand(HWND hDlg, int id, HWND UNUSED(hWndCtl), UINT UNUSED(codeNotify)) { switch(id) { case IDC_COPY: { std::vector buf(128*KiB); // (too big for stack) GetDlgItemTextW(hDlg, IDC_EDIT1, &buf[0], (int)buf.size()); sys_clipboard_set(&buf[0]); break; } case IDC_CONTINUE: EndDialog(hDlg, ERI_CONTINUE); break; case IDC_SUPPRESS: EndDialog(hDlg, ERI_SUPPRESS); break; case IDC_BREAK: EndDialog(hDlg, ERI_BREAK); break; case IDC_EXIT: EndDialog(hDlg, ERI_EXIT); break; default: break; } } static void dlg_OnSysCommand(HWND hDlg, UINT cmd, int UNUSED(x), int UNUSED(y)) { switch(cmd & 0xFFF0) // NB: lower 4 bits are reserved { // [X] clicked -> close dialog (doesn't happen automatically) case SC_CLOSE: EndDialog(hDlg, 0); break; default: break; } } static INT_PTR CALLBACK dlg_OnMessage(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: return HANDLE_WM_INITDIALOG(hDlg, wParam, lParam, dlg_OnInitDialog); case WM_SYSCOMMAND: return HANDLE_WM_SYSCOMMAND(hDlg, wParam, lParam, dlg_OnSysCommand); case WM_COMMAND: return HANDLE_WM_COMMAND(hDlg, wParam, lParam, dlg_OnCommand); case WM_MOVE: return HANDLE_WM_MOVE(hDlg, wParam, lParam, dlg_OnMove); case WM_GETMINMAXINFO: return HANDLE_WM_GETMINMAXINFO(hDlg, wParam, lParam, dlg_OnGetMinMaxInfo); case WM_SIZE: return HANDLE_WM_SIZE(hDlg, wParam, lParam, dlg_OnSize); default: // we didn't process the message; caller will perform default action. return FALSE; } } ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags) { // note: other threads might still be running, crash and take down the // process before we have a chance to display this error message. // ideally we would suspend them all and resume when finished; however, // they may be holding system-wide locks (e.g. heap or loader) that // are potentially needed by DialogBoxParam. in that case, deadlock // would result; this is much worse than a crash because no error // at all is displayed to the end-user. therefore, do nothing here. // temporarily remove any pending quit message from the queue because // it would prevent the dialog from being displayed (DialogBoxParam // returns IDOK without doing anything). will be restored below. // notes: // - this isn't only relevant at exit - Windows also posts one if // window init fails. therefore, it is important that errors can be // displayed regardless. // - by passing hWnd=0, we check all windows belonging to the current // thread. there is no reason to use hWndParent below. MSG msg; const BOOL isQuitPending = PeekMessage(&msg, 0, WM_QUIT, WM_QUIT, PM_REMOVE); const HINSTANCE hInstance = wutil_LibModuleHandle(); LPCWSTR lpTemplateName = MAKEINTRESOURCEW(IDD_DIALOG1); const DialogParams params = { text, flags }; // get the enclosing app's window handle. we can't just pass 0 or // the desktop window because the dialog must be modal (if the app // continues running, it may crash and take down the process before // we've managed to show the dialog). const HWND hWndParent = wutil_AppWindow(); INT_PTR ret = DialogBoxParamW(hInstance, lpTemplateName, hWndParent, dlg_OnMessage, (LPARAM)¶ms); if(isQuitPending) PostQuitMessage((int)msg.wParam); // failed; warn user and make sure we return an ErrorReactionInternal. if(ret == 0 || ret == -1) { debug_DisplayMessage(L"Error", L"Unable to display detailed error dialog."); return ERI_CONTINUE; } return (ErrorReactionInternal)ret; } //----------------------------------------------------------------------------- // misc //----------------------------------------------------------------------------- Status sys_StatusDescription(int user_err, wchar_t* buf, size_t max_chars) { // validate user_err - Win32 doesn't have negative error numbers if(user_err < 0) return ERR::FAIL; // NOWARN const DWORD err = user_err? (DWORD)user_err : GetLastError(); // no one likes to see "The operation completed successfully" in // error messages, so return more descriptive text instead. if(err == 0) { wcscpy_s(buf, max_chars, L"0 (no error code was set)"); return INFO::OK; } wchar_t message[400]; { const LPCVOID source = 0; // ignored (we're not using FROM_HMODULE etc.) const DWORD lang_id = 0; // look for neutral, then current locale va_list* args = 0; // we don't care about "inserts" const DWORD charsWritten = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, source, err, lang_id, message, (DWORD)ARRAY_SIZE(message), args); if(!charsWritten) WARN_RETURN(ERR::FAIL); ENSURE(charsWritten < max_chars); if(message[charsWritten-1] == '\n') message[charsWritten-1] = '\0'; if(message[charsWritten-2] == '\r') message[charsWritten-2] = '\0'; } const int charsWritten = swprintf_s(buf, max_chars, L"%d (%ls)", err, message); ENSURE(charsWritten != -1); return INFO::OK; } static Status GetModulePathname(HMODULE hModule, OsPath& pathname) { wchar_t pathnameBuf[32768]; // NTFS limit const DWORD length = (DWORD)ARRAY_SIZE(pathnameBuf); const DWORD charsWritten = GetModuleFileNameW(hModule, pathnameBuf, length); if(charsWritten == 0) // failed WARN_RETURN(StatusFromWin()); ENSURE(charsWritten < length); // why would the above buffer ever be exceeded? pathname = pathnameBuf; return INFO::OK; } Status sys_get_module_filename(void* addr, OsPath& pathname) { MEMORY_BASIC_INFORMATION mbi; const SIZE_T bytesWritten = VirtualQuery(addr, &mbi, sizeof(mbi)); if(!bytesWritten) WARN_RETURN(StatusFromWin()); ENSURE(bytesWritten >= sizeof(mbi)); return GetModulePathname((HMODULE)mbi.AllocationBase, pathname); } OsPath sys_ExecutablePathname() { WinScopedPreserveLastError s; OsPath pathname; ENSURE(GetModulePathname(0, pathname) == INFO::OK); return pathname; } std::wstring sys_get_user_name() { wchar_t usernameBuf[256]; DWORD size = ARRAY_SIZE(usernameBuf); if(!GetUserNameW(usernameBuf, &size)) return L""; return usernameBuf; } // callback for shell directory picker: used to set starting directory // (for user convenience). static int CALLBACK BrowseCallback(HWND hWnd, unsigned int msg, LPARAM UNUSED(lParam), LPARAM lpData) { if(msg == BFFM_INITIALIZED) { const WPARAM wParam = TRUE; // lpData is a Unicode string, not PIDL. // (MSDN: the return values for both of these BFFM_ notifications are ignored) (void)SendMessage(hWnd, BFFM_SETSELECTIONW, wParam, lpData); } return 0; } Status sys_pick_directory(OsPath& path) { // (must not use multi-threaded apartment due to BIF_NEWDIALOGSTYLE) const HRESULT hr = CoInitialize(0); ENSURE(hr == S_OK || hr == S_FALSE); // S_FALSE == already initialized // note: bi.pszDisplayName isn't the full path, so it isn't of any use. BROWSEINFOW bi; memset(&bi, 0, sizeof(bi)); bi.ulFlags = BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE|BIF_NONEWFOLDERBUTTON; // for setting starting directory: bi.lpfn = (BFFCALLBACK)BrowseCallback; const Path::String initialPath = OsString(path); // NB: BFFM_SETSELECTIONW can't deal with '/' separators bi.lParam = (LPARAM)initialPath.c_str(); const LPITEMIDLIST pidl = SHBrowseForFolderW(&bi); if(!pidl) // user canceled return INFO::SKIPPED; // translate ITEMIDLIST to string wchar_t pathBuf[MAX_PATH]; // mandated by SHGetPathFromIDListW const BOOL ok = SHGetPathFromIDListW(pidl, pathBuf); // free the ITEMIDLIST CoTaskMemFree(pidl); if(ok == TRUE) { path = pathBuf; return INFO::OK; } // Balance call to CoInitialize, which must have been successful CoUninitialize(); WARN_RETURN(StatusFromWin()); } Status sys_open_url(const std::string& url) { HINSTANCE r = ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); if ((int)(intptr_t)r > 32) return INFO::OK; WARN_RETURN(ERR::FAIL); } Status sys_generate_random_bytes(u8* buffer, size_t size) { HCRYPTPROV hCryptProv = 0; if(!CryptAcquireContext(&hCryptProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) WARN_RETURN(StatusFromWin()); memset(buffer, 0, size); if(!CryptGenRandom(hCryptProv, (DWORD)size, (BYTE*)buffer)) WARN_RETURN(StatusFromWin()); if(!CryptReleaseContext(hCryptProv, 0)) WARN_RETURN(StatusFromWin()); return INFO::OK; } #if CONFIG_ENABLE_BOOST /* * Given a string of the form * "example.com:80" * or * "ftp=ftp.example.com:80;http=example.com:80;https=example.com:80" * separated by semicolons or whitespace, * return the string "example.com:80". */ static std::wstring parse_proxy(const std::wstring& input) { if(input.find('=') == input.npos) return input; std::vector parts; split(parts, input, boost::algorithm::is_any_of("; \t\r\n"), boost::algorithm::token_compress_on); for(size_t i = 0; i < parts.size(); ++i) if(boost::algorithm::starts_with(parts[i], "http=")) return parts[i].substr(5); // If we got this far, proxies were only set for non-HTTP protocols return L""; } Status sys_get_proxy_config(const std::wstring& url, std::wstring& proxy) { WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions; memset(&autoProxyOptions, 0, sizeof(autoProxyOptions)); autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; autoProxyOptions.fAutoLogonIfChallenged = TRUE; WINHTTP_PROXY_INFO proxyInfo; memset(&proxyInfo, 0, sizeof(proxyInfo)); WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieConfig; memset(&ieConfig, 0, sizeof(ieConfig)); HINTERNET hSession = NULL; Status err = INFO::SKIPPED; bool useAutoDetect; if(WinHttpGetIEProxyConfigForCurrentUser(&ieConfig)) { if(ieConfig.lpszAutoConfigUrl) { // Use explicit auto-config script if specified useAutoDetect = true; autoProxyOptions.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; autoProxyOptions.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl; } else { // Use auto-discovery if enabled useAutoDetect = (ieConfig.fAutoDetect == TRUE); } } else { // Can't find IE config settings - fall back to auto-discovery useAutoDetect = true; } if(useAutoDetect) { hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if(hSession && WinHttpGetProxyForUrl(hSession, url.c_str(), &autoProxyOptions, &proxyInfo) && proxyInfo.lpszProxy) { proxy = parse_proxy(proxyInfo.lpszProxy); if(!proxy.empty()) { err = INFO::OK; goto done; } } } // No valid auto-config; try explicit proxy instead if(ieConfig.lpszProxy) { proxy = parse_proxy(ieConfig.lpszProxy); if(!proxy.empty()) { err = INFO::OK; goto done; } } done: if(ieConfig.lpszProxy) GlobalFree(ieConfig.lpszProxy); if(ieConfig.lpszProxyBypass) GlobalFree(ieConfig.lpszProxyBypass); if(ieConfig.lpszAutoConfigUrl) GlobalFree(ieConfig.lpszAutoConfigUrl); if(proxyInfo.lpszProxy) GlobalFree(proxyInfo.lpszProxy); if(proxyInfo.lpszProxyBypass) GlobalFree(proxyInfo.lpszProxyBypass); if(hSession) WinHttpCloseHandle(hSession); return err; } #endif FILE* sys_OpenFile(const OsPath& pathname, const char* mode) { FILE* f = 0; const std::wstring wmode(mode, mode+strlen(mode)); (void)_wfopen_s(&f, OsString(pathname).c_str(), wmode.c_str()); return f; } Index: ps/trunk/source/lib/sysdep/os/win/wversion.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wversion.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wversion.h (revision 19899) @@ -1,51 +1,51 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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. */ #ifndef INCLUDED_WVERSION #define INCLUDED_WVERSION /** * @return CurrentVersion string from registry **/ extern const wchar_t* wversion_String(); // (same format as WINVER) const size_t WVERSION_2K = 0x0500; const size_t WVERSION_XP = 0x0501; const size_t WVERSION_XP64 = 0x0502; const size_t WVERSION_VISTA = 0x0600; const size_t WVERSION_7 = 0x0601; const size_t WVERSION_8 = 0x0602; const size_t WVERSION_8_1 = 0x0603; const size_t WVERSION_10 = 0x0604; /** * @return one of the above WVERSION* values **/ LIB_API size_t wversion_Number(); /** * @return short textual representation of the version **/ extern const wchar_t* wversion_Family(); #endif // #ifndef INCLUDED_WVERSION Index: ps/trunk/source/lib/sysdep/os_cpu.h =================================================================== --- ps/trunk/source/lib/sysdep/os_cpu.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os_cpu.h (revision 19899) @@ -1,159 +1,159 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * OS-specific support functions relating to CPU and memory */ #ifndef INCLUDED_OS_CPU #define INCLUDED_OS_CPU namespace ERR { const Status OS_CPU_RESTRICTED_AFFINITY = -130100; } //----------------------------------------------------------------------------- // processor topology // processor ID = [0, os_cpu_NumProcessors()) // they are a numbering of the bits of the process affinity mask where the // least significant nonzero bit corresponds to ID 0. // rationale: this spares users from having to deal with noncontiguous IDs, // e.g. when administrative tools are used to restrict process affinity. /** * maximum number of processors supported by the OS (determined by the * number of bits in an affinity mask) **/ static const size_t os_cpu_MaxProcessors = sizeof(uintptr_t)*CHAR_BIT; /** * @return bit mask of processors that exist and are available to * this process. * its population count is by definition equal to os_cpu_NumProcessors(). **/ LIB_API uintptr_t os_cpu_ProcessorMask(); /** * @return the number of processors available to this process. * * note: this function is necessary because POSIX sysconf _SC_NPROCESSORS_CONF * is not suppored on MacOSX, else we would use that. **/ LIB_API size_t os_cpu_NumProcessors(); // note: we do not provide an os_cpu_CurrentProcessor routine. that would // require Windows 2003 or a lot of work. worse, its results would be // worthless because they may change immediately afterwards. instead, // the recommended approach is to pin OpenMP threads (whose ID can be // queried) to the processor with the same number. //----------------------------------------------------------------------------- // CPU and memory characteristics /** * @return a rough estimate of the CPU clock frequency. * this is usually accurate to a few MHz and is faster than measurement loops. **/ LIB_API double os_cpu_ClockFrequency(); /** * @return the size [bytes] of a MMU page (4096 on most IA-32 systems) **/ LIB_API size_t os_cpu_PageSize(); /** * @return the size [bytes] of a large MMU page (4 MiB on most IA-32 systems) * or zero if they are not supported. **/ LIB_API size_t os_cpu_LargePageSize(); /** * @return the size [MB] of physical memory as reported by the OS; * no caching/validation is performed. **/ LIB_API size_t os_cpu_QueryMemorySize(); /** * @return the size [MB] of physical memory; caches the result of * os_cpu_QueryMemorySize and overrides it with a more exact value * if SMBIOS information is available. **/ LIB_API size_t os_cpu_MemorySize(); /** * @return the current amount [MB] of available memory. **/ LIB_API size_t os_cpu_MemoryAvailable(); //----------------------------------------------------------------------------- // scheduling /** * restrict the current thread to a set of processors. * * @param processorMask a bit mask of acceptable processors * (bit index i corresponds to processor i) * @return the previous mask **/ LIB_API uintptr_t os_cpu_SetThreadAffinityMask(uintptr_t processorMask); class os_cpu_ScopedSetThreadAffinityMask { public: os_cpu_ScopedSetThreadAffinityMask(uintptr_t processorMask) : m_previousProcessorMask(os_cpu_SetThreadAffinityMask(processorMask)) { } ~os_cpu_ScopedSetThreadAffinityMask() { (void)os_cpu_SetThreadAffinityMask(m_previousProcessorMask); } private: uintptr_t m_previousProcessorMask; }; /** * called by os_cpu_CallByEachCPU. * @param processor ID of processor running the current thread for the * duration of this function. * @param cbData user-specified data passed through os_cpu_CallByEachCPU. **/ typedef void (*OsCpuCallback)(size_t processor, uintptr_t cbData); /** * execute the specified function once on each processor. * this proceeds serially (the callback is never reentered) in increasing * order of processor ID. * fails if process affinity prevents running on all processors. **/ LIB_API Status os_cpu_CallByEachCPU(OsCpuCallback cb, uintptr_t cbData); #endif // #ifndef INCLUDED_OS_CPU Index: ps/trunk/source/lib/sysdep/os/win/wstartup.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wstartup.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wstartup.h (revision 19899) @@ -1,45 +1,45 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * windows-specific startup code */ // linking with this component should automatically arrange for winit's // functions to be called at the appropriate times. // // the current implementation manages to trigger initialization in-between // calls to CRT init and the static C++ ctors. that means wpthread etc. // APIs are safe to use from ctors, and winit initializers are allowed // to use non-stateless CRT functions such as atexit. // // IMPORTANT NOTE: if compiling this into a static lib and not using VC8's // "use library dependency inputs" linking mode, the object file will be // discarded because it does not contain any symbols that resolve another // module's undefined external(s). for a discussion of this topic, see: // http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=144087 // workaround: in the main EXE project, reference a symbol from this module, // thus forcing it to be linked in. example: // #pragma comment(linker, "/include:_wstartup_InitAndRegisterShutdown") // (doing that in this module isn't sufficient, because it would only // affect the librarian and its generation of the static lib that holds // this file. instead, the process of linking the main EXE must be fixed.) Index: ps/trunk/source/lib/sysdep/os/win/wversion.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wversion.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wversion.cpp (revision 19899) @@ -1,106 +1,106 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "lib/sysdep/os/win/wversion.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/winit.h" WINIT_REGISTER_EARLY_INIT(wversion_Init); static wchar_t windowsVersionString[20]; static size_t windowsVersion; // see WVERSION_* const wchar_t* wversion_Family() { ENSURE(windowsVersion != 0); switch(windowsVersion) { case WVERSION_2K: return L"Win2k"; case WVERSION_XP: return L"WinXP"; case WVERSION_XP64: return L"WinXP64"; case WVERSION_VISTA: return L"Vista"; case WVERSION_7: return L"Win7"; case WVERSION_8: return L"Win8"; case WVERSION_8_1: return L"Win8.1"; case WVERSION_10: return L"Win10"; default: return L"Windows"; } } const wchar_t* wversion_String() { ENSURE(windowsVersionString[0] != '\0'); return windowsVersionString; } size_t wversion_Number() { ENSURE(windowsVersion != 0); return windowsVersion; } static Status wversion_Init() { // note: don't use GetVersion[Ex] because it gives the version of the // emulated OS when running an app with compatibility shims enabled. HKEY hKey; if(RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { DWORD size = sizeof(windowsVersionString); (void)RegQueryValueExW(hKey, L"CurrentVersion", 0, 0, (LPBYTE)windowsVersionString, &size); unsigned major = 0, minor = 0; // ICC 11.1.082 generates incorrect code for the following: // const int ret = swscanf_s(windowsVersionString, L"%u.%u", &major, &minor); std::wstringstream ss(windowsVersionString); ss >> major; wchar_t dot; ss >> dot; ENSURE(dot == '.'); ss >> minor; ENSURE(4 <= major && major <= 0xFF); ENSURE(minor <= 0xFF); windowsVersion = (major << 8) | minor; RegCloseKey(hKey); } else DEBUG_WARN_ERR(ERR::LOGIC); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os_cpu.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os_cpu.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os_cpu.cpp (revision 19899) @@ -1,84 +1,84 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * OS-specific support functions relating to CPU and memory */ #include "precompiled.h" #include "lib/sysdep/os_cpu.h" #include "lib/alignment.h" #include "lib/sysdep/smbios.h" #if OS_WIN # include "lib/sysdep/os/win/wcpu.h" #endif static const StatusDefinition osCpuStatusDefinitions[] = { { ERR::OS_CPU_RESTRICTED_AFFINITY, L"Cannot set desired CPU affinity" } }; STATUS_ADD_DEFINITIONS(osCpuStatusDefinitions); double os_cpu_ClockFrequency() { static double clockFrequency; if(clockFrequency != 0.0) // already initialized return clockFrequency; #if OS_WIN u32 freqMhz; if(wcpu_ReadFrequencyFromRegistry(freqMhz) == INFO::OK) return clockFrequency = freqMhz * 1e6; #endif const SMBIOS::Structures* structures = SMBIOS::GetStructures(); if(structures->Processor_) return clockFrequency = structures->Processor_->maxFrequency * 1e6; return clockFrequency = -1.0; // unknown } size_t os_cpu_MemorySize() { static size_t memorySize; if(memorySize != 0) // already initialized return memorySize; memorySize = os_cpu_QueryMemorySize(); // replace with the sum of all memory devices reported by SMBIOS if // that's within 10% of what the OS reported { const SMBIOS::Structures* structures = SMBIOS::GetStructures(); u64 memorySizeBytes = 0; for(const SMBIOS::MemoryDevice* p = structures->MemoryDevice_; p; p = p->next) memorySizeBytes += p->size; const size_t memorySize2 = memorySizeBytes/MiB; if(9*memorySize/10 <= memorySize2 && memorySize2 <= 11*memorySize/10) memorySize = memorySize2; } return memorySize; } Index: ps/trunk/source/lib/sysdep/os/win/wstartup.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wstartup.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wstartup.cpp (revision 19899) @@ -1,123 +1,123 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * windows-specific startup code */ #include "precompiled.h" #include "lib/sysdep/os/win/wstartup.h" #include "lib/sysdep/os/win/winit.h" /* Shutdown -------- our shutdown function must be called after static C++ dtors, since they may use wpthread and wtime functions. how to accomplish this? hooking ExitProcess is one way of ensuring we're the very last bit of code to be called. this has several big problems, though: - if other exit paths (such as CorExitProcess) are added by MS and triggered via external code injected into our process, we miss our cue to shut down. one annoying consequence would be that the Aken driver remains loaded. however, users also can terminate our process at any time, so we need to safely handle lack of shutdown anyway. - IAT hooking breaks if kernel32 is delay-loaded (who knows what people are going to do..) - Detours-style trampolines are nonportable (the Detours library currently only ships with code to handle IA-32) and quite risky, since antivirus programs may flag this activity. having all exit paths call our shutdown and then _cexit would work, provided we actually find and control all of them (unhandled exceptions and falling off the end of the last thread are non-obvious examples). that aside, it'd require changes to calling code, and we're trying to keep this mess hidden and transparent to users. the remaining alternative is to use atexit. this approach has the advantage of being covered by the CRT runtime checks and leak detector, because those are shut down after the atexit handlers are called. however, it does require that our shutdown callback to be the very last, i.e. registered first. fortunately, the init stage can guarantee this. Init ---- For the same reasons as above, our init really should happen before static C++ ctors are called. using WinMain as the entry point and then calling the application's main() doesn't satisy the above requirement, and is expressly forbidden by ANSI C. (VC apparently makes use of this and changes its calling convention. if we call it, everything appears to work but stack traces in release mode are incorrect, since symbol addresses are off by 4 bytes.) another alternative is re#defining the app's main function to app_main, having the OS call our main, and then dispatching to app_main. however, this leads to trouble when another library (e.g. SDL) wants to do the same. moreover, this file is compiled into a static library and used both for the 0ad executable as well as the separate self-test. this means we can't enable the main() hook for one and disable it in the other. requiring users to call us at the beginning of main is brittle in general, comes after static ctors, and is difficult to achieve in external code such as the (automatically generated) self-test. commandeering the entry point, doing init there and then calling mainCRTStartup would work, but doesn't allow the use of atexit for shutdown (nor any other non-stateless CRT functions to be called during init). the way out is to have an init-and-call-atexit function triggered by means of the CRT init mechanism. we arrange init order such that this happens before static C++ ctors, thus meeting all of the above requirements. (note: this is possible because crtexe.c and its .CRT$XI and .CRT$XC sections holding static initializers and ctors are linked into the application, not the CRT DLL.) */ // reference: see http://www.codeguru.com/cpp/misc/misc/threadsprocesses/article.php/c6945__1 EXTERN_C int wstartup_InitAndRegisterShutdown() { winit_CallInitFunctions(); atexit(winit_CallShutdownFunctions); return 0; } // insert our initialization function after _cinit and XIU ("User") block #if ARCH_AMD64 # define SECTION_ATTRIBUTES read #else # define SECTION_ATTRIBUTES read,write #endif #pragma section(".CRT$XIV", long,SECTION_ATTRIBUTES) #undef SECTION_ATTRIBUTES EXTERN_C __declspec(allocate(".CRT$XIV")) int(*wstartup_pInitAndRegisterShutdown)() = wstartup_InitAndRegisterShutdown; #pragma comment(linker, "/include:" STRINGIZE(DECORATED_NAME(wstartup_pInitAndRegisterShutdown))) Index: ps/trunk/source/lib/sysdep/os/win/wutil.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wutil.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os/win/wutil.h (revision 19899) @@ -1,205 +1,205 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ /* * various Windows-specific utilities */ #ifndef INCLUDED_WUTIL #define INCLUDED_WUTIL #include "lib/os_path.h" #include "lib/sysdep/os/win/win.h" template bool wutil_IsValidHandle(H h) { return h != 0 && h != INVALID_HANDLE_VALUE; } //----------------------------------------------------------------------------- // dynamic linking // define a function pointer (optionally prepend 'static') #define WUTIL_FUNC(varName, ret, params)\ ret (WINAPI* varName) params // rationale: // - splitting up WUTIL_FUNC and WUTIL_IMPORT is a bit verbose in // the common case of a local function pointer definition, // but allows one-time initialization of static variables. // - differentiating between procName and varName allows searching // for the actual definition of the function pointer in the code. // - a cast would require passing in ret/params. // - writing a type-punned pointer breaks strict-aliasing rules. #define WUTIL_IMPORT(hModule, procName, varName)\ STMT(\ const FARPROC f = GetProcAddress(hModule, #procName);\ memcpy(&varName, &f, sizeof(FARPROC));\ ) // note: Kernel32 is guaranteed to be loaded, so we don't // need to LoadLibrary and FreeLibrary. #define WUTIL_IMPORT_KERNEL32(procName, varName)\ WUTIL_IMPORT(GetModuleHandleW(L"kernel32.dll"), procName, varName) //----------------------------------------------------------------------------- // safe allocator extern void* wutil_Allocate(size_t size); extern void wutil_Free(void* p); //----------------------------------------------------------------------------- // locks // critical sections used by win-specific code enum WinLockId { WDBG_SYM_CS, // protects (non-reentrant) dbghelp.dll WDIR_WATCH_CS, NUM_CS }; extern void wutil_Lock(WinLockId id); extern void wutil_Unlock(WinLockId id); // used in a desperate attempt to avoid deadlock in wseh. extern bool wutil_IsLocked(WinLockId id); class WinScopedLock { public: WinScopedLock(WinLockId id) : m_id(id) { wutil_Lock(m_id); } ~WinScopedLock() { wutil_Unlock(m_id); } private: WinLockId m_id; }; //----------------------------------------------------------------------------- // errors /** * some WinAPI functions SetLastError(0) on success, which is bad because * it can hide previous errors. this class takes care of restoring the * previous value. **/ class WinScopedPreserveLastError { public: WinScopedPreserveLastError() : m_lastError(GetLastError()) { SetLastError(0); } ~WinScopedPreserveLastError() { if(m_lastError != 0 && GetLastError() == 0) SetLastError(m_lastError); } private: DWORD m_lastError; }; /** * @return the Status equivalent of GetLastError(), or ERR::FAIL if * there's no equivalent. * SetLastError(0) should be called before the Windows function to * make sure no stale errors are returned. **/ extern Status StatusFromWin(); //----------------------------------------------------------------------------- // command line extern int wutil_argc(); extern wchar_t** wutil_argv(); extern bool wutil_HasCommandLineArgument(const wchar_t* arg); //----------------------------------------------------------------------------- // directories extern const OsPath& wutil_SystemPath(); extern const OsPath& wutil_ExecutablePath(); extern const OsPath& wutil_LocalAppdataPath(); extern const OsPath& wutil_RoamingAppdataPath(); extern const OsPath& wutil_PersonalPath(); //----------------------------------------------------------------------------- // Wow64 extern bool wutil_IsWow64(); class WinScopedDisableWow64Redirection { public: WinScopedDisableWow64Redirection(); ~WinScopedDisableWow64Redirection(); private: void* m_wasRedirectionEnabled; }; //----------------------------------------------------------------------------- LIB_API Status wutil_SetPrivilege(const wchar_t* privilege, bool enable); /** * @return module handle of lib code (that of the main EXE if * linked statically, otherwise the DLL). * this is necessary for the error dialog. **/ extern HMODULE wutil_LibModuleHandle(); /** * @return handle to the first window owned by the current process, or * 0 if none exist (e.g. it hasn't yet created one). * * enumerates all top-level windows and stops if PID matches. * once this function returns a non-NULL handle, it will always * return that cached value. **/ extern HWND wutil_AppWindow(); #endif // #ifndef INCLUDED_WUTIL Index: ps/trunk/source/lib/sysdep/os.h =================================================================== --- ps/trunk/source/lib/sysdep/os.h (revision 19898) +++ ps/trunk/source/lib/sysdep/os.h (revision 19899) @@ -1,126 +1,126 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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. */ /* * OS-specific macros */ #ifndef INCLUDED_OS #define INCLUDED_OS // detect OS via predefined macros. rationale: // - these macros have consistent names and numerical values; using // them saves other code from having to know the obscure predefined macros. // - we'd like to use #if/#elif/#endif chains for e.g. OS_* to allow warning // if none is selected, but there's no good way to #define all inapplicable // settings to 0. doing so up front is hard to maintain and would require // #undef before setting any one to 1. #ifndef afterwards for each setting // is ugly and brittle as well. we therefore use #if/#else/#endif. // Windows #if defined(_WIN64) # define OS_WIN64 1 #else # define OS_WIN64 0 #endif #if defined(_WIN32) # define OS_WIN 1 #else # define OS_WIN 0 #endif // Linux #if defined(linux) || defined(__linux) || defined(__linux__) # define OS_LINUX 1 #else # define OS_LINUX 0 #endif // Android (subset of Linux) #if defined(__ANDROID__) # define OS_ANDROID 1 #else # define OS_ANDROID 0 #endif // Mac OS X #if (defined(__APPLE__) && defined(__MACH__)) # define OS_MACOSX 1 #else # define OS_MACOSX 0 #endif // BSD #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD_kernel__) # define OS_BSD 1 // OpenBSD has no aio.h and we need a way to disable it for it only # if defined(__OpenBSD__) # define OS_OPENBSD 1 # endif #else # define OS_BSD 0 #endif #ifndef OS_OPENBSD # define OS_OPENBSD 0 #endif // Solaris #if defined(sun) || defined(__sun) # define OS_SOLARIS 1 #else # define OS_SOLARIS 0 #endif // BeOS #if defined(__BEOS__) # define OS_BEOS 1 #else # define OS_BEOS 0 #endif // Mac OS 9 or below #if defined(macintosh) # define OS_MAC 1 #else # define OS_MAC 0 #endif // Amiga #if defined(AMIGA) # define OS_AMIGA 1 #else # define OS_AMIGA 0 #endif // Unix-based #if defined(unix) || defined(__unix) || defined(__unix__) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE) # define OS_UNIX 1 #else # define OS_UNIX 0 #endif // convenience: additionally set OS_UNIX for Unix-based OSes // note: doing this in an separate section instead of adding the extra define // to all affected OSes saves a few undefs or macro redefinition warnings. #if OS_LINUX || OS_MACOSX || OS_BSD || OS_SOLARIS # undef OS_UNIX # define OS_UNIX 1 #endif // convenience: additionally set OS_BSD for BSD-based OSes. see note above. #if OS_MACOSX # undef OS_BSD # define OS_BSD 1 #endif #endif // #ifndef INCLUDED_OS Index: ps/trunk/source/lib/sysdep/smbios.h =================================================================== --- ps/trunk/source/lib/sysdep/smbios.h (revision 19898) +++ ps/trunk/source/lib/sysdep/smbios.h (revision 19899) @@ -1,1232 +1,1232 @@ -/* Copyright (c) 2011 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 * "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. */ /* * provide access to System Management BIOS information */ #ifndef INCLUDED_SMBIOS #define INCLUDED_SMBIOS namespace SMBIOS { // to introduce another enumeration: // 1) add its name and underlying type here // 2) define a _ENUMERATORS macro specifying its enumerators // (prefer lower case to avoid conflicts with macros) #define ENUMERATIONS\ ENUMERATION(State, u8)\ ENUMERATION(ECC, u8)\ ENUMERATION(BiosFlags, u32)\ ENUMERATION(BiosFlags1, u8)\ ENUMERATION(BiosFlags2, u8)\ ENUMERATION(SystemWakeUpType, u8)\ ENUMERATION(BaseboardFlags, u8)\ ENUMERATION(BaseboardType, u8)\ ENUMERATION(ChassisType, u8)\ ENUMERATION(ChassisSecurityStatus, u8)\ ENUMERATION(ProcessorType, u8)\ ENUMERATION(ProcessorStatus, u8)\ ENUMERATION(ProcessorUpgrade, u8)\ ENUMERATION(ProcessorFlags, u16)\ ENUMERATION(CacheMode, u8)\ ENUMERATION(CacheLocation, u8)\ ENUMERATION(CacheConfigurationFlags, u16)\ ENUMERATION(CacheFlags, u16)\ ENUMERATION(CacheType, u8)\ ENUMERATION(CacheAssociativity, u8)\ ENUMERATION(PortConnectorType, u8)\ ENUMERATION(PortType, u8)\ ENUMERATION(SystemSlotType, u8)\ ENUMERATION(SystemSlotBusWidth, u8)\ ENUMERATION(SystemSlotUsage, u8)\ ENUMERATION(SystemSlotLength, u8)\ ENUMERATION(SystemSlotFlags1, u8)\ ENUMERATION(SystemSlotFlags2, u8)\ ENUMERATION(OnBoardDeviceType, u8)\ ENUMERATION(MemoryArrayLocation, u8)\ ENUMERATION(MemoryArrayUse, u8)\ ENUMERATION(MemoryDeviceFormFactor, u8)\ ENUMERATION(MemoryDeviceType, u8)\ ENUMERATION(MemoryDeviceTypeFlags, u16)\ ENUMERATION(PortableBatteryChemistry, u8)\ ENUMERATION(VoltageProbeLocation, u8)\ ENUMERATION(CoolingDeviceType, u8)\ ENUMERATION(TemperatureProbeLocation, u8)\ ENUMERATION(SystemBootStatus, u8)\ ENUMERATION(ManagementDeviceType, u8)\ ENUMERATION(ManagementDeviceAddressType, u8)\ ENUMERATION(SystemPowerSupplyCharacteristics, u16)\ ENUMERATION(SystemPowerSupplyType, u8)\ ENUMERATION(SystemPowerSupplyInputSwitching, u8) // to introduce another structure: // 1) add its name and ID here // 2) define a _FIELDS macro specifying its fields // 3) (optional) add a specialization of Fixup #define STRUCTURES\ STRUCTURE(Bios, 0)\ STRUCTURE(System, 1)\ STRUCTURE(Baseboard, 2)\ STRUCTURE(Chassis, 3)\ STRUCTURE(Processor, 4)\ /* MemoryController (5) and MemoryModule (6) are obsolete */\ STRUCTURE(Cache, 7)\ STRUCTURE(PortConnector, 8)\ STRUCTURE(SystemSlot, 9)\ STRUCTURE(OnBoardDevices, 10)\ /* OemStrings (11), SystemConfiguration (12), BiosLanguage (13), GroupAssociations (14), SystemEventLog (15) are optional */\ STRUCTURE(MemoryArray, 16)\ STRUCTURE(MemoryDevice, 17)\ /* MemoryError32 (18) is optional */\ STRUCTURE(MemoryArrayMappedAddress, 19)\ STRUCTURE(MemoryDeviceMappedAddress, 20)\ /* PointingDevice (21) is optional */\ STRUCTURE(PortableBattery, 22)\ /* SystemReset (23), HardwareSecurity (24), SystemPowerControls (25) are optional */\ STRUCTURE(VoltageProbe, 26)\ STRUCTURE(CoolingDevice, 27)\ STRUCTURE(TemperatureProbe, 28)\ /* ElectricalCurrentProbe (29), OutOfBandRemoteAccess (30), BootIntegrityServices (31) are optional */\ STRUCTURE(SystemBoot, 32)\ STRUCTURE(ManagementDevice, 34)\ STRUCTURE(ManagementDeviceComponent, 35)\ STRUCTURE(ManagementDeviceThreshold, 36)\ STRUCTURE(SystemPowerSupply, 39)\ STRUCTURE(OnboardDevices2, 41)\ /* MemoryError64 (33), MemoryChannel (37), IpmiDevice (38) are optional */ /* Additional (40), ManagementControllerHostInterface (42) are optional */ //----------------------------------------------------------------------------- // declarations required for the fields // indicates a field (:= member of a structure) is: enum FieldFlags { // computed via other fields (i.e. should not be copied from the SMBIOS data). F_DERIVED = 1, // not intended for display / use by applications (usually because // it is superseded by another - possibly derived - field). F_INTERNAL = 2, // a number that should be displayed in hexadecimal form. F_HEX = 4 }; // (wrapper classes allow special handling of certain fields via // template specialization and function overloads) // size [bytes] - displayed with auto-range template struct Size { Size(): value(0) {} Size(T value): value(value) {} T value; operator T() const { return value; } }; // SMBIOS structure handle - only displayed if meaningful struct Handle { Handle(): value(0) {} Handle(u16 value): value(value) {} u16 value; }; #define State_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(ok, 3)\ ENUM(noncritical, 4)\ ENUM(critical, 5)\ ENUM(nonrecoverable, 6) #define ECC_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(none, 3)\ ENUM(parity, 4)\ ENUM(single_bit, 5)\ ENUM(multiple_bit, 6)\ ENUM(crc, 7) #pragma pack(push, 1) //----------------------------------------------------------------------------- // Bios #define BiosFlags_ENUMERATORS\ ENUM(isa, 0x10)\ ENUM(mca, 0x20)\ ENUM(eisa, 0x40)\ ENUM(pci, 0x80)\ ENUM(pcmcia, 0x100)\ ENUM(plug_and_play, 0x200)\ ENUM(apm, 0x400)\ ENUM(upgradable, 0x800)\ ENUM(shadowing, 0x1000)\ ENUM(vl_vesa, 0x2000)\ ENUM(escd, 0x4000)\ ENUM(boot_cd, 0x8000)\ ENUM(selectable_boot, 0x10000)\ ENUM(socketed_rom, 0x20000)\ ENUM(boot_pcmcia, 0x40000)\ ENUM(edd, 0x80000)\ ENUM(int13a, 0x100000)\ ENUM(int13b, 0x200000)\ ENUM(int13c, 0x400000)\ ENUM(int13d, 0x800000)\ ENUM(int13e, 0x1000000)\ ENUM(int13f, 0x2000000)\ ENUM(int5, 0x4000000)\ ENUM(int9, 0x8000000)\ ENUM(int14, 0x10000000)\ ENUM(int17, 0x20000000)\ ENUM(int10, 0x40000000)\ ENUM(pc_98, 0x80000000) #define BiosFlags1_ENUMERATORS\ ENUM(acpi, 0x01)\ ENUM(usb_legacy, 0x02)\ ENUM(agp, 0x04)\ ENUM(boot_i2o, 0x08)\ ENUM(boot_ls_120, 0x10)\ ENUM(boot_zip_drive, 0x20)\ ENUM(boot_1394, 0x40)\ ENUM(smart_battery, 0x80) #define BiosFlags2_ENUMERATORS\ ENUM(bios_boot, 0x01)\ ENUM(function_key_boot, 0x02)\ ENUM(targeted_content_distribution, 0x04)\ ENUM(uefi, 0x08)\ ENUM(virtual_machine, 0x10) #define Bios_FIELDS\ FIELD(0, const char*, vendor, "")\ FIELD(0, const char*, version, "")\ FIELD(F_HEX, u16, startSegment, "")\ FIELD(0, const char*, releaseDate, "")\ FIELD(F_INTERNAL, u8, encodedSize, "")\ FIELD(0, BiosFlags, flags, "")\ FIELD(F_HEX, u32, vendorFlags, "")\ FIELD(0, BiosFlags1, flags1, "")\ FIELD(0, BiosFlags2, flags2, "")\ FIELD(F_DERIVED, Size, size, "") //----------------------------------------------------------------------------- // System #define SystemWakeUpType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(apm_timer, 3)\ ENUM(modem_ring, 4)\ ENUM(lan_remote, 5)\ ENUM(power_switch, 6)\ ENUM(pci_pme, 7)\ ENUM(ac_power_restored, 8) #define System_FIELDS\ FIELD(0, const char*, manufacturer, "")\ FIELD(0, const char*, productName, "")\ FIELD(0, const char*, version, "")\ FIELD(0, const char*, serialNumber, "")\ FIELD(F_HEX, u64, uuid0, "")\ FIELD(F_HEX, u64, uuid1, "")\ FIELD(0, SystemWakeUpType, wakeUpType, "")\ FIELD(0, const char*, skuNumber, "")\ FIELD(0, const char*, family, "") //----------------------------------------------------------------------------- // Baseboard #define BaseboardFlags_ENUMERATORS\ ENUM(motherboard, 0x01)\ ENUM(requires_add_in, 0x02)\ ENUM(removeable, 0x04)\ ENUM(replaceable, 0x08)\ ENUM(hot_swappable, 0x10) #define BaseboardType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(blade, 3)\ ENUM(connectivity_switch, 4)\ ENUM(system_management, 5)\ ENUM(processor, 6)\ ENUM(io, 7)\ ENUM(memory, 8)\ ENUM(daughter, 9)\ ENUM(motherboard, 10)\ ENUM(processor_memory, 11)\ ENUM(processor_io, 12)\ ENUM(interconnect, 13) #define Baseboard_FIELDS\ FIELD(0, const char*, manufacturer, "")\ FIELD(0, const char*, product, "")\ FIELD(0, const char*, version, "")\ FIELD(0, const char*, serialNumber, "")\ FIELD(0, const char*, assetTag, "")\ FIELD(0, BaseboardFlags, flags, "")\ FIELD(0, const char*, location, "")\ FIELD(0, Handle, hChassis, "")\ FIELD(0, BaseboardType, type, "")\ /* omit subsequent fields because we can't handle the variable-length contained objects */ //----------------------------------------------------------------------------- // Chassis #define ChassisType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(desktop, 3)\ ENUM(low_profile_desktop, 4)\ ENUM(pizza_box, 5)\ ENUM(mini_tower, 6)\ ENUM(tower, 7)\ ENUM(portable, 8)\ ENUM(laptop, 9)\ ENUM(notebook, 10)\ ENUM(handheld, 11)\ ENUM(docking_station, 12)\ ENUM(all_in_one, 13)\ ENUM(subnotebook, 14)\ ENUM(space_saving, 15)\ ENUM(lunchbox, 16)\ ENUM(main_server, 17)\ ENUM(expansion, 18)\ ENUM(sub, 19)\ ENUM(bus_expansion, 20)\ ENUM(peripheral, 21)\ ENUM(raid, 22)\ ENUM(rack_mount, 23)\ ENUM(sealed_case, 24)\ ENUM(multi_system, 25)\ ENUM(compact_pci, 26)\ ENUM(advanced_tca, 27)\ ENUM(blade, 28)\ ENUM(blade_enclosure, 29) #define ChassisSecurityStatus_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(none, 3)\ ENUM(external_interface_locked, 4)\ ENUM(external_interface_enabled, 5) #define Chassis_FIELDS\ FIELD(0, const char*, manufacturer, "")\ FIELD(0, ChassisType, type, "")\ FIELD(0, const char*, version, "")\ FIELD(0, const char*, serialNumber, "")\ FIELD(0, const char*, assetTag, "")\ FIELD(0, State, state, "")\ FIELD(0, State, powerState, "")\ FIELD(0, State, thermalState, "")\ FIELD(0, ChassisSecurityStatus, securityStatus, "")\ FIELD(0, u32, oemDefined, "")\ FIELD(0, u8, height, "U")\ FIELD(0, u8, numPowerCords, "")\ /* omit subsequent fields because we can't handle the variable-length contained objects */ //----------------------------------------------------------------------------- // Processor #define ProcessorType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(CPU, 3)\ ENUM(FPU, 4)\ ENUM(DSP, 5)\ ENUM(GPU, 6) #define ProcessorStatus_ENUMERATORS\ ENUM(unknown, 0)\ ENUM(other, 7)\ ENUM(enabled, 1)\ ENUM(user_disabled, 2)\ ENUM(post_disabled, 3)\ ENUM(idle, 4) #define ProcessorUpgrade_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(daughter, 3)\ ENUM(zif, 4)\ ENUM(piggyback, 5)\ ENUM(none, 6)\ ENUM(lif, 7)\ ENUM(slot_1, 8)\ ENUM(slot_2, 9)\ ENUM(socket_370, 10)\ ENUM(slot_a, 11)\ ENUM(slot_m, 12)\ ENUM(socket_423, 13)\ ENUM(socket_a, 14)\ ENUM(socket_478, 15)\ ENUM(socket_754, 16)\ ENUM(socket_940, 17)\ ENUM(socket_939, 18)\ ENUM(socket_604, 19)\ ENUM(socket_771, 20)\ ENUM(socket_775, 21)\ ENUM(socket_s1, 22)\ ENUM(socket_am2, 23)\ ENUM(socket_1207, 24)\ ENUM(socket_1366, 25)\ ENUM(socket_g34, 26)\ ENUM(socket_am3, 27)\ ENUM(socket_c32, 28)\ ENUM(socket_1156, 29)\ ENUM(socket_1567, 30)\ ENUM(socket_988a, 31)\ ENUM(socket_1288, 32)\ ENUM(socket_988b, 33)\ ENUM(socket_1023, 34)\ ENUM(socket_1224, 35)\ ENUM(socket_1155, 36)\ ENUM(socket_1356, 37)\ ENUM(socket_2011, 38)\ ENUM(socket_fs1, 39)\ ENUM(socket_fs2, 40)\ ENUM(socket_fm1, 41)\ ENUM(socket_fm2, 42) #define ProcessorFlags_ENUMERATORS\ ENUM(unknown, 0x2)\ ENUM(x64, 0x4)\ ENUM(multi_core, 0x8)/* indicates cores are present, but they might be disabled*/\ ENUM(ht, 0x10)\ ENUM(execute_protection, 0x20)\ ENUM(enhanced_virtualization, 0x40)\ ENUM(power_control, 0x80) #define Processor_FIELDS\ FIELD(0, const char*, socket, "")\ FIELD(0, ProcessorType, type, "")\ FIELD(0, u8, family, "") /* we don't bother providing enumerators for > 200 families */\ FIELD(0, const char*, manufacturer, "")\ FIELD(F_HEX, u64, id, "")\ FIELD(0, const char*, version, "")\ FIELD(0, u8, voltage, " dV")\ FIELD(0, u16, externalClockFrequency, " MHz")\ FIELD(0, u16, maxFrequency, " MHz")\ FIELD(0, u16, bootFrequency, " MHz")\ FIELD(0, ProcessorStatus, status, "")\ FIELD(0, ProcessorUpgrade, upgrade, "")\ FIELD(0, Handle, hL1, "")\ FIELD(0, Handle, hL2, "")\ FIELD(0, Handle, hL3, "")\ FIELD(0, const char*, serialNumber, "")\ FIELD(0, const char*, assetTag, "")\ FIELD(0, const char*, partNumber, "")\ FIELD(0, u8, coresPerPackage, "")\ FIELD(0, u8, enabledCores, "")\ FIELD(0, u8, logicalPerPackage, "")\ FIELD(0, ProcessorFlags, flags, "")\ FIELD(0, u16, family2, "")\ FIELD(F_DERIVED, bool, populated, "") //----------------------------------------------------------------------------- // Cache #define CacheMode_ENUMERATORS\ ENUM(write_through, 0)\ ENUM(write_back, 1)\ ENUM(varies, 2)\ ENUM(unknown, 3) #define CacheLocation_ENUMERATORS\ ENUM(internal, 0)\ ENUM(external, 1)\ ENUM(reserved, 2)\ ENUM(unknown, 3) #define CacheConfigurationFlags_ENUMERATORS\ ENUM(socketed, 0x08)\ ENUM(enabled, 0x80) #define CacheFlags_ENUMERATORS\ ENUM(other, 0x01)\ ENUM(unknown, 0x02)\ ENUM(non_burst, 0x04)\ ENUM(burst, 0x08)\ ENUM(pipeline_burst, 0x10)\ ENUM(synchronous, 0x20)\ ENUM(asynchronous, 0x40) #define CacheType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(instruction, 3)\ ENUM(data, 4)\ ENUM(unified, 5) #define CacheAssociativity_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(direct_mapped, 3)\ ENUM(A2, 4)\ ENUM(A4, 5)\ ENUM(full, 6)\ ENUM(A8, 7)\ ENUM(A16, 8)\ ENUM(A12, 9)\ ENUM(A24, 10)\ ENUM(A32, 11)\ ENUM(A48, 12)\ ENUM(A64, 13)\ ENUM(A20, 14) #define Cache_FIELDS\ FIELD(0, const char*, designation, "")\ FIELD(0, CacheConfigurationFlags, configuration, "")\ FIELD(F_INTERNAL, u16, maxSize16, "")\ FIELD(F_INTERNAL, u16, installedSize16, "")\ FIELD(0, CacheFlags, supportedFlags, "")\ FIELD(0, CacheFlags, currentFlags, "")\ FIELD(0, u8, speed, " ns")\ FIELD(0, ECC, ecc, "")\ FIELD(0, CacheType, type, "")\ FIELD(0, CacheAssociativity, associativity, "")\ FIELD(F_DERIVED, size_t, level, "") /* 1..8 */\ FIELD(F_DERIVED, CacheLocation, location, "")\ FIELD(F_DERIVED, CacheMode, mode, "")\ FIELD(F_DERIVED, Size, maxSize, "")\ FIELD(F_DERIVED, Size, installedSize, "") //----------------------------------------------------------------------------- // PortConnector #define PortConnectorType_ENUMERATORS\ ENUM(other, 255)\ ENUM(none, 0)\ ENUM(centronics, 1)\ ENUM(mini_centronics, 2)\ ENUM(proprietary, 3)\ ENUM(db25_male, 4)\ ENUM(db25_pin_female, 5)\ ENUM(db15_pin_male, 6)\ ENUM(db15_pin_female, 7)\ ENUM(db9_pin_male, 8)\ ENUM(db9_pin_female, 9)\ ENUM(rj11, 10)\ ENUM(rj45, 11)\ ENUM(mini_scsi, 12)\ ENUM(mini_din, 13)\ ENUM(micro_din, 14)\ ENUM(ps2, 15)\ ENUM(infrared, 16)\ ENUM(hp_hil, 17)\ ENUM(access_bus_usb, 18)\ ENUM(pc_ssa_scsi, 19)\ ENUM(din8_male, 20)\ ENUM(din8_female, 21)\ ENUM(on_board_ide, 22)\ ENUM(on_board_floppy, 23)\ ENUM(dual_inline_9, 24)\ ENUM(dual_inline_25, 25)\ ENUM(dual_inline_50, 26)\ ENUM(dual_inline_68, 27)\ ENUM(on_board_sound_input_from_cd, 28)\ ENUM(mini_centronics_14, 29)\ ENUM(mini_centronics_26, 30)\ ENUM(headphones, 31)\ ENUM(bnc, 32)\ ENUM(pc_firewire, 33)\ ENUM(sas_sata, 34)\ ENUM(pc_98, 160)\ ENUM(pc_98_hireso, 161)\ ENUM(pc_h98, 162)\ ENUM(pc_98_note, 163)\ ENUM(pc_98_full, 164) #define PortType_ENUMERATORS\ ENUM(other, 255)\ ENUM(none, 0)\ ENUM(parallel_xt_at, 1)\ ENUM(parallel_ps2, 2)\ ENUM(parallel_ecp, 3)\ ENUM(parallel_epp, 4)\ ENUM(parallel_ecepp, 5)\ ENUM(serial_xt_at, 6)\ ENUM(serial_16450, 7)\ ENUM(serial_16550, 8)\ ENUM(serial_16550a, 9)\ ENUM(scsi, 10)\ ENUM(midi, 11)\ ENUM(joystick, 12)\ ENUM(keyboard, 13)\ ENUM(mouse, 14)\ ENUM(ssa_scsi, 15)\ ENUM(usb, 16)\ ENUM(firewire, 17)\ ENUM(pcmcia_i, 18)\ ENUM(pcmcia_ii, 19)\ ENUM(pcmcia_iii, 20)\ ENUM(cardbus, 21)\ ENUM(access_bus, 22)\ ENUM(scsi_ii, 23)\ ENUM(scsi_wide, 24)\ ENUM(pc_98, 25)\ ENUM(pc_98_hireso, 26)\ ENUM(pc_h98, 27)\ ENUM(video, 28)\ ENUM(audio, 29)\ ENUM(modem, 30)\ ENUM(network, 31)\ ENUM(sata, 32)\ ENUM(sas, 33)\ ENUM(_8251_compatible, 160)\ ENUM(_8251_fifo_compatible, 161) #define PortConnector_FIELDS\ FIELD(0, const char*, internalDesignator, "")\ FIELD(0, PortConnectorType, internalConnectorType, "")\ FIELD(0, const char*, externalDesignator, "")\ FIELD(0, PortConnectorType, externalConnectorType, "")\ FIELD(0, PortType, portType, "") //----------------------------------------------------------------------------- // SystemSlot #define SystemSlotType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(isa, 3)\ ENUM(mca, 4)\ ENUM(eisa, 5)\ ENUM(pci, 6)\ ENUM(pcmcia, 7)\ ENUM(vesa, 8)\ ENUM(proprietary, 9)\ ENUM(processor, 10)\ ENUM(memory_card, 11)\ ENUM(io_riser, 12)\ ENUM(nubus, 13)\ ENUM(pci_66, 14)\ ENUM(agp, 15)\ ENUM(agp_2x, 16)\ ENUM(agp_4x, 17)\ ENUM(pcix, 18)\ ENUM(agp_8x, 19)\ ENUM(pc_98_c20, 160)\ ENUM(pc_98_c24, 161)\ ENUM(pc_98_e, 162)\ ENUM(pc_98_local_bus, 163)\ ENUM(pc_98_card, 164)\ ENUM(pcie, 165)\ ENUM(pcie_x1, 166)\ ENUM(pcie_x2, 167)\ ENUM(pcie_x4, 168)\ ENUM(pcie_x8, 169)\ ENUM(pcie_x16, 170)\ ENUM(pcie2, 171)\ ENUM(pcie2_x1, 172)\ ENUM(pcie2_x2, 173)\ ENUM(pcie2_x4, 174)\ ENUM(pcie2_x8, 175)\ ENUM(pcie2_x16, 176)\ ENUM(pcie3, 177)\ ENUM(pcie3_x1, 178)\ ENUM(pcie3_x2, 179)\ ENUM(pcie3_x4, 180)\ ENUM(pcie3_x8, 181)\ ENUM(pcie3_x16, 182) #define SystemSlotBusWidth_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(_8, 3)\ ENUM(_16, 4)\ ENUM(_32, 5)\ ENUM(_64, 6)\ ENUM(_128, 7)\ ENUM(x1, 8)\ ENUM(x2, 9)\ ENUM(x4, 10)\ ENUM(x8, 11)\ ENUM(x12, 12)\ ENUM(x16, 13)\ ENUM(x32, 14) #define SystemSlotUsage_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(available, 3)\ ENUM(in_use, 4) #define SystemSlotLength_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(_short, 3)\ ENUM(_long, 4) #define SystemSlotFlags1_ENUMERATORS\ ENUM(unknown, 0x1)\ ENUM(v5, 0x2)\ ENUM(v3_3, 0x4)\ ENUM(shared, 0x8)\ ENUM(pc_card_16, 0x10)\ ENUM(pc_cardbus, 0x20)\ ENUM(pc_zoom_video, 0x40)\ ENUM(pc_modem_ring_resume, 0x80) #define SystemSlotFlags2_ENUMERATORS\ ENUM(pme, 0x1)\ ENUM(hot_plug, 0x2)\ ENUM(smbus, 0x4)\ #define SystemSlot_FIELDS\ FIELD(0, const char*, designation, "")\ FIELD(0, SystemSlotType, type, "")\ FIELD(0, SystemSlotBusWidth, busWidth, "")\ FIELD(0, SystemSlotUsage, usage, "")\ FIELD(0, SystemSlotLength, length, "")\ FIELD(0, u16, id, "")\ FIELD(0, SystemSlotFlags1, flags1, "")\ FIELD(0, SystemSlotFlags2, flags2, "")\ FIELD(0, u16, segmentGroupNumber, "")\ FIELD(0, u8, busNumber, "")\ FIELD(F_INTERNAL, u8, functionAndDeviceNumber, "")\ FIELD(F_DERIVED, u8, deviceNumber, "")\ FIELD(F_DERIVED, u8, functionNumber, "") //----------------------------------------------------------------------------- // OnBoardDevices #define OnBoardDeviceType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(video, 3)\ ENUM(scsi_controller, 4)\ ENUM(ethernet, 5)\ ENUM(token_ring, 6)\ ENUM(sound, 7)\ ENUM(pata_controller, 8)\ ENUM(sata_controller, 9)\ ENUM(sas_controller, 10) #define OnBoardDevices_FIELDS\ FIELD(0, OnBoardDeviceType, type, "")\ FIELD(0, const char*, description, "")\ FIELD(F_DERIVED, bool, enabled, "")\ /* NB: this structure could contain any number of type/description pairs, but Dell BIOS only provides 1 */ //----------------------------------------------------------------------------- // MemoryArray #define MemoryArrayLocation_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(motherboard, 3)\ ENUM(isa_addon, 4)\ ENUM(eisa_addon, 5)\ ENUM(pci_addon, 6)\ ENUM(mca_addon, 7)\ ENUM(pcmcia_addon, 8)\ ENUM(proprietary_addon, 9)\ ENUM(nubus, 10)\ ENUM(pc_98_c20, 160)\ ENUM(pc_98_c24, 161)\ ENUM(pc_98_e, 162)\ ENUM(pc_98_local_bus, 163) #define MemoryArrayUse_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(system, 3)\ ENUM(video, 4)\ ENUM(flash, 5)\ ENUM(nvram, 6)\ ENUM(cache, 7) #define MemoryArray_FIELDS\ FIELD(0, MemoryArrayLocation, location, "")\ FIELD(0, MemoryArrayUse, use, "")\ FIELD(0, ECC, ecc, "")\ FIELD(F_INTERNAL, u32, maxCapacity32, "")\ FIELD(0, Handle, hError, "")\ FIELD(0, u16, numDevices, "")\ FIELD(0, Size, maxCapacity, "") //----------------------------------------------------------------------------- // MemoryDevice #define MemoryDeviceFormFactor_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(SIMM, 3)\ ENUM(SIP, 4)\ ENUM(chip, 5)\ ENUM(DIP, 6)\ ENUM(ZIP, 7)\ ENUM(proprietary_card, 8)\ ENUM(DIMM, 9)\ ENUM(TSOP, 10)\ ENUM(row_of_chips, 11)\ ENUM(RIMM, 12)\ ENUM(SODIMM, 13)\ ENUM(SRIMM, 14)\ ENUM(FBDIMM, 15) #define MemoryDeviceType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(DRAM, 3)\ ENUM(EDRAM, 4)\ ENUM(VRAM, 5)\ ENUM(SRAM, 6)\ ENUM(RAM, 7)\ ENUM(ROM, 8)\ ENUM(FLASH, 9)\ ENUM(EEPROM, 10)\ ENUM(FEPROM, 11)\ ENUM(EPROM, 12)\ ENUM(CRRAM, 13)\ ENUM(_3DRAM, 14)\ ENUM(SDRAM, 15)\ ENUM(SGRAM, 16)\ ENUM(RDRAM, 17)\ ENUM(DDR, 18)\ ENUM(DDR2, 19)\ ENUM(DDR2_FBDIMM, 20)\ ENUM(DDR3, 24)\ ENUM(FBD2, 25) #define MemoryDeviceTypeFlags_ENUMERATORS\ ENUM(other, 0x0002)\ ENUM(unknown, 0x0004)\ ENUM(fast_paged, 0x0008)\ ENUM(static_column, 0x0010)\ ENUM(pseudo_static, 0x0020)\ ENUM(rambus, 0x0040)\ ENUM(synchronous, 0x0080)\ ENUM(cmos, 0x0100)\ ENUM(edo, 0x0200)\ ENUM(window_dram, 0x0400)\ ENUM(cache_dram, 0x0800)\ ENUM(non_volatile, 0x1000)\ ENUM(buffered, 0x2000)\ ENUM(unbuffered, 0x4000) #define MemoryDevice_FIELDS\ FIELD(0, Handle, hMemoryArray, "")\ FIELD(0, Handle, hError, "")\ FIELD(0, u16, totalWidth, " bits")\ FIELD(0, u16, dataWidth, " bits")\ FIELD(F_INTERNAL, u16, size16, "")\ FIELD(0, MemoryDeviceFormFactor, formFactor, "")\ FIELD(0, u8, deviceSet, "")\ FIELD(0, const char*, locator, "")\ FIELD(0, const char*, bank, "")\ FIELD(0, MemoryDeviceType, type, "")\ FIELD(0, MemoryDeviceTypeFlags, typeFlags, "")\ FIELD(0, u16, speed, " MHz")\ FIELD(0, const char*, manufacturer, "")\ FIELD(0, const char*, serialNumber, "")\ FIELD(0, const char*, assetTag, "")\ FIELD(0, const char*, partNumber, "")\ FIELD(F_INTERNAL, u8, attributes, "")\ FIELD(F_INTERNAL, u32, size32, "")\ FIELD(0, u16, configuredSpeed, " MHz")\ FIELD(F_DERIVED, Size, size, "")\ FIELD(F_DERIVED, u8, rank, "")\ //----------------------------------------------------------------------------- // MemoryArrayMappedAddress #define MemoryArrayMappedAddress_FIELDS\ FIELD(F_INTERNAL, u32, startAddress32, "")\ FIELD(F_INTERNAL, u32, endAddress32, "")\ FIELD(0, Handle, hMemoryArray, "")\ FIELD(0, u8, partitionWidth, "")\ FIELD(F_HEX, u64, startAddress, "")\ FIELD(F_HEX, u64, endAddress, "") //----------------------------------------------------------------------------- // MemoryDeviceMappedAddress #define MemoryDeviceMappedAddress_FIELDS\ FIELD(F_INTERNAL, u32, startAddress32, "")\ FIELD(F_INTERNAL, u32, endAddress32, "")\ FIELD(0, Handle, hMemoryDevice, "")\ FIELD(0, Handle, hMemoryArrayMappedAddress, "")\ FIELD(0, u8, partitionRowPosition, "")\ FIELD(0, u8, interleavePosition, "")\ FIELD(0, u8, interleavedDataDepth, "")\ FIELD(F_HEX, u64, startAddress, "")\ FIELD(F_HEX, u64, endAddress, "") //---------------------------------------------------------------------------- // PortableBattery #define PortableBatteryChemistry_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(lead_acid, 3)\ ENUM(nickel_cadmium, 4)\ ENUM(nickel_metal_hydride, 5)\ ENUM(lithium_ion, 6)\ ENUM(zinc_air, 7)\ ENUM(lithium_polymer, 8) #define PortableBattery_FIELDS\ FIELD(0, const char*, location, "")\ FIELD(0, const char*, manufacturer, "")\ FIELD(0, const char*, date, "")\ FIELD(0, const char*, serialNumber, "")\ FIELD(0, const char*, deviceName, "")\ FIELD(0, PortableBatteryChemistry, chemistry, "")\ FIELD(0, u16, capacity, " mWh")\ FIELD(0, u16, voltage, " mV")\ FIELD(0, const char*, sbdsVersion, "")\ FIELD(0, u8, maxError, "%")\ FIELD(0, u16, sbdsSerialNumber, "")\ FIELD(0, u16, sbdsDate, "")\ FIELD(0, const char*, sbdsChemistry, "")\ FIELD(0, u8, capacityMultiplier, "")\ FIELD(0, u32, oemSpecific, "") //---------------------------------------------------------------------------- // VoltageProbe #define VoltageProbeLocation_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(processor, 3)\ ENUM(disk, 4)\ ENUM(peripheral_bay, 5)\ ENUM(system_management_module, 6)\ ENUM(motherboard, 7)\ ENUM(memory_module, 8)\ ENUM(processor_module, 9)\ ENUM(power_unit, 10)\ ENUM(add_in_card, 11) #define VoltageProbe_FIELDS\ FIELD(0, const char*, description, "")\ FIELD(F_INTERNAL, u8, locationAndStatus, "")\ FIELD(0, i16, maxValue, " mV")\ FIELD(0, i16, minValue, " mV")\ FIELD(0, i16, resolution, " x 0.1 mV")\ FIELD(0, i16, tolerance, " mV")\ FIELD(0, i16, accuracy, " x 0.01%")\ FIELD(0, u32, oemDefined, "")\ FIELD(0, i16, nominalValue, " mv")\ FIELD(F_DERIVED, VoltageProbeLocation, location, "")\ FIELD(F_DERIVED, State, status, "") //---------------------------------------------------------------------------- // CoolingDevice #define CoolingDeviceType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(fan, 3)\ ENUM(centrifugal_blower, 4)\ ENUM(chip_fan, 5)\ ENUM(cabinet_fan, 6)\ ENUM(power_supply_fan, 7)\ ENUM(heat_pipe, 8)\ ENUM(integrated_refrigeration, 9)\ ENUM(active_cooling, 16)\ ENUM(passive_cooling, 17) #define CoolingDevice_FIELDS\ FIELD(0, Handle, hTemperatureProbe, "")\ FIELD(F_INTERNAL, u8, typeAndStatus, "")\ FIELD(0, u8, group, "")\ FIELD(0, u32, oemDefined, "")\ FIELD(0, u16, nominalSpeed, " rpm")\ FIELD(0, const char*, description, "")\ FIELD(F_DERIVED, CoolingDeviceType, type, "")\ FIELD(F_DERIVED, State, status, "") //---------------------------------------------------------------------------- // TemperatureProbe #define TemperatureProbeLocation_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(processor, 3)\ ENUM(disk, 4)\ ENUM(peripheral_bay, 5)\ ENUM(system_management_module, 6)\ ENUM(motherboard, 7)\ ENUM(memory_module, 8)\ ENUM(processor_module, 9)\ ENUM(power_unit, 10)\ ENUM(add_in_card, 11)\ ENUM(front_panel_board, 12)\ ENUM(back_panel_board, 13)\ ENUM(power_system_board, 14)\ ENUM(drive_backplane, 15) #define TemperatureProbe_FIELDS\ FIELD(0, const char*, description, "")\ FIELD(F_INTERNAL, u8, locationAndStatus, "")\ FIELD(0, i16, maxValue, " dDegC")\ FIELD(0, i16, minValue, " dDegC")\ FIELD(0, i16, resolution, " mDegC")\ FIELD(0, i16, tolerance, " dDegC")\ FIELD(0, i16, accuracy, " x 0.01%")\ FIELD(0, u32, oemDefined, "")\ FIELD(0, i16, nominalValue, " dDegC")\ FIELD(F_DERIVED, TemperatureProbeLocation, location, "")\ FIELD(F_DERIVED, State, status, "") //---------------------------------------------------------------------------- // SystemBoot #define SystemBootStatus_ENUMERATORS\ ENUM(no_error, 0)\ ENUM(no_bootable_media, 1)\ ENUM(os_load_failed, 2)\ ENUM(hardware_failure_firmware, 3)\ ENUM(hardware_failure_os, 4)\ ENUM(user_requested_boot, 5)\ ENUM(security_violation, 6)\ ENUM(previously_requested_image, 7)\ ENUM(watchdog_expired, 8) #define SystemBoot_FIELDS\ FIELD(F_INTERNAL, u32, reserved32, "")\ FIELD(F_INTERNAL, u16, reserved16, "")\ FIELD(0, SystemBootStatus, status, "")\ //---------------------------------------------------------------------------- // ManagementDevice #define ManagementDeviceType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(LM75, 3)\ ENUM(LM78, 4)\ ENUM(LM79, 5)\ ENUM(LM80, 6)\ ENUM(LM81, 7)\ ENUM(ADM9240, 8)\ ENUM(DS1780, 9)\ ENUM(M1617, 0xA)\ ENUM(GL518SM, 0xB)\ ENUM(W83781D, 0xC)\ ENUM(HT82H791, 0xD)\ #define ManagementDeviceAddressType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(port, 3)\ ENUM(memory, 4)\ ENUM(smbus, 5) #define ManagementDevice_FIELDS\ FIELD(0, const char*, description, "")\ FIELD(0, ManagementDeviceType, type, "")\ FIELD(0, u32, address, "")\ FIELD(0, ManagementDeviceAddressType, addressType, "") //---------------------------------------------------------------------------- // ManagementDeviceComponent #define ManagementDeviceComponent_FIELDS\ FIELD(0, const char*, description, "")\ FIELD(0, Handle, hDevice, "")\ FIELD(0, Handle, hComponent, "")\ FIELD(0, Handle, hThreshold, "") //---------------------------------------------------------------------------- // ManagementDeviceThreshold #define ManagementDeviceThreshold_FIELDS\ FIELD(0, i16, nonCriticalLo, "")\ FIELD(0, i16, nonCriticalHi, "")\ FIELD(0, i16, criticalLo, "")\ FIELD(0, i16, criticalHi, "")\ FIELD(0, i16, nonrecoverableLo, "")\ FIELD(0, i16, nonrecoverableHi, "") //---------------------------------------------------------------------------- // SystemPowerSupply #define SystemPowerSupplyCharacteristics_ENUMERATORS\ ENUM(hot_replaceable, 1)\ ENUM(present, 2)\ ENUM(unplugged, 4) #define SystemPowerSupplyType_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(linear, 3)\ ENUM(switching, 4)\ ENUM(battery, 5)\ ENUM(ups, 6)\ ENUM(converter, 7)\ ENUM(regulator, 8) #define SystemPowerSupplyInputSwitching_ENUMERATORS\ ENUM(other, 1)\ ENUM(unknown, 2)\ ENUM(manual, 3)\ ENUM(auto_switch, 4)\ ENUM(wide_range, 5)\ ENUM(none, 6) #define SystemPowerSupply_FIELDS\ FIELD(0, u8, group, "")\ FIELD(0, const char*, location, "")\ FIELD(0, const char*, deviceName, "")\ FIELD(0, const char*, manufacturer, "")\ FIELD(0, const char*, serialNumber, "")\ FIELD(0, const char*, assetTag, "")\ FIELD(0, const char*, partNumber, "")\ FIELD(0, const char*, revisionLevel, "")\ FIELD(0, i16, maxPower, " mW")\ FIELD(0, SystemPowerSupplyCharacteristics, characteristics, "")\ FIELD(0, Handle, hVoltageProbe, "")\ FIELD(0, Handle, hCoolingDevice, "")\ FIELD(0, Handle, hCurrentProbe, "")\ FIELD(F_DERIVED, SystemPowerSupplyType, type, "")\ FIELD(F_DERIVED, State, status, "")\ FIELD(F_DERIVED, SystemPowerSupplyInputSwitching, inputSwitching, "")\ //---------------------------------------------------------------------------- // OnboardDevices2 #define OnboardDevices2_FIELDS\ FIELD(0, const char*, referenceDesignation, "")\ FIELD(0, OnBoardDeviceType, type, "")\ FIELD(0, u8, instance, "")\ FIELD(0, u16, groupNumber, "")\ FIELD(0, u8, busNumber, "")\ FIELD(F_INTERNAL, u8, functionAndDeviceNumber, "")\ FIELD(F_DERIVED, bool, enabled, "")\ FIELD(F_DERIVED, u8, deviceNumber, "")\ FIELD(F_DERIVED, u8, functionNumber, "")\ //----------------------------------------------------------------------------- struct Header { u8 id; u8 length; Handle handle; }; // define each enumeration: #define ENUM(enumerator, value) enumerator = value, // (a struct wrapper allows reusing common enumerator names such as `other'.) #define ENUMERATION(name, type)\ struct name\ {\ /* (determines how much data to read from SMBIOS) */\ typedef type T;\ name(): value((Enum)0) {}\ name(size_t num): value((Enum)num) {}\ /* (the existence of this member type indicates the field is an enum) */\ enum Enum { name##_ENUMERATORS sentinel } value;\ /* (allows generic Field comparison against numeric_limits) */\ operator size_t() const { return value; }\ }; ENUMERATIONS #undef ENUMERATION #undef ENUM // declare each structure #define FIELD(flags, type, name, units) type name; #define STRUCTURE(name, id)\ struct name\ {\ /* (allows searching for a structure with a given handle) */\ Header header;\ /* (defines a linked list of instances of this structure type) */\ name* next;\ name##_FIELDS\ }; STRUCTURES #undef STRUCTURE #undef FIELD // declare a struct holding pointers (freed at exit) to each structure struct Structures { #define STRUCTURE(name, id) name* name##_; STRUCTURES #undef STRUCTURE }; #pragma pack(pop) //----------------------------------------------------------------------------- /** * @return a pointer to a static Structures (i.e. always valid), with its * member pointers non-zero iff SMBIOS information is available and includes * the corresponding structure. * * thread-safe; return value should be cached (if possible) to avoid an * atomic comparison. **/ LIB_API const Structures* GetStructures(); /** * @return a string describing all structures (omitting fields with * meaningless or dummy values). **/ LIB_API std::string StringizeStructures(const Structures*); } // namespace SMBIOS #endif // #ifndef INCLUDED_SMBIOS Index: ps/trunk/source/lib/sysdep/tests/test_sysdep.h =================================================================== --- ps/trunk/source/lib/sysdep/tests/test_sysdep.h (revision 19898) +++ ps/trunk/source/lib/sysdep/tests/test_sysdep.h (revision 19899) @@ -1,200 +1,200 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "lib/self_test.h" #include "lib/lib.h" #include "lib/secure_crt.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/filesystem.h" #include "lib/sysdep/sysdep.h" #if OS_BSD || OS_LINUX # include "lib/sysdep/os/unix/unix_executable_pathname.h" # include "mocks/dlfcn.h" # include "mocks/unistd.h" #endif class TestSysdep : public CxxTest::TestSuite { public: void test_random() { u64 a = 0, b = 0; TS_ASSERT_OK(sys_generate_random_bytes((u8*)&a, sizeof(a))); TS_ASSERT_OK(sys_generate_random_bytes((u8*)&b, sizeof(b))); TS_ASSERT_DIFFERS(a, b); } void test_sys_ExecutablePathname() { OsPath path = sys_ExecutablePathname(); // Try it first with the real executable (i.e. the // one that's running this test code) // Check it's absolute TSM_ASSERT(L"Path: "+path.string(), path_is_absolute(path.string().c_str())); // Check the file exists struct stat s; TSM_ASSERT_EQUALS(L"Path: "+path.string(), wstat(path, &s), 0); } void test_unix_ExecutablePathname() { #if !(OS_BSD || OS_LINUX) } #else // Since the implementation uses realpath, the tested files need to // really exist. So set up a directory tree for testing: const char* tmpdir = getenv("TMPDIR"); if (! tmpdir) tmpdir = P_tmpdir; char root[PATH_MAX]; sprintf_s(root, ARRAY_SIZE(root), "%s/pyrogenesis-test-sysdep-XXXXXX", tmpdir); TS_ASSERT(mkdtemp(root)); char rootres[PATH_MAX]; TS_ASSERT(realpath(root, rootres)); std::string rootstr(rootres); OsPath rootstrw(rootstr); const char* dirs[] = { "/example", "/example/a", "/example/a/b", "/example/a/b/c", "/example/a/b/d", "/example/a/e", "/example/a/f" }; const char* files[] = { "/example/executable", "/example/a/f/executable", }; for (size_t i = 0; i < ARRAY_SIZE(dirs); ++i) { std::string name = rootstr + dirs[i]; TS_ASSERT_EQUALS(mkdir(name.c_str(), 0700), 0); } for (size_t i = 0; i < ARRAY_SIZE(files); ++i) { std::string name = rootstr + files[i]; FILE* f = fopen(name.c_str(), "w"); TS_ASSERT(f); fclose(f); } // Try with absolute paths { Mock_dladdr d(rootstr+"/example/executable"); TS_ASSERT_PATH_EQUALS(unix_ExecutablePathname(), rootstrw/L"example/executable"); } { Mock_dladdr d(rootstr+"/example/./a/b/../e/../../executable"); TS_ASSERT_PATH_EQUALS(unix_ExecutablePathname(), rootstrw/L"example/executable"); } // Try with relative paths { Mock_dladdr d("./executable"); Mock_getcwd m(rootstr+"/example"); TS_ASSERT_PATH_EQUALS(unix_ExecutablePathname(), rootstrw/L"example/executable"); } { Mock_dladdr d("./executable"); Mock_getcwd m(rootstr+"/example/"); TS_ASSERT_PATH_EQUALS(unix_ExecutablePathname(), rootstrw/L"example/executable"); } { Mock_dladdr d("../d/../../f/executable"); Mock_getcwd m(rootstr+"/example/a/b/c"); TS_ASSERT_PATH_EQUALS(unix_ExecutablePathname(), rootstrw/L"example/a/f/executable"); } // Try with pathless names { Mock_dladdr d("executable"); TS_ASSERT_PATH_EQUALS(unix_ExecutablePathname(), OsPath()); } // Clean up the temporary files for (size_t i = 0; i < ARRAY_SIZE(files); ++i) { std::string name = rootstr + files[i]; TS_ASSERT_EQUALS(unlink(name.c_str()), 0); } for (ssize_t i = ARRAY_SIZE(dirs)-1; i >= 0; --i) // reverse order { std::string name(root); name += dirs[i]; TS_ASSERT_EQUALS(rmdir(name.c_str()), 0); } TS_ASSERT_EQUALS(rmdir(root), 0); } // Mock classes for test_unix_ExecutablePathname class Mock_dladdr : public T::Base_dladdr { public: Mock_dladdr(const std::string& fname) : fname_(fname) { } int dladdr(void *UNUSED(addr), Dl_info *info) { info->dli_fname = fname_.c_str(); return 1; } private: std::string fname_; }; class Mock_getcwd : public T::Base_getcwd { public: Mock_getcwd(const std::string& buf) : buf_(buf) { } char* getcwd(char* buf, size_t size) { strncpy_s(buf, size, buf_.c_str(), buf_.length()); return buf; } private: std::string buf_; }; #endif // !(OS_BSD || OS_LINUX) private: bool path_is_absolute(const wchar_t* path) { // UNIX-style absolute paths if (path[0] == '/') return true; // Windows UNC absolute paths if (path[0] == '\\' && path[1] == '\\') return true; // Windows drive-letter absolute paths if (iswalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) return true; return false; } }; Index: ps/trunk/source/lib/tests/test_bits.h =================================================================== --- ps/trunk/source/lib/tests/test_bits.h (revision 19898) +++ ps/trunk/source/lib/tests/test_bits.h (revision 19899) @@ -1,169 +1,169 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/bits.h" //#define EQUALS(actual, expected) ENSURE((actual) == (expected)) #define EQUALS TS_ASSERT_EQUALS class TestBits : public CxxTest::TestSuite { public: void test_Bit() { EQUALS(Bit(0), 1u); EQUALS(Bit(8), 0x100u); EQUALS(Bit(31), u32(0x80000000ul)); EQUALS(Bit(1), u64(2)); EQUALS(Bit(32), u64(0x100000000ull)); EQUALS(Bit(63), u64(0x8000000000000000ull)); } void test_IsBitSet() { EQUALS(IsBitSet(0u, 1), false); EQUALS(IsBitSet(1u, 1), false); EQUALS(IsBitSet(2u, 1), true); EQUALS(IsBitSet(0xFFFFFFFFul, 0), true); EQUALS(IsBitSet(0xFFFFFFFFul, 31), true); EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 0), true); EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 31), true); EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 32), true); EQUALS(IsBitSet(0xFFFFFFFFFFFFFFFFull, 63), true); } void test_bit_mask() { EQUALS(bit_mask(0), 0); EQUALS(bit_mask(2), 0x3); EQUALS(bit_mask(16), 0xFFFF); EQUALS(bit_mask(0), 0u); EQUALS(bit_mask(2), 0x3u); EQUALS(bit_mask(32), 0xFFFFFFFFul); EQUALS(bit_mask(0), 0u); EQUALS(bit_mask(2), 0x3u); EQUALS(bit_mask(32), 0xFFFFFFFFull); EQUALS(bit_mask(64), 0xFFFFFFFFFFFFFFFFull); } void test_bits() { EQUALS(bits(0xFFFF, 0, 15), 0xFFFF); EQUALS(bits(0xFFFF, 0, 7), 0xFF); EQUALS(bits(0xFFFF, 8, 15), 0xFF); EQUALS(bits(0xFFFF, 14, 15), 0x3); EQUALS(bits(0xAA55, 4, 11), 0xA5); EQUALS(bits(0xAA55, 14, 15), 0x2); EQUALS(bits(0ul, 0, 31), 0ul); EQUALS(bits(0xFFFFFFFFul, 0, 31), 0xFFFFFFFFul); EQUALS(bits(0ull, 0, 63), 0ull); EQUALS(bits(0xFFFFFFFFull, 0, 31), 0xFFFFFFFFull); EQUALS(bits(0x0000FFFFFFFF0000ull, 16, 47), 0xFFFFFFFFull); EQUALS(bits(0xFFFFFFFFFFFFFFFFull, 0, 63), 0xFFFFFFFFFFFFFFFFull); EQUALS(bits(0xA5A5A5A5A5A5A5A5ull, 32, 63), 0xA5A5A5A5ull); } void test_PopulationCount() { EQUALS(PopulationCount(0), 0u); EQUALS(PopulationCount(4), 1u); EQUALS(PopulationCount(0x28), 2u); EQUALS(PopulationCount(0xFF), 8u); EQUALS(PopulationCount(0x0ul), 0u); EQUALS(PopulationCount(0x8ul), 1u); EQUALS(PopulationCount(0xFFFFul), 16u); EQUALS(PopulationCount(0xFFFFFFFFul), 32u); EQUALS(PopulationCount(0x0ull), 0u); EQUALS(PopulationCount(0x10ull), 1u); EQUALS(PopulationCount(0xFFFFull), 16u); EQUALS(PopulationCount(0xFFFFFFFFull), 32u); EQUALS(PopulationCount(0xFFFFFFFFFFFFFFFEull), 63u); EQUALS(PopulationCount(0xFFFFFFFFFFFFFFFFull), 64u); } void test_is_pow2() { EQUALS(is_pow2(0u), false); EQUALS(is_pow2(~0u), false); EQUALS(is_pow2(0x80000001), false); EQUALS(is_pow2(1), true); EQUALS(is_pow2(1u << 31), true); } void test_ceil_log2() { EQUALS(ceil_log2(3u), 2u); EQUALS(ceil_log2(0xffffffffu), 32u); EQUALS(ceil_log2(1u), 0u); EQUALS(ceil_log2(256u), 8u); EQUALS(ceil_log2(0x80000000u), 31u); } void test_floor_log2() { EQUALS(floor_log2(1.f), 0); EQUALS(floor_log2(3.f), 1); EQUALS(floor_log2(256.f), 8); } void test_round_up_to_pow2() { EQUALS(round_up_to_pow2(0u), 1u); EQUALS(round_up_to_pow2(1u), 1u); EQUALS(round_up_to_pow2(127u), 128u); EQUALS(round_up_to_pow2(128u), 128u); EQUALS(round_up_to_pow2(129u), 256u); } void test_round_down_to_pow2() { EQUALS(round_down_to_pow2(1u), 1u); EQUALS(round_down_to_pow2(127u), 64u); EQUALS(round_down_to_pow2(128u), 128u); EQUALS(round_down_to_pow2(129u), 128u); } void test_round_up() { EQUALS(round_up( 0u, 16u), 0u); EQUALS(round_up( 4u, 16u), 16u); EQUALS(round_up(15u, 16u), 16u); EQUALS(round_up(20u, 32u), 32u); EQUALS(round_up(29u, 32u), 32u); EQUALS(round_up(0x1000u, 0x1000u), 0x1000u); EQUALS(round_up(0x1001u, 0x1000u), 0x2000u); EQUALS(round_up(0x1900u, 0x1000u), 0x2000u); } void test_round_down() { EQUALS(round_down( 0u, 16u), 0u); EQUALS(round_down( 4u, 16u), 0u); EQUALS(round_down(15u, 16u), 0u); EQUALS(round_down(20u, 16u), 16u); EQUALS(round_down(29u, 16u), 16u); EQUALS(round_down(0x1900u, 0x1000u), 0x1000u); EQUALS(round_down(0x2001u, 0x2000u), 0x2000u); } }; Index: ps/trunk/source/lib/tests/test_lib.h =================================================================== --- ps/trunk/source/lib/tests/test_lib.h (revision 19898) +++ ps/trunk/source/lib/tests/test_lib.h (revision 19899) @@ -1,49 +1,49 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/lib.h" class TestLib : public CxxTest::TestSuite { public: void test_hi_lo() { TS_ASSERT_EQUALS(u64_hi(0x0123456789ABCDEFull), 0x01234567u); TS_ASSERT_EQUALS(u64_hi(0x0000000100000002ull), 0x00000001u); TS_ASSERT_EQUALS(u64_lo(0x0123456789ABCDEFull), 0x89ABCDEFu); TS_ASSERT_EQUALS(u64_lo(0x0000000100000002ull), 0x00000002u); TS_ASSERT_EQUALS(u32_hi(0x01234567u), 0x0123u); TS_ASSERT_EQUALS(u32_hi(0x00000001u), 0x0000u); TS_ASSERT_EQUALS(u32_lo(0x01234567u), 0x4567u); TS_ASSERT_EQUALS(u32_lo(0x00000001u), 0x0001u); TS_ASSERT_EQUALS(u64_from_u32(0xFFFFFFFFu, 0x80000008u), 0xFFFFFFFF80000008ull); TS_ASSERT_EQUALS(u32_from_u16(0x8000u, 0xFFFFu), 0x8000FFFFu); } // fp_to_u?? already validate the result. }; Index: ps/trunk/source/lib/tests/test_regex.h =================================================================== --- ps/trunk/source/lib/tests/test_regex.h (revision 19898) +++ ps/trunk/source/lib/tests/test_regex.h (revision 19899) @@ -1,51 +1,51 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/regex.h" class TestRegex : public CxxTest::TestSuite { public: void test_regex() { TS_ASSERT_EQUALS(match_wildcard(L"", L""), 1); TS_ASSERT_EQUALS(match_wildcard(L"a", 0), 1); // NULL matches everything TS_ASSERT_EQUALS(match_wildcard(L"abc", L"abc") , 1); // direct match TS_ASSERT_EQUALS(match_wildcard(L"abc", L"???") , 1); // only ? TS_ASSERT_EQUALS(match_wildcard(L"abc", L"*" ) , 1); // only * TS_ASSERT_EQUALS(match_wildcard(L"ab" , L"a?" ) , 1); // trailing ? TS_ASSERT_EQUALS(match_wildcard(L"abc", L"a?c") , 1); // middle ? TS_ASSERT_EQUALS(match_wildcard(L"abc", L"?bc") , 1); // leading ? TS_ASSERT_EQUALS(match_wildcard(L"abc", L"a*" ) , 1); // trailing * TS_ASSERT_EQUALS(match_wildcard(L"abcdef", L"ab*ef"), 1); // middle * TS_ASSERT_EQUALS(match_wildcard(L"abcdef", L"*f" ), 1); // leading * TS_ASSERT_EQUALS(match_wildcard(L"abcdef", L"a?cd*"), 1); // ? and * TS_ASSERT_EQUALS(match_wildcard(L"abcdef", L"a*d?f"), 1); // * and ? TS_ASSERT_EQUALS(match_wildcard(L"abcdef", L"a*d*" ), 1); // multiple * } }; Index: ps/trunk/source/lib/tex/tex_codec.cpp =================================================================== --- ps/trunk/source/lib/tex/tex_codec.cpp (revision 19898) +++ ps/trunk/source/lib/tex/tex_codec.cpp (revision 19899) @@ -1,150 +1,150 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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. */ /* * support routines for texture codecs */ #include "precompiled.h" #include "tex_codec.h" #include #include #include "lib/allocators/shared_ptr.h" // ArrayDeleter #include "tex.h" // Statically allocate all of the codecs... TexCodecDds DdsCodec; TexCodecPng PngCodec; TexCodecTga TgaCodec; TexCodecBmp BmpCodec; // Codecs will be searched in this order static const ITexCodec *codecs[] = {(ITexCodec *)&DdsCodec, (ITexCodec *)&PngCodec, (ITexCodec *)&TgaCodec, (ITexCodec *)&BmpCodec}; static const int codecs_len = sizeof(codecs) / sizeof(ITexCodec*); // find codec that recognizes the desired output file extension, // or return ERR::TEX_UNKNOWN_FORMAT if unknown. // note: does not raise a warning because it is used by // tex_is_known_extension. Status tex_codec_for_filename(const OsPath& extension, const ITexCodec** c) { for(int i = 0; i < codecs_len; ++i) { // we found it if(codecs[i]->is_ext(extension)) { *c = codecs[i]; return INFO::OK; } } return ERR::TEX_UNKNOWN_FORMAT; // NOWARN } // find codec that recognizes the header's magic field Status tex_codec_for_header(const u8* file, size_t file_size, const ITexCodec** c) { // we guarantee at least 4 bytes for is_hdr to look at if(file_size < 4) WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER); for(int i = 0; i < codecs_len; ++i) { // we found it if(codecs[i]->is_hdr(file)) { *c = codecs[i]; return INFO::OK; } } WARN_RETURN(ERR::TEX_UNKNOWN_FORMAT); } Status tex_codec_transform(Tex* t, size_t transforms) { Status ret = INFO::TEX_CODEC_CANNOT_HANDLE; // find codec that understands the data, and transform for(int i = 0; i < codecs_len; ++i) { Status err = codecs[i]->transform(t, transforms); // success if(err == INFO::OK) return INFO::OK; // something went wrong else if(err != INFO::TEX_CODEC_CANNOT_HANDLE) { ret = err; DEBUG_WARN_ERR(ERR::LOGIC); // codec indicates error } } return ret; } //----------------------------------------------------------------------------- // helper functions used by codecs //----------------------------------------------------------------------------- // allocate an array of row pointers that point into the given texture data. // indicates whether the file format is top-down or // bottom-up; the row array is inverted if necessary to match global // orienatation. (this is more efficient than "transforming" later) // // used by PNG and JPG codecs. // // note: we don't allocate the data param ourselves because this function is // needed for encoding, too (where data is already present). std::vector tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch, size_t src_flags, size_t dst_orientation) { const bool flip = !tex_orientations_match(src_flags, dst_orientation); std::vector rows(h); // determine start position and direction RowPtr pos = flip? data+pitch*(h-1) : data; const ssize_t add = flip? -(ssize_t)pitch : (ssize_t)pitch; const RowPtr end = flip? data-pitch : data+pitch*h; for(size_t i = 0; i < h; i++) { rows[i] = pos; pos += add; } ENSURE(pos == end); return rows; } Status tex_codec_write(Tex* t, size_t transforms, const void* hdr, size_t hdr_size, DynArray* da) { RETURN_STATUS_IF_ERR(t->transform(transforms)); void* img_data = t->get_data(); const size_t img_size = t->img_size(); RETURN_STATUS_IF_ERR(da_append(da, hdr, hdr_size)); RETURN_STATUS_IF_ERR(da_append(da, img_data, img_size)); return INFO::OK; } Index: ps/trunk/source/lib/types.h =================================================================== --- ps/trunk/source/lib/types.h (revision 19898) +++ ps/trunk/source/lib/types.h (revision 19899) @@ -1,44 +1,44 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * convenient type aliases (shorter than stdint.h's uintN_t) */ #ifndef INCLUDED_TYPES #define INCLUDED_TYPES #include "lib/posix/posix_types.h" typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef unsigned int uint; #endif // #ifndef INCLUDED_TYPES Index: ps/trunk/source/lib/sysdep/smbios.cpp =================================================================== --- ps/trunk/source/lib/sysdep/smbios.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/smbios.cpp (revision 19899) @@ -1,721 +1,721 @@ -/* Copyright (c) 2015 Wildfire Games +/* Copyright (C) 2015 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. */ /* * provide access to System Management BIOS information */ #include "precompiled.h" #include "lib/sysdep/smbios.h" #include "lib/bits.h" #include "lib/alignment.h" #include "lib/byte_order.h" // FOURCC_BE #include "lib/module_init.h" #if OS_WIN # include "lib/sysdep/os/win/wutil.h" # include "lib/sysdep/os/win/wfirmware.h" #endif #include namespace SMBIOS { //----------------------------------------------------------------------------- // GetTable #if OS_WIN static Status GetTable(wfirmware::Table& table) { // (MSDN mentions 'RSMB', but multi-character literals are implementation-defined.) const DWORD provider = FOURCC_BE('R','S','M','B'); // (MSDN says this will be 0, but we'll retrieve it for 100% correctness.) wfirmware::TableIds tableIds = wfirmware::GetTableIDs(provider); if(tableIds.empty()) return ERR::_1; // NOWARN (happens on 32-bit XP) table = wfirmware::GetTable(provider, tableIds[0]); if(table.empty()) WARN_RETURN(ERR::_2); // strip the WmiHeader struct WmiHeader { u8 used20CallingMethod; u8 majorVersion; u8 minorVersion; u8 dmiRevision; u32 length; }; const WmiHeader* wmiHeader = (const WmiHeader*)&table[0]; ENSURE(table.size() == sizeof(WmiHeader) + wmiHeader->length); memmove(&table[0], &table[sizeof(WmiHeader)], table.size()-sizeof(WmiHeader)); return INFO::OK; } #endif // OS_WIN //----------------------------------------------------------------------------- // strings // pointers to the strings (if any) at the end of an SMBIOS structure typedef std::vector Strings; static Strings ExtractStrings(const Header* header, const char* end, const Header*& next) { Strings strings; const char* pos = ((const char*)header) + header->length; while(pos <= end-2) { if(*pos == '\0') { pos++; if(*pos == 0) { pos++; break; } } strings.push_back(pos); pos += strlen(pos); } next = (const Header*)pos; return strings; } // storage for all structures' strings (must be copied from the original // wfirmware table since its std::vector container cannot be stored in a // static variable because we may be called before _cinit) static char* stringStorage; static char* stringStoragePos; // pointers to dynamically allocated structures static Structures structures; static void Cleanup() // called via atexit { SAFE_FREE(stringStorage); stringStoragePos = 0; // free each allocated structure #define STRUCTURE(name, id)\ while(structures.name##_)\ {\ name* next = structures.name##_->next;\ SAFE_FREE(structures.name##_);\ structures.name##_ = next;\ } STRUCTURES #undef STRUCTURE } //----------------------------------------------------------------------------- // FieldInitializer // define function templates that invoke a Visitor for each of a structure's fields #define FIELD(flags, type, name, units) visitor(flags, p.name, #name, units); #define STRUCTURE(name, id) template void VisitFields(name& p, Visitor& visitor) { name##_FIELDS } STRUCTURES #undef STRUCTURE #undef FIELD // initialize each of a structure's fields by copying from the SMBIOS data class FieldInitializer { NONCOPYABLE(FieldInitializer); // reference member public: FieldInitializer(const Header* header, const Strings& strings) : data((const u8*)(header+1)) , end((const u8*)header + header->length) , strings(strings) { } template void operator()(size_t flags, Field& field, const char* UNUSED(name), const char* UNUSED(units)) { if((flags & F_DERIVED) || data >= end) { field = Field(); return; } Read(field, 0); // SFINAE } private: template T ReadValue() { T value; memcpy(&value, data, sizeof(value)); data += sizeof(value); return value; } // construct from SMBIOS representations that don't match the // actual type (e.g. enum) template void Read(Field& field, typename Field::T*) { field = Field(ReadValue()); } template void Read(Field& field, ...) { field = ReadValue(); } const u8* data; const u8* end; const Strings& strings; }; // C++03 14.7.3(2): "An explicit specialization shall be declared [..] in the // namespace of which the enclosing class [..] is a member. // (this specialization avoids a "forcing value to bool true or false" warning) template<> void FieldInitializer::operator()(size_t flags, bool& UNUSED(t), const char* UNUSED(name), const char* UNUSED(units)) { // SMBIOS doesn't specify any individual booleans, so we're only called for // derived fields and don't need to do anything. ENSURE(flags & F_DERIVED); } template<> void FieldInitializer::operator()(size_t flags, const char*& t, const char* UNUSED(name), const char* UNUSED(units)) { t = 0; // (allow immediate `return' when the string is found to be invalid) u8 number; operator()(flags, number, 0, 0); if(number == 0) // no string given return; if(number > strings.size()) { debug_printf("SMBIOS: invalid string number %d (count=%d)\n", number, (int)strings.size()); return; } // copy to stringStorage strcpy(stringStoragePos, strings[number-1]); t = stringStoragePos; stringStoragePos += strlen(t)+1; } //----------------------------------------------------------------------------- // Fixup (e.g. compute derived fields) template void Fixup(Structure& UNUSED(structure)) { // primary template: do nothing } template<> void Fixup(Bios& p) { p.size = size_t(p.encodedSize+1) * 64*KiB; } template<> void Fixup(Processor& p) { p.populated = (p.status & 0x40) != 0; p.status = (ProcessorStatus)bits(p.status, 0, 2); if(p.voltage & 0x80) p.voltage &= ~0x80; else { // (arbitrarily) report the lowest supported value if(IsBitSet(p.voltage, 0)) p.voltage = 50; if(IsBitSet(p.voltage, 1)) p.voltage = 33; if(IsBitSet(p.voltage, 2)) p.voltage = 29; } } template<> void Fixup(Cache& p) { struct DecodeSize { u64 operator()(u16 size) const { const size_t granularity = IsBitSet(size, 15)? 64*KiB : 1*KiB; return u64(bits(size, 0, 14)) * granularity; } }; p.maxSize = DecodeSize()(p.maxSize16); p.installedSize = DecodeSize()(p.installedSize16); p.level = bits(p.configuration, 0, 2)+1; p.location = (CacheLocation)bits(p.configuration, 5, 6); p.mode = (CacheMode)bits(p.configuration, 8, 9); p.configuration = (CacheConfigurationFlags)(p.configuration & ~0x367); } template<> void Fixup(SystemSlot& p) { // (only initialize function and device numbers if functionAndDeviceNumber is valid) if(p.functionAndDeviceNumber != 0xFF) { p.functionNumber = bits(p.functionAndDeviceNumber, 0, 2); p.deviceNumber = bits(p.functionAndDeviceNumber, 3, 7); } } template<> void Fixup(OnBoardDevices& p) { p.enabled = (p.type.value & 0x80) != 0; p.type = (OnBoardDeviceType)(p.type & ~0x80); } template<> void Fixup(MemoryArray& p) { if(p.maxCapacity32 != (u32)INT32_MIN) p.maxCapacity = u64(p.maxCapacity32) * KiB; } template<> void Fixup(MemoryDevice& p) { if(p.size16 != INT16_MAX) p.size = u64(bits(p.size16, 0, 14)) * (IsBitSet(p.size16, 15)? 1*KiB : 1*MiB); else p.size = u64(bits(p.size32, 0, 30)) * MiB; p.rank = bits(p.attributes, 0, 3); } template<> void Fixup(MemoryArrayMappedAddress& p) { if(p.startAddress32 != UINT32_MAX) p.startAddress = u64(p.startAddress32) * KiB; if(p.endAddress32 != UINT32_MAX) p.endAddress = u64(p.endAddress32) * KiB; } template<> void Fixup(MemoryDeviceMappedAddress& p) { if(p.startAddress32 != UINT32_MAX) p.startAddress = u64(p.startAddress32) * KiB; if(p.endAddress32 != UINT32_MAX) p.endAddress = u64(p.endAddress32) * KiB; } template<> void Fixup(VoltageProbe& p) { p.location = (VoltageProbeLocation)bits(p.locationAndStatus, 0, 4); p.status = (State)bits(p.locationAndStatus, 5, 7); } template<> void Fixup(CoolingDevice& p) { p.type = (CoolingDeviceType)bits(p.typeAndStatus, 0, 4); p.status = (State)bits(p.typeAndStatus, 5, 7); } template<> void Fixup(TemperatureProbe& p) { p.location = (TemperatureProbeLocation)bits(p.locationAndStatus, 0, 4); p.status = (State)bits(p.locationAndStatus, 5, 7); } template<> void Fixup(SystemPowerSupply& p) { p.type = (SystemPowerSupplyType)bits(p.characteristics, 10, 13); p.status = (State)bits(p.characteristics, 7, 9); p.inputSwitching = (SystemPowerSupplyInputSwitching)bits(p.characteristics, 3, 6); p.characteristics = bits(p.characteristics, 0, 2); } template<> void Fixup(OnboardDevices2& p) { p.enabled = IsBitSet(p.type, 7); p.type = (OnBoardDeviceType)bits(p.type, 0, 6); p.deviceNumber = bits(p.functionAndDeviceNumber, 3, 7); p.functionNumber = bits(p.functionAndDeviceNumber, 0, 2); } //----------------------------------------------------------------------------- // InitStructures template void AddStructure(const Header* header, const Strings& strings, Structure*& listHead) { Structure* const p = (Structure*)calloc(1, sizeof(Structure)); // freed in Cleanup p->header = *header; if(listHead) { // insert at end of list to preserve order of caches/slots Structure* last = listHead; while(last->next) last = last->next; last->next = p; } else listHead = p; FieldInitializer fieldInitializer(header, strings); VisitFields(*p, fieldInitializer); Fixup(*p); } static Status InitStructures() { #if OS_WIN wfirmware::Table table; RETURN_STATUS_IF_ERR(GetTable(table)); #else std::vector table; return ERR::NOT_SUPPORTED; #endif // (instead of counting the total string size, just use the // SMBIOS size - typically 1-2 KB - as an upper bound.) stringStoragePos = stringStorage = (char*)calloc(table.size(), sizeof(char)); // freed in Cleanup if(!stringStorage) WARN_RETURN(ERR::NO_MEM); atexit(Cleanup); const Header* header = (const Header*)&table[0]; const Header* const end = (const Header*)(&table[0] + table.size()); for(;;) { if(header+1 > end) { debug_printf("SMBIOS: table not terminated\n"); break; } if(header->id == 127) // end break; if(header->length < sizeof(Header)) return ERR::_3; // NOWARN (happens on some unknown BIOS, see http://trac.wildfiregames.com/ticket/2985) const Header* next; const Strings strings = ExtractStrings(header, (const char*)end, next); switch(header->id) { #define STRUCTURE(name, id) case id: AddStructure(header, strings, structures.name##_); break; STRUCTURES #undef STRUCTURE default: if(32 < header->id && header->id < 126) // only mention non-proprietary structures of which we are not aware debug_printf("SMBIOS: unknown structure type %d\n", header->id); break; } header = next; } return INFO::OK; } //----------------------------------------------------------------------------- // StringFromEnum template std::string StringFromEnum(Enum UNUSED(field)) { return "(unknown enumeration)"; } #define ENUM(enumerator, VALUE)\ if(field.value == VALUE) /* single bit flag or matching enumerator */\ return #enumerator;\ if(!is_pow2(VALUE)) /* these aren't bit flags */\ {\ allowFlags = false;\ string.clear();\ }\ if(allowFlags && (field.value & (VALUE)))\ {\ if(!string.empty())\ string += "|";\ string += #enumerator;\ } #define ENUMERATION(name, type)\ template<>\ std::string StringFromEnum(name field)\ {\ std::string string;\ bool allowFlags = true;\ name##_ENUMERATORS\ /* (don't warn about the value 0, e.g. optional fields) */\ if(string.empty() && field != 0)\ {\ std::stringstream ss;\ ss << "(unknown " << #name << " " << field.value << ")";\ return ss.str();\ }\ return string;\ } ENUMERATIONS #undef ENUMERATION #undef ENUM //----------------------------------------------------------------------------- // FieldStringizer class FieldStringizer { NONCOPYABLE(FieldStringizer); // reference member public: FieldStringizer(std::stringstream& ss) : ss(ss) { } template void operator()(size_t flags, Field& field, const char* name, const char* units) { if(flags & F_INTERNAL) return; Write(flags, field, name, units, 0); // SFINAE } // special case for sizes [bytes] template void operator()(size_t flags, Size& size, const char* name, const char* units) { if(flags & F_INTERNAL) return; const u64 value = (u64)size.value; if(value == 0) return; u64 divisor; if(value > GiB) { divisor = GiB; units = " GiB"; } else if(value > MiB) { divisor = MiB; units = " MiB"; } else if(value > KiB) { divisor = KiB; units = " KiB"; } else { divisor = 1; units = " bytes"; } WriteName(name); // (avoid floating-point output unless division would truncate the value) if(value % divisor == 0) ss << (value/divisor); else ss << (double(value)/divisor); WriteUnits(units); } private: void WriteName(const char* name) { ss << " "; // indent ss << name << ": "; } void WriteUnits(const char* units) { ss << units; ss << "\n"; } // enumerations and bit flags template void Write(size_t UNUSED(flags), Field& field, const char* name, const char* units, typename Field::Enum*) { // 0 usually means "not included in structure", but some packed // enumerations actually use that value. therefore, only skip this // field if it is zero AND no matching enumerator is found. const std::string string = StringFromEnum(field); if(string.empty()) return; WriteName(name); ss << StringFromEnum(field); WriteUnits(units); } // all other field types template void Write(size_t flags, Field& field, const char* name, const char* units, ...) { // SMBIOS uses the smallest and sometimes also largest representable // signed/unsigned value to indicate `unknown' (except enumerators - // but those are handled in the other function overload), so skip them. if(field == std::numeric_limits::min() || field == std::numeric_limits::max()) return; WriteName(name); if(flags & F_HEX) ss << std::hex << std::uppercase; if(sizeof(field) == 1) // avoid printing as a character ss << unsigned(field); else ss << field; if(flags & F_HEX) ss << std::dec; // (revert to decimal, e.g. for displaying sizes) WriteUnits(units); } std::stringstream& ss; }; template<> void FieldStringizer::operator()(size_t flags, bool& value, const char* name, const char* units) { if(flags & F_INTERNAL) return; WriteName(name); ss << (value? "true" : "false"); WriteUnits(units); } template<> void FieldStringizer::operator()(size_t flags, Handle& handle, const char* name, const char* units) { if(flags & F_INTERNAL) return; // don't display useless handles if(handle.value == 0 || handle.value == 0xFFFE || handle.value == 0xFFFF) return; WriteName(name); ss << handle.value; WriteUnits(units); } template<> void FieldStringizer::operator()(size_t flags, const char*& value, const char* name, const char* units) { if(flags & F_INTERNAL) return; // don't display useless strings if(value == 0) return; std::string string(value); const size_t lastChar = string.find_last_not_of(' '); if(lastChar == std::string::npos) // nothing but spaces return; string.resize(lastChar+1); // strip trailing spaces if(!strcasecmp(value, "To Be Filled By O.E.M.")) return; WriteName(name); ss << "\"" << string << "\""; WriteUnits(units); } //----------------------------------------------------------------------------- // public interface const Structures* GetStructures() { static ModuleInitState initState; Status ret = ModuleInit(&initState, InitStructures); // (callers have to check if member pointers are nonzero anyway, so // we always return a valid pointer to simplify most use cases.) UNUSED2(ret); return &structures; } template void StringizeStructure(const char* name, Structure* p, std::stringstream& ss) { for(; p; p = p->next) { ss << "\n[" << name << "]\n"; FieldStringizer fieldStringizer(ss); VisitFields(*p, fieldStringizer); } } std::string StringizeStructures(const Structures* structures) { std::stringstream ss; #define STRUCTURE(name, id) StringizeStructure(#name, structures->name##_, ss); STRUCTURES #undef STRUCTURE return ss.str(); } } // namespace SMBIOS Index: ps/trunk/source/lib/sysdep/tests/test_rtl.h =================================================================== --- ps/trunk/source/lib/sysdep/tests/test_rtl.h (revision 19898) +++ ps/trunk/source/lib/sysdep/tests/test_rtl.h (revision 19899) @@ -1,54 +1,54 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/sysdep/rtl.h" class Test_rtl : public CxxTest::TestSuite { void _test_AllocateAligned_helper(size_t size, size_t align) { void* p = rtl_AllocateAligned(size, align); TS_ASSERT(p != NULL); TS_ASSERT_EQUALS((intptr_t)p % align, 0u); memset(p, 0x42, size); rtl_FreeAligned(p); } public: void test_AllocateAligned() { for (size_t s = 0; s < 64; ++s) { _test_AllocateAligned_helper(s, 8); _test_AllocateAligned_helper(s, 16); _test_AllocateAligned_helper(s, 64); _test_AllocateAligned_helper(s, 1024); _test_AllocateAligned_helper(s, 65536); } } void test_FreeAligned_null() { rtl_FreeAligned(NULL); } }; Index: ps/trunk/source/lib/tests/test_base32.h =================================================================== --- ps/trunk/source/lib/tests/test_base32.h (revision 19898) +++ ps/trunk/source/lib/tests/test_base32.h (revision 19899) @@ -1,62 +1,62 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/base32.h" class TestBase32 : public CxxTest::TestSuite { public: void test_base32() { // compare against previous output (generated via this base32() call) const u8 in[] = { 0x12, 0x57, 0x85, 0xA2, 0xF9, 0x41, 0xCD, 0x57, 0xF3 }; u8 out[20] = {0}; base32(ARRAY_SIZE(in), in, out); const u8 correct_out[] = "CJLYLIXZIHGVP4Y"; TS_ASSERT_SAME_DATA(out, correct_out, ARRAY_SIZE(correct_out)); } void test_base32_lengths() { #define TEST(in, expected) \ { \ u8 out[20] = {0}; \ base32(ARRAY_SIZE(in), in, out); \ const u8 correct_out[] = expected; \ TS_ASSERT_SAME_DATA(out, correct_out, ARRAY_SIZE(correct_out)); \ } const u8 in1[] = { 0xFF }; const u8 in2[] = { 0xFF, 0xFF }; const u8 in3[] = { 0xFF, 0xFF, 0xFF }; const u8 in4[] = { 0xFF, 0xFF, 0xFF, 0xFF }; const u8 in5[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; const u8 in6[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; TEST(in1, "74"); TEST(in2, "777Q"); TEST(in3, "77776"); TEST(in4, "777777Y"); TEST(in5, "77777777"); TEST(in6, "7777777774"); } }; Index: ps/trunk/source/lib/tests/test_fnv_hash.h =================================================================== --- ps/trunk/source/lib/tests/test_fnv_hash.h (revision 19898) +++ ps/trunk/source/lib/tests/test_fnv_hash.h (revision 19899) @@ -1,43 +1,43 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/fnv_hash.h" class TestFnvHash : public CxxTest::TestSuite { public: void test_fnv_hash() { TS_ASSERT_EQUALS(fnv_hash(""), 0x811C9DC5u); // verify initial value const u32 h1 = fnv_hash("abcdef"); TS_ASSERT_EQUALS(h1, 0xFF478A2A); // verify value for simple string TS_ASSERT_EQUALS(fnv_hash ("abcdef", 6), h1); // same result if hashing buffer TS_ASSERT_EQUALS(fnv_lc_hash("ABcDeF", 6), h1); // same result if case differs TS_ASSERT_EQUALS(fnv_hash64(""), 0xCBF29CE484222325ull); // verify initial value const u64 h2 = fnv_hash64("abcdef"); TS_ASSERT_EQUALS(h2, 0xD80BDA3FBE244A0Aull); // verify value for simple string TS_ASSERT_EQUALS(fnv_hash64("abcdef", 6), h2); // same result if hashing buffer } }; Index: ps/trunk/source/lib/tests/test_rand.h =================================================================== --- ps/trunk/source/lib/tests/test_rand.h (revision 19898) +++ ps/trunk/source/lib/tests/test_rand.h (revision 19899) @@ -1,65 +1,65 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/rand.h" class TestRand : public CxxTest::TestSuite { public: // complain if huge interval or min > max void TestParam() { debug_SkipErrors(ERR::INVALID_PARAM); TS_ASSERT_EQUALS(rand(1, 0), size_t(0)); TS_ASSERT_EQUALS(rand(2, ~0u), size_t(0)); const size_t numSkipped = debug_StopSkippingErrors(); TS_ASSERT_EQUALS(numSkipped, (size_t)2); } // returned number must be in [min, max) void TestReturnedRange() { for(int i = 0; i < 100; i++) { size_t min = rand(), max = min+rand(); size_t x = rand(min, max); TS_ASSERT(min <= x && x < max); } } // make sure both possible values are hit void TestTwoValues() { size_t ones = 0, twos = 0; for(int i = 0; i < 100; i++) { size_t x = rand(1, 3); // paranoia: don't use array (x might not be 1 or 2 - checked below) if(x == 1) ones++; if(x == 2) twos++; } TS_ASSERT_EQUALS(ones+twos, size_t(100)); TS_ASSERT(ones > 10 && twos > 10); } }; Index: ps/trunk/source/lib/tex/tex.h =================================================================== --- ps/trunk/source/lib/tex/tex.h (revision 19898) +++ ps/trunk/source/lib/tex/tex.h (revision 19899) @@ -1,435 +1,435 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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. */ /* * read/write 2d texture files; allows conversion between pixel formats * and automatic orientation correction. */ /** Introduction ------------ This module allows reading/writing 2d images in various file formats and encapsulates them in Tex objects. It supports converting between pixel formats; this is to an extent done automatically when reading/writing. Provision is also made for flipping all images to a default orientation. Format Conversion ----------------- Image file formats have major differences in their native pixel format: some store in BGR order, or have rows arranged bottom-up. We must balance runtime cost/complexity and convenience for the application (not dumping the entire problem on its lap). That means rejecting really obscure formats (e.g. right-to-left pixels), but converting everything else to uncompressed RGB "plain" format except where noted in enum TexFlags (1). Note: conversion is implemented as a pipeline: e.g. "DDS decompress + vertical flip" would be done by decompressing to RGB (DDS codec) and then flipping (generic transform). This is in contrast to all<->all conversion paths: that would be much more complex, if more efficient. Since any kind of preprocessing at runtime is undesirable (the absolute priority is minimizing load time), prefer file formats that are close to the final pixel format. 1) one of the exceptions is S3TC compressed textures. glCompressedTexImage2D requires these be passed in their original format; decompressing would be counterproductive. In this and similar cases, TexFlags indicates such deviations from the plain format. Default Orientation ------------------- After loading, all images (except DDS, because its orientation is indeterminate) are automatically converted to the global row orientation: top-down or bottom-up, as specified by tex_set_global_orientation. If that isn't called, the default is top-down to match Photoshop's DDS output (since this is meant to be the no-preprocessing-required optimized format). Reasons to change it might be to speed up loading bottom-up BMP or TGA images, or to match OpenGL's convention for convenience; however, be aware of the abovementioned issues with DDS. Rationale: it is not expected that this will happen at the renderer layer (a 'flip all texcoords' flag is too much trouble), so the application would have to do the same anyway. By taking care of it here, we unburden the app and save time, since some codecs (e.g. PNG) can flip for free when loading. Codecs / IO Implementation -------------------------- To ease adding support for new formats, they are organized as codecs. The interface aims to minimize code duplication, so it's organized following the principle of "Template Method" - this module both calls into codecs, and provides helper functions that they use. IO is done via VFS, but the codecs are decoupled from this and work with memory buffers. Access to them is endian-safe. When "writing", the image is put into an expandable memory region. This supports external libraries like libpng that do not know the output size beforehand, but avoids the need for a buffer between library and IO layer. Read and write are zero-copy. **/ #ifndef INCLUDED_TEX #define INCLUDED_TEX #include "lib/res/handle.h" #include "lib/os_path.h" #include "lib/file/vfs/vfs_path.h" #include "lib/allocators/dynarray.h" namespace ERR { const Status TEX_UNKNOWN_FORMAT = -120100; const Status TEX_INCOMPLETE_HEADER = -120101; const Status TEX_FMT_INVALID = -120102; const Status TEX_INVALID_COLOR_TYPE = -120103; const Status TEX_NOT_8BIT_PRECISION = -120104; const Status TEX_INVALID_LAYOUT = -120105; const Status TEX_COMPRESSED = -120106; const Status TEX_INVALID_SIZE = -120107; } namespace WARN { const Status TEX_INVALID_DATA = +120108; } namespace INFO { const Status TEX_CODEC_CANNOT_HANDLE = +120109; } /** * flags describing the pixel format. these are to be interpreted as * deviations from "plain" format, i.e. uncompressed RGB. **/ enum TexFlags { /** * flags & TEX_DXT is a field indicating compression. * if 0, the texture is uncompressed; * otherwise, it holds the S3TC type: 1,3,5 or DXT1A. * not converted by default - glCompressedTexImage2D receives * the compressed data. **/ TEX_DXT = 0x7, // mask /** * we need a special value for DXT1a to avoid having to consider * flags & TEX_ALPHA to determine S3TC type. * the value is arbitrary; do not rely on it! **/ DXT1A = 7, /** * indicates B and R pixel components are exchanged. depending on * flags & TEX_ALPHA or bpp, this means either BGR or BGRA. * not converted by default - it's an acceptable format for OpenGL. **/ TEX_BGR = 0x08, /** * indicates the image contains an alpha channel. this is set for * your convenience - there are many formats containing alpha and * divining this information from them is hard. * (conversion is not applicable here) **/ TEX_ALPHA = 0x10, /** * indicates the image is 8bpp greyscale. this is required to * differentiate between alpha-only and intensity formats. * not converted by default - it's an acceptable format for OpenGL. **/ TEX_GREY = 0x20, /** * flags & TEX_ORIENTATION is a field indicating orientation, * i.e. in what order the pixel rows are stored. * * tex_load always sets this to the global orientation * (and flips the image accordingly to match). * texture codecs may in intermediate steps during loading set this * to 0 if they don't know which way around they are (e.g. DDS), * or to whatever their file contains. **/ TEX_BOTTOM_UP = 0x40, TEX_TOP_DOWN = 0x80, TEX_ORIENTATION = TEX_BOTTOM_UP|TEX_TOP_DOWN, /// mask /** * indicates the image data includes mipmaps. they are stored from lowest * to highest (1x1), one after the other. * (conversion is not applicable here) **/ TEX_MIPMAPS = 0x100, TEX_UNDEFINED_FLAGS = ~0x1FF }; /** * stores all data describing an image. * we try to minimize size, since this is stored in OglTex resources * (which are big and pushing the h_mgr limit). **/ struct Tex { /** * file buffer or image data. note: during the course of transforms * (which may occur when being loaded), this may be replaced with * a new buffer (e.g. if decompressing file contents). **/ shared_ptr m_Data; size_t m_DataSize; /** * offset to image data in file. this is required since * tex_get_data needs to return the pixels, but data * returns the actual file buffer. zero-copy load and * write-back to file is also made possible. **/ size_t m_Ofs; size_t m_Width; size_t m_Height; size_t m_Bpp; /// see TexFlags and "Format Conversion" in docs. size_t m_Flags; ~Tex() { free(); } /** * Is the texture object valid and self-consistent? * * @return Status **/ Status validate() const; /** * free all resources associated with the image and make further * use of it impossible. * * @return Status **/ void free(); /** * decode an in-memory texture file into texture object. * * FYI, currently BMP, TGA, JPG, JP2, PNG, DDS are supported - but don't * rely on this (not all codecs may be included). * * @param data Input data. * @param data_size Its size [bytes]. * @return Status. **/ Status decode(const shared_ptr& data, size_t data_size); /** * encode a texture into a memory buffer in the desired file format. * * @param extension (including '.'). * @param da Output memory array. Allocated here; caller must free it * when no longer needed. Invalid unless function succeeds. * @return Status **/ Status encode(const OsPath& extension, DynArray* da); /** * store the given image data into a Tex object; this will be as if * it had been loaded via tex_load. * * rationale: support for in-memory images is necessary for * emulation of glCompressedTexImage2D and useful overall. * however, we don't want to provide an alternate interface for each API; * these would have to be changed whenever fields are added to Tex. * instead, provide one entry point for specifying images. * note: since we do not know how \ was allocated, the caller must free * it themselves (after calling tex_free, which is required regardless of * alloc type). * * we need only add bookkeeping information and "wrap" it in * our Tex struct, hence the name. * * @param w,h Pixel dimensions. * @param bpp Bits per pixel. * @param flags TexFlags. * @param data Img texture data. note: size is calculated from other params. * @param ofs * @return Status **/ Status wrap(size_t w, size_t h, size_t bpp, size_t flags, const shared_ptr& data, size_t ofs); // // modify image // /** * Change the pixel format. * * @param transforms TexFlags that are to be flipped. * @return Status **/ Status transform(size_t transforms); /** * Change the pixel format (2nd version) * (note: this is equivalent to Tex::transform(t, t-\>flags^new_flags). * * @param new_flags desired new value of TexFlags. * @return Status **/ Status transform_to(size_t new_flags); // // return image information // /** * return a pointer to the image data (pixels), taking into account any * header(s) that may come before it. * * @return pointer to data returned by mem_get_ptr (holds reference)! **/ u8* get_data(); /** * return the ARGB value of the 1x1 mipmap level of the texture. * * @return ARGB value (or 0 if texture does not have mipmaps) **/ u32 get_average_color() const; /** * return total byte size of the image pixels. (including mipmaps!) * rationale: this is preferable to calculating manually because it's * less error-prone (e.g. confusing bits_per_pixel with bytes). * * @return size [bytes] **/ size_t img_size() const; }; /** * Set the orientation to which all loaded images will * automatically be converted (excepting file formats that don't specify * their orientation, i.e. DDS). See "Default Orientation" in docs. * @param orientation Either TEX_BOTTOM_UP or TEX_TOP_DOWN. **/ extern void tex_set_global_orientation(int orientation); /** * special value for levels_to_skip: the callback will only be called * for the base mipmap level (i.e. 100%) **/ const int TEX_BASE_LEVEL_ONLY = -1; /** * callback function for each mipmap level. * * @param level number; 0 for base level (i.e. 100%), or the first one * in case some were skipped. * @param level_w, level_h pixel dimensions (powers of 2, never 0) * @param level_data the level's texels * @param level_data_size [bytes] * @param cbData passed through from tex_util_foreach_mipmap. **/ typedef void (*MipmapCB)(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_data_size, void* RESTRICT cbData); /** * for a series of mipmaps stored from base to highest, call back for * each level. * * @param w,h Pixel dimensions. * @param bpp Bits per pixel. * @param data Series of mipmaps. * @param levels_to_skip Number of levels (counting from base) to skip, or * TEX_BASE_LEVEL_ONLY to only call back for the base image. * Rationale: this avoids needing to special case for images with or * without mipmaps. * @param data_padding Minimum pixel dimensions of mipmap levels. * This is used in S3TC images, where each level is actually stored in * 4x4 blocks. usually 1 to indicate levels are consecutive. * @param cb MipmapCB to call. * @param cbData Extra data to pass to cb. **/ extern void tex_util_foreach_mipmap(size_t w, size_t h, size_t bpp, const u8* data, int levels_to_skip, size_t data_padding, MipmapCB cb, void* RESTRICT cbData); // // image writing // /** * Is the file's extension that of a texture format supported by tex_load? * * Rationale: tex_load complains if the given file is of an * unsupported type. this API allows users to preempt that warning * (by checking the filename themselves), and also provides for e.g. * enumerating only images in a file picker. * an alternative might be a flag to suppress warning about invalid files, * but this is open to misuse. * * @param pathname Only the extension (starting with '.') is used. case-insensitive. * @return bool **/ extern bool tex_is_known_extension(const VfsPath& pathname); /** * return the minimum header size (i.e. offset to pixel data) of the * file format corresponding to the filename. * * rationale: this can be used to optimize calls to tex_write: when * allocating the buffer that will hold the image, allocate this much * extra and pass the pointer as base+hdr_size. this allows writing the * header directly into the output buffer and makes for zero-copy IO. * * @param filename Filename; only the extension (that after '.') is used. * case-insensitive. * @return size [bytes] or 0 on error (i.e. no codec found). **/ extern size_t tex_hdr_size(const VfsPath& filename); #endif // INCLUDED_TEX Index: ps/trunk/source/lib/timer.h =================================================================== --- ps/trunk/source/lib/timer.h (revision 19898) +++ ps/trunk/source/lib/timer.h (revision 19899) @@ -1,393 +1,393 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 high resolution timer */ #ifndef INCLUDED_TIMER #define INCLUDED_TIMER #include "lib/config2.h" // CONFIG2_TIMER_ALLOW_RDTSC #include "lib/sysdep/cpu.h" // cpu_AtomicAdd #if ARCH_X86_X64 && CONFIG2_TIMER_ALLOW_RDTSC # include "lib/sysdep/os_cpu.h" // os_cpu_ClockFrequency # include "lib/sysdep/arch/x86_x64/x86_x64.h" // x86_x64::rdtsc #endif #include "lib/utf8.h" /** * timer_Time will subsequently return values relative to the current time. **/ LIB_API void timer_LatchStartTime(); /** * @return high resolution (> 1 us) timestamp [s]. **/ LIB_API double timer_Time(); /** * @return resolution [s] of the timer. **/ LIB_API double timer_Resolution(); // (allow using XADD (faster than CMPXCHG) in 64-bit builds without casting) #if ARCH_AMD64 typedef intptr_t Cycles; #else typedef i64 Cycles; #endif /** * internal helper functions for returning an easily readable * string (i.e. re-scaled to appropriate units) **/ LIB_API std::string StringForSeconds(double seconds); LIB_API std::string StringForCycles(Cycles cycles); //----------------------------------------------------------------------------- // scope timing /// used by TIMER class ScopeTimer { NONCOPYABLE(ScopeTimer); public: ScopeTimer(const wchar_t* description) : m_t0(timer_Time()), m_description(description) { } ~ScopeTimer() { const double t1 = timer_Time(); const std::string elapsedTimeString = StringForSeconds(t1-m_t0); debug_printf("TIMER| %s: %s\n", utf8_from_wstring(m_description).c_str(), elapsedTimeString.c_str()); } private: double m_t0; const wchar_t* m_description; }; /** * Measures the time taken to execute code up until end of the current scope; * displays it via debug_printf. Can safely be nested. * Useful for measuring time spent in a function or basic block. * must remain valid over the lifetime of this object; * a string literal is safest. * * Example usage: * void func() * { * TIMER(L"description"); * // code to be measured * } **/ #define TIMER(description) ScopeTimer UID__(description) /** * Measures the time taken to execute code between BEGIN and END markers; * displays it via debug_printf. Can safely be nested. * Useful for measuring several pieces of code within the same function/block. * must remain valid over the lifetime of this object; * a string literal is safest. * * Caveats: * - this wraps the code to be measured in a basic block, so any * variables defined there are invisible to surrounding code. * - the description passed to END isn't inspected; you are responsible for * ensuring correct nesting! * * Example usage: * void func2() * { * // uninteresting code * TIMER_BEGIN(L"description2"); * // code to be measured * TIMER_END(L"description2"); * // uninteresting code * } **/ #define TIMER_BEGIN(description) { ScopeTimer UID__(description) #define TIMER_END(description) } //----------------------------------------------------------------------------- // cumulative timer API // this supplements in-game profiling by providing low-overhead, // high resolution time accounting of specific areas. // since TIMER_ACCRUE et al. are called so often, we try to keep // overhead to an absolute minimum. storing raw tick counts (e.g. CPU cycles // returned by x86_x64::rdtsc) instead of absolute time has two benefits: // - no need to convert from raw->time on every call // (instead, it's only done once when displaying the totals) // - possibly less overhead to querying the time itself // (timer_Time may be using slower time sources with ~3us overhead) // // however, the cycle count is not necessarily a measure of wall-clock time // (see http://www.gamedev.net/reference/programming/features/timing). // therefore, on systems with SpeedStep active, measurements of I/O or other // non-CPU bound activity may be skewed. this is ok because the timer is // only used for profiling; just be aware of the issue. // if this is a problem, disable CONFIG2_TIMER_ALLOW_RDTSC. // // note that overflow isn't an issue either way (63 bit cycle counts // at 10 GHz cover intervals of 29 years). #if ARCH_X86_X64 && CONFIG2_TIMER_ALLOW_RDTSC class TimerUnit { public: void SetToZero() { m_cycles = 0; } void SetFromTimer() { m_cycles = x86_x64::rdtsc(); } void AddDifference(TimerUnit t0, TimerUnit t1) { m_cycles += t1.m_cycles - t0.m_cycles; } void AddDifferenceAtomic(TimerUnit t0, TimerUnit t1) { const Cycles delta = t1.m_cycles - t0.m_cycles; #if ARCH_AMD64 cpu_AtomicAdd(&m_cycles, delta); #elif ARCH_IA32 retry: if(!cpu_CAS64(&m_cycles, m_cycles, m_cycles+delta)) goto retry; #else # error "port" #endif } void Subtract(TimerUnit t) { m_cycles -= t.m_cycles; } std::string ToString() const { ENSURE(m_cycles >= 0); return StringForCycles(m_cycles); } double ToSeconds() const { return (double)m_cycles / os_cpu_ClockFrequency(); } private: Cycles m_cycles; }; #else class TimerUnit { public: void SetToZero() { m_seconds = 0.0; } void SetFromTimer() { m_seconds = timer_Time(); } void AddDifference(TimerUnit t0, TimerUnit t1) { m_seconds += t1.m_seconds - t0.m_seconds; } void AddDifferenceAtomic(TimerUnit t0, TimerUnit t1) { retry: i64 oldRepresentation; memcpy(&oldRepresentation, &m_seconds, sizeof(oldRepresentation)); const double seconds = m_seconds + t1.m_seconds - t0.m_seconds; i64 newRepresentation; memcpy(&newRepresentation, &seconds, sizeof(newRepresentation)); if(!cpu_CAS64((volatile i64*)&m_seconds, oldRepresentation, newRepresentation)) goto retry; } void Subtract(TimerUnit t) { m_seconds -= t.m_seconds; } std::string ToString() const { ENSURE(m_seconds >= 0.0); return StringForSeconds(m_seconds); } double ToSeconds() const { return m_seconds; } private: double m_seconds; }; #endif // opaque - do not access its fields! // note: must be defined here because clients instantiate them; // fields cannot be made private due to POD requirement. struct TimerClient { TimerUnit sum; // total bill // only store a pointer for efficiency. const wchar_t* description; TimerClient* next; // how often the timer was billed (helps measure relative // performance of something that is done indeterminately often). intptr_t num_calls; }; /** * make the given TimerClient (usually instantiated as static data) * ready for use. returns its address for TIMER_ADD_CLIENT's convenience. * this client's total (which is increased by a BillingPolicy) will be * displayed by timer_DisplayClientTotals. * notes: * - may be called at any time; * - always succeeds (there's no fixed limit); * - free() is not needed nor possible. * - description must remain valid until exit; a string literal is safest. **/ LIB_API TimerClient* timer_AddClient(TimerClient* tc, const wchar_t* description); /** * "allocate" a new TimerClient that will keep track of the total time * billed to it, along with a description string. These are displayed when * timer_DisplayClientTotals is called. * Invoke this at file or function scope; a (static) TimerClient pointer of * name \ will be defined, which should be passed to TIMER_ACCRUE. **/ #define TIMER_ADD_CLIENT(id)\ static TimerClient UID__;\ static TimerClient* id = timer_AddClient(&UID__, WIDEN(#id)) /** * bill the difference between t0 and t1 to the client's total. **/ struct BillingPolicy_Default { void operator()(TimerClient* tc, TimerUnit t0, TimerUnit t1) const { tc->sum.AddDifference(t0, t1); tc->num_calls++; } }; /** * thread-safe (not used by default due to its higher overhead) * note: we can't just use thread-local variables to avoid * synchronization overhead because we don't have control over all * threads (for accumulating their separate timer copies). **/ struct BillingPolicy_Atomic { void operator()(TimerClient* tc, TimerUnit t0, TimerUnit t1) const { tc->sum.AddDifferenceAtomic(t0, t1); cpu_AtomicAdd(&tc->num_calls, +1); } }; /** * display all clients' totals; does not reset them. * typically called at exit. **/ LIB_API void timer_DisplayClientTotals(); /// used by TIMER_ACCRUE template class ScopeTimerAccrue { NONCOPYABLE(ScopeTimerAccrue); public: ScopeTimerAccrue(TimerClient* tc) : m_tc(tc) { m_t0.SetFromTimer(); } ~ScopeTimerAccrue() { TimerUnit t1; t1.SetFromTimer(); BillingPolicy()(m_tc, m_t0, t1); } private: TimerUnit m_t0; TimerClient* m_tc; }; /** * Measure the time taken to execute code up until end of the current scope; * bill it to the given TimerClient object. Can safely be nested. * Useful for measuring total time spent in a function or basic block over the * entire program. * `client' is an identifier registered via TIMER_ADD_CLIENT. * * Example usage: * TIMER_ADD_CLIENT(client); * * void func() * { * TIMER_ACCRUE(client); * // code to be measured * } * * [later or at exit] * timer_DisplayClientTotals(); **/ #define TIMER_ACCRUE(client) ScopeTimerAccrue<> UID__(client) #define TIMER_ACCRUE_ATOMIC(client) ScopeTimerAccrue UID__(client) #endif // #ifndef INCLUDED_TIMER Index: ps/trunk/source/lib/wsecure_crt.cpp =================================================================== --- ps/trunk/source/lib/wsecure_crt.cpp (revision 19898) +++ ps/trunk/source/lib/wsecure_crt.cpp (revision 19899) @@ -1,29 +1,29 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ // unicode version of secure_crt #include "precompiled.h" #define WSECURE_CRT #include "secure_crt.cpp" #undef WSECURE_CRT Index: ps/trunk/source/lib/sysdep/rtl.h =================================================================== --- ps/trunk/source/lib/sysdep/rtl.h (revision 19898) +++ ps/trunk/source/lib/sysdep/rtl.h (revision 19899) @@ -1,33 +1,33 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * functions specific to the toolset's runtime library */ #ifndef INCLUDED_RTL #define INCLUDED_RTL LIB_API void* rtl_AllocateAligned(size_t size, size_t alignment); LIB_API void rtl_FreeAligned(void* alignedPointer); #endif // #ifndef INCLUDED_RTL Index: ps/trunk/source/lib/sysdep/rtl/msc/msc.cpp =================================================================== --- ps/trunk/source/lib/sysdep/rtl/msc/msc.cpp (revision 19898) +++ ps/trunk/source/lib/sysdep/rtl/msc/msc.cpp (revision 19899) @@ -1,34 +1,34 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/sysdep/rtl.h" void* rtl_AllocateAligned(size_t size, size_t alignment) { return _aligned_malloc(size, alignment); } void rtl_FreeAligned(void* alignedPointer) { _aligned_free(alignedPointer); } Index: ps/trunk/source/lib/sysdep/stl.h =================================================================== --- ps/trunk/source/lib/sysdep/stl.h (revision 19898) +++ ps/trunk/source/lib/sysdep/stl.h (revision 19899) @@ -1,81 +1,81 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ /* * fixes for various STL implementations */ #ifndef INCLUDED_STL #define INCLUDED_STL #include "lib/config.h" #include "compiler.h" #include // indirectly pull in bits/c++config.h on Linux, so __GLIBCXX__ is defined // detect STL version // .. Dinkumware #if MSC_VERSION # include // defines _CPPLIB_VER #endif #if defined(_CPPLIB_VER) # define STL_DINKUMWARE _CPPLIB_VER #else # define STL_DINKUMWARE 0 #endif // .. GCC #if defined(__GLIBCPP__) # define STL_GCC __GLIBCPP__ #elif defined(__GLIBCXX__) # define STL_GCC __GLIBCXX__ #else # define STL_GCC 0 #endif // .. ICC #if defined(__INTEL_CXXLIB_ICC) # define STL_ICC __INTEL_CXXLIB_ICC #else # define STL_ICC 0 #endif // disable (slow!) iterator checks in release builds (unless someone already defined this) #if STL_DINKUMWARE && defined(NDEBUG) && !defined(_SECURE_SCL) # define _SECURE_SCL 0 #endif // pass "disable exceptions" setting on to the STL #if CONFIG_DISABLE_EXCEPTIONS # if STL_DINKUMWARE # define _HAS_EXCEPTIONS 0 # else # define STL_NO_EXCEPTIONS # endif #endif // OS X - fix some stream template instantiations that break 10.5 compatibility on newer SDKs #if OS_MACOSX # include "os/osx/osx_stl_fixes.h" #endif #endif // #ifndef INCLUDED_STL Index: ps/trunk/source/lib/sysdep/vm.h =================================================================== --- ps/trunk/source/lib/sysdep/vm.h (revision 19898) +++ ps/trunk/source/lib/sysdep/vm.h (revision 19899) @@ -1,154 +1,154 @@ -/* Copyright (c) 2011 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 * "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. */ /* * virtual memory interface. supercedes POSIX mmap; provides support for * large pages, autocommit, and specifying protection flags during allocation. */ #ifndef INCLUDED_SYSDEP_VM #define INCLUDED_SYSDEP_VM #include "lib/posix/posix_mman.h" // PROT_* namespace vm { // committing large pages (2 MiB) instead of regular 4 KiB pages can // increase TLB coverage and reduce misses for sequential access patterns. // however, small page TLBs have more entries, making them better suited // to random accesses. it may also take a long time to find/free up // contiguous regions of physical memory for large pages. applications // can express their preference or go along with the default, // which depends on several factors such as allocation size. enum PageType { kLarge, // use large if available kSmall, // always use small kDefault // heuristic }; /** * reserve address space and set the parameters for any later * on-demand commits. * * @param size desired number of bytes. any additional space * in the last page is also accessible. * @param commitSize [bytes] how much to commit each time. * larger values reduce the number of page faults at the cost of * additional internal fragmentation. must be a multiple of * largePageSize unless pageType == kSmall. * @param pageType chooses between large/small pages for commits. * @param prot memory protection flags for newly committed pages. * @return base address (aligned to the respective page size) or * 0 if address space/descriptor storage is exhausted * (an error dialog will also be raised). * must be freed via ReleaseAddressSpace. **/ LIB_API void* ReserveAddressSpace(size_t size, size_t commitSize = largePageSize, PageType pageType = kDefault, int prot = PROT_READ|PROT_WRITE); /** * release address space and decommit any memory. * * @param p a pointer previously returned by ReserveAddressSpace. * @param size is required by the POSIX implementation and * ignored on Windows. it also ensures compatibility with UniqueRange. **/ LIB_API void ReleaseAddressSpace(void* p, size_t size = 0); /** * map physical memory to previously reserved address space. * * @param address, size need not be aligned, but this function commits * any pages intersecting that interval. * @param pageType, prot - see ReserveAddressSpace. * @return whether memory was successfully committed. * * note: committing only maps virtual pages and does not actually allocate * page frames. Windows XP uses a first-touch heuristic - the page will * be taken from the node whose processor caused the fault. * therefore, worker threads should be the first to write to their memory. * * (this is surprisingly slow in XP, possibly due to PFN lock contention) **/ LIB_API bool Commit(uintptr_t address, size_t size, PageType pageType = kDefault, int prot = PROT_READ|PROT_WRITE); /** * unmap physical memory. * * @return whether the operation succeeded. **/ LIB_API bool Decommit(uintptr_t address, size_t size); /** * set the memory protection flags for all pages that intersect * the given interval. * the pages must currently be committed. * * @param prot memory protection flags: PROT_NONE or a combination of * PROT_READ, PROT_WRITE, PROT_EXEC. **/ LIB_API bool Protect(uintptr_t address, size_t size, int prot); /** * reserve address space and commit memory. * * @param size [bytes] to allocate. * @param pageType, prot - see ReserveAddressSpace. * @return zero-initialized memory aligned to the respective * page size. **/ LIB_API void* Allocate(size_t size, PageType pageType = kDefault, int prot = PROT_READ|PROT_WRITE); /** * decommit memory and release address space. * * @param p a pointer previously returned by Allocate. * @param size is required by the POSIX implementation and * ignored on Windows. it also ensures compatibility with UniqueRange. * * (this differs from ReleaseAddressSpace, which must account for * extra padding/alignment to largePageSize.) **/ LIB_API void Free(void* p, size_t size = 0); /** * install a handler that attempts to commit memory whenever a * read/write page fault is encountered. thread-safe. **/ LIB_API void BeginOnDemandCommits(); /** * decrements the reference count begun by BeginOnDemandCommit and * removes the page fault handler when it reaches 0. thread-safe. **/ LIB_API void EndOnDemandCommits(); LIB_API void DumpStatistics(); } // namespace vm #endif // #ifndef INCLUDED_SYSDEP_VM Index: ps/trunk/source/lib/tests/test_byte_order.h =================================================================== --- ps/trunk/source/lib/tests/test_byte_order.h (revision 19898) +++ ps/trunk/source/lib/tests/test_byte_order.h (revision 19899) @@ -1,98 +1,98 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/byte_order.h" class TestByteOrder : public CxxTest::TestSuite { public: void test_conversion() { const u32 x = 0x01234567u; u8 LS_byte; memcpy(&LS_byte, &x, 1); // little endian if(LS_byte == 0x67) { TS_ASSERT_EQUALS(to_le16(0x0123u), 0x0123u); TS_ASSERT_EQUALS(to_le32(0x01234567u), 0x01234567u); TS_ASSERT_EQUALS(to_le64(0x0123456789ABCDEFull), 0x0123456789ABCDEFull); TS_ASSERT_EQUALS(to_be16(0x0123u), 0x2301u); TS_ASSERT_EQUALS(to_be32(0x01234567u), 0x67452301u); TS_ASSERT_EQUALS(to_be64(0x0123456789ABCDEFull), 0xEFCDAB8967452301ull); } // big endian else if(LS_byte == 0x01) { TS_ASSERT_EQUALS(to_le16(0x0123u), 0x2301u); TS_ASSERT_EQUALS(to_le32(0x01234567u), 0x67452301u); TS_ASSERT_EQUALS(to_le64(0x0123456789ABCDEFull), 0xEFCDAB8967452301ull); TS_ASSERT_EQUALS(to_be16(0x0123u), 0x0123u); TS_ASSERT_EQUALS(to_be32(0x01234567u), 0x01234567u); TS_ASSERT_EQUALS(to_be64(0x0123456789ABCDEFull), 0x0123456789ABCDEFull); } else TS_FAIL("endian determination failed"); // note: no need to test read_?e* / write_?e* - they are // trivial wrappers on top of to_?e*. } void test_movzx() { const u8 d1[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; const u8 d2[] = { 0x43, 0x12, 0x23, 0xA4 }; TS_ASSERT_EQUALS(movzx_le64(d1, 1), 0x01ull); TS_ASSERT_EQUALS(movzx_le64(d1, 2), 0x0201ull); TS_ASSERT_EQUALS(movzx_le64(d1, 8), 0x0807060504030201ull); TS_ASSERT_EQUALS(movzx_le64(d2, 4), 0xA4231243ull); TS_ASSERT_EQUALS(movzx_le64(d2+3, 1), 0xA4ull); TS_ASSERT_EQUALS(movzx_be64(d1, 1), 0x01ull); TS_ASSERT_EQUALS(movzx_be64(d1, 2), 0x0102ull); TS_ASSERT_EQUALS(movzx_be64(d1, 8), 0x0102030405060708ull); TS_ASSERT_EQUALS(movzx_be64(d2, 4), 0x431223A4ull); TS_ASSERT_EQUALS(movzx_be64(d2+3, 1), 0xA4ull); } void test_movsx() { const u8 d1[] = { 0x09, 0xFE }; const u8 d2[] = { 0xD9, 0x2C, 0xDD, 0x8F }; const u8 d3[] = { 0x92, 0x26, 0x88, 0xF1, 0x35, 0xAC, 0x01, 0x83 }; TS_ASSERT_EQUALS(movsx_le64(d1, 1), (i64)0x09ull); TS_ASSERT_EQUALS(movsx_le64(d1, 2), (i64)0xFFFFFFFFFFFFFE09ull); TS_ASSERT_EQUALS(movsx_le64(d2, 4), (i64)0xFFFFFFFF8FDD2CD9ull); TS_ASSERT_EQUALS(movsx_le64(d3, 8), (i64)0x8301AC35F1882692ull); TS_ASSERT_EQUALS(movsx_be64(d1, 1), (i64)0x09ull); TS_ASSERT_EQUALS(movsx_be64(d1, 2), (i64)0x00000000000009FEull); TS_ASSERT_EQUALS(movsx_be64(d2, 4), (i64)0xFFFFFFFFD92CDD8Full); TS_ASSERT_EQUALS(movsx_be64(d3, 8), (i64)0x922688F135AC0183ull); } }; Index: ps/trunk/source/lib/tests/test_path.h =================================================================== --- ps/trunk/source/lib/tests/test_path.h (revision 19898) +++ ps/trunk/source/lib/tests/test_path.h (revision 19899) @@ -1,80 +1,80 @@ -/* Copyright (c) 2012 Wildfire Games +/* Copyright (C) 2012 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 "lib/self_test.h" #include "lib/path.h" #include "lib/os_path.h" class TestPath : public CxxTest::TestSuite { public: void test_ctor() { const char* s1 = "a/b/c"; const char* s2 = "a/b/\xEF\xBF\xBF"; const wchar_t* w1 = L"a/b/c"; const wchar_t w2[] = { 'a', '/', 'b', '/', 0xEF, 0xBF, 0xBF, 0 }; const wchar_t w3[] = { 'a', '/', 'b', '/', 0xFFFF, 0 }; // Empty strings Path p0a; Path p0b = Path(std::string()); Path p0c = Path(std::wstring()); TS_ASSERT(p0a.empty()); TS_ASSERT_WSTR_EQUALS(p0a.string(), p0b.string()); TS_ASSERT_WSTR_EQUALS(p0a.string(), p0c.string()); // Construct from various types Path ps1a = Path(s1); Path ps2a = Path(s2); Path ps1b = Path(std::string(s1)); Path ps2b = Path(std::string(s2)); Path pw1a = Path(w1); Path pw2a = Path(w2); Path pw3a = Path(w3); Path pw1b = Path(std::wstring(w1)); Path pw2b = Path(std::wstring(w2)); Path pw3b = Path(std::wstring(w3)); TS_ASSERT_WSTR_EQUALS(ps1a.string(), w1); TS_ASSERT_WSTR_EQUALS(ps1b.string(), w1); TS_ASSERT_WSTR_EQUALS(pw1a.string(), w1); TS_ASSERT_WSTR_EQUALS(pw1b.string(), w1); TS_ASSERT_WSTR_EQUALS(ps2a.string(), w2); TS_ASSERT_WSTR_EQUALS(ps2b.string(), w2); TS_ASSERT_WSTR_EQUALS(pw2a.string(), w2); TS_ASSERT_WSTR_EQUALS(pw2b.string(), w2); TS_ASSERT_WSTR_EQUALS(pw3a.string(), w3); TS_ASSERT_WSTR_EQUALS(pw3b.string(), w3); #if OS_WIN TS_ASSERT_WSTR_EQUALS(OsString(pw2a), w2); TS_ASSERT_WSTR_EQUALS(OsString(pw3a), w3); #else TS_ASSERT_STR_EQUALS(OsString(pw2a), s2); // OsString(pw3a) causes an intentional assertion failure, but we can't test that #endif } }; Index: ps/trunk/source/lib/tests/test_secure_crt.h =================================================================== --- ps/trunk/source/lib/tests/test_secure_crt.h (revision 19898) +++ ps/trunk/source/lib/tests/test_secure_crt.h (revision 19899) @@ -1,359 +1,359 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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 "lib/self_test.h" #include "lib/secure_crt.h" // note: we only test the char version. this avoids having to // expose secure_crt.cpp's tchar / tcpy etc. macros in the header and/or // writing a copy of this test for the unicode version. // secure_crt.cpp's unicode functions are the same anyway // (they're implemented via the abovementioned tcpy macro redirection). #if OS_WIN // helper class to disable CRT error dialogs class SuppressErrors { public: SuppressErrors() { // Redirect the assertion output to somewhere where it shouldn't be noticed old_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); // Replace the invalid parameter handler with one that ignores everything old_handler = _set_invalid_parameter_handler(&invalid_parameter_handler); } ~SuppressErrors() { _CrtSetReportMode(_CRT_ASSERT, old_mode); _set_invalid_parameter_handler(old_handler); } private: int old_mode; _invalid_parameter_handler old_handler; static void invalid_parameter_handler(const wchar_t* UNUSED(expression), const wchar_t* UNUSED(function), const wchar_t* UNUSED(file), unsigned int UNUSED(line), uintptr_t UNUSED(pReserved)) { return; } }; #else class SuppressErrors { public: SuppressErrors() { } }; #endif class TestString_s : public CxxTest::TestSuite { // note: avoid 4-byte strings - they would trigger WARN::IF_PTR_LEN. const char* const s0; const char* const s1; const char* const s5; const char* const s10; const wchar_t* const ws10; char d1[1]; char d2[2]; char d3[3]; char d5[5]; char d6[6]; char d10[10]; wchar_t wd10[10]; char d11[11]; char no_null[7]; static void TEST_LEN(const char* string, size_t limit, size_t expected_len) { TS_ASSERT_EQUALS(int(strnlen((string), int(limit))), int(expected_len)); } static void TEST_CPY(char* dst, size_t dst_max, const char* src, int expected_ret, const char* expected_dst) { int ret = strcpy_s(dst, dst_max, src); TS_ASSERT_EQUALS(ret, expected_ret); if(dst != 0) TS_ASSERT(!strcmp(dst, expected_dst)); } static void TEST_CPY2(char* dst, size_t max_dst_chars, const char* src, int expected_ret, const char* expected_dst) { int ret = strcpy_s((dst), max_dst_chars, (src)); TS_ASSERT_EQUALS(ret, expected_ret); if(dst != 0) TS_ASSERT(!strcmp(dst, expected_dst)); } static void TEST_NCPY(char* dst, size_t max_dst_chars, const char* src, size_t max_src_chars, int expected_ret, const char* expected_dst) { int ret = strncpy_s(dst, max_dst_chars, src, max_src_chars); TS_ASSERT_EQUALS(ret, expected_ret); if(dst != 0) TS_ASSERT(!strcmp(dst, expected_dst)); } static void TEST_CAT(char* dst, size_t max_dst_chars, const char* src, int expected_ret, const char* expected_dst) { int ret = strcat_s(dst, max_dst_chars, src); TS_ASSERT_EQUALS(ret, expected_ret); if(dst != 0) TS_ASSERT(!strcmp(dst, expected_dst)); } static void TEST_CAT2(char* dst, size_t max_dst_chars, const char* src, const char* dst_val, int expected_ret, const char* expected_dst) { strcpy(dst, dst_val); int ret = strcat_s(dst, max_dst_chars, src); TS_ASSERT_EQUALS(ret, expected_ret); if(dst != 0) TS_ASSERT(!strcmp(dst, expected_dst)); } static void TEST_NCAT(char* dst, size_t max_dst_chars, const char* src, size_t max_src_chars, const char* dst_val, int expected_ret, const char* expected_dst) { strcpy(dst, dst_val); int ret = strncat_s(dst, max_dst_chars, src, (max_src_chars)); TS_ASSERT_EQUALS(ret, expected_ret); if(dst != 0) TS_ASSERT(!strcmp(dst, expected_dst)); } public: TestString_s() : s0(""), s1("a"), s5("abcde"), s10("abcdefghij"), ws10(L"abcdefghij") { const char no_null_tmp[] = { 'n','o','_','n','u','l','l'}; memcpy(no_null, no_null_tmp, sizeof(no_null)); } // contains all tests that verify correct behavior for bogus input. // our implementation suppresses error dialogs while the self-test is active, // but others (e.g. the functions shipped with VC8) need the code in // SuppressErrors to disable the error dialogs. void test_param_validation() { SuppressErrors suppress; #if EMULATE_SECURE_CRT # define SKIP_ERRORS(err) debug_SkipErrors(err) # define STOP_SKIPPING_ERRORS(expectedCount) TS_ASSERT_EQUALS(debug_StopSkippingErrors(), (size_t)expectedCount) #else # define SKIP_ERRORS(err) (void)0 # define STOP_SKIPPING_ERRORS(expectedCount) (void)0 #endif SKIP_ERRORS(ERR::INVALID_POINTER); TEST_CPY(0 ,0,0 , EINVAL,""); // all invalid TEST_CPY(0 ,0,s1, EINVAL,""); // dst = 0, max = 0 TEST_CPY(0 ,1,s1, EINVAL,""); // dst = 0, max > 0 TEST_CPY(d1,1,0 , EINVAL,""); // src = 0 STOP_SKIPPING_ERRORS(4); SKIP_ERRORS(ERR::INVALID_SIZE); TEST_CPY(d1,0,s1, EINVAL,""); // max_dst_chars = 0 TEST_CPY2(d1,1, s1, ERANGE,""); TEST_CPY2(d1,1, s5, ERANGE,""); TEST_CPY2(d5,5, s5, ERANGE,""); STOP_SKIPPING_ERRORS(4); SKIP_ERRORS(ERR::INVALID_SIZE); TEST_NCPY(d1,1 ,s1,1, ERANGE,""); TEST_NCPY(d1,1 ,s5,1, ERANGE,""); TEST_NCPY(d5,5 ,s5,5, ERANGE,""); STOP_SKIPPING_ERRORS(3); SKIP_ERRORS(ERR::INVALID_POINTER); TEST_CAT(0 ,0,0 , EINVAL,""); // all invalid TEST_CAT(0 ,0,s1, EINVAL,""); // dst = 0, max = 0 TEST_CAT(0 ,1,s1, EINVAL,""); // dst = 0, max > 0 TEST_CAT(d1,1,0 , EINVAL,""); // src = 0 STOP_SKIPPING_ERRORS(4); SKIP_ERRORS(ERR::INVALID_SIZE); TEST_CAT(d1,0,s1, EINVAL,""); // max_dst_chars = 0 STOP_SKIPPING_ERRORS(1); SKIP_ERRORS(ERR::STRING_NOT_TERMINATED); TEST_CAT(no_null,5,s1, EINVAL,""); // dst not terminated STOP_SKIPPING_ERRORS(1); SKIP_ERRORS(ERR::INVALID_SIZE); TEST_CAT2(d1,1, s1, "",ERANGE,""); TEST_CAT2(d1,1, s5, "",ERANGE,""); TEST_CAT2(d10,10, s10, "",ERANGE,""); // empty, total overflow TEST_CAT2(d10,10, s5, "12345",ERANGE,""); // not empty, overflow TEST_CAT2(d10,10, s10, "12345",ERANGE,""); // not empty, total overflow STOP_SKIPPING_ERRORS(5); SKIP_ERRORS(ERR::INVALID_SIZE); TEST_NCAT(d1,1, s1,1, "",ERANGE,""); TEST_NCAT(d1,1, s5,5, "",ERANGE,""); TEST_NCAT(d10,10, s10,10, "",ERANGE,""); // empty, total overflow TEST_NCAT(d10,10, s5,5, "12345",ERANGE,""); // not empty, overflow TEST_NCAT(d10,10, s10,10, "12345",ERANGE,""); // not empty, total overflow STOP_SKIPPING_ERRORS(5); #undef SKIP_ERRORS #undef STOP_SKIPPING_ERRORS } void test_length() { TEST_LEN(s0, 0 , 0 ); TEST_LEN(s0, 1 , 0 ); TEST_LEN(s0, 50, 0 ); TEST_LEN(s1, 0 , 0 ); TEST_LEN(s1, 1 , 1 ); TEST_LEN(s1, 50, 1 ); TEST_LEN(s5, 0 , 0 ); TEST_LEN(s5, 1 , 1 ); TEST_LEN(s5, 50, 5 ); TEST_LEN(s10,9 , 9 ); TEST_LEN(s10,10, 10); TEST_LEN(s10,11, 10); } void test_copy() { TEST_CPY2(d2,2 ,s1, 0,"a"); TEST_CPY2(d6,6 ,s5, 0,"abcde"); TEST_CPY2(d11,11, s5, 0,"abcde"); TEST_NCPY(d2,2 ,s1,1, 0,"a"); TEST_NCPY(d6,6 ,s5,5, 0,"abcde"); TEST_NCPY(d11,11, s5,5, 0,"abcde"); strcpy(d5, "----"); TEST_NCPY(d5,5, s5,0 , 0,""); // specified behavior! see 3.6.2.1.1 #4 TEST_NCPY(d5,5, s5,1 , 0,"a"); TEST_NCPY(d6,6, s5,5 , 0,"abcde"); TEST_NCPY(d6,6, s5,10, 0,"abcde"); } void test_concatenate() { TEST_CAT2(d3,3, s1, "1",0,"1a"); TEST_CAT2(d5,5, s1, "1",0,"1a"); TEST_CAT2(d6,6, s5, "",0,"abcde"); TEST_CAT2(d10,10, s5, "",0,"abcde"); TEST_CAT2(d10,10, s5, "1234",0,"1234abcde"); TEST_NCAT(d3,3, s1,1, "1",0,"1a"); TEST_NCAT(d5,5, s1,1, "1",0,"1a"); TEST_NCAT(d6,6, s5,5, "",0,"abcde"); TEST_NCAT(d10,10, s5,5, "",0,"abcde"); TEST_NCAT(d10,10, s5,5, "1234",0,"1234abcde"); TEST_NCAT(d5,5, s5,0, "----",0,"----"); TEST_NCAT(d5,5, s5,1, "",0,"a"); TEST_NCAT(d5,5, s5,4, "",0,"abcd"); TEST_NCAT(d5,5, s5,2, "12",0,"12ab"); TEST_NCAT(d6,6, s5,10, "",0,"abcde"); } static void TEST_PRINTF(char* dst, size_t max_dst_chars, const char* dst_val, int expected_ret, const char* expected_dst, const char* fmt, ...) { if (dst) strcpy(dst, dst_val); va_list ap; va_start(ap, fmt); int ret = vsprintf_s(dst, max_dst_chars, fmt, ap); va_end(ap); TS_ASSERT_EQUALS(ret, expected_ret); if (dst) TS_ASSERT_STR_EQUALS(dst, expected_dst); } static void TEST_WPRINTF(wchar_t* dst, size_t max_dst_chars, const wchar_t* dst_val, int expected_ret, const wchar_t* expected_dst, const wchar_t* fmt, ...) { if (dst) wcscpy(dst, dst_val); va_list ap; va_start(ap, fmt); int ret = vswprintf_s(dst, max_dst_chars, fmt, ap); va_end(ap); TS_ASSERT_EQUALS(ret, expected_ret); if (dst) TS_ASSERT_WSTR_EQUALS(dst, expected_dst); } void test_printf_overflow() { TEST_PRINTF(d10,10, s10, 4, "1234", "%d", 1234); TEST_PRINTF(d10,5, s10, 4, "1234", "%d", 1234); SuppressErrors suppress; TEST_PRINTF(d10,4, s10, -1, "", "%d", 1234); TEST_PRINTF(d10,3, s10, -1, "", "%d", 1234); TEST_PRINTF(d10,0, s10, -1, "abcdefghij", "%d", 1234); TEST_PRINTF(NULL,0, NULL, -1, "", "%d", 1234); TEST_PRINTF(d10,10, s10, -1, "abcdefghij", NULL); } void test_wprintf_overflow() { TEST_WPRINTF(wd10,10, ws10, 4, L"1234", L"%d", 1234); TEST_WPRINTF(wd10,5, ws10, 4, L"1234", L"%d", 1234); SuppressErrors suppress; TEST_WPRINTF(wd10,4, ws10, -1, L"", L"%d", 1234); TEST_WPRINTF(wd10,3, ws10, -1, L"", L"%d", 1234); TEST_WPRINTF(wd10,0, ws10, -1, L"abcdefghij", L"%d", 1234); TEST_WPRINTF(NULL,0, NULL, -1, L"", L"%d", 1234); TEST_WPRINTF(wd10,10, ws10, -1, L"abcdefghij", NULL); } void test_printf_strings() { TEST_PRINTF(d10,10, s10, 3, "123", "%s", "123"); TEST_PRINTF(d10,10, s10, 3, "123", "%hs", "123"); TEST_PRINTF(d10,10, s10, 3, "123", "%ls", L"123"); } void test_wprintf_strings() { TEST_WPRINTF(wd10,10, ws10, 3, L"123", L"%hs", "123"); TEST_WPRINTF(wd10,10, ws10, 3, L"123", L"%ls", L"123"); } }; Index: ps/trunk/source/lib/tex/tex_codec.h =================================================================== --- ps/trunk/source/lib/tex/tex_codec.h (revision 19898) +++ ps/trunk/source/lib/tex/tex_codec.h (revision 19899) @@ -1,244 +1,244 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * support routines and interface for texture codecs. */ #ifndef INCLUDED_TEX_CODEC #define INCLUDED_TEX_CODEC #include "tex.h" #include "tex_internal.h" // for codec's convenience /** * virtual method table for TexCodecs. * rationale: this works in C and also allows storing name and next in vtbl. * 'template method'-style interface to increase code reuse and * simplify writing new codecs. **/ class ITexCodec { public: /** * decode the file into a Tex structure. * * @param data input data array (non-const, because the texture * may have to be flipped in-place - see "texture orientation"). * @param size [bytes] of data, always >= 4 * (this is usually enough to compare the header's "magic" field, * and no legitimate file will be smaller) * @param t output texture object * @return Status **/ virtual Status decode(u8* data, size_t size, Tex* RESTRICT t) const = 0; /** * encode the texture data into the codec's file format (in memory). * * @param t input texture object. note: non-const because encoding may * require a Tex::transform. * @param da output data array, allocated by codec. * rationale: some codecs cannot calculate the output size beforehand * (e.g. PNG output via libpng), so the output memory cannot be allocated * by the caller. * @return Status **/ virtual Status encode(Tex* RESTRICT t, DynArray* RESTRICT da) const = 0; /** * transform the texture's pixel format. * * @param t texture object * @param transforms: OR-ed combination of TEX_* flags that are to * be changed. note: the codec needs only handle situations specific * to its format; generic pixel format transforms are handled by * the caller. **/ virtual Status transform(Tex* t, size_t transforms) const = 0; /** * indicate if the data appears to be an instance of this codec's header, * i.e. can this codec decode it? * * @param file input data; only guaranteed to be 4 bytes! * (this should be enough to examine the header's 'magic' field) * @return bool **/ virtual bool is_hdr(const u8* file) const = 0; /** * is the extension that of a file format supported by this codec? * * rationale: cannot just return the extension string and have * caller compare it (-> smaller code) because a codec's file format * may have several valid extensions (e.g. jpg and jpeg). * * @param extension (including '.') * @return bool **/ virtual bool is_ext(const OsPath& extension) const = 0; /** * return size of the file header supported by this codec. * * @param file the specific header to return length of (taking its * variable-length fields into account). if NULL, return minimum * guaranteed header size, i.e. the header without any * variable-length fields. * @return size [bytes] **/ virtual size_t hdr_size(const u8* file) const = 0; /** * name of codec for debug purposes. typically set via TEX_CODEC_REGISTER. **/ virtual const wchar_t* get_name() const = 0; virtual ~ITexCodec() {} }; class TexCodecPng:ITexCodec { public: virtual Status decode(u8* data, size_t size, Tex* RESTRICT t) const; virtual Status encode(Tex* RESTRICT t, DynArray* RESTRICT da) const; virtual Status transform(Tex* t, size_t transforms) const; virtual bool is_hdr(const u8* file) const; virtual bool is_ext(const OsPath& extension) const; virtual size_t hdr_size(const u8* file) const; virtual const wchar_t* get_name() const { static const wchar_t *name = L"png"; return name; }; }; class TexCodecDds:ITexCodec { public: virtual Status decode(u8* data, size_t size, Tex* RESTRICT t) const; virtual Status encode(Tex* RESTRICT t, DynArray* RESTRICT da) const; virtual Status transform(Tex* t, size_t transforms) const; virtual bool is_hdr(const u8* file) const; virtual bool is_ext(const OsPath& extension) const; virtual size_t hdr_size(const u8* file) const; virtual const wchar_t* get_name() const { static const wchar_t *name = L"dds"; return name; }; }; class TexCodecTga:ITexCodec { public: virtual Status decode(u8* data, size_t size, Tex* RESTRICT t) const; virtual Status encode(Tex* RESTRICT t, DynArray* RESTRICT da) const; virtual Status transform(Tex* t, size_t transforms) const; virtual bool is_hdr(const u8* file) const; virtual bool is_ext(const OsPath& extension) const; virtual size_t hdr_size(const u8* file) const; virtual const wchar_t* get_name() const { static const wchar_t *name = L"tga"; return name; }; }; class TexCodecBmp:ITexCodec { public: virtual Status decode(u8* data, size_t size, Tex* RESTRICT t) const; virtual Status encode(Tex* RESTRICT t, DynArray* RESTRICT da) const; virtual Status transform(Tex* t, size_t transforms) const; virtual bool is_hdr(const u8* file) const; virtual bool is_ext(const OsPath& extension) const; virtual size_t hdr_size(const u8* file) const; virtual const wchar_t* get_name() const { static const wchar_t *name = L"bmp"; return name; }; }; /** * Find codec that recognizes the desired output file extension. * * @param extension * @param c (out) vtbl of responsible codec * @return Status; ERR::RES_UNKNOWN_FORMAT (without warning, because this is * called by tex_is_known_extension) if no codec indicates they can * handle the given extension. **/ extern Status tex_codec_for_filename(const OsPath& extension, const ITexCodec** c); /** * find codec that recognizes the header's magic field. * * @param data typically contents of file, but need only include the * (first 4 bytes of) header. * @param data_size [bytes] * @param c (out) vtbl of responsible codec * @return Status; ERR::RES_UNKNOWN_FORMAT if no codec indicates they can * handle the given format (header). **/ extern Status tex_codec_for_header(const u8* data, size_t data_size, const ITexCodec** c); /** * transform the texture's pixel format. * tries each codec's transform method once, or until one indicates success. * * @param t texture object * @param transforms: OR-ed combination of TEX_* flags that are to * be changed. * @return Status **/ extern Status tex_codec_transform(Tex* t, size_t transforms); /** * allocate an array of row pointers that point into the given texture data. * for texture decoders that support output via row pointers (e.g. PNG), * this allows flipping the image vertically (useful when matching bottom-up * textures to a global orientation) directly, which is much more * efficient than transforming later via copying all pixels. * * @param data the texture data into which row pointers will point. * note: we don't allocate it here because this function is * needed for encoding, too (where data is already present). * @param h height [pixels] of texture. * @param pitch size [bytes] of one texture row, i.e. width*bytes_per_pixel. * @param src_flags TexFlags of source texture. used to extract its * orientation. * @param dst_orientation desired orientation of the output data. * can be one of TEX_BOTTOM_UP, TEX_TOP_DOWN, or 0 for the * "global orientation". * depending on src and dst, the row array is flipped if necessary. **/ typedef const u8* RowPtr; extern std::vector tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch, size_t src_flags, size_t dst_orientation); /** * apply transforms and then copy header and image into output buffer. * * @param t input texture object * @param transforms transformations to be applied to pixel format * @param hdr header data * @param hdr_size [bytes] * @param da output data array (will be expanded as necessary) * @return Status **/ extern Status tex_codec_write(Tex* t, size_t transforms, const void* hdr, size_t hdr_size, DynArray* da); #endif // #ifndef INCLUDED_TEX_CODEC Index: ps/trunk/source/lib/utf8.cpp =================================================================== --- ps/trunk/source/lib/utf8.cpp (revision 19898) +++ ps/trunk/source/lib/utf8.cpp (revision 19899) @@ -1,244 +1,244 @@ -/* Copyright (c) 2017 Wildfire Games +/* Copyright (C) 2017 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 "lib/utf8.h" static const StatusDefinition utf8StatusDefinitions[] = { { ERR::UTF8_SURROGATE, L"UTF-16 surrogate pairs aren't supported" }, { ERR::UTF8_OUTSIDE_BMP, L"Code point outside BMP (> 0x10000)" }, { ERR::UTF8_NONCHARACTER, L"Noncharacter (e.g. WEOF)" }, { ERR::UTF8_INVALID_UTF8, L"Invalid UTF-8 sequence" } }; STATUS_ADD_DEFINITIONS(utf8StatusDefinitions); // adapted from http://unicode.org/Public/PROGRAMS/CVTUTF/ConvertUTF.c // which bears the following notice: /* * Copyright 2001-2004 Unicode, Inc. * * Disclaimer * * This source code is provided as is by Unicode, Inc. No claims are * made as to fitness for any particular purpose. No warranties of any * kind are expressed or implied. The recipient agrees to determine * applicability of information provided. If this file has been * purchased on magnetic or optical media from Unicode, Inc., the * sole remedy for any claim will be exchange of defective media * within 90 days of receipt. * * Limitations on Rights to Redistribute This Code * * Unicode, Inc. hereby grants the right to freely use the information * supplied in this file in the creation of products supporting the * Unicode Standard, and to make copies of this file in any form * for internal or external distribution as long as this notice * remains attached. */ // design rationale: // - to cope with wchar_t differences between VC (UTF-16) and // GCC (UCS-4), we only allow codepoints in the BMP. // encoded UTF-8 sequences are therefore no longer than 3 bytes. // - surrogates are disabled because variable-length strings // violate the purpose of using wchar_t instead of UTF-8. // - replacing disallowed characters instead of aborting outright // avoids overly inconveniencing users and eases debugging. // this implementation survives http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt // (must be unsigned to avoid sign extension) typedef u8 UTF8; typedef u32 UTF32; // called from ReplaceIfInvalid and UTF8Codec::Decode static UTF32 RaiseError(Status err, Status* perr) { if(perr) // caller wants return code, not warning dialog { if(*perr == INFO::OK) // only return the first error (see header) *perr = err; } else { wchar_t error[200]; debug_printf("UTF8 error: %s\n", utf8_from_wstring(StatusDescription(err, error, ARRAY_SIZE(error))).c_str()); } return 0xFFFDul; // replacement character } static UTF32 ReplaceIfInvalid(UTF32 u, Status* err) { // disallow surrogates if(0xD800ul <= u && u <= 0xDFFFul) return RaiseError(ERR::UTF8_SURROGATE, err); // outside BMP (UTF-16 representation would require surrogates) if(u > 0xFFFFul) return RaiseError(ERR::UTF8_OUTSIDE_BMP, err); // noncharacter (note: WEOF (0xFFFF) causes VC's swprintf to fail) if(u == 0xFFFEul || u == 0xFFFFul || (0xFDD0ul <= u && u <= 0xFDEFul)) return RaiseError(ERR::UTF8_NONCHARACTER, err); return u; } class UTF8Codec { public: static void Encode(UTF32 u, UTF8*& dstPos) { switch (Size(u)) { case 1: *dstPos++ = UTF8(u); break; case 2: *dstPos++ = UTF8((u >> 6) | 0xC0); *dstPos++ = UTF8((u | 0x80u) & 0xBFu); break; case 3: *dstPos++ = UTF8((u >> 12) | 0xE0); *dstPos++ = UTF8(((u >> 6) | 0x80u) & 0xBFu); *dstPos++ = UTF8((u | 0x80u) & 0xBFu); break; } } // @return decoded scalar, or replacementCharacter on error static UTF32 Decode(const UTF8*& srcPos, const UTF8* const srcEnd, Status* err) { const size_t size = SizeFromFirstByte(*srcPos); if(!IsValid(srcPos, size, srcEnd)) { srcPos += 1; // only skip the offending byte (increases chances of resynchronization) return RaiseError(ERR::UTF8_INVALID_UTF8, err); } UTF32 u = 0; for(size_t i = 0; i < size-1; i++) { u += UTF32(*srcPos++); u <<= 6; } u += UTF32(*srcPos++); static const UTF32 offsets[1+4] = { 0, 0x00000000ul, 0x00003080ul, 0x000E2080ul, 0x03C82080UL }; u -= offsets[size]; return u; } private: static inline size_t Size(UTF32 u) { if(u < 0x80) return 1; if(u < 0x800) return 2; // ReplaceIfInvalid ensures > 3 byte encodings are never used. return 3; } static inline size_t SizeFromFirstByte(UTF8 firstByte) { if(firstByte < 0xC0) return 1; if(firstByte < 0xE0) return 2; if(firstByte < 0xF0) return 3; // IsValid rejects firstByte values that would cause > 4 byte encodings. return 4; } // c.f. Unicode 3.1 Table 3-7 // @param size obtained via SizeFromFirstByte (our caller also uses it) static bool IsValid(const UTF8* const src, size_t size, const UTF8* const srcEnd) { if(src+size > srcEnd) // not enough data return false; if(src[0] < 0x80) return true; if(!(0xC2 <= src[0] && src[0] <= 0xF4)) return false; // special cases (stricter than the loop) if(src[0] == 0xE0 && src[1] < 0xA0) return false; if(src[0] == 0xED && src[1] > 0x9F) return false; if(src[0] == 0xF0 && src[1] < 0x90) return false; if(src[0] == 0xF4 && src[1] > 0x8F) return false; for(size_t i = 1; i < size; i++) { if(!(0x80 <= src[i] && src[i] <= 0xBF)) return false; } return true; } }; //----------------------------------------------------------------------------- std::string utf8_from_wstring(const std::wstring& src, Status* err) { if(err) *err = INFO::OK; std::string dst(src.size()*3+1, ' '); // see UTF8Codec::Size; +1 ensures &dst[0] is valid UTF8* dstPos = (UTF8*)&dst[0]; for(size_t i = 0; i < src.size(); i++) { const UTF32 u = ReplaceIfInvalid(UTF32(src[i]), err); UTF8Codec::Encode(u, dstPos); } dst.resize(dstPos - (UTF8*)&dst[0]); return dst; } std::wstring wstring_from_utf8(const std::string& src, Status* err) { if(err) *err = INFO::OK; std::wstring dst; dst.reserve(src.size()); const UTF8* srcPos = (const UTF8*)src.data(); const UTF8* const srcEnd = srcPos + src.size(); while(srcPos < srcEnd) { const UTF32 u = UTF8Codec::Decode(srcPos, srcEnd, err); dst.push_back((wchar_t)ReplaceIfInvalid(u, err)); } return dst; } Index: ps/trunk/source/ps/Profiler2.cpp =================================================================== --- ps/trunk/source/ps/Profiler2.cpp (revision 19898) +++ ps/trunk/source/ps/Profiler2.cpp (revision 19899) @@ -1,999 +1,999 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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 "Profiler2.h" #include "lib/allocators/shared_ptr.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Profiler2GPU.h" #include "third_party/mongoose/mongoose.h" #include #include CProfiler2 g_Profiler2; // A human-recognisable pattern (for debugging) followed by random bytes (for uniqueness) const u8 CProfiler2::RESYNC_MAGIC[8] = {0x11, 0x22, 0x33, 0x44, 0xf4, 0x93, 0xbe, 0x15}; CProfiler2::CProfiler2() : m_Initialised(false), m_FrameNumber(0), m_MgContext(NULL), m_GPU(NULL) { } CProfiler2::~CProfiler2() { if (m_Initialised) Shutdown(); } /** * Mongoose callback. Run in an arbitrary thread (possibly concurrently with other requests). */ static void* MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info) { CProfiler2* profiler = (CProfiler2*)request_info->user_data; ENSURE(profiler); void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling const char* header200 = "HTTP/1.1 200 OK\r\n" "Access-Control-Allow-Origin: *\r\n" // TODO: not great for security "Content-Type: text/plain; charset=utf-8\r\n\r\n"; const char* header404 = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/plain; charset=utf-8\r\n\r\n" "Unrecognised URI"; const char* header400 = "HTTP/1.1 400 Bad Request\r\n" "Content-Type: text/plain; charset=utf-8\r\n\r\n" "Invalid request"; switch (event) { case MG_NEW_REQUEST: { std::stringstream stream; std::string uri = request_info->uri; if (uri == "/download") { profiler->SaveToFile(); } else if (uri == "/overview") { profiler->ConstructJSONOverview(stream); } else if (uri == "/query") { if (!request_info->query_string) { mg_printf(conn, "%s (no query string)", header400); return handled; } // Identify the requested thread char buf[256]; int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), "thread", buf, ARRAY_SIZE(buf)); if (len < 0) { mg_printf(conn, "%s (no 'thread')", header400); return handled; } std::string thread(buf); const char* err = profiler->ConstructJSONResponse(stream, thread); if (err) { mg_printf(conn, "%s (%s)", header400, err); return handled; } } else { mg_printf(conn, "%s", header404); return handled; } mg_printf(conn, "%s", header200); std::string str = stream.str(); mg_write(conn, str.c_str(), str.length()); return handled; } case MG_HTTP_ERROR: return NULL; case MG_EVENT_LOG: // Called by Mongoose's cry() LOGERROR("Mongoose error: %s", request_info->log_message); return NULL; case MG_INIT_SSL: return NULL; default: debug_warn(L"Invalid Mongoose event type"); return NULL; } }; void CProfiler2::Initialise() { ENSURE(!m_Initialised); int err = pthread_key_create(&m_TLS, &CProfiler2::TLSDtor); ENSURE(err == 0); m_Initialised = true; RegisterCurrentThread("main"); } void CProfiler2::InitialiseGPU() { ENSURE(!m_GPU); m_GPU = new CProfiler2GPU(*this); } void CProfiler2::EnableHTTP() { ENSURE(m_Initialised); LOGMESSAGERENDER("Starting profiler2 HTTP server"); // Ignore multiple enablings if (m_MgContext) return; const char *options[] = { "listening_ports", "127.0.0.1:8000", // bind to localhost for security "num_threads", "6", // enough for the browser's parallel connection limit NULL }; m_MgContext = mg_start(MgCallback, this, options); ENSURE(m_MgContext); } void CProfiler2::EnableGPU() { ENSURE(m_Initialised); if (!m_GPU) { LOGMESSAGERENDER("Starting profiler2 GPU mode"); InitialiseGPU(); } } void CProfiler2::ShutdownGPU() { LOGMESSAGERENDER("Shutting down profiler2 GPU mode"); SAFE_DELETE(m_GPU); } void CProfiler2::ShutDownHTTP() { LOGMESSAGERENDER("Shutting down profiler2 HTTP server"); if (m_MgContext) { mg_stop(m_MgContext); m_MgContext = NULL; } } void CProfiler2::Toggle() { // TODO: Maybe we can open the browser to the profiler page automatically if (m_GPU && m_MgContext) { ShutdownGPU(); ShutDownHTTP(); } else if (!m_GPU && !m_MgContext) { EnableGPU(); EnableHTTP(); } } void CProfiler2::Shutdown() { ENSURE(m_Initialised); ENSURE(!m_GPU); // must shutdown GPU before profiler if (m_MgContext) { mg_stop(m_MgContext); m_MgContext = NULL; } // the destructor is not called for the main thread // we have to call it manually to avoid memory leaks ENSURE(ThreadUtil::IsMainThread()); void * dataptr = pthread_getspecific(m_TLS); TLSDtor(dataptr); int err = pthread_key_delete(m_TLS); ENSURE(err == 0); m_Initialised = false; } void CProfiler2::RecordGPUFrameStart() { if (m_GPU) m_GPU->FrameStart(); } void CProfiler2::RecordGPUFrameEnd() { if (m_GPU) m_GPU->FrameEnd(); } void CProfiler2::RecordGPURegionEnter(const char* id) { if (m_GPU) m_GPU->RegionEnter(id); } void CProfiler2::RecordGPURegionLeave(const char* id) { if (m_GPU) m_GPU->RegionLeave(id); } /** * Called by pthreads when a registered thread is destroyed. */ void CProfiler2::TLSDtor(void* data) { ThreadStorage* storage = (ThreadStorage*)data; storage->GetProfiler().RemoveThreadStorage(storage); delete (ThreadStorage*)data; } void CProfiler2::RegisterCurrentThread(const std::string& name) { ENSURE(m_Initialised); ENSURE(pthread_getspecific(m_TLS) == NULL); // mustn't register a thread more than once ThreadStorage* storage = new ThreadStorage(*this, name); int err = pthread_setspecific(m_TLS, storage); ENSURE(err == 0); RecordSyncMarker(); RecordEvent("thread start"); AddThreadStorage(storage); } void CProfiler2::AddThreadStorage(ThreadStorage* storage) { CScopeLock lock(m_Mutex); m_Threads.push_back(storage); } void CProfiler2::RemoveThreadStorage(ThreadStorage* storage) { CScopeLock lock(m_Mutex); m_Threads.erase(std::find(m_Threads.begin(), m_Threads.end(), storage)); } CProfiler2::ThreadStorage::ThreadStorage(CProfiler2& profiler, const std::string& name) : m_Profiler(profiler), m_Name(name), m_BufferPos0(0), m_BufferPos1(0), m_LastTime(timer_Time()), m_HeldDepth(0) { m_Buffer = new u8[BUFFER_SIZE]; memset(m_Buffer, ITEM_NOP, BUFFER_SIZE); } CProfiler2::ThreadStorage::~ThreadStorage() { delete[] m_Buffer; } void CProfiler2::ThreadStorage::Write(EItem type, const void* item, u32 itemSize) { if (m_HeldDepth > 0) { WriteHold(type, item, itemSize); return; } // See m_BufferPos0 etc for comments on synchronisation u32 size = 1 + itemSize; u32 start = m_BufferPos0; if (start + size > BUFFER_SIZE) { // The remainder of the buffer is too small - fill the rest // with NOPs then start from offset 0, so we don't have to // bother splitting the real item across the end of the buffer m_BufferPos0 = size; COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer memset(m_Buffer + start, 0, BUFFER_SIZE - start); start = 0; } else { m_BufferPos0 = start + size; COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer } m_Buffer[start] = (u8)type; memcpy(&m_Buffer[start + 1], item, itemSize); COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer m_BufferPos1 = start + size; } void CProfiler2::ThreadStorage::WriteHold(EItem type, const void* item, u32 itemSize) { u32 size = 1 + itemSize; if (m_HoldBuffers[m_HeldDepth - 1].pos + size > CProfiler2::HOLD_BUFFER_SIZE) return; // we held on too much data, ignore the rest m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos] = (u8)type; memcpy(&m_HoldBuffers[m_HeldDepth - 1].buffer[m_HoldBuffers[m_HeldDepth - 1].pos + 1], item, itemSize); m_HoldBuffers[m_HeldDepth - 1].pos += size; } std::string CProfiler2::ThreadStorage::GetBuffer() { // Called from an arbitrary thread (not the one writing to the buffer). // // See comments on m_BufferPos0 etc. shared_ptr buffer(new u8[BUFFER_SIZE], ArrayDeleter()); u32 pos1 = m_BufferPos1; COMPILER_FENCE; // must read m_BufferPos1 before m_Buffer memcpy(buffer.get(), m_Buffer, BUFFER_SIZE); COMPILER_FENCE; // must read m_BufferPos0 after m_Buffer u32 pos0 = m_BufferPos0; // The range [pos1, pos0) modulo BUFFER_SIZE is invalid, so concatenate the rest of the buffer if (pos1 <= pos0) // invalid range is in the middle of the buffer return std::string(buffer.get()+pos0, buffer.get()+BUFFER_SIZE) + std::string(buffer.get(), buffer.get()+pos1); else // invalid wrap is wrapped around the end/start buffer return std::string(buffer.get()+pos0, buffer.get()+pos1); } void CProfiler2::ThreadStorage::RecordAttribute(const char* fmt, va_list argp) { char buffer[MAX_ATTRIBUTE_LENGTH + 4] = {0}; // first 4 bytes are used for storing length int len = vsnprintf(buffer + 4, MAX_ATTRIBUTE_LENGTH - 1, fmt, argp); // subtract 1 from length to make MSVC vsnprintf safe // (Don't use vsprintf_s because it treats overflow as fatal) // Terminate the string if the printing was truncated if (len < 0 || len >= (int)MAX_ATTRIBUTE_LENGTH - 1) { strncpy(buffer + 4 + MAX_ATTRIBUTE_LENGTH - 4, "...", 4); len = MAX_ATTRIBUTE_LENGTH - 1; // excluding null terminator } // Store the length in the buffer memcpy(buffer, &len, sizeof(len)); Write(ITEM_ATTRIBUTE, buffer, 4 + len); } size_t CProfiler2::ThreadStorage::HoldLevel() { return m_HeldDepth; } u8 CProfiler2::ThreadStorage::HoldType() { return m_HoldBuffers[m_HeldDepth - 1].type; } void CProfiler2::ThreadStorage::PutOnHold(u8 newType) { m_HeldDepth++; m_HoldBuffers[m_HeldDepth - 1].clear(); m_HoldBuffers[m_HeldDepth - 1].setType(newType); } // this flattens the stack, use it sensibly void rewriteBuffer(u8* buffer, u32& bufferSize) { double startTime = timer_Time(); u32 size = bufferSize; u32 readPos = 0; double initialTime = -1; double total_time = -1; const char* regionName; std::set topLevelArgs; typedef std::tuple > infoPerType; std::unordered_map timeByType; std::vector last_time_stack; std::vector last_names; // never too many hacks std::string current_attribute = ""; std::map time_per_attribute; // Let's read the first event { u8 type = buffer[readPos]; ++readPos; if (type != CProfiler2::ITEM_ENTER) { debug_warn("Profiler2: Condensing a region should run into ITEM_ENTER first"); return; // do nothing } CProfiler2::SItem_dt_id item; memcpy(&item, buffer + readPos, sizeof(item)); readPos += sizeof(item); regionName = item.id; last_names.push_back(item.id); initialTime = (double)item.dt; } int enter = 1; int leaves = 0; // Read subsequent events. Flatten hierarchy because it would get too complicated otherwise. // To make sure time doesn't bloat, subtract time from nested events while (readPos < size) { u8 type = buffer[readPos]; ++readPos; switch (type) { case CProfiler2::ITEM_NOP: { // ignore break; } case CProfiler2::ITEM_SYNC: { debug_warn("Aggregated regions should not be used across frames"); // still try to act sane readPos += sizeof(double); readPos += sizeof(CProfiler2::RESYNC_MAGIC); break; } case CProfiler2::ITEM_EVENT: { // skip for now readPos += sizeof(CProfiler2::SItem_dt_id); break; } case CProfiler2::ITEM_ENTER: { enter++; CProfiler2::SItem_dt_id item; memcpy(&item, buffer + readPos, sizeof(item)); readPos += sizeof(item); last_time_stack.push_back((double)item.dt); last_names.push_back(item.id); current_attribute = ""; break; } case CProfiler2::ITEM_LEAVE: { float item_time; memcpy(&item_time, buffer + readPos, sizeof(float)); readPos += sizeof(float); leaves++; if (last_names.empty()) { // we somehow lost the first entry in the process debug_warn("Invalid buffer for condensing"); } const char* item_name = last_names.back(); last_names.pop_back(); if (last_time_stack.empty()) { // this is the leave for the whole scope total_time = (double)item_time; break; } double time = (double)item_time - last_time_stack.back(); std::string name = std::string(item_name); auto TimeForType = timeByType.find(name); if (TimeForType == timeByType.end()) { // keep reference to the original char pointer to make sure we don't break things down the line std::get<0>(timeByType[name]) = item_name; std::get<1>(timeByType[name]) = 0; } std::get<1>(timeByType[name]) += time; last_time_stack.pop_back(); // if we were nested, subtract our time from the below scope by making it look like it starts later if (!last_time_stack.empty()) last_time_stack.back() += time; if (!current_attribute.empty()) { time_per_attribute[current_attribute] += time; } break; } case CProfiler2::ITEM_ATTRIBUTE: { // skip for now u32 len; memcpy(&len, buffer + readPos, sizeof(len)); ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH); readPos += sizeof(len); char message[CProfiler2::MAX_ATTRIBUTE_LENGTH] = {0}; memcpy(&message[0], buffer + readPos, len); CStr mess = CStr((const char*)message, len); if (!last_names.empty()) { auto it = timeByType.find(std::string(last_names.back())); if (it == timeByType.end()) topLevelArgs.insert(mess); else std::get<2>(timeByType[std::string(last_names.back())]).insert(mess); } readPos += len; current_attribute = mess; break; } default: debug_warn(L"Invalid profiler item when condensing buffer"); continue; } } // rewrite the buffer // what we rewrite will always be smaller than the current buffer's size u32 writePos = 0; double curTime = initialTime; // the region enter { CProfiler2::SItem_dt_id item = { (float)curTime, regionName }; buffer[writePos] = (u8)CProfiler2::ITEM_ENTER; memcpy(buffer + writePos + 1, &item, sizeof(item)); writePos += sizeof(item) + 1; // add a nanosecond for sanity curTime += 0.000001; } // sub-events, aggregated for (auto& type : timeByType) { CProfiler2::SItem_dt_id item = { (float)curTime, std::get<0>(type.second) }; buffer[writePos] = (u8)CProfiler2::ITEM_ENTER; memcpy(buffer + writePos + 1, &item, sizeof(item)); writePos += sizeof(item) + 1; // write relevant attributes if present for (const auto& attrib : std::get<2>(type.second)) { buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE; writePos++; std::string basic = attrib; auto time_attrib = time_per_attribute.find(attrib); if (time_attrib != time_per_attribute.end()) basic += " " + CStr::FromInt(1000000*time_attrib->second) + "us"; u32 length = basic.size(); memcpy(buffer + writePos, &length, sizeof(length)); writePos += sizeof(length); memcpy(buffer + writePos, basic.c_str(), length); writePos += length; } curTime += std::get<1>(type.second); float leave_time = (float)curTime; buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE; memcpy(buffer + writePos + 1, &leave_time, sizeof(float)); writePos += sizeof(float) + 1; } // Time of computation { CProfiler2::SItem_dt_id item = { (float)curTime, "CondenseBuffer" }; buffer[writePos] = (u8)CProfiler2::ITEM_ENTER; memcpy(buffer + writePos + 1, &item, sizeof(item)); writePos += sizeof(item) + 1; } { float time_out = (float)(curTime + timer_Time() - startTime); buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE; memcpy(buffer + writePos + 1, &time_out, sizeof(float)); writePos += sizeof(float) + 1; // add a nanosecond for sanity curTime += 0.000001; } // the region leave { if (total_time < 0) { total_time = curTime + 0.000001; buffer[writePos] = (u8)CProfiler2::ITEM_ATTRIBUTE; writePos++; u32 length = sizeof("buffer overflow"); memcpy(buffer + writePos, &length, sizeof(length)); writePos += sizeof(length); memcpy(buffer + writePos, "buffer overflow", length); writePos += length; } else if (total_time < curTime) { // this seems to happen on rare occasions. curTime = total_time; } float leave_time = (float)total_time; buffer[writePos] = (u8)CProfiler2::ITEM_LEAVE; memcpy(buffer + writePos + 1, &leave_time, sizeof(float)); writePos += sizeof(float) + 1; } bufferSize = writePos; } void CProfiler2::ThreadStorage::HoldToBuffer(bool condensed) { ENSURE(m_HeldDepth); if (condensed) { // rewrite the buffer to show aggregated data rewriteBuffer(m_HoldBuffers[m_HeldDepth - 1].buffer, m_HoldBuffers[m_HeldDepth - 1].pos); } if (m_HeldDepth > 1) { // copy onto buffer below HoldBuffer& copied = m_HoldBuffers[m_HeldDepth - 1]; HoldBuffer& target = m_HoldBuffers[m_HeldDepth - 2]; if (target.pos + copied.pos > HOLD_BUFFER_SIZE) return; // too much data, too bad memcpy(&target.buffer[target.pos], copied.buffer, copied.pos); target.pos += copied.pos; } else { u32 size = m_HoldBuffers[m_HeldDepth - 1].pos; u32 start = m_BufferPos0; if (start + size > BUFFER_SIZE) { m_BufferPos0 = size; COMPILER_FENCE; memset(m_Buffer + start, 0, BUFFER_SIZE - start); start = 0; } else { m_BufferPos0 = start + size; COMPILER_FENCE; // must write m_BufferPos0 before m_Buffer } memcpy(&m_Buffer[start], m_HoldBuffers[m_HeldDepth - 1].buffer, size); COMPILER_FENCE; // must write m_BufferPos1 after m_Buffer m_BufferPos1 = start + size; } m_HeldDepth--; } void CProfiler2::ThreadStorage::ThrowawayHoldBuffer() { if (!m_HeldDepth) return; m_HeldDepth--; } void CProfiler2::ConstructJSONOverview(std::ostream& stream) { TIMER(L"profile2 overview"); CScopeLock lock(m_Mutex); stream << "{\"threads\":["; for (size_t i = 0; i < m_Threads.size(); ++i) { if (i != 0) stream << ","; stream << "{\"name\":\"" << CStr(m_Threads[i]->GetName()).EscapeToPrintableASCII() << "\"}"; } stream << "]}"; } /** * Given a buffer and a visitor class (with functions OnEvent, OnEnter, OnLeave, OnAttribute), * calls the visitor for every item in the buffer. */ template void RunBufferVisitor(const std::string& buffer, V& visitor) { TIMER(L"profile2 visitor"); // The buffer doesn't necessarily start at the beginning of an item // (we just grabbed it from some arbitrary point in the middle), // so scan forwards until we find a sync marker. // (This is probably pretty inefficient.) u32 realStart = (u32)-1; // the start point decided by the scan algorithm for (u32 start = 0; start + 1 + sizeof(CProfiler2::RESYNC_MAGIC) <= buffer.length(); ++start) { if (buffer[start] == CProfiler2::ITEM_SYNC && memcmp(buffer.c_str() + start + 1, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0) { realStart = start; break; } } ENSURE(realStart != (u32)-1); // we should have found a sync point somewhere in the buffer u32 pos = realStart; // the position as we step through the buffer double lastTime = -1; // set to non-negative by EVENT_SYNC; we ignore all items before that // since we can't compute their absolute times while (pos < buffer.length()) { u8 type = buffer[pos]; ++pos; switch (type) { case CProfiler2::ITEM_NOP: { // ignore break; } case CProfiler2::ITEM_SYNC: { u8 magic[sizeof(CProfiler2::RESYNC_MAGIC)]; double t; memcpy(magic, buffer.c_str()+pos, ARRAY_SIZE(magic)); ENSURE(memcmp(magic, &CProfiler2::RESYNC_MAGIC, sizeof(CProfiler2::RESYNC_MAGIC)) == 0); pos += sizeof(CProfiler2::RESYNC_MAGIC); memcpy(&t, buffer.c_str()+pos, sizeof(t)); pos += sizeof(t); lastTime = t; visitor.OnSync(lastTime); break; } case CProfiler2::ITEM_EVENT: { CProfiler2::SItem_dt_id item; memcpy(&item, buffer.c_str()+pos, sizeof(item)); pos += sizeof(item); if (lastTime >= 0) { visitor.OnEvent(lastTime + (double)item.dt, item.id); } break; } case CProfiler2::ITEM_ENTER: { CProfiler2::SItem_dt_id item; memcpy(&item, buffer.c_str()+pos, sizeof(item)); pos += sizeof(item); if (lastTime >= 0) { visitor.OnEnter(lastTime + (double)item.dt, item.id); } break; } case CProfiler2::ITEM_LEAVE: { float leave_time; memcpy(&leave_time, buffer.c_str() + pos, sizeof(float)); pos += sizeof(float); if (lastTime >= 0) { visitor.OnLeave(lastTime + (double)leave_time); } break; } case CProfiler2::ITEM_ATTRIBUTE: { u32 len; memcpy(&len, buffer.c_str()+pos, sizeof(len)); ENSURE(len <= CProfiler2::MAX_ATTRIBUTE_LENGTH); pos += sizeof(len); std::string attribute(buffer.c_str()+pos, buffer.c_str()+pos+len); pos += len; if (lastTime >= 0) { visitor.OnAttribute(attribute); } break; } default: debug_warn(L"Invalid profiler item when parsing buffer"); return; } } }; /** * Visitor class that dumps events as JSON. * TODO: this is pretty inefficient (in implementation and in output format). */ struct BufferVisitor_Dump { NONCOPYABLE(BufferVisitor_Dump); public: BufferVisitor_Dump(std::ostream& stream) : m_Stream(stream) { } void OnSync(double UNUSED(time)) { // Split the array of items into an array of array (arbitrarily splitting // around the sync points) to avoid array-too-large errors in JSON decoders m_Stream << "null], [\n"; } void OnEvent(double time, const char* id) { m_Stream << "[1," << std::fixed << std::setprecision(9) << time; m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n"; } void OnEnter(double time, const char* id) { m_Stream << "[2," << std::fixed << std::setprecision(9) << time; m_Stream << ",\"" << CStr(id).EscapeToPrintableASCII() << "\"],\n"; } void OnLeave(double time) { m_Stream << "[3," << std::fixed << std::setprecision(9) << time << "],\n"; } void OnAttribute(const std::string& attr) { m_Stream << "[4,\"" << CStr(attr).EscapeToPrintableASCII() << "\"],\n"; } std::ostream& m_Stream; }; const char* CProfiler2::ConstructJSONResponse(std::ostream& stream, const std::string& thread) { TIMER(L"profile2 query"); std::string buffer; { TIMER(L"profile2 get buffer"); CScopeLock lock(m_Mutex); // lock against changes to m_Threads or deletions of ThreadStorage ThreadStorage* storage = NULL; for (size_t i = 0; i < m_Threads.size(); ++i) { if (m_Threads[i]->GetName() == thread) { storage = m_Threads[i]; break; } } if (!storage) return "cannot find named thread"; stream << "{\"events\":[\n"; stream << "[\n"; buffer = storage->GetBuffer(); } BufferVisitor_Dump visitor(stream); RunBufferVisitor(buffer, visitor); stream << "null]\n]}"; return NULL; } void CProfiler2::SaveToFile() { OsPath path = psLogDir()/"profile2.jsonp"; std::ofstream stream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc); ENSURE(stream.good()); std::vector threads; { CScopeLock lock(m_Mutex); threads = m_Threads; } stream << "profileDataCB({\"threads\": [\n"; for (size_t i = 0; i < threads.size(); ++i) { if (i != 0) stream << ",\n"; stream << "{\"name\":\"" << CStr(threads[i]->GetName()).EscapeToPrintableASCII() << "\",\n"; stream << "\"data\": "; ConstructJSONResponse(stream, threads[i]->GetName()); stream << "\n}"; } stream << "\n]});\n"; } CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) : m_Name(name), m_Limit(spikeLimit), m_PushedHold(true) { if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_SPIKE); else m_PushedHold = false; COMPILER_FENCE; g_Profiler2.RecordRegionEnter(m_Name); m_StartTime = g_Profiler2.GetTime(); } CProfile2SpikeRegion::~CProfile2SpikeRegion() { double time = g_Profiler2.GetTime(); g_Profiler2.RecordRegionLeave(); bool shouldWrite = time - m_StartTime > m_Limit; if (m_PushedHold) g_Profiler2.StopHoldingMessages(shouldWrite); } CProfile2AggregatedRegion::CProfile2AggregatedRegion(const char* name, double spikeLimit) : m_Name(name), m_Limit(spikeLimit), m_PushedHold(true) { if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_AGGREGATE); else m_PushedHold = false; COMPILER_FENCE; g_Profiler2.RecordRegionEnter(m_Name); m_StartTime = g_Profiler2.GetTime(); } CProfile2AggregatedRegion::~CProfile2AggregatedRegion() { double time = g_Profiler2.GetTime(); g_Profiler2.RecordRegionLeave(); bool shouldWrite = time - m_StartTime > m_Limit; if (m_PushedHold) g_Profiler2.StopHoldingMessages(shouldWrite, true); } Index: ps/trunk/source/third_party/encryption/pkcs5_pbkdf2.h =================================================================== --- ps/trunk/source/third_party/encryption/pkcs5_pbkdf2.h (revision 19898) +++ ps/trunk/source/third_party/encryption/pkcs5_pbkdf2.h (revision 19899) @@ -1,44 +1,44 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (C) 2013 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. */ #ifndef PKCS5_PBKD2_INCLUDED #define PKCS5_PBKD2_INCLUDED // We need to know SHA_DIGEST_SIZE. #include "third_party/encryption/sha.h" /** * Simple PBKDF2 implementation for hard to crack passwords * @param output The output buffer for the digested hash * @param key The initial key we want to hash * @param key_len Length of the key in bytes * @param salt The salt we use to iteratively hash the key. * @param salt_len Length of the salt in bytes * @param iterations Number of salting iterations * @return 0 on success, -1 on error */ int pbkdf2(unsigned char (&output)[SHA_DIGEST_SIZE], const unsigned char* key, size_t key_len, const unsigned char* salt, size_t salt_len, unsigned iterations); #endif // PKCS5_PBKD2_INCLUDED Index: ps/trunk/source/tools/profiler2/ReportDraw.js =================================================================== --- ps/trunk/source/tools/profiler2/ReportDraw.js (revision 19898) +++ ps/trunk/source/tools/profiler2/ReportDraw.js (revision 19899) @@ -1,479 +1,479 @@ -// Copyright (c) 2016 Wildfire Games +// Copyright (C) 2016 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. // Handles the drawing of a report var g_report_draw = (function() { var outInterface = {}; var mouse_is_down = null; function rebuild_canvases(raw_data) { g_canvas = {}; g_canvas.canvas_frames = $('').get(0); g_canvas.threads = {}; for (var thread = 0; thread < raw_data.threads.length; thread++) g_canvas.threads[thread] = $('').get(0); g_canvas.canvas_zoom = $('').get(0); g_canvas.text_output = $('
').get(0);
 
     $('#timelines').empty();
         $('#timelines').append("

Main thread frames

"); $('#timelines').append(g_canvas.canvas_frames); for (var thread = 0; thread < raw_data.threads.length; thread++) { $('#timelines').append("

" + raw_data.threads[thread].name + "

"); $('#timelines').append($(g_canvas.threads[thread])); } $('#timelines').append("

Zoomed frames

"); $('#timelines').append(g_canvas.canvas_zoom); $('#timelines').append(g_canvas.text_output); } function update_display(report, range) { let data = report.data(); let raw_data = report.raw_data(); let main_data = data.threads[g_main_thread]; rebuild_canvases(raw_data); if (range.seconds) { range.tmax = main_data.frames[main_data.frames.length-1].t1; range.tmin = main_data.frames[main_data.frames.length-1].t1-range.seconds; } else if (range.frames) { range.tmax = main_data.frames[main_data.frames.length-1].t1; range.tmin = main_data.frames[main_data.frames.length-1-range.frames].t0; } $(g_canvas.text_output).empty(); display_frames(data.threads[g_main_thread], g_canvas.canvas_frames, range); display_events(data.threads[g_main_thread], g_canvas.canvas_frames, range); set_frames_zoom_handlers(report, g_canvas.canvas_frames); set_tooltip_handlers(g_canvas.canvas_frames); $(g_canvas.canvas_zoom).unbind(); set_zoom_handlers(data.threads[g_main_thread], data.threads[g_main_thread], g_canvas.threads[g_main_thread], g_canvas.canvas_zoom); set_tooltip_handlers(data.canvas_zoom); for (var i = 0; i < data.threads.length; i++) { $(g_canvas.threads[i]).unbind(); let events = slice_intervals(data.threads[i], range); display_hierarchy(data.threads[i], events, g_canvas.threads[i], {}); set_zoom_handlers(data.threads[i], events, g_canvas.threads[i], g_canvas.canvas_zoom); set_tooltip_handlers(g_canvas.threads[i]); }; } outInterface.update_display = update_display; function display_frames(data, canvas, range) { canvas._tooltips = []; var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); var xpadding = 8; var padding_top = 40; var width = canvas.width - xpadding*2; var height = canvas.height - padding_top - 4; var tmin = data.tmin; var tmax = data.tmax; var dx = width / (tmax-tmin); canvas._zoomData = { 'x_to_t': x => tmin + (x - xpadding) / dx, 't_to_x': t => (t - tmin) * dx + xpadding }; // log 100 scale, skip < 15 ms (60fps) var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100)); ctx.strokeStyle = 'rgb(0, 0, 0)'; ctx.fillStyle = 'rgb(255, 255, 255)'; for (var i = 0; i < data.frames.length; ++i) { var frame = data.frames[i]; var duration = frame.t1 - frame.t0; var x0 = xpadding + dx*(frame.t0 - tmin); var x1 = x0 + dx*duration; var y1 = canvas.height; var y0 = y1 * scale(duration*1000); ctx.beginPath(); ctx.rect(x0, y0, x1-x0, y1-y0); ctx.stroke(); canvas._tooltips.push({ 'x0': x0, 'x1': x1, 'y0': y0, 'y1': y1, 'text': function(frame, duration) { return function() { var t = 'Frame
'; t += 'Length: ' + time_label(duration) + '
'; if (frame.attrs) { frame.attrs.forEach(function(attr) { t += attr + '
'; }); } return t; }} (frame, duration) }); } [16, 33, 200, 500].forEach(function(t) { var y1 = canvas.height; var y0 = y1 * scale(t); var y = Math.floor(y0) + 0.5; ctx.beginPath(); ctx.moveTo(xpadding, y); ctx.lineTo(canvas.width - xpadding, y); ctx.strokeStyle = 'rgb(255, 0, 0)'; ctx.stroke(); ctx.fillStyle = 'rgb(255, 0, 0)'; ctx.fillText(t+'ms', 0, y-2); }); ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; ctx.beginPath(); ctx.rect(xpadding + dx*(range.tmin - tmin), 0, dx*(range.tmax - range.tmin), canvas.height); ctx.fill(); ctx.stroke(); ctx.restore(); } outInterface.display_frames = display_frames; function display_events(data, canvas) { var ctx = canvas.getContext('2d'); ctx.save(); var x_to_time = canvas._zoomData.x_to_t; var time_to_x = canvas._zoomData.t_to_x; for (var i = 0; i < data.events.length; ++i) { var event = data.events[i]; if (event.id == '__framestart') continue; if (event.id == 'gui event' && event.attrs && event.attrs[0] == 'type: mousemove') continue; var x = time_to_x(event.t); var y = 32; if (x < 2) continue; var x0 = x; var x1 = x; var y0 = y-4; var y1 = y+4; ctx.strokeStyle = 'rgb(255, 0, 0)'; ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke(); canvas._tooltips.push({ 'x0': x0, 'x1': x1, 'y0': y0, 'y1': y1, 'text': function(event) { return function() { var t = '' + event.id + '
'; if (event.attrs) { event.attrs.forEach(function(attr) { t += attr + '
'; }); } return t; }} (event) }); } ctx.restore(); } outInterface.display_events = display_events; function display_hierarchy(main_data, data, canvas, range, zoom) { canvas._tooltips = []; var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.font = '12px sans-serif'; var xpadding = 8; var padding_top = 40; var width = canvas.width - xpadding*2; var height = canvas.height - padding_top - 4; var tmin, tmax, start, end; if (range.tmin) { tmin = range.tmin; tmax = range.tmax; } else { tmin = data.tmin; tmax = data.tmax; } canvas._hierarchyData = { 'range': range, 'tmin': tmin, 'tmax': tmax }; function time_to_x(t) { return xpadding + (t - tmin) / (tmax - tmin) * width; } function x_to_time(x) { return tmin + (x - xpadding) * (tmax - tmin) / width; } ctx.save(); ctx.textAlign = 'center'; ctx.strokeStyle = 'rgb(192, 192, 192)'; ctx.beginPath(); var precision = -3; while ((tmax-tmin)*Math.pow(10, 3+precision) < 25) ++precision; if (precision > 10) precision = 10; if (precision < 0) precision = 0; var ticks_per_sec = Math.pow(10, 3+precision); var major_tick_interval = 5; for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i) { var major = (i % major_tick_interval == 0); var x = Math.floor(time_to_x(tmin + i/ticks_per_sec)); ctx.moveTo(x-0.5, padding_top - (major ? 4 : 2)); ctx.lineTo(x-0.5, padding_top + height); if (major) ctx.fillText((i*1000/ticks_per_sec).toFixed(precision), x, padding_top - 8); } ctx.stroke(); ctx.restore(); var BAR_SPACING = 16; for (var i = 0; i < data.intervals.length; ++i) { var interval = data.intervals[i]; if (interval.tmax <= tmin || interval.tmin > tmax) continue; var x0 = Math.floor(time_to_x(interval.t0)); var x1 = Math.floor(time_to_x(interval.t1)); if (x1-x0 < 1) continue; var y0 = padding_top + interval.depth * BAR_SPACING; var y1 = y0 + BAR_SPACING; var label = interval.id; if (interval.attrs) { if (/^\d+$/.exec(interval.attrs[0])) label += ' ' + interval.attrs[0]; else label += ' [...]'; } ctx.fillStyle = interval.colour; ctx.strokeStyle = 'black'; ctx.beginPath(); ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0); ctx.fill(); ctx.stroke(); ctx.fillStyle = 'black'; ctx.fillText(label, x0+2, y0+BAR_SPACING-4, Math.max(1, x1-x0-4)); canvas._tooltips.push({ 'x0': x0, 'x1': x1, 'y0': y0, 'y1': y1, 'text': function(interval) { return function() { var t = '' + interval.id + '
'; t += 'Length: ' + time_label(interval.duration) + '
'; if (interval.attrs) { interval.attrs.forEach(function(attr) { t += attr + '
'; }); } return t; }} (interval) }); } for (var i = 0; i < main_data.frames.length; ++i) { var frame = main_data.frames[i]; if (frame.t0 < tmin || frame.t0 > tmax) continue; var x = Math.floor(time_to_x(frame.t0)); ctx.save(); ctx.lineWidth = 3; ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; ctx.beginPath(); ctx.moveTo(x+0.5, 0); ctx.lineTo(x+0.5, canvas.height); ctx.stroke(); ctx.fillText(((frame.t1 - frame.t0) * 1000).toFixed(0)+'ms', x+2, padding_top - 24); ctx.restore(); } if (zoom) { var x0 = time_to_x(zoom.tmin); var x1 = time_to_x(zoom.tmax); ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; ctx.fillStyle = 'rgba(128, 128, 255, 0.2)'; ctx.beginPath(); ctx.moveTo(x0+0.5, 0.5); ctx.lineTo(x1+0.5, 0.5); ctx.lineTo(x1+0.5 + 4, canvas.height-0.5); ctx.lineTo(x0+0.5 - 4, canvas.height-0.5); ctx.closePath(); ctx.fill(); ctx.stroke(); } ctx.restore(); } outInterface.display_hierarchy = display_hierarchy; function set_frames_zoom_handlers(report, canvas0) { function do_zoom(report, event) { var zdata = canvas0._zoomData; var relativeX = event.pageX - this.offsetLeft; var relativeY = event.pageY - this.offsetTop; var width = relativeY / canvas0.height; width = width*width; width *= zdata.x_to_t(canvas0.width)/10; var tavg = zdata.x_to_t(relativeX); var tmax = tavg + width/2; var tmin = tavg - width/2; var range = {'tmin': tmin, 'tmax': tmax}; update_display(report, range); } $(canvas0).unbind(); $(canvas0).mousedown(function(event) { mouse_is_down = canvas0; do_zoom.call(this, report, event); }); $(canvas0).mouseup(function(event) { mouse_is_down = null; }); $(canvas0).mousemove(function(event) { if (mouse_is_down) do_zoom.call(this, report, event); }); } function set_zoom_handlers(main_data, data, canvas0, canvas1) { function do_zoom(event) { var hdata = canvas0._hierarchyData; function x_to_time(x) { return hdata.tmin + x * (hdata.tmax - hdata.tmin) / canvas0.width; } var relativeX = event.pageX - this.offsetLeft; var relativeY = (event.pageY + this.offsetTop) / canvas0.height; relativeY = relativeY - 0.5; relativeY *= 5; relativeY *= relativeY; var width = relativeY / canvas0.height; width = width*width; width = 3 + width * x_to_time(canvas0.width)/10; var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) }; display_hierarchy(main_data, data, canvas0, hdata.range, zoom); display_hierarchy(main_data, data, canvas1, zoom, undefined); set_tooltip_handlers(canvas1); } $(canvas0).mousedown(function(event) { mouse_is_down = canvas0; do_zoom.call(this, event); }); $(canvas0).mouseup(function(event) { mouse_is_down = null; }); $(canvas0).mousemove(function(event) { if (mouse_is_down) do_zoom.call(this, event); }); } return outInterface; })(); \ No newline at end of file Index: ps/trunk/source/tools/templatesanalyzer/unitTables.py =================================================================== --- ps/trunk/source/tools/templatesanalyzer/unitTables.py (revision 19898) +++ ps/trunk/source/tools/templatesanalyzer/unitTables.py (revision 19899) @@ -1,566 +1,566 @@ -# Copyright (c) 2015 Wildfire Games +# Copyright (C) 2015 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. import xml.etree.ElementTree as ET import os import glob AttackTypes = ["Hack","Pierce","Crush"] Resources = ["food", "wood", "stone", "metal"] # Generic templates to load # The way this works is it tries all generic templates # But only loads those who have one of the following parents # EG adding "template_unit.xml" will load all units. LoadTemplatesIfParent = ["template_unit_infantry.xml", "template_unit_cavalry.xml", "template_unit_champion.xml", "template_unit_hero.xml"] # Those describe Civs to analyze. # The script will load all entities that derive (to the nth degree) from one of the above templates. Civs = ["athen", "brit", "cart", "gaul", "iber", "mace", "maur", "pers", "ptol", "rome", "sele", "spart"] # Remote Civ templates with those strings in their name. FilterOut = ["marian", "thureophoros", "thorakites", "kardakes"] # Sorting parameters for the "roster variety" table ComparativeSortByCav = True ComparativeSortByChamp = True SortTypes = ["Support", "Pike", "Spear", "Sword", "Archer", "Javelin", "Sling", "Elephant"] # Classes # Disable if you want the more compact basic data. Enable to allow filtering and sorting in-place. AddSortingOverlay = True # This is the path to the /templates/ folder to consider. Change this for mod support. basePath = os.path.realpath(__file__).replace("unitTables.py","") + "../../../binaries/data/mods/public/simulation/templates/" # For performance purposes, cache opened templates files. globalTemplatesList = {} def htbout(file, balise, value): file.write("<" + balise + ">" + value + "\n" ) def htout(file, value): file.write("

" + value + "

\n" ) def fastParse(templateName): if templateName in globalTemplatesList: return globalTemplatesList[templateName] globalTemplatesList[templateName] = ET.parse(templateName) return globalTemplatesList[templateName] # This function checks that a template has the given parent. def hasParentTemplate(UnitName, parentName): Template = fastParse(UnitName) found = False Name = UnitName while found != True and Template.getroot().get("parent") != None: Name = Template.getroot().get("parent") + ".xml" if Name == parentName: return True Template = ET.parse(Name) return False def NumericStatProcess(unitValue, templateValue): if not "op" in templateValue.attrib: return float(templateValue.text) if (templateValue.attrib["op"] == "add"): unitValue += float(templateValue.text) elif (templateValue.attrib["op"] == "sub"): unitValue -= float(templateValue.text) elif (templateValue.attrib["op"] == "mul"): unitValue *= float(templateValue.text) elif (templateValue.attrib["op"] == "div"): unitValue /= float(templateValue.text) return unitValue # This function parses the entity values manually. def CalcUnit(UnitName, existingUnit = None): unit = { 'HP' : "0", "BuildTime" : "0", "Cost" : { 'food' : "0", "wood" : "0", "stone" : "0", "metal" : "0", "population" : "0"}, 'Attack' : { "Melee" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 }, "Ranged" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 } }, 'RepeatRate' : {"Melee" : "0", "Ranged" : "0"},'PrepRate' : {"Melee" : "0", "Ranged" : "0"}, "Armour" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0}, "Ranged" : False, "Classes" : [], "AttackBonuses" : {}, "Restricted" : [], "WalkSpeed" : 0, "Range" : 0, "Spread" : 0, "Civ" : None } if (existingUnit != None): unit = existingUnit Template = fastParse(UnitName) # Recursively get data from our parent which we'll override. if (Template.getroot().get("parent") != None): unit = CalcUnit(Template.getroot().get("parent") + ".xml", unit) unit["Parent"] = Template.getroot().get("parent") + ".xml" if (Template.find("./Identity/Civ") != None): unit['Civ'] = Template.find("./Identity/Civ").text if (Template.find("./Health/Max") != None): unit['HP'] = NumericStatProcess(unit['HP'], Template.find("./Health/Max")) if (Template.find("./Cost/BuildTime") != None): unit['BuildTime'] = NumericStatProcess(unit['BuildTime'], Template.find("./Cost/BuildTime")) if (Template.find("./Cost/Resources") != None): for type in list(Template.find("./Cost/Resources")): unit['Cost'][type.tag] = NumericStatProcess(unit['Cost'][type.tag], type) if (Template.find("./Cost/Population") != None): unit['Cost']["population"] = NumericStatProcess(unit['Cost']["population"], Template.find("./Cost/Population")) if (Template.find("./Attack/Melee") != None): if (Template.find("./Attack/Melee/RepeatTime") != None): unit['RepeatRate']["Melee"] = NumericStatProcess(unit['RepeatRate']["Melee"], Template.find("./Attack/Melee/RepeatTime")) if (Template.find("./Attack/Melee/PrepareTime") != None): unit['PrepRate']["Melee"] = NumericStatProcess(unit['PrepRate']["Melee"], Template.find("./Attack/Melee/PrepareTime")) for atttype in AttackTypes: if (Template.find("./Attack/Melee/"+atttype) != None): unit['Attack']['Melee'][atttype] = NumericStatProcess(unit['Attack']['Melee'][atttype], Template.find("./Attack/Melee/"+atttype)) if (Template.find("./Attack/Melee/Bonuses") != None): for Bonus in Template.find("./Attack/Melee/Bonuses"): Against = [] CivAg = [] if (Bonus.find("Classes") != None and Bonus.find("Classes").text != None): Against = Bonus.find("Classes").text.split(" ") if (Bonus.find("Civ") != None and Bonus.find("Civ").text != None): CivAg = Bonus.find("Civ").text.split(" ") Val = float(Bonus.find("Multiplier").text) unit["AttackBonuses"][Bonus.tag] = {"Classes" : Against, "Civs" : CivAg, "Multiplier" : Val} if (Template.find("./Attack/Melee/RestrictedClasses") != None): newClasses = Template.find("./Attack/Melee/RestrictedClasses").text.split(" ") for elem in newClasses: if (elem.find("-") != -1): newClasses.pop(newClasses.index(elem)) if elem in unit["Restricted"]: unit["Restricted"].pop(newClasses.index(elem)) unit["Restricted"] += newClasses if (Template.find("./Attack/Ranged") != None): unit['Ranged'] = True if (Template.find("./Attack/Ranged/MaxRange") != None): unit['Range'] = NumericStatProcess(unit['Range'], Template.find("./Attack/Ranged/MaxRange")) if (Template.find("./Attack/Ranged/Spread") != None): unit['Spread'] = NumericStatProcess(unit['Spread'], Template.find("./Attack/Ranged/Spread")) if (Template.find("./Attack/Ranged/RepeatTime") != None): unit['RepeatRate']["Ranged"] = NumericStatProcess(unit['RepeatRate']["Ranged"], Template.find("./Attack/Ranged/RepeatTime")) if (Template.find("./Attack/Ranged/PrepareTime") != None): unit['PrepRate']["Ranged"] = NumericStatProcess(unit['PrepRate']["Ranged"], Template.find("./Attack/Ranged/PrepareTime")) for atttype in AttackTypes: if (Template.find("./Attack/Ranged/"+atttype) != None): unit['Attack']['Ranged'][atttype] = NumericStatProcess(unit['Attack']['Ranged'][atttype], Template.find("./Attack/Ranged/"+atttype)) if (Template.find("./Attack/Ranged/Bonuses") != None): for Bonus in Template.find("./Attack/Ranged/Bonuses"): Against = [] CivAg = [] if (Bonus.find("Classes") != None and Bonus.find("Classes").text != None): Against = Bonus.find("Classes").text.split(" ") if (Bonus.find("Civ") != None and Bonus.find("Civ").text != None): CivAg = Bonus.find("Civ").text.split(" ") Val = float(Bonus.find("Multiplier").text) unit["AttackBonuses"][Bonus.tag] = {"Classes" : Against, "Civs" : CivAg, "Multiplier" : Val} if (Template.find("./Attack/Melee/RestrictedClasses") != None): newClasses = Template.find("./Attack/Melee/RestrictedClasses").text.split(" ") for elem in newClasses: if (elem.find("-") != -1): newClasses.pop(newClasses.index(elem)) if elem in unit["Restricted"]: unit["Restricted"].pop(newClasses.index(elem)) unit["Restricted"] += newClasses if (Template.find("./Armour") != None): for atttype in AttackTypes: if (Template.find("./Armour/"+atttype) != None): unit['Armour'][atttype] = NumericStatProcess(unit['Armour'][atttype], Template.find("./Armour/"+atttype)) if (Template.find("./UnitMotion") != None): if (Template.find("./UnitMotion/WalkSpeed") != None): unit['WalkSpeed'] = NumericStatProcess(unit['WalkSpeed'], Template.find("./UnitMotion/WalkSpeed")) if (Template.find("./Identity/VisibleClasses") != None): newClasses = Template.find("./Identity/VisibleClasses").text.split(" ") for elem in newClasses: if (elem.find("-") != -1): newClasses.pop(newClasses.index(elem)) if elem in unit["Classes"]: unit["Classes"].pop(newClasses.index(elem)) unit["Classes"] += newClasses if (Template.find("./Identity/Classes") != None): newClasses = Template.find("./Identity/Classes").text.split(" ") for elem in newClasses: if (elem.find("-") != -1): newClasses.pop(newClasses.index(elem)) if elem in unit["Classes"]: unit["Classes"].pop(newClasses.index(elem)) unit["Classes"] += newClasses return unit def WriteUnit(Name, UnitDict): ret = "" ret += "" + Name + "" ret += "" + str(int(UnitDict["HP"])) + "" ret += "" +str("%.0f" % float(UnitDict["BuildTime"])) + "" ret += "" + str("%.1f" % float(UnitDict["WalkSpeed"])) + "" for atype in AttackTypes: PercentValue = 1.0 - (0.9 ** float(UnitDict["Armour"][atype])) ret += "" + str("%.0f" % float(UnitDict["Armour"][atype])) + " / " + str("%.0f" % (PercentValue*100.0)) + "%" attType = ("Ranged" if UnitDict["Ranged"] == True else "Melee") if UnitDict["RepeatRate"][attType] != "0": for atype in AttackTypes: repeatTime = float(UnitDict["RepeatRate"][attType])/1000.0 ret += "" + str("%.1f" % (float(UnitDict["Attack"][attType][atype])/repeatTime)) + "" ret += "" + str("%.1f" % (float(UnitDict["RepeatRate"][attType])/1000.0)) + "" else: for atype in AttackTypes: ret += " - " ret += " - " if UnitDict["Ranged"] == True and UnitDict["Range"] > 0: ret += "" + str("%.1f" % float(UnitDict["Range"])) + "" spread = float(UnitDict["Spread"]) ret += "" + str("%.1f" % spread) + "" else: ret += " - - " for rtype in Resources: ret += "" + str("%.0f" % float(UnitDict["Cost"][rtype])) + "" ret += "" + str("%.0f" % float(UnitDict["Cost"]["population"])) + "" ret += "" for Bonus in UnitDict["AttackBonuses"]: ret += "[" for classe in UnitDict["AttackBonuses"][Bonus]["Classes"]: ret += classe + " " ret += ': ' + str(UnitDict["AttackBonuses"][Bonus]["Multiplier"]) + "] " ret += "" ret += "\n" return ret # Sort the templates dictionary. def SortFn(A): sortVal = 0 for classe in SortTypes: sortVal += 1 if classe in A[1]["Classes"]: break if ComparativeSortByChamp == True and A[0].find("champion") == -1: sortVal -= 20 if ComparativeSortByCav == True and A[0].find("cavalry") == -1: sortVal -= 10 if A[1]["Civ"] != None and A[1]["Civ"] in Civs: sortVal += 100 * Civs.index(A[1]["Civ"]) return sortVal # helper to write coloured text. def WriteColouredDiff(file, diff, PositOrNegat): def cleverParse(diff): if float(diff) - int(diff) < 0.001: return str(int(diff)) else: return str("%.1f" % float(diff)) if (PositOrNegat == "positive"): file.write(" 0 else "0,150,0")) + ");\">" + cleverParse(diff) + "") elif (PositOrNegat == "negative"): file.write("" + cleverParse(diff) + "") else: complain ############################################################ ############################################################ # Create the HTML file f = open(os.path.realpath(__file__).replace("unitTables.py","") + 'unit_summary_table.html', 'w') f.write("\n\n\n Unit Tables\n \n\n") htbout(f,"h1","Unit Summary Table") f.write("\n") os.chdir(basePath) ############################################################ # Load generic templates templates = {} htbout(f,"h2", "Units") f.write("\n") f.write("") f.write("\n") f.write("") f.write("\n\n") for template in list(glob.glob('template_*.xml')): if os.path.isfile(template): found = False for possParent in LoadTemplatesIfParent: if hasParentTemplate(template, possParent): found = True break if found == True: templates[template] = CalcUnit(template) f.write(WriteUnit(template, templates[template])) f.write("
HP BuildTime Speed(walk) Armour Attack (DPS) Costs Efficient Against
HPC HPCRateRangeSpread\n(/100m) FWSMP
") ############################################################ # Load Civ specific templates CivTemplates = {} for Civ in Civs: CivTemplates[Civ] = {} # Load all templates that start with that civ indicator for template in list(glob.glob('units/' + Civ + '_*.xml')): if os.path.isfile(template): # filter based on FilterOut breakIt = False for filter in FilterOut: if template.find(filter) != -1: breakIt = True if breakIt: continue # filter based on loaded generic templates breakIt = True for possParent in LoadTemplatesIfParent: if hasParentTemplate(template, possParent): breakIt = False break if breakIt: continue unit = CalcUnit(template) # Remove variants for now if unit["Parent"].find("template_") == -1: continue # load template CivTemplates[Civ][template] = unit ############################################################ f.write("\n\n

Units Specializations

\n") f.write("

This table compares each template to its parent, showing the differences between the two.
Note that like any table, you can copy/paste this in Excel (or Numbers or ...) and sort it.

") TemplatesByParent = {} #Get them in the array for Civ in Civs: for CivUnitTemplate in CivTemplates[Civ]: parent = CivTemplates[Civ][CivUnitTemplate]["Parent"] if parent in templates and templates[parent]["Civ"] == None: if parent not in TemplatesByParent: TemplatesByParent[parent] = [] TemplatesByParent[parent].append( (CivUnitTemplate,CivTemplates[Civ][CivUnitTemplate])) #Sort them by civ and write them in a table. f.write("\n") f.write("") f.write("\n") f.write("") f.write("\n") for parent in TemplatesByParent: TemplatesByParent[parent].sort(key=lambda x : Civs.index(x[1]["Civ"])) for tp in TemplatesByParent[parent]: f.write("") f.write("") # HP diff = int(tp[1]["HP"]) - int(templates[parent]["HP"]) WriteColouredDiff(f, diff, "negative") # Build Time diff = int(tp[1]["BuildTime"]) - int(templates[parent]["BuildTime"]) WriteColouredDiff(f, diff, "positive") # walk speed diff = float(tp[1]["WalkSpeed"]) - float(templates[parent]["WalkSpeed"]) WriteColouredDiff(f, diff, "negative") # Armor for atype in AttackTypes: diff = float(tp[1]["Armour"][atype]) - float(templates[parent]["Armour"][atype]) WriteColouredDiff(f, diff, "negative") # Attack types (DPS) and rate. attType = ("Ranged" if tp[1]["Ranged"] == True else "Melee") if tp[1]["RepeatRate"][attType] != "0": for atype in AttackTypes: myDPS = float(tp[1]["Attack"][attType][atype]) / (float(tp[1]["RepeatRate"][attType])/1000.0) parentDPS = float(templates[parent]["Attack"][attType][atype]) / (float(templates[parent]["RepeatRate"][attType])/1000.0) WriteColouredDiff(f, myDPS - parentDPS, "negative") WriteColouredDiff(f, float(tp[1]["RepeatRate"][attType])/1000.0 - float(templates[parent]["RepeatRate"][attType])/1000.0, "negative") # range and spread if tp[1]["Ranged"] == True: WriteColouredDiff(f, float(tp[1]["Range"]) - float(templates[parent]["Range"]), "negative") mySpread = float(tp[1]["Spread"]) parentSpread = float(templates[parent]["Spread"]) WriteColouredDiff(f, mySpread - parentSpread, "positive") else: f.write("") else: f.write("") for rtype in Resources: WriteColouredDiff(f, float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype]), "positive") WriteColouredDiff(f, float(tp[1]["Cost"]["population"]) - float(templates[parent]["Cost"]["population"]), "positive") f.write("") f.write("\n") f.write("
HP BuildTime Speed Armour Attack Costs Civ
HPC HPCRateRangeSpread FWSMP
" + parent.replace(".xml","").replace("template_","") + "" + tp[0].replace(".xml","").replace("units/","") + "" + tp[1]["Civ"] + "
") # Table of unit having or not having some units. f.write("\n\n

Roster Variety

\n") f.write("

This table show which civilizations have units who derive from each loaded generic template.
Green means 1 deriving unit, blue means 2, black means 3 or more.
The total is the total number of loaded units for this civ, which may be more than the total of units inheriting from loaded templates.

") f.write("
\n") f.write("\n") for civ in Civs: f.write("\n") f.write("\n") sortedDict = sorted(templates.items(), key=SortFn) for tp in sortedDict: if tp[0] not in TemplatesByParent: continue f.write("\n") for civ in Civs: found = 0 for temp in TemplatesByParent[tp[0]]: if temp[1]["Civ"] == civ: found += 1 if found == 1: f.write("") elif found == 2: f.write("") elif found >= 3: f.write("") else: f.write("") f.write("\n") f.write("\n") for civ in Civs: count = 0 for units in CivTemplates[civ]: count += 1 f.write("\n") f.write("\n") f.write("
Template" + civ + "
" + tp[0] +"
Total:" + str(count) + "
") # Add a simple script to allow filtering on sorting directly in the HTML page. if AddSortingOverlay: f.write("\n\ \n\ \n") f.write("\n") Index: ps/trunk/source/ps/Profiler2GPU.h =================================================================== --- ps/trunk/source/ps/Profiler2GPU.h (revision 19898) +++ ps/trunk/source/ps/Profiler2GPU.h (revision 19899) @@ -1,50 +1,50 @@ -/* Copyright (c) 2011 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 * "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. */ class CProfiler2; class CProfiler2GPU_ARB_timer_query; class CProfiler2GPU_EXT_timer_query; class CProfiler2GPU_INTEL_performance_queries; /** * Used by CProfiler2 for GPU profiling support. */ class CProfiler2GPU { NONCOPYABLE(CProfiler2GPU); public: CProfiler2GPU(CProfiler2& profiler); ~CProfiler2GPU(); void FrameStart(); void FrameEnd(); void RegionEnter(const char* id); void RegionLeave(const char* id); private: CProfiler2& m_Profiler; CProfiler2GPU_ARB_timer_query* m_ProfilerARB; CProfiler2GPU_EXT_timer_query* m_ProfilerEXT; CProfiler2GPU_INTEL_performance_queries* m_ProfilerINTEL; }; Index: ps/trunk/source/tools/profiler2/Profiler2Report.js =================================================================== --- ps/trunk/source/tools/profiler2/Profiler2Report.js (revision 19898) +++ ps/trunk/source/tools/profiler2/Profiler2Report.js (revision 19899) @@ -1,426 +1,426 @@ -// Copyright (c) 2016 Wildfire Games +// Copyright (C) 2016 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. // Profiler2Report module // Create one instance per profiler report you wish to open. // This gives you the interface to access the raw and processed data var Profiler2Report = function(callback, tryLive, file) { var outInterface = {}; // Item types returned by the engine var ITEM_EVENT = 1; var ITEM_ENTER = 2; var ITEM_LEAVE = 3; var ITEM_ATTRIBUTE = 4; var g_used_colours = {}; var g_raw_data; var g_data; function refresh(callback, tryLive, file) { if (tryLive) refresh_live(callback, file); else refresh_jsonp(callback, file); } outInterface.refresh = refresh; function refresh_jsonp(callback, source) { if (!source) { callback(false); return } var reader = new FileReader(); reader.onload = function(e) { refresh_from_jsonp(callback, e.target.result); } reader.onerror = function(e) { alert("Failed to load report file"); callback(false); return; } reader.readAsText(source); } function refresh_from_jsonp(callback, content) { var script = document.createElement('script'); window.profileDataCB = function(data) { script.parentNode.removeChild(script); var threads = []; data.threads.forEach(function(thread) { var canvas = $(''); threads.push({'name': thread.name, 'data': { 'events': concat_events(thread.data) }, 'canvas': canvas.get(0)}); }); g_raw_data = { 'threads': threads }; compute_data(); callback(true); }; script.innerHTML = content; document.body.appendChild(script); } function refresh_live(callback, file) { $.ajax({ url: 'http://127.0.0.1:8000/overview', dataType: 'json', success: function (data) { var threads = []; data.threads.forEach(function(thread) { threads.push({'name': thread.name}); }); var callback_data = { 'threads': threads, 'completed': 0 }; threads.forEach(function(thread) { refresh_thread(callback, thread, callback_data); }); }, error: function (jqXHR, textStatus, errorThrown) { console.log('Failed to connect to server ("'+textStatus+'")'); callback(false); } }); } function refresh_thread(callback, thread, callback_data) { $.ajax({ url: 'http://127.0.0.1:8000/query', dataType: 'json', data: { 'thread': thread.name }, success: function (data) { data.events = concat_events(data); thread.data = data; if (++callback_data.completed == callback_data.threads.length) { g_raw_data = { 'threads': callback_data.threads }; compute_data(); callback(true); } }, error: function (jqXHR, textStatus, errorThrown) { alert('Failed to connect to server ("'+textStatus+'")'); } }); } function compute_data(range) { g_data = { "threads" : [] }; g_data_by_frame = { "threads" : [] }; for (let thread = 0; thread < g_raw_data.threads.length; thread++) { let processed_data = process_raw_data(g_raw_data.threads[thread].data.events, range ); if (!processed_data.intervals.length && !processed_data.events.length) continue; g_data.threads[thread] = processed_data; g_data.threads[thread].intervals_by_type_frame = {}; if (!g_data.threads[thread].frames.length) continue // compute intervals by types and frames if there are frames. for (let type in g_data.threads[thread].intervals_by_type) { let current_frame = 0; g_data.threads[thread].intervals_by_type_frame[type] = [[]]; for (let i = 0; i < g_data.threads[thread].intervals_by_type[type].length;i++) { let event = g_data.threads[thread].intervals[g_data.threads[thread].intervals_by_type[type][i]]; while (current_frame < g_data.threads[thread].frames.length && event.t0 > g_data.threads[thread].frames[current_frame].t1) { g_data.threads[thread].intervals_by_type_frame[type].push([]); current_frame++; } if (current_frame < g_data.threads[thread].frames.length) g_data.threads[thread].intervals_by_type_frame[type][current_frame].push(g_data.threads[thread].intervals_by_type[type][i]); } } }; } function process_raw_data(data, range) { if (!data.length) return { 'frames': [], 'events': [], 'intervals': [], 'intervals_by_type' : {}, 'tmin': 0, 'tmax': 0 }; var start, end; var tmin, tmax; var frames = []; var last_frame_time_start = undefined; var last_frame_time_end = undefined; var stack = []; for (var i = 0; i < data.length; ++i) { if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') { if (last_frame_time_end) frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end}); last_frame_time_start = data[i][1]; } if (data[i][0] == ITEM_ENTER) stack.push(data[i][2]); if (data[i][0] == ITEM_LEAVE) { if (stack[stack.length-1] == 'frame') last_frame_time_end = data[i][1]; stack.pop(); } } if(!range) { range = { "tmin" : data[0][1], "tmax" : data[data.length-1][1] }; } if (range.numframes) { for (var i = data.length - 1; i > 0; --i) { if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') { end = i; break; } } var framesfound = 0; for (var i = end - 1; i > 0; --i) { if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') { start = i; if (++framesfound == range.numframes) break; } } tmin = data[start][1]; tmax = data[end][1]; } else if (range.seconds) { var end = data.length - 1; for (var i = end; i > 0; --i) { var type = data[i][0]; if (type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) { tmax = data[i][1]; break; } } tmin = tmax - range.seconds; for (var i = end; i > 0; --i) { var type = data[i][0]; if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) break; start = i; } } else { start = 0; end = data.length - 1; tmin = range.tmin; tmax = range.tmax; for (var i = data.length-1; i > 0; --i) { var type = data[i][0]; if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmax) { end = i; break; } } for (var i = end; i > 0; --i) { var type = data[i][0]; if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) break; start = i; } // Move the start/end outwards by another frame, so we don't lose data at the edges while (start > 0) { --start; if (data[start][0] == ITEM_EVENT && data[start][2] == '__framestart') break; } while (end < data.length-1) { ++end; if (data[end][0] == ITEM_EVENT && data[end][2] == '__framestart') break; } } var num_colours = 0; var events = []; // Read events for the entire data period (not just start..end) var lastWasEvent = false; for (var i = 0; i < data.length; ++i) { if (data[i][0] == ITEM_EVENT) { events.push({'t': data[i][1], 'id': data[i][2]}); lastWasEvent = true; } else if (data[i][0] == ITEM_ATTRIBUTE) { if (lastWasEvent) { if (!events[events.length-1].attrs) events[events.length-1].attrs = []; events[events.length-1].attrs.push(data[i][1]); } } else { lastWasEvent = false; } } var intervals = []; var intervals_by_type = {}; // Read intervals from the focused data period (start..end) stack = []; var lastT = 0; var lastWasEvent = false; for (var i = start; i <= end; ++i) { if (data[i][0] == ITEM_EVENT) { // if (data[i][1] < lastT) // console.log('Time went backwards: ' + (data[i][1] - lastT)); lastT = data[i][1]; lastWasEvent = true; } else if (data[i][0] == ITEM_ENTER) { // if (data[i][1] < lastT) // console.log('Time - ENTER went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); stack.push({'t0': data[i][1], 'id': data[i][2]}); lastT = data[i][1]; lastWasEvent = false; } else if (data[i][0] == ITEM_LEAVE) { // if (data[i][1] < lastT) // console.log('Time - LEAVE went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i])); lastT = data[i][1]; lastWasEvent = false; if (!stack.length) continue; var interval = stack.pop(); if (!g_used_colours[interval.id]) g_used_colours[interval.id] = new_colour(num_colours++); interval.colour = g_used_colours[interval.id]; interval.t1 = data[i][1]; interval.duration = interval.t1 - interval.t0; interval.depth = stack.length; //console.log(JSON.stringify(interval)); intervals.push(interval); if (interval.id in intervals_by_type) intervals_by_type[interval.id].push(intervals.length-1); else intervals_by_type[interval.id] = [intervals.length-1]; if (interval.id == "Script" && interval.attrs && interval.attrs.length) { let curT = interval.t0; for (let subItem in interval.attrs) { let sub = interval.attrs[subItem]; if (sub.search("buffer") != -1) continue; let newInterv = {}; newInterv.t0 = curT; newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000; if (!newInterv.duration) continue; newInterv.t1 = curT + newInterv.duration; curT += newInterv.duration; newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1"); newInterv.colour = g_used_colours[interval.id]; newInterv.depth = interval.depth+1; intervals.push(newInterv); if (newInterv.id in intervals_by_type) intervals_by_type[newInterv.id].push(intervals.length-1); else intervals_by_type[newInterv.id] = [intervals.length-1]; } } } else if (data[i][0] == ITEM_ATTRIBUTE) { if (!lastWasEvent && stack.length) { if (!stack[stack.length-1].attrs) stack[stack.length-1].attrs = []; stack[stack.length-1].attrs.push(data[i][1]); } } } return { 'frames': frames, 'events': events, 'intervals': intervals, 'intervals_by_type' : intervals_by_type, 'tmin': tmin, 'tmax': tmax }; } outInterface.data = function() { return g_data; }; outInterface.raw_data = function() { return g_raw_data; }; outInterface.data_by_frame = function() { return g_data_by_frame; }; refresh(callback, tryLive, file); return outInterface; }; Index: ps/trunk/source/tools/springimport/convert.pl =================================================================== --- ps/trunk/source/tools/springimport/convert.pl (revision 19898) +++ ps/trunk/source/tools/springimport/convert.pl (revision 19899) @@ -1,507 +1,507 @@ -# Copyright (c) 2010 Wildfire Games +# 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 is a fairly hacky converter from the .3do format used by Spring # to the Collada + actor XML format used by 0 A.D. use strict; use warnings; use Data::Dumper; use TextureAtlas; use SpringPalette; use constant SCALE => 3.0 / 1048576; my $ROOT = "../../../binaries/data/mods/public"; my $SOURCE = "../../../../../misc/spring/BA712"; sub parse_3do { my ($fh) = @_; my $b; read $fh, $b, 13*4 or die; my ($sig, $num_verts, $num_prims, $sel_prim, $x, $y, $z, $off_name, undef, $off_verts, $off_prims, $off_sib, $off_child) = unpack 'l*', $b; die unless $sig == 1; seek $fh, $off_name, 0; my $name = ''; while (1) { read $fh, my $c, 1 or die; last if $c eq "\0"; $name .= $c; } seek $fh, $off_verts, 0; read $fh, $b, 12*$num_verts or die; my @verts = unpack 'l*', $b; my @prims; for my $p (0..$num_prims-1) { seek $fh, $off_prims + 32*$p, 0; read $fh, $b, 32 or die; my ($palette, $num_idxs, undef, $off_idxs, $off_tex) = unpack 'l*', $b; seek $fh, $off_idxs, 0; read $fh, $b, 2*$num_idxs or die; my @idxs = unpack 's*', $b; my $texture; if ($off_tex) { $texture = ''; seek $fh, $off_tex, 0; while (1) { read $fh, my $c, 1 or die; last if $c eq "\0"; $texture .= $c; } } push @prims, { palette => $palette, idxs => \@idxs, texture => $texture, }; } my $ret = { name => $name, verts => \@verts, prims => \@prims, offset => [$x, $y, $z], }; if ($off_child) { seek $fh, $off_child, 0; $ret->{children} = [ parse_3do($fh) ]; } if ($off_sib) { seek $fh, $off_sib, 0; return ($ret, parse_3do($fh)); } else { return ($ret); } } sub write_geometries { my ($obj, $atlas, $name, $fh) = @_; my $num_verts = @{ $obj->{verts} } / 3; my $num_prims = @{ $obj->{prims} }; my @vertex_normals; for (0..$num_verts-1) { push @vertex_normals, [0,0,0]; } my @prim_normals; for my $prim (@{ $obj->{prims} }) { my @v0 = ($obj->{verts}[$prim->{idxs}[0]*3], $obj->{verts}[$prim->{idxs}[0]*3+1], $obj->{verts}[$prim->{idxs}[0]*3+2]); my @v1 = ($obj->{verts}[$prim->{idxs}[1]*3], $obj->{verts}[$prim->{idxs}[1]*3+1], $obj->{verts}[$prim->{idxs}[1]*3+2]); my @v2 = ($obj->{verts}[$prim->{idxs}[2]*3], $obj->{verts}[$prim->{idxs}[2]*3+1], $obj->{verts}[$prim->{idxs}[2]*3+2]); my @d10 = ($v1[0] - $v0[0], $v1[1] - $v0[1], $v1[2] - $v0[2]); my @d20 = ($v2[0] - $v0[0], $v2[1] - $v0[1], $v2[2] - $v0[2]); my @x = ($d10[1]*$d20[2] - $d10[2]*$d20[1], $d10[2]*$d20[0] - $d10[0]*$d20[2], $d10[0]*$d20[1] - $d10[1]*$d20[0]); my $d = sqrt($x[0]*$x[0] + $x[1]*$x[1] + $x[2]*$x[2]); my @n = $d ? ($x[0]/$d, $x[1]/$d, $x[2]/$d) : (0,1,0); push @prim_normals, \@n; for my $v (@{ $prim->{idxs} }) { $vertex_normals[$v][0] += $n[0]; $vertex_normals[$v][1] += $n[1]; $vertex_normals[$v][2] += $n[2]; } } for (0..$num_verts-1) { my @n = @{ $vertex_normals[$_] }; my $d = sqrt($n[0]*$n[0] + $n[1]*$n[1] + $n[2]*$n[2]); $vertex_normals[$_] = $d ? [$n[0]/$d, $n[1]/$d, $n[2]/$d] : [0,0,0]; } print $fh < EOF for my $n (0..$num_verts-1) { printf $fh "%.6f %.6f %.6f\n", $obj->{verts}[$n*3]*SCALE, $obj->{verts}[$n*3+1]*SCALE, $obj->{verts}[$n*3+2]*SCALE; } print $fh < EOF for my $n (0..$num_verts-1) { printf $fh "%.6f %.6f %.6f\n", @{$vertex_normals[$n]}; } my @uvs; for my $prim (@{ $obj->{prims} }) { my ($u0,$v0, $u1,$v1) = $atlas->get_texcoords(texture_filename($prim)); if (@{ $prim->{idxs} } == 3) { push @uvs, $u0,$v0, $u0,$v1, $u1,$v1; } elsif (@{ $prim->{idxs} } == 4) { push @uvs, $u0,$v0, $u0,$v1, $u1,$v1, $u1,$v0; } else { push @uvs, ($u1,$v0) x @{ $prim->{idxs} }; } } my $num_uvs = @uvs/2; print $fh < @uvs EOF my $i = 0; my $j = 0; for my $prim (@{ $obj->{prims} }) { print $fh "

"; for (@{ $prim->{idxs} }) { print $fh "$_ $_ $j "; ++$j; } ++$i; print $fh "

\n"; } print $fh <
EOF } sub write_mesh { my ($obj, $atlas, $name, $fh) = @_; print $fh < Y_UP EOF write_geometries($obj, $atlas, $name, $fh); print $fh < EOF } sub write_joint { my ($joint, $fh, $indent) = @_; print $fh qq[$indent\n]; print $fh qq[$indent ] . (sprintf "%.6f %.6f %.6f", map $_*SCALE, $joint->{offset}[0], $joint->{offset}[1], -$joint->{offset}[2]) . qq[\n]; for my $c (@{ $joint->{children} }) { write_joint($c, $fh, "$indent "); } print $fh qq[$indent\n]; } sub write_skeleton { my ($skeleton, $rootobj, $atlas, $name, $fh) = @_; print $fh < Y_UP EOF write_geometries($rootobj, $atlas, $name, $fh); print $fh < 0 1.0 0 180 EOF write_joint($_, $fh, " ") for @$skeleton; print $fh < EOF } sub extract_joint_names { my ($joint) = @_; return ($joint->{name}, map { extract_joint_names($_) } @{ $joint->{children} }); } sub texture_filename { my ($prim) = @_; if ($prim->{texture}) { my $base = "$prim->{texture}"; my @files = ("${base}.tga", "${base}00.tga", "${base}00.bmp", "${base}00.BMP"); push @files, map { lc $_ } @files; @files = grep { -e "$SOURCE/unittextures/tatex/$_" } @files; die "can't find $prim->{texture}" unless @files; return "$SOURCE/unittextures/tatex/$files[0]"; } else { return SpringPalette::get_image($prim->{palette}); } } sub write_skeleton_actor { my ($skeleton, $name, $fh) = @_; print $fh < spring/$name.dae spring/$name.tga EOF my @joints = map { extract_joint_names($_) } @$skeleton; for my $n (@joints[1..$#joints]) { print $fh qq[ \n]; } print $fh < player_trans.xml EOF } sub write_mesh_actor { my ($mesh, $prefix, $name, $fh) = @_; #warn Dumper $mesh; print $fh < spring/$name.dae spring/$prefix.tga player_trans.xml EOF } sub write_entity { my ($prefix, $fh) = @_; print $fh < hele $prefix ??? 9.0 spring/$prefix.xml EOF if ($prefix =~ /^(armaas|armbats|corcrus)$/) { print $fh < true EOF } print $fh < EOF } sub extract_textures { my ($obj, $atlas) = @_; my @meshes; my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} }; for (@prims) { $atlas->add(texture_filename($_)); } extract_textures($_, $atlas) for @{ $obj->{children} }; } sub extract_meshes { my ($obj) = @_; my @prims = grep { @{ $_->{idxs} } >= 3 } @{ $obj->{prims} }; my @meshes; push @meshes, { name => $obj->{name}, verts => $obj->{verts}, prims => \@prims }; push @meshes, map { extract_meshes($_) } @{ $obj->{children} }; return @meshes; } sub extract_skeleton { my ($obj) = @_; return { name => $obj->{name}, offset => $obj->{offset}, children => [ map { extract_skeleton($_) } @{ $obj->{children} } ] }; } sub write_model { my ($prefix, $model) = @_; my $atlas = new TextureAtlas(256); extract_textures($model, $atlas); $atlas->finish("$ROOT/art/textures/skins/spring/$prefix.tga"); my @meshes = extract_meshes($model); #print Dumper \@meshes; #exit; my @skeleton = extract_skeleton($model); #print Dumper \@skeleton; { open my $fhm, '>', "$ROOT/art/meshes/spring/$prefix.dae" or die $!; write_skeleton(\@skeleton, $meshes[0], $atlas, $prefix, $fhm); open my $fha, '>', "$ROOT/art/actors/spring/$prefix.xml" or die $!; write_skeleton_actor(\@skeleton, $prefix, $fha); } for my $mesh (@meshes[1..$#meshes]) { my $name = $prefix.'-'.$mesh->{name}; open my $fhm, '>', "$ROOT/art/meshes/spring/$name.dae" or die $!; write_mesh($mesh, $atlas, $name, $fhm); open my $fha, '>', "$ROOT/art/actors/spring/$name.xml" or die $!; write_mesh_actor($mesh, $prefix, $name, $fha); } { open my $fhe, '>', "$ROOT/simulation/templates/spring/$prefix.xml" or die $!; write_entity($prefix, $fhe); } } #for my $n (qw(armpw armcom KrogTaar armlab corak corlab)) { for my $n (map { (/.*\/(.*)\.3do/)[0] } grep /\/(cor|arm)[a-z]+\./, <$SOURCE/Objects3d/*.3do>) { print "$n\n"; open my $t, "$SOURCE/Objects3d/$n.3do" or die "$n: $!"; binmode $t; my $model = parse_3do($t); #print Dumper $model; write_model($n, $model); } Index: ps/trunk/source/ps/Profiler2.h =================================================================== --- ps/trunk/source/ps/Profiler2.h (revision 19898) +++ ps/trunk/source/ps/Profiler2.h (revision 19899) @@ -1,556 +1,556 @@ -/* Copyright (c) 2016 Wildfire Games +/* Copyright (C) 2016 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. */ /** * @file * New profiler (complementing the older CProfileManager) * * The profiler is designed for analysing framerate fluctuations or glitches, * and temporal relationships between threads. * This contrasts with CProfilerManager and most external profiling tools, * which are designed more for measuring average throughput over a number of * frames. * * To view the profiler output, press F11 to enable the HTTP output mode * and then open source/tools/profiler2/profiler2.html in a web browser. * * There is a single global CProfiler2 instance (g_Profiler2), * providing the API used by the rest of the game. * The game can record the entry/exit timings of a region of code * using the PROFILE2 macro, and can record other events using * PROFILE2_EVENT. * Regions and events can be annotated with arbitrary string attributes, * specified with printf-style format strings, using PROFILE2_ATTR * (e.g. PROFILE2_ATTR("frame: %d", m_FrameNum) ). * * This is designed for relatively coarse-grained profiling, or for rare events. * Don't use it for regions that are typically less than ~0.1msecs, or that are * called hundreds of times per frame. (The old CProfilerManager is better * for that.) * * New threads must call g_Profiler2.RegisterCurrentThread before any other * profiler functions. * * The main thread should call g_Profiler2.RecordFrameStart at the start of * each frame. * Other threads should call g_Profiler2.RecordSyncMarker occasionally, * especially if it's been a long time since their last call to the profiler, * or if they've made thousands of calls since the last sync marker. * * The profiler is implemented with thread-local fixed-size ring buffers, * which store a sequence of variable-length items indicating the time * of the event and associated data (pointers to names, attribute strings, etc). * An HTTP server provides access to the data: when requested, it will make * a copy of a thread's buffer, then parse the items and return them in JSON * format. The profiler2.html requests and processes and visualises this data. * * The RecordSyncMarker calls are necessary to correct for time drift and to * let the buffer parser accurately detect the start of an item in the byte stream. * * This design aims to minimise the performance overhead of recording data, * and to simplify the visualisation of the data by doing it externally in an * environment with better UI tools (i.e. HTML) instead of within the game engine. * * The initial setup of g_Profiler2 must happen in the game's main thread. * RegisterCurrentThread and the Record functions may be called from any thread. * The HTTP server runs its own threads, which may call the ConstructJSON functions. */ #ifndef INCLUDED_PROFILER2 #define INCLUDED_PROFILER2 #include "lib/timer.h" #include "ps/ThreadUtil.h" struct mg_context; // Note: Lots of functions are defined inline, to hypothetically // minimise performance overhead. class CProfiler2GPU; class CProfiler2 { friend class CProfiler2GPU_base; friend class CProfile2SpikeRegion; friend class CProfile2AggregatedRegion; public: // Items stored in the buffers: /// Item type identifiers enum EItem { ITEM_NOP = 0, ITEM_SYNC = 1, // magic value used for parse syncing ITEM_EVENT = 2, // single event ITEM_ENTER = 3, // entering a region ITEM_LEAVE = 4, // leaving a region (must be correctly nested) ITEM_ATTRIBUTE = 5, // arbitrary string associated with current region, or latest event (if the previous item was an event) }; static const size_t MAX_ATTRIBUTE_LENGTH = 256; // includes null terminator, which isn't stored /// An arbitrary number to help resyncing with the item stream when parsing. static const u8 RESYNC_MAGIC[8]; /** * An item with a relative time and an ID string pointer. */ struct SItem_dt_id { float dt; // time relative to last event const char* id; }; private: // TODO: what's a good size? // TODO: different threads might want different sizes static const size_t BUFFER_SIZE = 4*1024*1024; static const size_t HOLD_BUFFER_SIZE = 128 * 1024; /** * Class instantiated in every registered thread. */ class ThreadStorage { NONCOPYABLE(ThreadStorage); public: ThreadStorage(CProfiler2& profiler, const std::string& name); ~ThreadStorage(); enum { BUFFER_NORMAL, BUFFER_SPIKE, BUFFER_AGGREGATE }; void RecordSyncMarker(double t) { // Store the magic string followed by the absolute time // (to correct for drift caused by the precision of relative // times stored in other items) u8 buffer[sizeof(RESYNC_MAGIC) + sizeof(t)]; memcpy(buffer, &RESYNC_MAGIC, sizeof(RESYNC_MAGIC)); memcpy(buffer + sizeof(RESYNC_MAGIC), &t, sizeof(t)); Write(ITEM_SYNC, buffer, ARRAY_SIZE(buffer)); m_LastTime = t; } void Record(EItem type, double t, const char* id) { // Store a relative time instead of absolute, so we can use floats // (to save memory) without suffering from precision problems SItem_dt_id item = { (float)(t - m_LastTime), id }; Write(type, &item, sizeof(item)); } void RecordFrameStart(double t) { RecordSyncMarker(t); Record(ITEM_EVENT, t, "__framestart"); // magic string recognised by the visualiser } void RecordLeave(double t) { float time = (float)(t - m_LastTime); Write(ITEM_LEAVE, &time, sizeof(float)); } void RecordAttribute(const char* fmt, va_list argp) VPRINTF_ARGS(2); void RecordAttributePrintf(const char* fmt, ...) PRINTF_ARGS(2) { va_list argp; va_start(argp, fmt); RecordAttribute(fmt, argp); va_end(argp); } size_t HoldLevel(); u8 HoldType(); void PutOnHold(u8 type); void HoldToBuffer(bool condensed); void ThrowawayHoldBuffer(); CProfiler2& GetProfiler() { return m_Profiler; } const std::string& GetName() { return m_Name; } /** * Returns a copy of a subset of the thread's buffer. * Not guaranteed to start on an item boundary. * May be called by any thread. */ std::string GetBuffer(); private: /** * Store an item into the buffer. */ void Write(EItem type, const void* item, u32 itemSize); void WriteHold(EItem type, const void* item, u32 itemSize); CProfiler2& m_Profiler; std::string m_Name; double m_LastTime; // used for computing relative times u8* m_Buffer; struct HoldBuffer { friend class ThreadStorage; public: HoldBuffer() { buffer = new u8[HOLD_BUFFER_SIZE]; memset(buffer, ITEM_NOP, HOLD_BUFFER_SIZE); pos = 0; } ~HoldBuffer() { delete[] buffer; } void clear() { pos = 0; } void setType(u8 newType) { type = newType; } u8* buffer; u32 pos; u8 type; }; HoldBuffer m_HoldBuffers[8]; size_t m_HeldDepth; // To allow hopefully-safe reading of the buffer from a separate thread, // without any expensive synchronisation in the recording thread, // two copies of the current buffer write position are stored. // BufferPos0 is updated before writing; BufferPos1 is updated after writing. // GetBuffer can read Pos1, copy the buffer, read Pos0, then assume any bytes // outside the range Pos1 <= x < Pos0 are safe to use. (Any in that range might // be half-written and corrupted.) (All ranges are modulo BUFFER_SIZE.) // Outside of Write(), these will always be equal. // // TODO: does this attempt at synchronisation (plus use of COMPILER_FENCE etc) // actually work in practice? u32 m_BufferPos0; u32 m_BufferPos1; }; public: CProfiler2(); ~CProfiler2(); /** * Call in main thread to set up the profiler, * before calling any other profiler functions. */ void Initialise(); /** * Call in main thread to enable the HTTP server. * (Disabled by default for security and performance * and to avoid annoying a firewall.) */ void EnableHTTP(); /** * Call in main thread to enable the GPU profiling support, * after OpenGL has been initialised. */ void EnableGPU(); /** * Call in main thread to shut down the GPU profiling support, * before shutting down OpenGL. */ void ShutdownGPU(); /** * Call in main thread to shut down the profiler's HTTP server */ void ShutDownHTTP(); /** * Call in main thread to enable/disable the profiler */ void Toggle(); /** * Call in main thread to shut everything down. * All other profiled threads should have been terminated already. */ void Shutdown(); /** * Call in any thread to enable the profiler in that thread. * @p name should be unique, and is used by the visualiser to identify * this thread. */ void RegisterCurrentThread(const std::string& name); /** * Non-main threads should call this occasionally, * especially if it's been a long time since their last call to the profiler, * or if they've made thousands of calls since the last sync marker. */ void RecordSyncMarker() { GetThreadStorage().RecordSyncMarker(GetTime()); } /** * Call in main thread at the start of a frame. */ void RecordFrameStart() { ENSURE(ThreadUtil::IsMainThread()); GetThreadStorage().RecordFrameStart(GetTime()); } void RecordEvent(const char* id) { GetThreadStorage().Record(ITEM_EVENT, GetTime(), id); } void RecordRegionEnter(const char* id) { GetThreadStorage().Record(ITEM_ENTER, GetTime(), id); } void RecordRegionEnter(const char* id, double time) { GetThreadStorage().Record(ITEM_ENTER, time, id); } void RecordRegionLeave() { GetThreadStorage().RecordLeave(GetTime()); } void RecordAttribute(const char* fmt, ...) PRINTF_ARGS(2) { va_list argp; va_start(argp, fmt); GetThreadStorage().RecordAttribute(fmt, argp); va_end(argp); } void RecordGPUFrameStart(); void RecordGPUFrameEnd(); void RecordGPURegionEnter(const char* id); void RecordGPURegionLeave(const char* id); /** * Hold onto messages until a call to release or write the held messages. */ size_t HoldLevel() { return GetThreadStorage().HoldLevel(); } u8 HoldType() { return GetThreadStorage().HoldType(); } void HoldMessages(u8 type) { GetThreadStorage().PutOnHold(type); } void StopHoldingMessages(bool writeToBuffer, bool condensed = false) { if (writeToBuffer) GetThreadStorage().HoldToBuffer(condensed); else GetThreadStorage().ThrowawayHoldBuffer(); } /** * Call in any thread to produce a JSON representation of the general * state of the application. */ void ConstructJSONOverview(std::ostream& stream); /** * Call in any thread to produce a JSON representation of the buffer * for a given thread. * Returns NULL on success, or an error string. */ const char* ConstructJSONResponse(std::ostream& stream, const std::string& thread); /** * Call in any thread to save a JSONP representation of the buffers * for all threads, to a file named profile2.jsonp in the logs directory. */ void SaveToFile(); double GetTime() { return timer_Time(); } int GetFrameNumber() { return m_FrameNumber; } void IncrementFrameNumber() { ++m_FrameNumber; } void AddThreadStorage(ThreadStorage* storage); void RemoveThreadStorage(ThreadStorage* storage); private: void InitialiseGPU(); static void TLSDtor(void* data); ThreadStorage& GetThreadStorage() { ThreadStorage* storage = (ThreadStorage*)pthread_getspecific(m_TLS); ASSERT(storage); return *storage; } bool m_Initialised; int m_FrameNumber; mg_context* m_MgContext; pthread_key_t m_TLS; CProfiler2GPU* m_GPU; CMutex m_Mutex; std::vector m_Threads; // thread-safe; protected by m_Mutex }; extern CProfiler2 g_Profiler2; /** * Scope-based enter/leave helper. */ class CProfile2Region { public: CProfile2Region(const char* name) : m_Name(name) { g_Profiler2.RecordRegionEnter(m_Name); } ~CProfile2Region() { g_Profiler2.RecordRegionLeave(); } protected: const char* m_Name; }; /** * Scope-based enter/leave helper. */ class CProfile2SpikeRegion { public: CProfile2SpikeRegion(const char* name, double spikeLimit); ~CProfile2SpikeRegion(); private: const char* m_Name; double m_Limit; double m_StartTime; bool m_PushedHold; }; /** * Scope-based enter/leave helper. */ class CProfile2AggregatedRegion { public: CProfile2AggregatedRegion(const char* name, double spikeLimit); ~CProfile2AggregatedRegion(); private: const char* m_Name; double m_Limit; double m_StartTime; bool m_PushedHold; }; /** * Scope-based GPU enter/leave helper. */ class CProfile2GPURegion { public: CProfile2GPURegion(const char* name) : m_Name(name) { g_Profiler2.RecordGPURegionEnter(m_Name); } ~CProfile2GPURegion() { g_Profiler2.RecordGPURegionLeave(m_Name); } private: const char* m_Name; }; /** * Starts timing from now until the end of the current scope. * @p region is the name to associate with this region (should be * a constant string literal; the pointer must remain valid forever). * Regions may be nested, but preferably shouldn't be nested deeply since * it hurts the visualisation. */ #define PROFILE2(region) CProfile2Region profile2__(region) #define PROFILE2_IFSPIKE(region, limit) CProfile2SpikeRegion profile2__(region, limit) #define PROFILE2_AGGREGATED(region, limit) CProfile2AggregatedRegion profile2__(region, limit) #define PROFILE2_GPU(region) CProfile2GPURegion profile2gpu__(region) /** * Record the named event at the current time. */ #define PROFILE2_EVENT(name) g_Profiler2.RecordEvent(name) /** * Associates a string (with printf-style formatting) with the current * region or event. * (If the last profiler call was PROFILE2_EVENT, it associates with that * event; otherwise it associates with the currently-active region.) */ #define PROFILE2_ATTR g_Profiler2.RecordAttribute #endif // INCLUDED_PROFILER2 Index: ps/trunk/source/tools/i18n/extractors/extractors.py =================================================================== --- ps/trunk/source/tools/i18n/extractors/extractors.py (revision 19898) +++ ps/trunk/source/tools/i18n/extractors/extractors.py (revision 19899) @@ -1,479 +1,479 @@ # -*- coding:utf-8 -*- # -# Copyright (C) 2016 Wildfire Games +# Copyright (C) 2016 Wildfire Games. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the # following conditions are met: # # Redistributions of source code must retain the above copyright notice, this list of conditions and the following # disclaimer. # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided with the distribution. # The name of the author may not be used to endorse or promote products derived from this software without specific # prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, division, print_function, unicode_literals import codecs, re, os, sys import json as jsonParser from tokenize import generate_tokens, COMMENT, NAME, OP, STRING from textwrap import dedent def pathmatch(mask, path): """ Matches paths to a mask, where the mask supports * and **. Paths use / as the separator * matches a sequence of characters without /. ** matches a sequence of characters without / followed by a / and sequence of characters without / :return: true iff path matches the mask, false otherwise """ s = re.split(r"([*][*]?)", mask) p = "" for i in xrange(len(s)): if i % 2 != 0: p = p + "[^/]+" if len(s[i]) == 2: p = p + "(/[^/]+)*" else: p = p + re.escape(s[i]) p = p + "$" return re.match(p, path) != None class Extractor(object): def __init__(self, directoryPath, filemasks, options): self.directoryPath = directoryPath self.options = options if isinstance(filemasks, dict): self.includeMasks = filemasks["includeMasks"] self.excludeMasks = filemasks["excludeMasks"] else: self.includeMasks = filemasks self.excludeMasks = [] def run(self): """ Extracts messages. :return: An iterator over ``(message, plural, context, (location, pos), comment)`` tuples. :rtype: ``iterator`` """ directoryAbsolutePath = os.path.abspath(self.directoryPath) for root, folders, filenames in os.walk(directoryAbsolutePath): for subdir in folders: if subdir.startswith('.') or subdir.startswith('_'): folders.remove(subdir) folders.sort() filenames.sort() for filename in filenames: filename = os.path.relpath(os.path.join(root, filename), self.directoryPath).replace(os.sep, '/') for filemask in self.excludeMasks: if pathmatch(filemask, filename): break else: for filemask in self.includeMasks: if pathmatch(filemask, filename): filepath = os.path.join(directoryAbsolutePath, filename) for message, plural, context, breadcrumb, position, comments in self.extractFromFile(filepath): # Replace spaces in filenames by non-breaking spaces so that word # wrapping in po files does not split up our paths yield message, plural, context, (filename.replace(' ', u"\xa0") + (":"+breadcrumb if breadcrumb else ""), position), comments def extractFromFile(self, filepath): """ Extracts messages from a specific file. :return: An iterator over ``(message, plural, context, breadcrumb, position, comments)`` tuples. :rtype: ``iterator`` """ pass class javascript(Extractor): """ Extract messages from JavaScript source code. """ empty_msgid_warning = ( '%s: warning: Empty msgid. It is reserved by GNU gettext: gettext("") ' 'returns the header entry with meta information, not the empty string.' ) def extractJavascriptFromFile(self, fileObject): from extractors.jslexer import tokenize, unquote_string funcname = message_lineno = None messages = [] last_argument = None translator_comments = [] concatenate_next = False last_token = None call_stack = -1 comment_tags = self.options.get('commentTags', []) keywords = self.options.get('keywords', {}).keys() for token in tokenize(fileObject.read()): if token.type == 'operator' and token.value == '(': if funcname: message_lineno = token.lineno call_stack += 1 elif call_stack == -1 and token.type == 'linecomment': value = token.value[2:].strip() if translator_comments and \ translator_comments[-1][0] == token.lineno - 1: translator_comments.append((token.lineno, value)) continue for comment_tag in comment_tags: if value.startswith(comment_tag): translator_comments.append((token.lineno, value.strip())) break elif token.type == 'multilinecomment': # only one multi-line comment may preceed a translation translator_comments = [] value = token.value[2:-2].strip() for comment_tag in comment_tags: if value.startswith(comment_tag): lines = value.splitlines() if lines: lines[0] = lines[0].strip() lines[1:] = dedent('\n'.join(lines[1:])).splitlines() for offset, line in enumerate(lines): translator_comments.append((token.lineno + offset, line)) break elif funcname and call_stack == 0: if token.type == 'operator' and token.value == ')': if last_argument is not None: messages.append(last_argument) if len(messages) > 1: messages = tuple(messages) elif messages: messages = messages[0] else: messages = None # Comments don't apply unless they immediately precede the # message if translator_comments and \ translator_comments[-1][0] < message_lineno - 1: translator_comments = [] if messages is not None: yield (message_lineno, funcname, messages, [comment[1] for comment in translator_comments]) funcname = message_lineno = last_argument = None concatenate_next = False translator_comments = [] messages = [] call_stack = -1 elif token.type == 'string': new_value = unquote_string(token.value) if concatenate_next: last_argument = (last_argument or '') + new_value concatenate_next = False else: last_argument = new_value elif token.type == 'operator': if token.value == ',': if last_argument is not None: messages.append(last_argument) last_argument = None else: messages.append(None) concatenate_next = False elif token.value == '+': concatenate_next = True elif call_stack > 0 and token.type == 'operator' \ and token.value == ')': call_stack -= 1 elif funcname and call_stack == -1: funcname = None elif call_stack == -1 and token.type == 'name' and \ token.value in keywords and \ (last_token is None or last_token.type != 'name' or last_token.value != 'function'): funcname = token.value last_token = token def extractFromFile(self, filepath): with codecs.open(filepath, 'r', encoding='utf-8-sig') as fileObject: for lineno, funcname, messages, comments in self.extractJavascriptFromFile(fileObject): if funcname: spec = self.options.get('keywords', {})[funcname] or (1,) else: spec = (1,) if not isinstance(messages, (list, tuple)): messages = [messages] if not messages: continue # Validate the messages against the keyword's specification context = None msgs = [] invalid = False # last_index is 1 based like the keyword spec last_index = len(messages) for index in spec: if isinstance(index, (list, tuple)): context = messages[index[0] - 1] continue if last_index < index: # Not enough arguments invalid = True break message = messages[index - 1] if message is None: invalid = True break msgs.append(message) if invalid: continue # keyword spec indexes are 1 based, therefore '-1' if isinstance(spec[0], (tuple, list)): # context-aware *gettext method first_msg_index = spec[1] - 1 else: first_msg_index = spec[0] - 1 if not messages[first_msg_index]: # An empty string msgid isn't valid, emit a warning where = '%s:%i' % (hasattr(fileObject, 'name') and \ fileObject.name or '(unknown)', lineno) print(self.empty_msgid_warning % where, file=sys.stderr) continue messages = tuple(msgs) message = messages[0] plural = None if len(messages) == 2: plural = messages[1] yield message, plural, context, None, lineno, comments class cpp(javascript): """ Extract messages from C++ source code. """ pass class txt(Extractor): """ Extract messages from plain text files. """ def extractFromFile(self, filepath): with codecs.open(filepath, "r", encoding='utf-8-sig') as fileObject: lineCount = 0 for line in [line.strip("\n\r") for line in fileObject.readlines()]: lineCount += 1 if line: yield line, None, None, None, lineCount, [] class json(Extractor): """ Extract messages from JSON files. """ def __init__(self, directoryPath=None, filemasks=[], options={}): super(json, self).__init__(directoryPath, filemasks, options) self.breadcrumbs = [] self.keywords = self.options.get("keywords", {}) self.context = self.options.get("context", None) self.comments = self.options.get("comments", []) def setOptions(self, options): self.options = options self.keywords = self.options.get("keywords", {}) self.context = self.options.get("context", None) self.comments = self.options.get("comments", []) @staticmethod def formatBreadcrumbs(breadcrumbs): firstPiece = breadcrumbs[0] if isinstance(firstPiece, int): outputString = "[" + str(firstPiece) + "]" else: outputString = firstPiece for piece in breadcrumbs[1:]: if isinstance(piece, int): outputString += "[" + str(piece) + "]" else: outputString += "." + piece return outputString def extractFromFile(self, filepath): with codecs.open(filepath, "r", 'utf-8') as fileObject: for message, breadcrumbs in self.extractFromString(fileObject.read()): yield message, None, self.context, self.formatBreadcrumbs(breadcrumbs), -1, self.comments def extractFromString(self, string): self.breadcrumbs = [] jsonDocument = jsonParser.loads(string) if isinstance(jsonDocument, list): for message, breadcrumbs in self.parseList(jsonDocument): if message: # Skip empty strings. yield message, breadcrumbs elif isinstance(jsonDocument, dict): for message, breadcrumbs in self.parseDictionary(jsonDocument): if message: # Skip empty strings. yield message, breadcrumbs else: raise Exception("Unexpected JSON document parent structure (not a list or a dictionary). You must extend the JSON extractor to support it.") def parseList(self, itemsList): index = 0 for listItem in itemsList: self.breadcrumbs.append(index) if isinstance(listItem, list): for message, breadcrumbs in self.parseList(listItem): yield message, breadcrumbs elif isinstance(listItem, dict): for message, breadcrumbs in self.parseDictionary(listItem): yield message, breadcrumbs del self.breadcrumbs[-1] index += 1 def parseDictionary(self, dictionary): for keyword in dictionary: self.breadcrumbs.append(keyword) if keyword in self.keywords: if isinstance(dictionary[keyword], unicode): yield dictionary[keyword], self.breadcrumbs elif isinstance(dictionary[keyword], list): for message, breadcrumbs in self.extractList(dictionary[keyword]): yield message, breadcrumbs elif isinstance(dictionary[keyword], dict): for message, breadcrumbs in self.extractDictionary(dictionary[keyword]): yield message, breadcrumbs elif isinstance(dictionary[keyword], list): for message, breadcrumbs in self.parseList(dictionary[keyword]): yield message, breadcrumbs elif isinstance(dictionary[keyword], dict): for message, breadcrumbs in self.parseDictionary(dictionary[keyword]): yield message, breadcrumbs del self.breadcrumbs[-1] def extractList(self, itemsList): index = 0 for listItem in itemsList: self.breadcrumbs.append(index) if isinstance(listItem, unicode): yield listItem, self.breadcrumbs del self.breadcrumbs[-1] index += 1 def extractDictionary(self, dictionary): for keyword in dictionary: self.breadcrumbs.append(keyword) if isinstance(dictionary[keyword], unicode): yield dictionary[keyword], self.breadcrumbs del self.breadcrumbs[-1] class xml(Extractor): """ Extract messages from XML files. """ def __init__(self, directoryPath, filemasks, options): super(xml, self).__init__(directoryPath, filemasks, options) self.keywords = self.options.get("keywords", {}) self.jsonExtractor = None def getJsonExtractor(self): if not self.jsonExtractor: self.jsonExtractor = json() return self.jsonExtractor def extractFromFile(self, filepath): from lxml import etree with codecs.open(filepath, "r", encoding='utf-8-sig') as fileObject: xmlDocument = etree.parse(fileObject) for keyword in self.keywords: for element in xmlDocument.iter(keyword): position = element.sourceline if element.text is not None: context = None comments = [] if "extractJson" in self.keywords[keyword]: jsonExtractor = self.getJsonExtractor() jsonExtractor.setOptions(self.keywords[keyword]["extractJson"]) for message, breadcrumbs in jsonExtractor.extractFromString(element.text): yield message, None, context, json.formatBreadcrumbs(breadcrumbs), position, comments else: breadcrumb = None if "locationAttributes" in self.keywords[keyword]: attributes = [element.get(attribute) for attribute in self.keywords[keyword]["locationAttributes"] if attribute in element.attrib] breadcrumb = "({attributes})".format(attributes=", ".join(attributes)) if "tagAsContext" in self.keywords[keyword]: context = keyword if "context" in element.attrib: context = unicode(element.get("context")) if "comment" in element.attrib: comment = element.get("comment") comment = u" ".join(comment.split()) # Remove tabs, line breaks and unecessary spaces. comments.append(comment) if "splitOnWhitespace" in self.keywords[keyword]: for splitText in element.text.split(): # split on whitespace is used for token lists, there, a leading '-' means the token has to be removed, so it's not to be processed here either if splitText[0] != "-": yield unicode(splitText), None, context, breadcrumb, position, comments else: yield unicode(element.text), None, context, breadcrumb, position, comments # Hack from http://stackoverflow.com/a/2819788 class FakeSectionHeader(object): def __init__(self, fp): self.fp = fp self.sechead = '[root]\n' def readline(self): if self.sechead: try: return self.sechead finally: self.sechead = None else: return self.fp.readline() class ini(Extractor): """ Extract messages from INI files. """ def __init__(self, directoryPath, filemasks, options): super(ini, self).__init__(directoryPath, filemasks, options) self.keywords = self.options.get("keywords", []) def extractFromFile(self, filepath): import ConfigParser config = ConfigParser.RawConfigParser() config.readfp(FakeSectionHeader(open(filepath))) for keyword in self.keywords: message = config.get("root", keyword).strip('"').strip("'") context = None position = " ({})".format(keyword) comments = [] yield message, None, context, None, position, comments Index: ps/trunk/source/tools/profiler2/profiler2.js =================================================================== --- ps/trunk/source/tools/profiler2/profiler2.js (revision 19898) +++ ps/trunk/source/tools/profiler2/profiler2.js (revision 19899) @@ -1,503 +1,503 @@ -// Copyright (c) 2016 Wildfire Games +// Copyright (C) 2016 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 file is the main handler, which deals with loading reports and showing the analysis graphs // the latter could probably be put in a separate module // global array of Profiler2Report objects var g_reports = []; var g_main_thread = 0; var g_current_report = 0; var g_profile_path = null; var g_active_elements = []; var g_loading_timeout = null; function save_as_file() { $.ajax({ url: 'http://127.0.0.1:8000/download', success: function () { }, error: function (jqXHR, textStatus, errorThrown) { } }); } function get_history_data(report, thread, type) { var ret = {"time_by_frame":[], "max" : 0, "log_scale" : null}; var report_data = g_reports[report].data().threads[thread]; var interval_data = report_data.intervals; let data = report_data.intervals_by_type_frame[type]; if (!data) return ret; let max = 0; let avg = []; let current_frame = 0; for (let i = 0; i < data.length; i++) { ret.time_by_frame.push(0); for (let p = 0; p < data[i].length; p++) ret.time_by_frame[ret.time_by_frame.length-1] += interval_data[data[i][p]].duration; } // somehow JS sorts 0.03 lower than 3e-7 otherwise let sorted = ret.time_by_frame.slice(0).sort((a,b) => a-b); ret.max = sorted[sorted.length-1]; avg = sorted[Math.round(avg.length/2)]; if (ret.max > avg * 3) ret.log_scale = true; return ret; } function draw_frequency_graph() { let canvas = document.getElementById("canvas_frequency"); canvas._tooltips = []; let context = canvas.getContext("2d"); context.clearRect(0, 0, canvas.width, canvas.height); let legend = document.getElementById("frequency_graph").querySelector("aside"); legend.innerHTML = ""; if (!g_active_elements.length) return; var series_data = {}; var use_log_scale = null; var x_scale = 0; var y_scale = 0; var padding = 10; var item_nb = 0; var tooltip_helper = {}; for (let typeI in g_active_elements) { for (let rep in g_reports) { item_nb++; let data = get_history_data(rep, g_main_thread, g_active_elements[typeI]); let name = rep + "/" + g_active_elements[typeI]; if (document.getElementById('fulln').checked) series_data[name] = data.time_by_frame.sort((a,b) => a-b); else series_data[name] = data.time_by_frame.filter(a=>a).sort((a,b) => a-b); if (series_data[name].length > x_scale) x_scale = series_data[name].length; if (data.max > y_scale) y_scale = data.max; if (use_log_scale === null && data.log_scale) use_log_scale = true; } } if (use_log_scale) { let legend_item = document.createElement("p"); legend_item.style.borderColor = "transparent"; legend_item.textContent = " -- log x scale -- "; legend.appendChild(legend_item); } let id = 0; for (let type in series_data) { let colour = graph_colour(id); let time_by_frame = series_data[type]; let p = 0; let last_val = 0; let nb = document.createElement("p"); nb.style.borderColor = colour; nb.textContent = type + " - n=" + time_by_frame.length; legend.appendChild(nb); for (var i = 0; i < time_by_frame.length; i++) { let x0 = i/time_by_frame.length*(canvas.width-padding*2) + padding; if (i == 0) x0 = 0; let x1 = (i+1)/time_by_frame.length*(canvas.width-padding*2) + padding; if (i == time_by_frame.length-1) x1 = (time_by_frame.length-1)*canvas.width; let y = time_by_frame[i]/y_scale; if (use_log_scale) y = Math.log10(1 + time_by_frame[i]/y_scale * 9); context.globalCompositeOperation = "lighter"; context.beginPath(); context.strokeStyle = colour context.lineWidth = 0.5; context.moveTo(x0,canvas.height * (1 - last_val)); context.lineTo(x1,canvas.height * (1 - y)); context.stroke(); last_val = y; if (!tooltip_helper[Math.floor(x0)]) tooltip_helper[Math.floor(x0)] = []; tooltip_helper[Math.floor(x0)].push([y, type]); } id++; } for (let i in tooltip_helper) { let tooltips = tooltip_helper[i]; let text = ""; for (let j in tooltips) if (tooltips[j][0] != undefined && text.search(tooltips[j][1])===-1) text += "Series " + tooltips[j][1] + ": " + time_label((tooltips[j][0])*y_scale,1) + "
"; canvas._tooltips.push({ 'x0': +i, 'x1': +i+1, 'y0': 0, 'y1': canvas.height, 'text': function(text) { return function() { return text; } }(text) }); } set_tooltip_handlers(canvas); [0.02,0.05,0.1,0.25,0.5,0.75].forEach(function(y_val) { let y = y_val; if (use_log_scale) y = Math.log10(1 + y_val * 9); context.beginPath(); context.lineWidth="1"; context.strokeStyle = "rgba(0,0,0,0.2)"; context.moveTo(0,canvas.height * (1- y)); context.lineTo(canvas.width,canvas.height * (1 - y)); context.stroke(); context.fillStyle = "gray"; context.font = "10px Arial"; context.textAlign="left"; context.fillText(time_label(y*y_scale,0), 2, canvas.height * (1 - y) - 2 ); }); } function draw_history_graph() { let canvas = document.getElementById("canvas_history"); canvas._tooltips = []; let context = canvas.getContext("2d"); context.clearRect(0, 0, canvas.width, canvas.height); let legend = document.getElementById("history_graph").querySelector("aside"); legend.innerHTML = ""; if (!g_active_elements.length) return; var series_data = {}; var use_log_scale = null; var frames_nb = Infinity; var x_scale = 0; var y_scale = 0; var item_nb = 0; var tooltip_helper = {}; for (let typeI in g_active_elements) { for (let rep in g_reports) { if (g_reports[rep].data().threads[g_main_thread].frames.length < frames_nb) frames_nb = g_reports[rep].data().threads[g_main_thread].frames.length; item_nb++; let data = get_history_data(rep, g_main_thread, g_active_elements[typeI]); if (!document.getElementById('smooth').value) series_data[rep + "/" + g_active_elements[typeI]] = data.time_by_frame; else series_data[rep + "/" + g_active_elements[typeI]] = smooth_1D_array(data.time_by_frame, +document.getElementById('smooth').value); if (data.max > y_scale) y_scale = data.max; if (use_log_scale === null && data.log_scale) use_log_scale = true; } } if (use_log_scale) { let legend_item = document.createElement("p"); legend_item.style.borderColor = "transparent"; legend_item.textContent = " -- log y scale -- "; legend.appendChild(legend_item); } canvas.width = Math.max(frames_nb,600); x_scale = frames_nb / canvas.width; let id = 0; for (let type in series_data) { let colour = graph_colour(id); let legend_item = document.createElement("p"); legend_item.style.borderColor = colour; legend_item.textContent = type; legend.appendChild(legend_item); let time_by_frame = series_data[type]; let last_val = 0; for (var i = 0; i < frames_nb; i++) { let smoothed_time = time_by_frame[i];//smooth_1D(time_by_frame.slice(0), i, 3); let y = smoothed_time/y_scale; if (use_log_scale) y = Math.log10(1 + smoothed_time/y_scale * 9); if (item_nb === 1) { context.beginPath(); context.fillStyle = colour; context.fillRect(i/x_scale,canvas.height,1/x_scale,-y*canvas.height); } else { if ( i == frames_nb-1) continue; context.globalCompositeOperation = "lighten"; context.beginPath(); context.strokeStyle = colour context.lineWidth = 0.5; context.moveTo(i/x_scale,canvas.height * (1 - last_val)); context.lineTo((i+1)/x_scale,canvas.height * (1 - y)); context.stroke(); } last_val = y; if (!tooltip_helper[Math.floor(i/x_scale)]) tooltip_helper[Math.floor(i/x_scale)] = []; tooltip_helper[Math.floor(i/x_scale)].push([y, type]); } id++; } for (let i in tooltip_helper) { let tooltips = tooltip_helper[i]; let text = "Frame " + i*x_scale + "
"; for (let j in tooltips) if (tooltips[j][0] != undefined && text.search(tooltips[j][1])===-1) text += "Series " + tooltips[j][1] + ": " + time_label((tooltips[j][0])*y_scale,1) + "
"; canvas._tooltips.push({ 'x0': +i, 'x1': +i+1, 'y0': 0, 'y1': canvas.height, 'text': function(text) { return function() { return text; } }(text) }); } set_tooltip_handlers(canvas); [0.1,0.25,0.5,0.75].forEach(function(y_val) { let y = y_val; if (use_log_scale) y = Math.log10(1 + y_val * 9); context.beginPath(); context.lineWidth="1"; context.strokeStyle = "rgba(0,0,0,0.2)"; context.moveTo(0,canvas.height * (1- y)); context.lineTo(canvas.width,canvas.height * (1 - y)); context.stroke(); context.fillStyle = "gray"; context.font = "10px Arial"; context.textAlign="left"; context.fillText(time_label(y*y_scale,0), 2, canvas.height * (1 - y) - 2 ); }); } function compare_reports() { let section = document.getElementById("comparison"); section.innerHTML = "

Report Comparison

"; if (g_reports.length < 2) { section.innerHTML += "

Too few reports loaded

"; return; } if (g_active_elements.length != 1) { section.innerHTML += "

Too many of too few elements selected

"; return; } let frames_nb = g_reports[0].data().threads[g_main_thread].frames.length; for (let rep in g_reports) if (g_reports[rep].data().threads[g_main_thread].frames.length < frames_nb) frames_nb = g_reports[rep].data().threads[g_main_thread].frames.length; if (frames_nb != g_reports[0].data().threads[g_main_thread].frames.length) section.innerHTML += "

Only the first " + frames_nb + " frames will be considered.

"; let reports_data = []; for (let rep in g_reports) { let raw_data = get_history_data(rep, g_main_thread, g_active_elements[0]).time_by_frame; reports_data.push({"time_data" : raw_data.slice(0,frames_nb), "sorted_data" : raw_data.slice(0,frames_nb).sort((a,b) => a-b)}); } let table_output = "
" for (let rep in reports_data) { let report = reports_data[rep]; table_output += ""; // median table_output += "" // max table_output += "" let frames_better = 0; let frames_diff = 0; for (let f in report.time_data) { if (report.time_data[f] <= reports_data[0].time_data[f]) frames_better++; frames_diff += report.time_data[f] - reports_data[0].time_data[f]; } table_output += ""; table_output += ""; } section.innerHTML += table_output + "
Profiler VariableMedianMaximum% better framestime difference per frame
Report " + rep + (rep == 0 ? " (reference)":"") + "" + time_label(report.sorted_data[Math.floor(report.sorted_data.length/2)]) + "" + time_label(report.sorted_data[report.sorted_data.length-1]) + "" + (frames_better/frames_nb*100).toFixed(0) + "%" + time_label((frames_diff/frames_nb),2) + "
"; } function recompute_choices(report, thread) { var choices = document.getElementById("choices").querySelector("nav"); choices.innerHTML = "

Choices

"; var types = {}; var data = report.data().threads[thread]; for (let i = 0; i < data.intervals.length; i++) types[data.intervals[i].id] = 0; var sorted_keys = Object.keys(types).sort(); for (let key in sorted_keys) { let type = sorted_keys[key]; let p = document.createElement("p"); p.textContent = type; if (g_active_elements.indexOf(p.textContent) !== -1) p.className = "active"; choices.appendChild(p); p.onclick = function() { if (g_active_elements.indexOf(p.textContent) !== -1) { p.className = ""; g_active_elements = g_active_elements.filter( x => x != p.textContent); update_analysis(); return; } g_active_elements.push(p.textContent); p.className = "active"; update_analysis(); } } update_analysis(); } function update_analysis() { compare_reports(); draw_history_graph(); draw_frequency_graph(); } function load_report_from_file(evt) { var file = evt.target.files[0]; if (!file) return; load_report(false, file); evt.target.value = null; } function load_report(trylive, file) { if (g_loading_timeout != undefined) return; let reportID = g_reports.length; let nav = document.querySelector("header nav"); let newRep = document.createElement("p"); newRep.textContent = file.name; newRep.className = "loading"; newRep.id = "Report" + reportID; newRep.dataset.id = reportID; nav.appendChild(newRep); g_reports.push(Profiler2Report(on_report_loaded, trylive, file)); g_loading_timeout = setTimeout(function() { on_report_loaded(false); }, 5000); } function on_report_loaded(success) { let element = document.getElementById("Report" + (g_reports.length-1)); let report = g_reports[g_reports.length-1]; if (!success) { element.className = "fail"; setTimeout(function() { element.parentNode.removeChild(element); clearTimeout(g_loading_timeout); g_loading_timeout = null; }, 1000 ); g_reports = g_reports.slice(0,-1); if (g_reports.length === 0) g_current_report = null; return; } clearTimeout(g_loading_timeout); g_loading_timeout = null; select_report(+element.dataset.id); element.onclick = function() { select_report(+element.dataset.id);}; } function select_report(id) { if (g_current_report != undefined) document.getElementById("Report" + g_current_report).className = ""; document.getElementById("Report" + id).className = "active"; g_current_report = id; // Load up our canvas g_report_draw.update_display(g_reports[id],{"seconds":5}); recompute_choices(g_reports[id], g_main_thread); } window.onload = function() { // Try loading the report live load_report(true, {"name":"live"}); // add new reports document.getElementById('report_load_input').addEventListener('change', load_report_from_file, false); } Index: ps/trunk/source/lib/sysdep/sysdep.h =================================================================== --- ps/trunk/source/lib/sysdep/sysdep.h (revision 19898) +++ ps/trunk/source/lib/sysdep/sysdep.h (revision 19899) @@ -1,188 +1,188 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ /* * various system-specific function implementations */ #ifndef INCLUDED_SYSDEP #define INCLUDED_SYSDEP #include "lib/debug.h" // ErrorReactionInternal #include "lib/os_path.h" // // output // /** * display a message. * * @param caption title message * @param msg message contents * * implemented as a MessageBox on Win32 and printf on Unix. * called from debug_DisplayMessage. **/ extern void sys_display_msg(const wchar_t* caption, const wchar_t* msg); /** * show the error dialog. * * @param text to display (practically unlimited length) * @param flags: see DebugDisplayErrorFlags. * @return ErrorReactionInternal (except ERI_EXIT, which is acted on immediately) * * called from debug_DisplayError unless overridden by means of * ah_display_error. **/ extern ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags); // // misc // /** * @return whether a debugger is attached to the process * (if so, it is safe to use debug_break; otherwise, that would * raise an exception) **/ LIB_API bool sys_IsDebuggerPresent(); /** * @return a wide string conversion of the platform's encoding of main's argv. * * (NB: wseh.cpp defines a wmain that converts argv to UTF-8 and calls main(), * but only if LIB_STATIC_LINK) **/ LIB_API std::wstring sys_WideFromArgv(const char* argv_i); /** * describe the current OS error state. * * @param err: if not 0, use that as the error code to translate; otherwise, * uses GetLastError or similar. * @param buf output buffer * @param max_chars * * rationale: it is expected to be rare that OS return/error codes are * actually seen by user code, but we leave the possibility open. **/ extern Status sys_StatusDescription(int err, wchar_t* buf, size_t max_chars); /** * determine filename of the module to whom an address belongs. * * @param addr * @param pathname Full path to module (unchanged unless INFO::OK is returned). * @return Status * * note: this is useful for handling exceptions in other modules. **/ Status sys_get_module_filename(void* addr, OsPath& pathname); /** * @return full pathname of the current executable. * * this is useful for determining installation directory, e.g. for VFS. **/ LIB_API OsPath sys_ExecutablePathname(); /** * Get the current user's login name. * * @return login name, or empty string on error */ extern std::wstring sys_get_user_name(); /** * Have the user choose a directory via OS dialog. * * @param path Path's input value determines the starting directory for * faster browsing. if INFO::OK is returned, it receives * chosen directory path. **/ extern Status sys_pick_directory(OsPath& path); /** * Open the user's default web browser to the given URL. **/ extern Status sys_open_url(const std::string& url); /** * return the largest sector size [bytes] of any storage medium * (HD, optical, etc.) in the system. * * this may be a bit slow to determine (iterates over all drives), * but caches the result so subsequent calls are free. * (caveat: device changes won't be noticed during this program run) * * sector size is relevant because Windows aio requires all IO * buffers, offsets and lengths to be a multiple of it. this requirement * is also carried over into the vfs / file.cpp interfaces for efficiency * (avoids the need for copying to/from align buffers). * * waio uses the sector size to (in some cases) align IOs if * they aren't already, but it's also needed by user code when * aligning their buffers to meet the requirements. * * the largest size is used so that we can read from any drive. while this * is a bit wasteful (more padding) and requires iterating over all drives, * it is the only safe way: this may be called before we know which * drives will be needed, and hardlinks may confuse things. **/ extern size_t sys_max_sector_size(); /** * generate high-quality random bytes. * * this should only be used with small numbers of bytes, to avoid * hogging the system's entropy. **/ LIB_API Status sys_generate_random_bytes(u8* buf, size_t count); /** * get the proxy address for accessing the given HTTP URL. * * this may be very slow (tens of seconds). * * @return INFO::OK on success; INFO::SKIPPED if no proxy found. **/ LIB_API Status sys_get_proxy_config(const std::wstring& url, std::wstring& proxy); /** * open a file like with fopen (but taking an OsPath argument). */ LIB_API FILE* sys_OpenFile(const OsPath& pathname, const char* mode); /** * directory separation character **/ #if OS_WIN # define SYS_DIR_SEP '\\' #else # define SYS_DIR_SEP '/' #endif #endif // #ifndef INCLUDED_SYSDEP Index: ps/trunk/source/lib/tests/test_adts.h =================================================================== --- ps/trunk/source/lib/tests/test_adts.h (revision 19898) +++ ps/trunk/source/lib/tests/test_adts.h (revision 19899) @@ -1,98 +1,98 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/adts/ring_buf.h" #include "lib/rand.h" class TestRingbuf : public CxxTest::TestSuite { static const size_t N = 49; // RingBuf capacity static const int S = 100; // number of test items public: void test_insert_remove() { RingBuf buf; for(int i = 1; i < S; i++) { buf.push_back(i); TS_ASSERT_EQUALS(buf.front(), i); buf.pop_front(); } TS_ASSERT(buf.size() == 0 && buf.empty()); } void test_fill_overwrite_old() { RingBuf buf; for(int i = 1; i < S; i++) buf.push_back(i); TS_ASSERT_EQUALS(buf.size(), N); int first = buf.front(); TS_ASSERT_EQUALS(first, (int)(S-1 -N +1)); for(size_t i = 0; i < N; i++) { TS_ASSERT_EQUALS(buf.front(), first); first++; buf.pop_front(); } TS_ASSERT(buf.size() == 0 && buf.empty()); } void test_randomized_insert_remove() { srand(1); RingBuf buf; std::deque deq; for(size_t rep = 0; rep < 1000; rep++) { size_t rnd_op = rand(0, 10); // 70% - insert if(rnd_op >= 3) { int item = rand(); buf.push_back(item); deq.push_back(item); int excess_items = (int)deq.size() - N; if(excess_items > 0) { for(int i = 0; i < excess_items; i++) { deq.pop_front(); } } } // 30% - pop front (only if not empty) else if(!deq.empty()) { buf.pop_front(); deq.pop_front(); } TS_ASSERT_EQUALS(buf.size(), deq.size()); RingBuf::iterator begin = buf.begin(), end = buf.end(); TS_ASSERT(std::equal(begin, end, deq.begin())); } } }; Index: ps/trunk/source/lib/tests/test_cache_adt.h =================================================================== --- ps/trunk/source/lib/tests/test_cache_adt.h (revision 19898) +++ ps/trunk/source/lib/tests/test_cache_adt.h (revision 19899) @@ -1,132 +1,132 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/adts/cache_adt.h" #include "lib/rand.h" class TestCache: public CxxTest::TestSuite { public: void test_cache_perf() { Cache c1; Cache c1r; Cache c2; Cache c2r; Cache c3; Cache c3r; #if defined(ENABLE_CACHE_POLICY_BENCHMARK) || 0 // set max priority, to reduce interference while measuring. int old_policy; static sched_param old_param; // (static => 0-init) pthread_getschedparam(pthread_self(), &old_policy, &old_param); static sched_param max_param; max_param.sched_priority = sched_get_priority_max(SCHED_FIFO); pthread_setschedparam(pthread_self(), SCHED_FIFO, &max_param); #define MEASURE(c, desc)\ {\ srand(1);\ int cnt = 1;\ TIMER_BEGIN(desc);\ for(int i = 0; i < 30000; i++)\ {\ /* 70% add (random objects) */\ bool add = rand(1,10) < 7;\ if(add)\ {\ int key = cnt++;\ int val = cnt++;\ size_t size = (size_t)rand(1,100);\ size_t cost = (size_t)rand(1,100);\ c.add(key, val, size, cost);\ }\ else\ {\ size_t size;\ int value;\ c.remove_least_valuable(&value, &size);\ }\ }\ TIMER_END(desc);\ } MEASURE(c1, "naive") MEASURE(c1r, "naiverecip") MEASURE(c2, "cached") MEASURE(c2r, "cachedrecip") MEASURE(c3, "lazy") MEASURE(c3r, "lazyrecip") // restore previous policy and priority. pthread_setschedparam(pthread_self(), old_policy, &old_param); exit(1134); #endif } // ensures all 3 variants of Landlord<> behave the same // [PT: disabled because it's far too slow] void DISABLED_test_cache_policies() { Cache c1; Cache c2; Cache c3; srand(1); int cnt = 1; for(int i = 0; i < 1000; i++) { // 70% add (random objects) bool add = rand(1,10) < 7; if(add) { int key = cnt++; int val = cnt++; size_t size = (size_t)rand(1,100); size_t cost = (size_t)rand(1,100); c1.add(key, val, size, cost); c2.add(key, val, size, cost); c3.add(key, val, size, cost); } // 30% delete - make sure "least valuable" was same for all else { size_t size1, size2, size3; int value1, value2, value3; bool removed1, removed2, removed3; removed1 = c1.remove_least_valuable(&value1, &size1); removed2 = c2.remove_least_valuable(&value2, &size2); removed3 = c3.remove_least_valuable(&value3, &size3); TS_ASSERT_EQUALS(removed1, removed2); TS_ASSERT_EQUALS(removed2, removed3); if (removed1) { TS_ASSERT_EQUALS(size1, size2); TS_ASSERT_EQUALS(value1, value2); TS_ASSERT_EQUALS(size2, size3); TS_ASSERT_EQUALS(value2, value3); } } // else } // for i } }; Index: ps/trunk/source/lib/tests/test_path_util.h =================================================================== --- ps/trunk/source/lib/tests/test_path_util.h (revision 19898) +++ ps/trunk/source/lib/tests/test_path_util.h (revision 19899) @@ -1,78 +1,78 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/lib.h" #include "lib/self_test.h" #include "lib/path.h" // Macros, not functions, to get proper line number reports when tests fail #define TEST_NAME_ONLY(path, correct_result) \ { \ const wchar_t* result = path_name_only(path); \ TS_ASSERT_WSTR_EQUALS(result, correct_result); \ } class TestPathUtil : public CxxTest::TestSuite { public: void test_subpath() { // obvious true TS_ASSERT(path_is_subpath(L"abc/def/", L"abc/def/") == true); // same TS_ASSERT(path_is_subpath(L"abc/def/", L"abc/") == true); // 2 is subpath TS_ASSERT(path_is_subpath(L"abc/", L"abc/def/") == true); // 1 is subpath // nonobvious true TS_ASSERT(path_is_subpath(L"", L"") == true); TS_ASSERT(path_is_subpath(L"abc/def/", L"abc/def") == true); // no '/' ! // obvious false TS_ASSERT(path_is_subpath(L"abc", L"def") == false); // different, no path // nonobvious false TS_ASSERT(path_is_subpath(L"abc", L"") == false); // empty comparand // .. different but followed by common subdir TS_ASSERT(path_is_subpath(L"abc/def/", L"ghi/def/") == false); TS_ASSERT(path_is_subpath(L"abc/def/", L"abc/ghi") == false); } // TODO: can't test path validate yet without suppress-error-dialog void test_name_only() { // path with filename TEST_NAME_ONLY(L"abc/def", L"def"); // nonportable path with filename TEST_NAME_ONLY(L"abc\\def\\ghi", L"ghi"); // mixed path with filename TEST_NAME_ONLY(L"abc/def\\ghi", L"ghi"); // mixed path with filename (2) TEST_NAME_ONLY(L"abc\\def/ghi", L"ghi"); // filename only TEST_NAME_ONLY(L"abc", L"abc"); // empty TEST_NAME_ONLY(L"", L""); } }; Index: ps/trunk/source/lib/tests/test_wchar.h =================================================================== --- ps/trunk/source/lib/tests/test_wchar.h (revision 19898) +++ ps/trunk/source/lib/tests/test_wchar.h (revision 19899) @@ -1,85 +1,85 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 "lib/self_test.h" #include "lib/utf8.h" // (copied from CStr test) class Test_wchar : public CxxTest::TestSuite { public: void test_utf8_utf16_conversion() { const wchar_t chr_utf16[] = { 0x12, 0xff, 0x1234, 0x3456, 0x5678, 0x7890, 0x9abc, 0xbcde, 0xfffd }; const unsigned char chr_utf8[] = { 0x12, 0xc3, 0xbf, 0xe1, 0x88, 0xb4, 0xe3, 0x91, 0x96, 0xe5, 0x99, 0xb8, 0xe7, 0xa2, 0x90, 0xe9, 0xaa, 0xbc, 0xeb, 0xb3, 0x9e, 0xef, 0xbf, 0xbd }; const std::wstring str_utf16(chr_utf16, ARRAY_SIZE(chr_utf16)); const std::string str_utf8 = utf8_from_wstring(str_utf16); TS_ASSERT_EQUALS(str_utf8.length(), ARRAY_SIZE(chr_utf8)); TS_ASSERT_SAME_DATA(str_utf8.data(), chr_utf8, ARRAY_SIZE(chr_utf8)*sizeof(char)); const std::wstring str_utf16b = wstring_from_utf8(str_utf8); TS_ASSERT_WSTR_EQUALS(str_utf16b, str_utf16); } void test_invalid_utf8() { struct { const char* utf8; const wchar_t* utf16; } tests[] = { { "a\xef", L"a\xfffd" }, { "b\xef\xbf", L"b\xfffd\xfffd" }, { "c\xef\xbf\x01", L"c\xfffd\xfffd\x0001" }, { "d\xffX\x80Y\x80" , L"d\xfffdX\xfffdY\xfffd" } }; for (size_t i = 0; i < ARRAY_SIZE(tests); ++i) { const std::string str_utf8(tests[i].utf8); const std::wstring str_utf16(tests[i].utf16); Status err; const std::wstring str_utf8to16 = wstring_from_utf8(str_utf8, &err); TS_ASSERT_EQUALS(err, ERR::UTF8_INVALID_UTF8); TS_ASSERT_EQUALS(str_utf16.length(), str_utf8to16.length()); TS_ASSERT_SAME_DATA(str_utf8to16.data(), str_utf16.data(), str_utf16.length()*sizeof(wchar_t)); } } }; Index: ps/trunk/source/lib/timer.cpp =================================================================== --- ps/trunk/source/lib/timer.cpp (revision 19898) +++ ps/trunk/source/lib/timer.cpp (revision 19899) @@ -1,235 +1,235 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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 high resolution timer */ #include "precompiled.h" #include "lib/timer.h" #include // std::stringstream #include #include #include #include #include "lib/module_init.h" #include "lib/posix/posix_pthread.h" #include "lib/posix/posix_time.h" # include "lib/sysdep/cpu.h" #if OS_WIN # include "lib/sysdep/os/win/whrt/whrt.h" #endif #if OS_UNIX # include #endif #if OS_UNIX || OS_WIN # define HAVE_GETTIMEOFDAY 1 #else # define HAVE_GETTIMEOFDAY 0 #endif #if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0) || OS_WIN # define HAVE_CLOCK_GETTIME 1 #else # define HAVE_CLOCK_GETTIME 0 #endif // rationale for wrapping gettimeofday and clock_gettime, instead of just // emulating them where not available: allows returning higher-resolution // timer values than their us / ns interface, via double [seconds]. // they're also not guaranteed to be monotonic. #if HAVE_CLOCK_GETTIME static struct timespec start; #elif HAVE_GETTIMEOFDAY static struct timeval start; #endif //----------------------------------------------------------------------------- // timer API void timer_LatchStartTime() { #if OS_WIN // whrt_Time starts at zero, nothing needs to be done. #elif HAVE_CLOCK_GETTIME (void)clock_gettime(CLOCK_REALTIME, &start); #elif HAVE_GETTIMEOFDAY gettimeofday(&start, 0); #endif } static pthread_mutex_t ensure_monotonic_mutex = PTHREAD_MUTEX_INITIALIZER; // NB: does not guarantee strict monotonicity - callers must avoid // dividing by the difference of two equal times. static void EnsureMonotonic(double& newTime) { pthread_mutex_lock(&ensure_monotonic_mutex); static double maxTime; maxTime = std::max(maxTime, newTime); newTime = maxTime; pthread_mutex_unlock(&ensure_monotonic_mutex); } double timer_Time() { double t; #if OS_WIN t = whrt_Time(); #elif HAVE_CLOCK_GETTIME ENSURE(start.tv_sec || start.tv_nsec); // must have called timer_LatchStartTime first struct timespec cur; (void)clock_gettime(CLOCK_REALTIME, &cur); t = (cur.tv_sec - start.tv_sec) + (cur.tv_nsec - start.tv_nsec)*1e-9; #elif HAVE_GETTIMEOFDAY ENSURE(start.tv_sec || start.tv_usec); // must have called timer_LatchStartTime first struct timeval cur; gettimeofday(&cur, 0); t = (cur.tv_sec - start.tv_sec) + (cur.tv_usec - start.tv_usec)*1e-6; #else # error "timer_Time: add timer implementation for this platform!" #endif EnsureMonotonic(t); return t; } // cached because the default implementation may take several milliseconds static double resolution; static Status InitResolution() { #if OS_WIN resolution = whrt_Resolution(); #elif HAVE_CLOCK_GETTIME struct timespec ts; if(clock_getres(CLOCK_REALTIME, &ts) == 0) resolution = ts.tv_nsec * 1e-9; #else const double t0 = timer_Time(); double t1, t2; do t1 = timer_Time(); while(t1 == t0); do t2 = timer_Time(); while(t2 == t1); resolution = t2-t1; #endif return INFO::OK; } double timer_Resolution() { static ModuleInitState initState; ModuleInit(&initState, InitResolution); return resolution; } //----------------------------------------------------------------------------- // client API // intrusive linked-list of all clients. a fixed-size limit would be // acceptable (since timers are added manually), but the list is easy // to implement and only has the drawback of exposing TimerClient to users. // // do not use std::list et al. for this! we must be callable at any time, // especially before NLSO ctors run or before heap init. static size_t numClients; static TimerClient* clients; TimerClient* timer_AddClient(TimerClient* tc, const wchar_t* description) { tc->sum.SetToZero(); tc->description = description; // insert at front of list tc->next = clients; clients = tc; numClients++; return tc; } void timer_DisplayClientTotals() { debug_printf("TIMER TOTALS (%lu clients)\n", (unsigned long)numClients); debug_printf("-----------------------------------------------------\n"); while(clients) { // (make sure list and count are consistent) ENSURE(numClients != 0); TimerClient* tc = clients; clients = tc->next; numClients--; const std::string duration = tc->sum.ToString(); debug_printf(" %s: %s (%lux)\n", utf8_from_wstring(tc->description).c_str(), duration.c_str(), (unsigned long)tc->num_calls); } debug_printf("-----------------------------------------------------\n"); } //----------------------------------------------------------------------------- std::string StringForSeconds(double seconds) { double scale = 1e6; const char* unit = " us"; if(seconds > 1.0) scale = 1, unit = " s"; else if(seconds > 1e-3) scale = 1e3, unit = " ms"; std::stringstream ss; ss << seconds*scale; ss << unit; return ss.str(); } std::string StringForCycles(Cycles cycles) { double scale = 1.0; const char* unit = " c"; if(cycles > 10000000000LL) // 10 Gc scale = 1e-9, unit = " Gc"; else if(cycles > 10000000) // 10 Mc scale = 1e-6, unit = " Mc"; else if(cycles > 10000) // 10 kc scale = 1e-3, unit = " kc"; std::stringstream ss; ss << cycles*scale; ss << unit; return ss.str(); } Index: ps/trunk/source/lib/utf8.h =================================================================== --- ps/trunk/source/lib/utf8.h (revision 19898) +++ ps/trunk/source/lib/utf8.h (revision 19899) @@ -1,52 +1,52 @@ -/* Copyright (c) 2010 Wildfire Games +/* 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. */ #ifndef INCLUDED_UTF8 #define INCLUDED_UTF8 // note: error codes are returned via optional output parameter. namespace ERR { const Status UTF8_SURROGATE = -100700; const Status UTF8_OUTSIDE_BMP = -100701; const Status UTF8_NONCHARACTER = -100702; const Status UTF8_INVALID_UTF8 = -100703; } /** * convert UTF-8 to a wide string (UTF-16 or UCS-4, depending on the * platform's wchar_t). * * @param s input string (UTF-8) * @param err if nonzero, this receives the first error encountered * (the rest may be subsequent faults) or INFO::OK if all went well. * otherwise, the function raises a warning dialog for every * error/warning. **/ LIB_API std::wstring wstring_from_utf8(const std::string& s, Status* err = 0); /** * opposite of wstring_from_utf8 **/ LIB_API std::string utf8_from_wstring(const std::wstring& s, Status* err = 0); #endif // #ifndef INCLUDED_UTF8 Index: ps/trunk/source/ps/Profiler2GPU.cpp =================================================================== --- ps/trunk/source/ps/Profiler2GPU.cpp (revision 19898) +++ ps/trunk/source/ps/Profiler2GPU.cpp (revision 19899) @@ -1,874 +1,874 @@ -/* Copyright (c) 2014 Wildfire Games +/* Copyright (C) 2014 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 "Profiler2GPU.h" #include "lib/ogl.h" #include "lib/allocators/shared_ptr.h" #include "ps/ConfigDB.h" #include "ps/Profiler2.h" #if !CONFIG2_GLES class CProfiler2GPU_base { NONCOPYABLE(CProfiler2GPU_base); protected: CProfiler2GPU_base(CProfiler2& profiler, const char* name) : m_Profiler(profiler), m_Storage(profiler, name) { m_Storage.RecordSyncMarker(m_Profiler.GetTime()); m_Storage.Record(CProfiler2::ITEM_EVENT, m_Profiler.GetTime(), "thread start"); m_Profiler.AddThreadStorage(&m_Storage); } ~CProfiler2GPU_base() { m_Profiler.RemoveThreadStorage(&m_Storage); } CProfiler2& m_Profiler; CProfiler2::ThreadStorage m_Storage; }; ////////////////////////////////////////////////////////////////////////// // Base class for ARB_timer_query, EXT_timer_query class CProfiler2GPU_timer_query : public CProfiler2GPU_base { protected: CProfiler2GPU_timer_query(CProfiler2& profiler, const char* name) : CProfiler2GPU_base(profiler, name) { } ~CProfiler2GPU_timer_query() { if (!m_FreeQueries.empty()) pglDeleteQueriesARB(m_FreeQueries.size(), &m_FreeQueries[0]); ogl_WarnIfError(); } // Returns a new GL query object (or a recycled old one) GLuint NewQuery() { if (m_FreeQueries.empty()) { // Generate a batch of new queries m_FreeQueries.resize(8); pglGenQueriesARB(m_FreeQueries.size(), &m_FreeQueries[0]); ogl_WarnIfError(); } GLuint query = m_FreeQueries.back(); m_FreeQueries.pop_back(); return query; } std::vector m_FreeQueries; // query objects that are allocated but not currently in used }; ////////////////////////////////////////////////////////////////////////// /* * GL_ARB_timer_query supports sync and async queries for absolute GPU * timestamps, which lets us time regions of code relative to the CPU. * At the start of a frame, we record the CPU time and sync GPU timestamp, * giving the time-vs-timestamp offset. * At each enter/leave-region event, we do an async GPU timestamp query. * When all the queries for a frame have their results available, * we convert their GPU timestamps into CPU times and record the data. */ class CProfiler2GPU_ARB_timer_query : public CProfiler2GPU_timer_query { struct SEvent { const char* id; GLuint query; bool isEnter; // true if entering region; false if leaving }; struct SFrame { u32 num; double syncTimeStart; // CPU time at start of maybe this frame or a recent one GLint64 syncTimestampStart; // GL timestamp corresponding to timeStart std::vector events; }; std::deque m_Frames; public: static bool IsSupported() { return ogl_HaveExtension("GL_ARB_timer_query"); } CProfiler2GPU_ARB_timer_query(CProfiler2& profiler) : CProfiler2GPU_timer_query(profiler, "gpu_arb") { // TODO: maybe we should check QUERY_COUNTER_BITS to ensure it's // high enough (but apparently it might trigger GL errors on ATI) } ~CProfiler2GPU_ARB_timer_query() { // Pop frames to return queries to the free list while (!m_Frames.empty()) PopFrontFrame(); } void FrameStart() { ProcessFrames(); SFrame frame; frame.num = m_Profiler.GetFrameNumber(); // On (at least) some NVIDIA Windows drivers, when GPU-bound, or when // vsync enabled and not CPU-bound, the first glGet* call at the start // of a frame appears to trigger a wait (to stop the GPU getting too // far behind, or to wait for the vsync period). // That will be this GL_TIMESTAMP get, which potentially distorts the // reported results. So we'll only do it fairly rarely, and for most // frames we'll just assume the clocks don't drift much const double RESYNC_PERIOD = 1.0; // seconds double now = m_Profiler.GetTime(); if (m_Frames.empty() || now > m_Frames.back().syncTimeStart + RESYNC_PERIOD) { PROFILE2("profile timestamp resync"); pglGetInteger64v(GL_TIMESTAMP, &frame.syncTimestampStart); ogl_WarnIfError(); frame.syncTimeStart = m_Profiler.GetTime(); // (Have to do GetTime again after GL_TIMESTAMP, because GL_TIMESTAMP // might wait a while before returning its now-current timestamp) } else { // Reuse the previous frame's sync data frame.syncTimeStart = m_Frames[m_Frames.size()-1].syncTimeStart; frame.syncTimestampStart = m_Frames[m_Frames.size()-1].syncTimestampStart; } m_Frames.push_back(frame); RegionEnter("frame"); } void FrameEnd() { RegionLeave("frame"); } void RecordRegion(const char* id, bool isEnter) { ENSURE(!m_Frames.empty()); SFrame& frame = m_Frames.back(); SEvent event; event.id = id; event.query = NewQuery(); event.isEnter = isEnter; pglQueryCounter(event.query, GL_TIMESTAMP); ogl_WarnIfError(); frame.events.push_back(event); } void RegionEnter(const char* id) { RecordRegion(id, true); } void RegionLeave(const char* id) { RecordRegion(id, false); } private: void ProcessFrames() { while (!m_Frames.empty()) { SFrame& frame = m_Frames.front(); // Queries become available in order so we only need to check the last one GLint available = 0; pglGetQueryObjectivARB(frame.events.back().query, GL_QUERY_RESULT_AVAILABLE, &available); ogl_WarnIfError(); if (!available) break; // The frame's queries are now available, so retrieve and record all their results: for (size_t i = 0; i < frame.events.size(); ++i) { GLuint64 queryTimestamp = 0; pglGetQueryObjectui64v(frame.events[i].query, GL_QUERY_RESULT, &queryTimestamp); // (use the non-suffixed function here, as defined by GL_ARB_timer_query) ogl_WarnIfError(); // Convert to absolute CPU-clock time double t = frame.syncTimeStart + (double)(queryTimestamp - frame.syncTimestampStart) / 1e9; // Record a frame-start for syncing if (i == 0) m_Storage.RecordFrameStart(t); if (frame.events[i].isEnter) m_Storage.Record(CProfiler2::ITEM_ENTER, t, frame.events[i].id); else m_Storage.RecordLeave(t); // Associate the frame number with the "frame" region if (i == 0) m_Storage.RecordAttributePrintf("%u", frame.num); } PopFrontFrame(); } } void PopFrontFrame() { ENSURE(!m_Frames.empty()); SFrame& frame = m_Frames.front(); for (size_t i = 0; i < frame.events.size(); ++i) m_FreeQueries.push_back(frame.events[i].query); m_Frames.pop_front(); } }; ////////////////////////////////////////////////////////////////////////// /* * GL_EXT_timer_query only supports async queries for elapsed time, * and only a single simultaneous query. * We can't correctly convert it to absolute time, so we just pretend * each GPU frame starts the same time as the CPU for that frame. * We do a query for elapsed time between every adjacent enter/leave-region event. * When all the queries for a frame have their results available, * we sum the elapsed times to calculate when each event occurs within the * frame, and record the data. */ class CProfiler2GPU_EXT_timer_query : public CProfiler2GPU_timer_query { struct SEvent { const char* id; GLuint query; // query for time elapsed from this event until the next, or 0 for final event bool isEnter; // true if entering region; false if leaving }; struct SFrame { u32 num; double timeStart; // CPU time at frame start std::vector events; }; std::deque m_Frames; public: static bool IsSupported() { return ogl_HaveExtension("GL_EXT_timer_query"); } CProfiler2GPU_EXT_timer_query(CProfiler2& profiler) : CProfiler2GPU_timer_query(profiler, "gpu_ext") { } ~CProfiler2GPU_EXT_timer_query() { // Pop frames to return queries to the free list while (!m_Frames.empty()) PopFrontFrame(); } void FrameStart() { ProcessFrames(); SFrame frame; frame.num = m_Profiler.GetFrameNumber(); frame.timeStart = m_Profiler.GetTime(); m_Frames.push_back(frame); RegionEnter("frame"); } void FrameEnd() { RegionLeave("frame"); pglEndQueryARB(GL_TIME_ELAPSED); ogl_WarnIfError(); } void RecordRegion(const char* id, bool isEnter) { ENSURE(!m_Frames.empty()); SFrame& frame = m_Frames.back(); // Must call glEndQuery before calling glGenQueries (via NewQuery), // for compatibility with the GL_EXT_timer_query spec (which says // GL_INVALID_OPERATION if a query of any target is active; the ARB // spec and OpenGL specs don't appear to say that, but the AMD drivers // implement that error (see Trac #1033)) if (!frame.events.empty()) { pglEndQueryARB(GL_TIME_ELAPSED); ogl_WarnIfError(); } SEvent event; event.id = id; event.query = NewQuery(); event.isEnter = isEnter; pglBeginQueryARB(GL_TIME_ELAPSED, event.query); ogl_WarnIfError(); frame.events.push_back(event); } void RegionEnter(const char* id) { RecordRegion(id, true); } void RegionLeave(const char* id) { RecordRegion(id, false); } private: void ProcessFrames() { while (!m_Frames.empty()) { SFrame& frame = m_Frames.front(); // Queries become available in order so we only need to check the last one GLint available = 0; pglGetQueryObjectivARB(frame.events.back().query, GL_QUERY_RESULT_AVAILABLE, &available); ogl_WarnIfError(); if (!available) break; // The frame's queries are now available, so retrieve and record all their results: double t = frame.timeStart; m_Storage.RecordFrameStart(t); for (size_t i = 0; i < frame.events.size(); ++i) { if (frame.events[i].isEnter) m_Storage.Record(CProfiler2::ITEM_ENTER, t, frame.events[i].id); else m_Storage.RecordLeave(t); // Associate the frame number with the "frame" region if (i == 0) m_Storage.RecordAttributePrintf("%u", frame.num); // Advance by the elapsed time to the next event GLuint64 queryElapsed = 0; pglGetQueryObjectui64vEXT(frame.events[i].query, GL_QUERY_RESULT, &queryElapsed); // (use the EXT-suffixed function here, as defined by GL_EXT_timer_query) ogl_WarnIfError(); t += (double)queryElapsed / 1e9; } PopFrontFrame(); } } void PopFrontFrame() { ENSURE(!m_Frames.empty()); SFrame& frame = m_Frames.front(); for (size_t i = 0; i < frame.events.size(); ++i) m_FreeQueries.push_back(frame.events[i].query); m_Frames.pop_front(); } }; ////////////////////////////////////////////////////////////////////////// /* * GL_INTEL_performance_queries is not officially documented * (see http://zaynar.co.uk/docs/gl-intel-performance-queries.html) * but it's potentially useful so we'll support it anyway. * It supports async queries giving elapsed time plus a load of other * counters that we'd like to use, and supports many simultaneous queries * (unlike GL_EXT_timer_query). * There are multiple query types (typically 2), each with its own set of * multiple counters. * On each enter-region event, we start a new set of queries. * On each leave-region event, we end the corresponding set of queries. * We can't tell the offsets between the enter events of nested regions, * so we pretend they all got entered at the same time. */ class CProfiler2GPU_INTEL_performance_queries : public CProfiler2GPU_base { struct SEvent { const char* id; bool isEnter; std::vector queries; // if isEnter, one per SPerfQueryType; else empty }; struct SFrame { u32 num; double timeStart; // CPU time at frame start std::vector events; std::vector activeRegions; // stack of indexes into events }; std::deque m_Frames; // Counters listed by the graphics driver for a particular query type struct SPerfCounter { std::string name; std::string desc; GLuint offset; GLuint size; GLuint type; }; // Query types listed by the graphics driver struct SPerfQueryType { GLuint queryTypeId; std::string name; GLuint counterBufferSize; std::vector counters; std::vector freeQueries; // query objects that are allocated but not currently in use }; std::vector m_QueryTypes; #define INTEL_PERFQUERIES_NONBLOCK 0x83FA #define INTEL_PERFQUERIES_BLOCK 0x83FB #define INTEL_PERFQUERIES_TYPE_UNSIGNED_INT 0x9402 #define INTEL_PERFQUERIES_TYPE_UNSIGNED_INT64 0x9403 #define INTEL_PERFQUERIES_TYPE_FLOAT 0x9404 #define INTEL_PERFQUERIES_TYPE_BOOL 0x9406 public: static bool IsSupported() { return ogl_HaveExtension("GL_INTEL_performance_queries"); } CProfiler2GPU_INTEL_performance_queries(CProfiler2& profiler) : CProfiler2GPU_base(profiler, "gpu_intel") { LoadPerfCounters(); } ~CProfiler2GPU_INTEL_performance_queries() { // Pop frames to return queries to the free list while (!m_Frames.empty()) PopFrontFrame(); for (size_t i = 0; i < m_QueryTypes.size(); ++i) for (size_t j = 0; j < m_QueryTypes[i].freeQueries.size(); ++j) pglDeletePerfQueryINTEL(m_QueryTypes[i].freeQueries[j]); ogl_WarnIfError(); } void FrameStart() { ProcessFrames(); SFrame frame; frame.num = m_Profiler.GetFrameNumber(); frame.timeStart = m_Profiler.GetTime(); m_Frames.push_back(frame); RegionEnter("frame"); } void FrameEnd() { RegionLeave("frame"); } void RegionEnter(const char* id) { ENSURE(!m_Frames.empty()); SFrame& frame = m_Frames.back(); SEvent event; event.id = id; event.isEnter = true; for (size_t i = 0; i < m_QueryTypes.size(); ++i) { GLuint id = NewQuery(i); pglBeginPerfQueryINTEL(id); ogl_WarnIfError(); event.queries.push_back(id); } frame.activeRegions.push_back(frame.events.size()); frame.events.push_back(event); } void RegionLeave(const char* id) { ENSURE(!m_Frames.empty()); SFrame& frame = m_Frames.back(); ENSURE(!frame.activeRegions.empty()); SEvent& activeEvent = frame.events[frame.activeRegions.back()]; for (size_t i = 0; i < m_QueryTypes.size(); ++i) { pglEndPerfQueryINTEL(activeEvent.queries[i]); ogl_WarnIfError(); } frame.activeRegions.pop_back(); SEvent event; event.id = id; event.isEnter = false; frame.events.push_back(event); } private: GLuint NewQuery(size_t queryIdx) { ENSURE(queryIdx < m_QueryTypes.size()); if (m_QueryTypes[queryIdx].freeQueries.empty()) { GLuint id; pglCreatePerfQueryINTEL(m_QueryTypes[queryIdx].queryTypeId, &id); ogl_WarnIfError(); return id; } GLuint id = m_QueryTypes[queryIdx].freeQueries.back(); m_QueryTypes[queryIdx].freeQueries.pop_back(); return id; } void ProcessFrames() { while (!m_Frames.empty()) { SFrame& frame = m_Frames.front(); // Queries don't become available in order, so check them all before // trying to read the results from any for (size_t j = 0; j < m_QueryTypes.size(); ++j) { size_t size = m_QueryTypes[j].counterBufferSize; shared_ptr buf(new char[size], ArrayDeleter()); for (size_t i = 0; i < frame.events.size(); ++i) { if (!frame.events[i].isEnter) continue; GLuint length = 0; pglGetPerfQueryDataINTEL(frame.events[i].queries[j], INTEL_PERFQUERIES_NONBLOCK, size, buf.get(), &length); ogl_WarnIfError(); if (length == 0) return; } } double lastTime = frame.timeStart; std::stack endTimes; m_Storage.RecordFrameStart(frame.timeStart); for (size_t i = 0; i < frame.events.size(); ++i) { if (frame.events[i].isEnter) { m_Storage.Record(CProfiler2::ITEM_ENTER, lastTime, frame.events[i].id); if (i == 0) m_Storage.RecordAttributePrintf("%u", frame.num); double elapsed = 0.0; for (size_t j = 0; j < m_QueryTypes.size(); ++j) { GLuint length; char* buf = new char[m_QueryTypes[j].counterBufferSize]; pglGetPerfQueryDataINTEL(frame.events[i].queries[j], INTEL_PERFQUERIES_BLOCK, m_QueryTypes[j].counterBufferSize, buf, &length); ogl_WarnIfError(); ENSURE(length == m_QueryTypes[j].counterBufferSize); m_Storage.RecordAttributePrintf("-- %s --", m_QueryTypes[j].name.c_str()); for (size_t k = 0; k < m_QueryTypes[j].counters.size(); ++k) { SPerfCounter& counter = m_QueryTypes[j].counters[k]; if (counter.type == INTEL_PERFQUERIES_TYPE_UNSIGNED_INT) { ENSURE(counter.size == 4); GLuint value = 0; memcpy(&value, buf + counter.offset, counter.size); m_Storage.RecordAttributePrintf("%s: %u", counter.name.c_str(), value); } else if (counter.type == INTEL_PERFQUERIES_TYPE_UNSIGNED_INT64) { ENSURE(counter.size == 8); GLuint64 value = 0; memcpy(&value, buf + counter.offset, counter.size); m_Storage.RecordAttributePrintf("%s: %.0f", counter.name.c_str(), (double)value); if (counter.name == "TotalTime") elapsed = (double)value / 1e6; } else if (counter.type == INTEL_PERFQUERIES_TYPE_FLOAT) { ENSURE(counter.size == 4); GLfloat value = 0; memcpy(&value, buf + counter.offset, counter.size); m_Storage.RecordAttributePrintf("%s: %f", counter.name.c_str(), value); } else if (counter.type == INTEL_PERFQUERIES_TYPE_BOOL) { ENSURE(counter.size == 4); GLuint value = 0; memcpy(&value, buf + counter.offset, counter.size); ENSURE(value == 0 || value == 1); m_Storage.RecordAttributePrintf("%s: %u", counter.name.c_str(), value); } else { //debug_warn(L"unrecognised Intel performance counter type"); } } delete[] buf; } endTimes.push(lastTime + elapsed); } else { lastTime = endTimes.top(); endTimes.pop(); m_Storage.RecordLeave(lastTime); } } PopFrontFrame(); } } void PopFrontFrame() { ENSURE(!m_Frames.empty()); SFrame& frame = m_Frames.front(); for (size_t i = 0; i < frame.events.size(); ++i) if (frame.events[i].isEnter) for (size_t j = 0; j < m_QueryTypes.size(); ++j) m_QueryTypes[j].freeQueries.push_back(frame.events[i].queries[j]); m_Frames.pop_front(); } void LoadPerfCounters() { GLuint queryTypeId; pglGetFirstPerfQueryIdINTEL(&queryTypeId); ogl_WarnIfError(); do { char queryName[256]; GLuint counterBufferSize, numCounters, maxQueries, unknown; pglGetPerfQueryInfoINTEL(queryTypeId, ARRAY_SIZE(queryName), queryName, &counterBufferSize, &numCounters, &maxQueries, &unknown); ogl_WarnIfError(); ENSURE(unknown == 1); SPerfQueryType query; query.queryTypeId = queryTypeId; query.name = queryName; query.counterBufferSize = counterBufferSize; for (GLuint counterId = 1; counterId <= numCounters; ++counterId) { char counterName[256]; char counterDesc[2048]; GLuint counterOffset, counterSize, counterUsage, counterType; GLuint64 unknown2; pglGetPerfCounterInfoINTEL(queryTypeId, counterId, ARRAY_SIZE(counterName), counterName, ARRAY_SIZE(counterDesc), counterDesc, &counterOffset, &counterSize, &counterUsage, &counterType, &unknown2); ogl_WarnIfError(); ENSURE(unknown2 == 0 || unknown2 == 1); SPerfCounter counter; counter.name = counterName; counter.desc = counterDesc; counter.offset = counterOffset; counter.size = counterSize; counter.type = counterType; query.counters.push_back(counter); } m_QueryTypes.push_back(query); pglGetNextPerfQueryIdINTEL(queryTypeId, &queryTypeId); ogl_WarnIfError(); } while (queryTypeId); } }; ////////////////////////////////////////////////////////////////////////// CProfiler2GPU::CProfiler2GPU(CProfiler2& profiler) : m_Profiler(profiler), m_ProfilerARB(NULL), m_ProfilerEXT(NULL), m_ProfilerINTEL(NULL) { bool enabledARB = false; bool enabledEXT = false; bool enabledINTEL = false; CFG_GET_VAL("profiler2.gpu.arb.enable", enabledARB); CFG_GET_VAL("profiler2.gpu.ext.enable", enabledEXT); CFG_GET_VAL("profiler2.gpu.intel.enable", enabledINTEL); // Only enable either ARB or EXT, not both, because they are redundant // (EXT is only needed for compatibility with older systems), and because // using both triggers GL_INVALID_OPERATION on AMD drivers (see comment // in CProfiler2GPU_EXT_timer_query::RecordRegion) if (enabledARB && CProfiler2GPU_ARB_timer_query::IsSupported()) { m_ProfilerARB = new CProfiler2GPU_ARB_timer_query(m_Profiler); } else if (enabledEXT && CProfiler2GPU_EXT_timer_query::IsSupported()) { m_ProfilerEXT = new CProfiler2GPU_EXT_timer_query(m_Profiler); } // The INTEL mode should be compatible with ARB/EXT (though no current // drivers support both), and provides complementary data, so enable it // when possible if (enabledINTEL && CProfiler2GPU_INTEL_performance_queries::IsSupported()) { m_ProfilerINTEL = new CProfiler2GPU_INTEL_performance_queries(m_Profiler); } } CProfiler2GPU::~CProfiler2GPU() { SAFE_DELETE(m_ProfilerARB); SAFE_DELETE(m_ProfilerEXT); SAFE_DELETE(m_ProfilerINTEL); } void CProfiler2GPU::FrameStart() { if (m_ProfilerARB) m_ProfilerARB->FrameStart(); if (m_ProfilerEXT) m_ProfilerEXT->FrameStart(); if (m_ProfilerINTEL) m_ProfilerINTEL->FrameStart(); } void CProfiler2GPU::FrameEnd() { if (m_ProfilerARB) m_ProfilerARB->FrameEnd(); if (m_ProfilerEXT) m_ProfilerEXT->FrameEnd(); if (m_ProfilerINTEL) m_ProfilerINTEL->FrameEnd(); } void CProfiler2GPU::RegionEnter(const char* id) { if (m_ProfilerARB) m_ProfilerARB->RegionEnter(id); if (m_ProfilerEXT) m_ProfilerEXT->RegionEnter(id); if (m_ProfilerINTEL) m_ProfilerINTEL->RegionEnter(id); } void CProfiler2GPU::RegionLeave(const char* id) { if (m_ProfilerARB) m_ProfilerARB->RegionLeave(id); if (m_ProfilerEXT) m_ProfilerEXT->RegionLeave(id); if (m_ProfilerINTEL) m_ProfilerINTEL->RegionLeave(id); } #else // CONFIG2_GLES CProfiler2GPU::CProfiler2GPU(CProfiler2& profiler) : m_Profiler(profiler), m_ProfilerARB(NULL), m_ProfilerEXT(NULL), m_ProfilerINTEL(NULL) { } CProfiler2GPU::~CProfiler2GPU() { } void CProfiler2GPU::FrameStart() { } void CProfiler2GPU::FrameEnd() { } void CProfiler2GPU::RegionEnter(const char* UNUSED(id)) { } void CProfiler2GPU::RegionLeave(const char* UNUSED(id)) { } #endif Index: ps/trunk/source/tools/i18n/extractors/jslexer.py =================================================================== --- ps/trunk/source/tools/i18n/extractors/jslexer.py (revision 19898) +++ ps/trunk/source/tools/i18n/extractors/jslexer.py (revision 19899) @@ -1,192 +1,192 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2008-2011 Edgewall Software -# Copyright (C) 2013-2014 Wildfire Games +# Copyright (C) 2008-2011 Edgewall Software. +# Copyright (C) 2013-2014 Wildfire Games. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the # following conditions are met: # # Redistributions of source code must retain the above copyright notice, this list of conditions and the following # disclaimer. # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided with the distribution. # The name of the author may not be used to endorse or promote products derived from this software without specific # prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs: # • http://babel.edgewall.org/log/trunk/babel/messages # • http://trac.wildfiregames.com/browser/ps/trunk/source/tools/i18n/extractors/jslexer.py """A simple JavaScript 1.5 lexer which is used for the JavaScript extractor. """ from __future__ import absolute_import, division, print_function, unicode_literals from operator import itemgetter import re operators = [ '+', '-', '*', '%', '!=', '==', '<', '>', '<=', '>=', '=', '+=', '-=', '*=', '%=', '<<', '>>', '>>>', '<<=', '>>=', '>>>=', '&', '&=', '|', '|=', '&&', '||', '^', '^=', '(', ')', '[', ']', '{', '}', '!', '--', '++', '~', ',', ';', '.', ':' ] operators.sort(key=lambda x: -len(x)) escapes = {'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t'} rules = [ (None, re.compile(r'\s+(?u)')), (None, re.compile(r'