Index: ps/trunk/source/lib/sysdep/os/win/wstartup.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wstartup.cpp (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/wstartup.cpp (nonexistent) @@ -1,123 +0,0 @@ -/* 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))) Property changes on: ps/trunk/source/lib/sysdep/os/win/wstartup.cpp ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/lib/sysdep/os/win/winit.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/winit.cpp (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/winit.cpp (nonexistent) @@ -1,95 +0,0 @@ -/* 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); -} Property changes on: ps/trunk/source/lib/sysdep/os/win/winit.cpp ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/lib/sysdep/os/win/wstartup.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wstartup.h (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/wstartup.h (nonexistent) @@ -1,45 +0,0 @@ -/* 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.) Property changes on: ps/trunk/source/lib/sysdep/os/win/wstartup.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/lib/sysdep/os/win/winit.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/winit.h (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/winit.h (nonexistent) @@ -1,172 +0,0 @@ -/* 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 Property changes on: ps/trunk/source/lib/sysdep/os/win/winit.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/build/premake/premake5.lua =================================================================== --- ps/trunk/build/premake/premake5.lua (revision 27809) +++ ps/trunk/build/premake/premake5.lua (revision 27810) @@ -1,1566 +1,1561 @@ newoption { trigger = "android", description = "Use non-working Android cross-compiling mode" } newoption { trigger = "atlas", description = "Include Atlas scenario editor projects" } newoption { trigger = "coverage", description = "Enable code coverage data collection (GCC only)" } newoption { trigger = "gles", description = "Use non-working OpenGL ES 2.0 mode" } newoption { trigger = "icc", description = "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)" } newoption { trigger = "jenkins-tests", description = "Configure CxxTest to use the XmlPrinter runner which produces Jenkins-compatible output" } newoption { trigger = "minimal-flags", description = "Only set compiler/linker flags that are really needed. Has no effect on Windows builds" } newoption { trigger = "outpath", description = "Location for generated project files" } newoption { trigger = "with-system-mozjs", description = "Search standard paths for libmozjs91, instead of using bundled copy" } newoption { trigger = "with-system-nvtt", description = "Search standard paths for nvidia-texture-tools library, instead of using bundled copy" } newoption { trigger = "with-valgrind", description = "Enable Valgrind support (non-Windows only)" } newoption { trigger = "without-audio", description = "Disable use of OpenAL/Ogg/Vorbis APIs" } newoption { trigger = "without-lobby", description = "Disable the use of gloox and the multiplayer lobby" } newoption { trigger = "without-miniupnpc", description = "Disable use of miniupnpc for port forwarding" } newoption { trigger = "without-nvtt", description = "Disable use of NVTT" } newoption { trigger = "without-pch", description = "Disable generation and usage of precompiled headers" } newoption { trigger = "without-tests", description = "Disable generation of test projects" } -- Linux/BSD specific options newoption { trigger = "prefer-local-libs", description = "Prefer locally built libs. Any local libraries used must also be listed within a file within /etc/ld.so.conf.d so the dynamic linker can find them at runtime." } -- OS X specific options newoption { trigger = "macosx-version-min", description = "Set minimum required version of the OS X API, the build will possibly fail if an older SDK is used, while newer API functions will be weakly linked (i.e. resolved at runtime)" } newoption { trigger = "sysroot", description = "Set compiler system root path, used for building against a non-system SDK. For example /usr/local becomes SYSROOT/user/local" } -- Windows specific options newoption { trigger = "build-shared-glooxwrapper", description = "Rebuild glooxwrapper DLL for Windows. Requires the same compiler version that gloox was built with" } newoption { trigger = "use-shared-glooxwrapper", description = "Use prebuilt glooxwrapper DLL for Windows" } newoption { trigger = "large-address-aware", description = "Make the executable large address aware. Do not use for development, in order to spot memory issues easily" } -- Install options newoption { trigger = "bindir", description = "Directory for executables (typically '/usr/games'); default is to be relocatable" } newoption { trigger = "datadir", description = "Directory for data files (typically '/usr/share/games/0ad'); default is ../data/ relative to executable" } newoption { trigger = "libdir", description = "Directory for libraries (typically '/usr/lib/games/0ad'); default is ./ relative to executable" } -- Root directory of project checkout relative to this .lua file rootdir = "../.." dofile("extern_libs5.lua") -- detect compiler for non-Windows if os.istarget("macosx") then cc = "clang" elseif os.istarget("linux") and _OPTIONS["icc"] then cc = "icc" elseif os.istarget("bsd") and os.getversion().description == "FreeBSD" then cc = "clang" elseif not os.istarget("windows") then cc = os.getenv("CC") if cc == nil or cc == "" then local hasgcc = os.execute("which gcc > .gccpath") local f = io.open(".gccpath", "r") local gccpath = f:read("*line") f:close() os.execute("rm .gccpath") if gccpath == nil then cc = "clang" else cc = "gcc" end end end -- detect CPU architecture (simplistic) -- The user can target an architecture with HOSTTYPE, but the game still selects some know value. arch = "x86" macos_arch = "x86_64" if _OPTIONS["android"] then arch = "arm" elseif os.istarget("windows") then if os.getenv("HOSTTYPE") then arch = os.getenv("HOSTTYPE") elseif os.getenv("PROCESSOR_ARCHITECTURE") == "amd64" or os.getenv("PROCESSOR_ARCHITEW6432") == "amd64" then arch = "amd64" end else local machine = "x86_64" if os.getenv("HOSTTYPE") and os.getenv("HOSTTYPE") ~= '' then machine = os.getenv("HOSTTYPE") else os.execute(cc .. " -dumpmachine > .gccmachine.tmp") local f = io.open(".gccmachine.tmp", "r") machine = f:read("*line") f:close() end -- Special handling on mac os where xcode needs special flags. if os.istarget("macosx") then if string.find(machine, "arm64") then arch = "aarch64" macos_arch = "arm64" else arch = "amd64" macos_arch = "x86_64" end elseif string.find(machine, "x86_64") == 1 or string.find(machine, "amd64") == 1 then arch = "amd64" elseif string.find(machine, "i.86") == 1 then arch = "x86" elseif string.find(machine, "arm") == 1 then arch = "arm" elseif string.find(machine, "aarch64") == 1 then arch = "aarch64" elseif string.find(machine, "e2k") == 1 then arch = "e2k" elseif string.find(machine, "ppc64") == 1 or string.find(machine, "powerpc64") == 1 then arch = "ppc64" else print("WARNING: Cannot determine architecture from GCC, assuming x86") end end -- Test whether we need to link libexecinfo. -- This is mostly the case on musl systems, as well as on BSD systems : only glibc provides the -- backtrace symbols we require in the libc, for other libcs we use the libexecinfo library. local link_execinfo = false if os.istarget("bsd") then link_execinfo = true elseif os.istarget("linux") then local _, link_errorCode = os.outputof(cc .. " ./tests/execinfo.c -o /dev/null") if link_errorCode ~= 0 then link_execinfo = true end end -- Set up the Workspace workspace "pyrogenesis" targetdir(rootdir.."/binaries/system") libdirs(rootdir.."/binaries/system") if not _OPTIONS["outpath"] then error("You must specify the 'outpath' parameter") end location(_OPTIONS["outpath"]) configurations { "Release", "Debug" } source_root = rootdir.."/source/" -- default for most projects - overridden by local in others -- Rationale: projects should not have any additional include paths except for -- those required by external libraries. Instead, we should always write the -- full relative path, e.g. #include "maths/Vector3d.h". This avoids confusion -- ("which file is meant?") and avoids enormous include path lists. -- projects: engine static libs, main exe, atlas, atlas frontends, test. -------------------------------------------------------------------------------- -- project helper functions -------------------------------------------------------------------------------- function project_set_target(project_name) -- Note: On Windows, ".exe" is added on the end, on unices the name is used directly local obj_dir_prefix = _OPTIONS["outpath"].."/obj/"..project_name.."_" filter "Debug" objdir(obj_dir_prefix.."Debug") targetsuffix("_dbg") filter "Release" objdir(obj_dir_prefix.."Release") filter { } end function project_set_build_flags() editandcontinue "Off" if not _OPTIONS["minimal-flags"] then symbols "On" end if cc ~= "icc" and (os.istarget("windows") or not _OPTIONS["minimal-flags"]) then -- adds the -Wall compiler flag warnings "Extra" -- this causes far too many warnings/remarks on ICC end -- disable Windows debug heap, since it makes malloc/free hugely slower when -- running inside a debugger if os.istarget("windows") then debugenvs { "_NO_DEBUG_HEAP=1" } end filter "Debug" defines { "DEBUG" } filter "Release" if os.istarget("windows") or not _OPTIONS["minimal-flags"] then optimize "Speed" end defines { "NDEBUG", "CONFIG_FINAL=1" } filter { } if _OPTIONS["gles"] then defines { "CONFIG2_GLES=1" } end if _OPTIONS["without-audio"] then defines { "CONFIG2_AUDIO=0" } end if _OPTIONS["without-nvtt"] then defines { "CONFIG2_NVTT=0" } end if _OPTIONS["without-lobby"] then defines { "CONFIG2_LOBBY=0" } end if _OPTIONS["without-miniupnpc"] then defines { "CONFIG2_MINIUPNPC=0" } end -- Enable C++17 standard. filter "action:vs*" buildoptions { "/std:c++17" } filter "action:not vs*" buildoptions { "-std=c++17" } filter {} -- various platform-specific build flags if os.istarget("windows") then flags { "MultiProcessorCompile" } -- Since KB4088875 Windows 7 has a soft requirement for SSE2. -- Windows 8+ and Firefox ESR52 make it hard requirement. -- Finally since VS2012 it's enabled implicitely when not set. vectorextensions "SSE2" -- use native wchar_t type (not typedef to unsigned short) nativewchar "on" else -- *nix -- TODO, FIXME: This check is incorrect because it means that some additional flags will be added inside the "else" branch if the -- compiler is ICC and minimal-flags is specified (ticket: #2994) if cc == "icc" and not _OPTIONS["minimal-flags"] then buildoptions { "-w1", -- "-Wabi", -- "-Wp64", -- complains about OBJECT_TO_JSVAL which is annoying "-Wpointer-arith", "-Wreturn-type", -- "-Wshadow", "-Wuninitialized", "-Wunknown-pragmas", "-Wunused-function", "-wd1292" -- avoid lots of 'attribute "__nonnull__" ignored' } filter "Debug" buildoptions { "-O0" } -- ICC defaults to -O2 filter { } if os.istarget("macosx") then linkoptions { "-multiply_defined","suppress" } end else -- exclude most non-essential build options for minimal-flags if not _OPTIONS["minimal-flags"] then buildoptions { -- enable most of the standard warnings "-Wno-switch", -- enumeration value not handled in switch (this is sometimes useful, but results in lots of noise) "-Wno-reorder", -- order of initialization list in constructors (lots of noise) "-Wno-invalid-offsetof", -- offsetof on non-POD types (see comment in renderer/PatchRData.cpp) "-Wextra", "-Wno-missing-field-initializers", -- (this is common in external headers we can't fix) -- add some other useful warnings that need to be enabled explicitly "-Wunused-parameter", "-Wredundant-decls", -- (useful for finding some multiply-included header files) -- "-Wformat=2", -- (useful sometimes, but a bit noisy, so skip it by default) -- "-Wcast-qual", -- (useful for checking const-correctness, but a bit noisy, so skip it by default) "-Wnon-virtual-dtor", -- (sometimes noisy but finds real bugs) "-Wundef", -- (useful for finding macro name typos) -- enable security features (stack checking etc) that shouldn't have -- a significant effect on performance and can catch bugs "-fstack-protector-all", "-U_FORTIFY_SOURCE", -- (avoid redefinition warning if already defined) "-D_FORTIFY_SOURCE=2", -- always enable strict aliasing (useful in debug builds because of the warnings) "-fstrict-aliasing", -- don't omit frame pointers (for now), because performance will be impacted -- negatively by the way this breaks profilers more than it will be impacted -- positively by the optimisation "-fno-omit-frame-pointer" } if not _OPTIONS["without-pch"] then buildoptions { -- do something (?) so that ccache can handle compilation with PCH enabled -- (ccache 3.1+ also requires CCACHE_SLOPPINESS=time_macros for this to work) "-fpch-preprocess" } end if os.istarget("linux") or os.istarget("bsd") then buildoptions { "-fPIC" } linkoptions { "-Wl,--no-undefined", "-Wl,--as-needed", "-Wl,-z,relro" } end if arch == "x86" then buildoptions { -- To support intrinsics like __sync_bool_compare_and_swap on x86 -- we need to set -march to something that supports them (i686). -- We use pentium3 to also enable other features like mmx and sse, -- while tuning for generic to have good performance on every -- supported CPU. -- Note that all these features are already supported on amd64. "-march=pentium3 -mtune=generic", -- This allows x86 operating systems to handle the 2GB+ public mod. "-D_FILE_OFFSET_BITS=64" } end end if arch == "arm" then -- disable warnings about va_list ABI change and use -- compile-time flags for futher configuration. buildoptions { "-Wno-psabi" } if _OPTIONS["android"] then -- Android uses softfp, so we should too. buildoptions { "-mfloat-abi=softfp" } end end if _OPTIONS["coverage"] then buildoptions { "-fprofile-arcs", "-ftest-coverage" } links { "gcov" } end -- MacOS 10.12 only supports intel processors with SSE 4.1, so enable that. if os.istarget("macosx") and arch == "amd64" then buildoptions { "-msse4.1" } end -- Check if SDK path should be used if _OPTIONS["sysroot"] then buildoptions { "-isysroot " .. _OPTIONS["sysroot"] } linkoptions { "-Wl,-syslibroot," .. _OPTIONS["sysroot"] } end -- On OS X, sometimes we need to specify the minimum API version to use if _OPTIONS["macosx-version-min"] then buildoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } -- clang and llvm-gcc look at mmacosx-version-min to determine link target -- and CRT version, and use it to set the macosx_version_min linker flag linkoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } end -- Only libc++ is supported on MacOS if os.istarget("macosx") then buildoptions { "-stdlib=libc++" } linkoptions { "-stdlib=libc++" } end end buildoptions { -- Hide symbols in dynamic shared objects by default, for efficiency and for equivalence with -- Windows - they should be exported explicitly with __attribute__ ((visibility ("default"))) "-fvisibility=hidden" } if _OPTIONS["bindir"] then defines { "INSTALLED_BINDIR=" .. _OPTIONS["bindir"] } end if _OPTIONS["datadir"] then defines { "INSTALLED_DATADIR=" .. _OPTIONS["datadir"] } end if _OPTIONS["libdir"] then defines { "INSTALLED_LIBDIR=" .. _OPTIONS["libdir"] } end if os.istarget("linux") or os.istarget("bsd") then if _OPTIONS["prefer-local-libs"] then libdirs { "/usr/local/lib" } end -- To use our local shared libraries, they need to be found in the -- runtime dynamic linker path. Add their path to -rpath. if _OPTIONS["libdir"] then linkoptions {"-Wl,-rpath," .. _OPTIONS["libdir"] } else -- On FreeBSD we need to allow use of $ORIGIN if os.istarget("bsd") then linkoptions { "-Wl,-z,origin" } end -- Adding the executable path and taking care of correct escaping if _ACTION == "gmake" then linkoptions { "-Wl,-rpath,'$$ORIGIN'" } elseif _ACTION == "codeblocks" then linkoptions { "-Wl,-R\\\\$$$ORIGIN" } end end end end end -- create a project and set the attributes that are common to all projects. function project_create(project_name, target_type) project(project_name) language "C++" kind(target_type) filter "action:vs2017" toolset "v141_xp" filter {} filter "action:vs*" buildoptions "/utf-8" filter {} project_set_target(project_name) project_set_build_flags() end -- OSX creates a .app bundle if the project type of the main application is set to "WindowedApp". -- We don't want this because this bundle would be broken (it lacks all the resources and external dependencies, Info.plist etc...) -- Windows opens a console in the background if it's set to ConsoleApp, which is not what we want. -- I didn't check if this setting matters for linux, but WindowedApp works there. function get_main_project_target_type() if _OPTIONS["android"] then return "SharedLib" elseif os.istarget("macosx") then return "ConsoleApp" else return "WindowedApp" end end -- source_root: rel_source_dirs and rel_include_dirs are relative to this directory -- rel_source_dirs: A table of subdirectories. All source files in these directories are added. -- rel_include_dirs: A table of subdirectories to be included. -- extra_params: table including zero or more of the following: -- * no_pch: If specified, no precompiled headers are used for this project. -- * pch_dir: If specified, this directory will be used for precompiled headers instead of the default -- /pch//. -- * extra_files: table of filenames (relative to source_root) to add to project -- * extra_links: table of library names to add to link step function project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) for i,v in pairs(rel_source_dirs) do local prefix = source_root..v.."/" files { prefix.."*.cpp", prefix.."*.h", prefix.."*.inl", prefix.."*.js", prefix.."*.asm", prefix.."*.mm" } end -- Put the project-specific PCH directory at the start of the -- include path, so '#include "precompiled.h"' will look in -- there first local pch_dir if not extra_params["pch_dir"] then pch_dir = source_root .. "pch/" .. project().name .. "/" else pch_dir = extra_params["pch_dir"] end includedirs { pch_dir } -- Precompiled Headers -- rationale: we need one PCH per static lib, since one global header would -- increase dependencies. To that end, we can either include them as -- "projectdir/precompiled.h", or add "source/PCH/projectdir" to the -- include path and put the PCH there. The latter is better because -- many projects contain several dirs and it's unclear where there the -- PCH should be stored. This way is also a bit easier to use in that -- source files always include "precompiled.h". -- Notes: -- * Visual Assist manages to use the project include path and can -- correctly open these files from the IDE. -- * precompiled.cpp (needed to "Create" the PCH) also goes in -- the abovementioned dir. if (not _OPTIONS["without-pch"] and not extra_params["no_pch"]) then filter "action:vs*" pchheader("precompiled.h") filter "action:xcode*" pchheader("../"..pch_dir.."precompiled.h") filter { "action:not vs*", "action:not xcode*" } pchheader(pch_dir.."precompiled.h") filter {} pchsource(pch_dir.."precompiled.cpp") defines { "CONFIG_ENABLE_PCH=1" } files { pch_dir.."precompiled.h", pch_dir.."precompiled.cpp" } else defines { "CONFIG_ENABLE_PCH=0" } flags { "NoPCH" } end -- next is source root dir, for absolute (nonrelative) includes -- (e.g. "lib/precompiled.h") includedirs { source_root } for i,v in pairs(rel_include_dirs) do includedirs { source_root .. v } end if extra_params["extra_files"] then for i,v in pairs(extra_params["extra_files"]) do -- .rc files are only needed on Windows if path.getextension(v) ~= ".rc" or os.istarget("windows") then files { source_root .. v } end end end if extra_params["extra_links"] then links { extra_params["extra_links"] } end end -- Add command-line options to set up the manifest dependencies for Windows -- (See lib/sysdep/os/win/manifest.cpp) function project_add_manifest() linkoptions { "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df'\"" } end -------------------------------------------------------------------------------- -- engine static libraries -------------------------------------------------------------------------------- -- the engine is split up into several static libraries. this eases separate -- distribution of those components, reduces dependencies a bit, and can -- also speed up builds. -- more to the point, it is necessary to efficiently support a separate -- test executable that also includes much of the game code. -- names of all static libs created. automatically added to the -- main app project later (see explanation at end of this file) static_lib_names = {} static_lib_names_debug = {} static_lib_names_release = {} -- set up one of the static libraries into which the main engine code is split. -- extra_params: -- no_default_link: If specified, linking won't be done by default. -- For the rest of extra_params, see project_add_contents(). -- note: rel_source_dirs and rel_include_dirs are relative to global source_root. function setup_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "StaticLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end -- Deactivate Run Time Type Information. Performance of dynamic_cast is very poor. -- The exception to this principle is Atlas UI, which is not a static library. rtti "off" if os.istarget("macosx") then architecture(macos_arch) buildoptions { "-arch " .. macos_arch } linkoptions { "-arch " .. macos_arch } xcodebuildsettings { ARCHS = macos_arch } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end end function setup_third_party_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) setup_static_lib_project(project_name, rel_source_dirs, extern_libs, extra_params) includedirs { source_root .. "third_party/" .. project_name .. "/include/" } end function setup_shared_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "SharedLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end if os.istarget("windows") then links { "delayimp" } elseif os.istarget("macosx") then architecture(macos_arch) buildoptions { "-arch " .. macos_arch } linkoptions { "-arch " .. macos_arch } xcodebuildsettings { ARCHS = macos_arch } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end end -- this is where the source tree is chopped up into static libs. -- can be changed very easily; just copy+paste a new setup_static_lib_project, -- or remove existing ones. static libs are automagically added to -- main_exe link step. function setup_all_libs () -- relative to global source_root. local source_dirs = {} -- names of external libraries used (see libraries_dir comment) local extern_libs = {} source_dirs = { "network", } extern_libs = { "spidermonkey", "enet", "sdl", "boost", -- dragged in via server->simulation.h->random and NetSession.h->lockfree "fmt", } if not _OPTIONS["without-miniupnpc"] then table.insert(extern_libs, "miniupnpc") end setup_static_lib_project("network", source_dirs, extern_libs, {}) source_dirs = { "rlinterface", } extern_libs = { "boost", -- dragged in via simulation.h and scriptinterface.h "fmt", "spidermonkey", } setup_static_lib_project("rlinterface", source_dirs, extern_libs, { no_pch = 1 }) source_dirs = { "third_party/tinygettext/src", } extern_libs = { "iconv", "boost", "fmt", } setup_third_party_static_lib_project("tinygettext", source_dirs, extern_libs, { } ) -- it's an external library and we don't want to modify its source to fix warnings, so we just disable them to avoid noise in the compile output filter "action:vs*" buildoptions { "/wd4127", "/wd4309", "/wd4800", "/wd4100", "/wd4996", "/wd4099", "/wd4503" } filter {} if not _OPTIONS["without-lobby"] then source_dirs = { "lobby", "lobby/scripting", "i18n", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "enet", "gloox", "icu", "iconv", "libsodium", "tinygettext", "fmt", } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) if _OPTIONS["use-shared-glooxwrapper"] and not _OPTIONS["build-shared-glooxwrapper"] then table.insert(static_lib_names_debug, "glooxwrapper_dbg") table.insert(static_lib_names_release, "glooxwrapper") else source_dirs = { "lobby/glooxwrapper", } extern_libs = { "boost", "gloox", "fmt", } if _OPTIONS["build-shared-glooxwrapper"] then setup_shared_lib_project("glooxwrapper", source_dirs, extern_libs, {}) else setup_static_lib_project("glooxwrapper", source_dirs, extern_libs, {}) end end else source_dirs = { "lobby/scripting", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "libsodium", "fmt", } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) files { source_root.."lobby/Globals.cpp" } end source_dirs = { "simulation2", "simulation2/components", "simulation2/helpers", "simulation2/scripting", "simulation2/serialization", "simulation2/system", "simulation2/testcomponents", } extern_libs = { "boost", "spidermonkey", "fmt", } setup_static_lib_project("simulation2", source_dirs, extern_libs, {}) source_dirs = { "scriptinterface", "scriptinterface/third_party" } extern_libs = { "boost", "spidermonkey", "valgrind", "sdl", "fmt", } setup_static_lib_project("scriptinterface", source_dirs, extern_libs, {}) source_dirs = { "ps", "ps/scripting", "network/scripting", "ps/GameSetup", "ps/XMB", "ps/XML", "soundmanager", "soundmanager/data", "soundmanager/items", "soundmanager/scripting", "maths", "maths/scripting", "i18n", "i18n/scripting", } extern_libs = { "spidermonkey", "sdl", -- key definitions "libxml2", "zlib", "boost", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", "fmt", "freetype", } if not _OPTIONS["without-nvtt"] then table.insert(extern_libs, "nvtt") end if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") table.insert(extern_libs, "vorbis") end setup_static_lib_project("engine", source_dirs, extern_libs, {}) source_dirs = { "graphics", "graphics/scripting", "renderer", "renderer/backend", "renderer/backend/dummy", "renderer/backend/gl", "renderer/backend/vulkan", "renderer/scripting", "third_party/mikktspace", "third_party/ogre3d_preprocessor", "third_party/vma" } extern_libs = { "sdl", -- key definitions "spidermonkey", -- for graphics/scripting "boost", "fmt", "freetype", "icu", } if not _OPTIONS["without-nvtt"] then table.insert(extern_libs, "nvtt") end setup_static_lib_project("graphics", source_dirs, extern_libs, {}) source_dirs = { "tools/atlas/GameInterface", "tools/atlas/GameInterface/Handlers" } extern_libs = { "boost", "sdl", -- key definitions "spidermonkey", "fmt", } setup_static_lib_project("atlas", source_dirs, extern_libs, {}) source_dirs = { "gui", "gui/ObjectTypes", "gui/ObjectBases", "gui/Scripting", "gui/SettingTypes", "i18n" } extern_libs = { "spidermonkey", "sdl", -- key definitions "boost", "enet", "tinygettext", "icu", "iconv", "fmt", } if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") end setup_static_lib_project("gui", source_dirs, extern_libs, {}) source_dirs = { "lib", "lib/adts", "lib/allocators", "lib/external_libraries", "lib/file", "lib/file/archive", "lib/file/common", "lib/file/io", "lib/file/vfs", "lib/pch", "lib/posix", "lib/res", "lib/res/graphics", "lib/sysdep", "lib/tex" } extern_libs = { "boost", "sdl", "openal", "libpng", "zlib", "valgrind", "cxxtest", "fmt", } -- CPU architecture-specific if arch == "amd64" then table.insert(source_dirs, "lib/sysdep/arch/amd64"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "x86" then table.insert(source_dirs, "lib/sysdep/arch/ia32"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "arm" then table.insert(source_dirs, "lib/sysdep/arch/arm"); elseif arch == "aarch64" then table.insert(source_dirs, "lib/sysdep/arch/aarch64"); elseif arch == "e2k" then table.insert(source_dirs, "lib/sysdep/arch/e2k"); elseif arch == "ppc64" then table.insert(source_dirs, "lib/sysdep/arch/ppc64"); end -- OS-specific sysdep_dirs = { linux = { "lib/sysdep/os/linux", "lib/sysdep/os/unix" }, -- note: RC file must be added to main_exe project. -- note: don't add "lib/sysdep/os/win/aken.cpp" because that must be compiled with the DDK. windows = { "lib/sysdep/os/win", "lib/sysdep/os/win/wposix", "lib/sysdep/os/win/whrt" }, macosx = { "lib/sysdep/os/osx", "lib/sysdep/os/unix" }, bsd = { "lib/sysdep/os/bsd", "lib/sysdep/os/unix", "lib/sysdep/os/unix/x" }, } for i,v in pairs(sysdep_dirs[os.target()]) do table.insert(source_dirs, v); end if os.istarget("linux") then if _OPTIONS["android"] then table.insert(source_dirs, "lib/sysdep/os/android") else table.insert(source_dirs, "lib/sysdep/os/unix/x") end end -- On OSX, disable precompiled headers because C++ files and Objective-C++ files are -- mixed in this project. To fix that, we would need per-file basis configuration which -- is not yet supported by the gmake action in premake. We should look into using gmake2. extra_params = {} if os.istarget("macosx") then extra_params = { no_pch = 1 } end -- runtime-library-specific if _ACTION == "vs2017" then table.insert(source_dirs, "lib/sysdep/rtl/msc"); else table.insert(source_dirs, "lib/sysdep/rtl/gcc"); end setup_static_lib_project("lowlevel", source_dirs, extern_libs, extra_params) extern_libs = { "glad" } if not os.istarget("windows") and not _OPTIONS["android"] and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(used_extern_libs, "x11") end setup_static_lib_project("gladwrapper", {}, used_extern_libs, { no_pch = 1 }) glad_path = libraries_source_dir.."glad/" if externalincludedirs then externalincludedirs { glad_path.."include" } else sysincludedirs { glad_path.."include" } end files { glad_path.."src/vulkan.cpp" } if _OPTIONS["gles"] then files { glad_path.."src/gles2.cpp" } else files { glad_path.."src/gl.cpp" } if os.istarget("windows") then files { glad_path.."src/wgl.cpp" } elseif os.istarget("linux") or os.istarget("bsd") then files { glad_path.."src/egl.cpp", glad_path.."src/glx.cpp" } end end -- Third-party libraries that are built as part of the main project, -- not built externally and then linked source_dirs = { "third_party/mongoose", } extern_libs = { } setup_static_lib_project("mongoose", source_dirs, extern_libs, { no_pch = 1 }) -- CxxTest mock function support extern_libs = { "boost", "cxxtest", } -- 'real' implementations, to be linked against the main executable -- (files are added manually and not with setup_static_lib_project -- because not all files in the directory are included) setup_static_lib_project("mocks_real", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { "mocks/*.h", source_root.."mocks/*_real.cpp" } -- 'test' implementations, to be linked against the test executable setup_static_lib_project("mocks_test", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { source_root.."mocks/*.h", source_root.."mocks/*_test.cpp" } end -------------------------------------------------------------------------------- -- main EXE -------------------------------------------------------------------------------- -- used for main EXE as well as test used_extern_libs = { "sdl", "libpng", "zlib", "spidermonkey", "libxml2", "boost", "cxxtest", "comsuppw", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", "fmt", "freetype", "valgrind", } if not os.istarget("windows") and not _OPTIONS["android"] and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(used_extern_libs, "x11") end if not _OPTIONS["without-audio"] then table.insert(used_extern_libs, "openal") table.insert(used_extern_libs, "vorbis") end if not _OPTIONS["without-nvtt"] then table.insert(used_extern_libs, "nvtt") end if not _OPTIONS["without-lobby"] then table.insert(used_extern_libs, "gloox") end if not _OPTIONS["without-miniupnpc"] then table.insert(used_extern_libs, "miniupnpc") end -- Bundles static libs together with main.cpp and builds game executable. function setup_main_exe () local target_type = get_main_project_target_type() project_create("pyrogenesis", target_type) filter "system:not macosx" linkgroups 'On' filter {} links { "mocks_real" } local extra_params = { extra_files = { "main.cpp" }, no_pch = 1 } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } rtti "off" -- Platform Specifics if os.istarget("windows") then files { source_root.."lib/sysdep/os/win/icon.rc" } -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } linkoptions { -- wraps main thread in a __try block(see wseh.cpp). replace with mainCRTStartup if that's undesired. "/ENTRY:wseh_EntryPoint", - - -- see wstartup.h - "/INCLUDE:_wstartup_InitAndRegisterShutdown", } links { "delayimp" } -- allow the executable to use more than 2GB of RAM. -- this should not be enabled during development, so that memory issues are easily spotted. if _OPTIONS["large-address-aware"] then linkoptions { "/LARGEADDRESSAWARE" } end -- see manifest.cpp project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } links { "log" } end if link_execinfo then links { "execinfo" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } elseif os.istarget("macosx") then links { "pthread" } links { "ApplicationServices.framework", "Cocoa.framework", "CoreFoundation.framework" } architecture(macos_arch) buildoptions { "-arch " .. macos_arch } linkoptions { "-arch " .. macos_arch } xcodebuildsettings { ARCHS = macos_arch } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end end -------------------------------------------------------------------------------- -- atlas -------------------------------------------------------------------------------- -- setup a typical Atlas component project -- extra_params, rel_source_dirs and rel_include_dirs: as in project_add_contents; function setup_atlas_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) local source_root = rootdir.."/source/tools/atlas/" .. project_name .. "/" project_create(project_name, target_type) -- if not specified, the default for atlas pch files is in the project root. if not extra_params["pch_dir"] then extra_params["pch_dir"] = source_root end project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then -- Link to required libraries links { "winmm", "delayimp" } elseif os.istarget("macosx") then architecture(macos_arch) buildoptions { "-arch " .. macos_arch } linkoptions { "-arch " .. macos_arch } xcodebuildsettings { ARCHS = macos_arch } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end elseif os.istarget("linux") or os.istarget("bsd") then if os.getversion().description == "FreeBSD" then buildoptions { "-fPIC" } linkoptions { "-fPIC" } else buildoptions { "-rdynamic", "-fPIC" } linkoptions { "-fPIC", "-rdynamic" } end -- warnings triggered by wxWidgets buildoptions { "-Wno-unused-local-typedefs" } end end -- build all Atlas component projects function setup_atlas_projects() setup_atlas_project("AtlasObject", "StaticLib", { -- src ".", "../../../third_party/jsonspirit" },{ -- include "../../../third_party/jsonspirit" },{ -- extern_libs "boost", "iconv", "libxml2" },{ -- extra_params no_pch = 1 }) atlas_src = { "ActorEditor", "CustomControls/Buttons", "CustomControls/Canvas", "CustomControls/ColorDialog", "CustomControls/DraggableListCtrl", "CustomControls/EditableListCtrl", "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/MapDialog", "CustomControls/MapResizeDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", "General", "General/VideoRecorder", "Misc", "ScenarioEditor", "ScenarioEditor/Sections/Common", "ScenarioEditor/Sections/Cinema", "ScenarioEditor/Sections/Environment", "ScenarioEditor/Sections/Map", "ScenarioEditor/Sections/Object", "ScenarioEditor/Sections/Player", "ScenarioEditor/Sections/Terrain", "ScenarioEditor/Tools", "ScenarioEditor/Tools/Common", } atlas_extra_links = { "AtlasObject" } atlas_extern_libs = { "boost", "comsuppw", "iconv", "libxml2", "sdl", -- key definitions "wxwidgets", "zlib", } if not os.istarget("windows") and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(atlas_extern_libs, "x11") end setup_atlas_project("AtlasUI", "SharedLib", atlas_src, { -- include "..", "CustomControls", "Misc", "../../../third_party/jsonspirit" }, atlas_extern_libs, { -- extra_params pch_dir = rootdir.."/source/tools/atlas/AtlasUI/Misc/", no_pch = false, extra_links = atlas_extra_links, extra_files = { "Misc/atlas.rc" } }) end -- Atlas 'frontend' tool-launching projects function setup_atlas_frontend_project (project_name) local target_type = get_main_project_target_type() project_create(project_name, target_type) local source_root = rootdir.."/source/tools/atlas/AtlasFrontends/" files { source_root..project_name..".cpp" } if os.istarget("windows") then files { source_root..project_name..".rc" } end includedirs { source_root .. ".." } -- Platform Specifics if os.istarget("windows") then -- see manifest.cpp project_add_manifest() else -- Non-Windows, = Unix links { "AtlasObject" } if os.istarget("macosx") then architecture(macos_arch) buildoptions { "-arch " .. macos_arch } linkoptions { "-arch " .. macos_arch } xcodebuildsettings { ARCHS = macos_arch } end end links { "AtlasUI" } end function setup_atlas_frontends() setup_atlas_frontend_project("ActorEditor") end -------------------------------------------------------------------------------- -- collada -------------------------------------------------------------------------------- function setup_collada_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) project_create(project_name, target_type) local source_root = source_root.."collada/" extra_params["pch_dir"] = source_root project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then characterset "MBCS" elseif os.istarget("linux") then defines { "LINUX" } links { "dl", } -- FCollada is not aliasing-safe, so disallow dangerous optimisations -- (TODO: It'd be nice to fix FCollada, but that looks hard) buildoptions { "-fno-strict-aliasing" } if os.getversion().description ~= "FreeBSD" then buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } end elseif os.istarget("bsd") then if os.getversion().description == "OpenBSD" then links { "c", } end if os.getversion().description == "GNU/kFreeBSD" then links { "dl", } end buildoptions { "-fno-strict-aliasing" } buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } elseif os.istarget("macosx") then -- define MACOS-something? buildoptions { "-fno-strict-aliasing" } -- On OSX, fcollada uses a few utility functions from coreservices links { "CoreServices.framework" } architecture(macos_arch) buildoptions { "-arch " .. macos_arch } linkoptions { "-arch " .. macos_arch } xcodebuildsettings { ARCHS = macos_arch } end end -- build all Collada component projects function setup_collada_projects() setup_collada_project("Collada", "SharedLib", { -- src "." },{ -- include },{ -- extern_libs "fcollada", "iconv", "libxml2" },{ -- extra_params }) end -------------------------------------------------------------------------------- -- tests -------------------------------------------------------------------------------- function setup_tests() local cxxtest = require "cxxtest" if os.istarget("windows") then cxxtest.setpath(rootdir.."/build/bin/cxxtestgen.exe") else cxxtest.setpath(rootdir.."/libraries/source/cxxtest-4.4/bin/cxxtestgen") end local runner = "ErrorPrinter" if _OPTIONS["jenkins-tests"] then runner = "XmlPrinter" end local include_files = { -- Precompiled headers - the header is added to all generated .cpp files -- note that the header isn't actually precompiled here, only #included -- so that the build stage can use it as a precompiled header. "precompiled.h", } local test_root_include_files = { "precompiled.h", } if os.istarget("windows") then -- This is required to build against SDL 2.0.12 (starting from 2.0.4) on Windows. -- Refs #3138 table.insert(test_root_include_files, "lib/external_libraries/libsdl.h") end cxxtest.init(source_root, true, runner, include_files, test_root_include_files) local target_type = get_main_project_target_type() project_create("test", target_type) -- Find header files in 'test' subdirectories local all_files = os.matchfiles(source_root .. "**/tests/*.h") local test_files = {} for i,v in pairs(all_files) do -- Don't include sysdep tests on the wrong sys -- Don't include Atlas tests unless Atlas is being built if not (string.find(v, "/sysdep/os/win/") and not os.istarget("windows")) and not (string.find(v, "/tools/atlas/") and not _OPTIONS["atlas"]) and not (string.find(v, "/sysdep/arch/x86_x64/") and ((arch ~= "amd64") or (arch ~= "x86"))) then table.insert(test_files, v) end end cxxtest.configure_project(test_files) filter "system:not macosx" linkgroups 'On' filter {} links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } links { "mocks_test" } if _OPTIONS["atlas"] then links { "AtlasObject" } end extra_params = { extra_files = { "test_setup.cpp" }, } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } rtti "off" -- TODO: should fix the duplication between this OS-specific linking -- code, and the similar version in setup_main_exe if os.istarget("windows") then -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } - -- see wstartup.h - linkoptions { "/INCLUDE:_wstartup_InitAndRegisterShutdown" } -- Enables console for the TEST project on Windows linkoptions { "/SUBSYSTEM:CONSOLE" } links { "delayimp" } project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if link_execinfo then links { "execinfo" } end if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } includedirs { source_root .. "pch/test/" } elseif os.istarget("macosx") then architecture(macos_arch) buildoptions { "-arch " .. macos_arch } linkoptions { "-arch " .. macos_arch } xcodebuildsettings { ARCHS = macos_arch } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end end -- must come first, so that VC sets it as the default project and therefore -- allows running via F5 without the "where is the EXE" dialog. setup_main_exe() setup_all_libs() -- add the static libs to the main EXE project. only now (after -- setup_all_libs has run) are the lib names known. cannot move -- setup_main_exe to run after setup_all_libs (see comment above). -- we also don't want to hardcode the names - that would require more -- work when changing the static lib breakdown. project("pyrogenesis") -- Set the main project active links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } if _OPTIONS["atlas"] then setup_atlas_projects() setup_atlas_frontends() end setup_collada_projects() if not _OPTIONS["without-tests"] then setup_tests() end Index: ps/trunk/source/lib/sysdep/os/win/wdir_watch.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdir_watch.cpp (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/wdir_watch.cpp (revision 27810) @@ -1,383 +1,378 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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 std::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() +Status wdir_watch_Init() { s_dirWatchManager = new DirWatchManager; return INFO::OK; } -static Status wdir_watch_Shutdown() +Status wdir_watch_Shutdown() { SAFE_DELETE(s_dirWatchManager); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wposix/waio.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/waio.cpp (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/wposix/waio.cpp (revision 27810) @@ -1,694 +1,691 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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 #include -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 = g_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() +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/wposix_internal.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/wposix_internal.h (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/wposix/wposix_internal.h (revision 27810) @@ -1,37 +1,36 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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/wutil.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wutil.cpp (revision 27809) +++ ps/trunk/source/lib/sysdep/os/win/wutil.cpp (revision 27810) @@ -1,322 +1,317 @@ /* Copyright (C) 2023 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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/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 #include #include -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; } } //----------------------------------------------------------------------------- // directories // Helper to avoid duplicating this setup static OsPath GetFolderPath(int csidl) { WinScopedPreserveLastError s; 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 OsPath(path); } OsPath wutil_LocalAppdataPath() { // Local application data. return GetFolderPath(CSIDL_LOCAL_APPDATA); } OsPath wutil_RoamingAppdataPath() { // Roaming application data. return GetFolderPath(CSIDL_APPDATA); } OsPath wutil_PersonalPath() { // My documents. return GetFolderPath(CSIDL_PERSONAL); } //----------------------------------------------------------------------------- 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 HMODULE wutil_LibModuleHandle() { return GetModuleHandle(0); } //----------------------------------------------------------------------------- // 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; void wutil_SetAppWindow(SDL_Window* window) { SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); if (SDL_GetWindowWMInfo(window, &wmInfo) && wmInfo.subsystem == SDL_SYSWM_WINDOWS) hAppWindow = wmInfo.info.win.window; } void* wutil_GetAppHDC() { return GetDC(hAppWindow); } void wutil_SetAppWindow(void* hwnd) { hAppWindow = reinterpret_cast(hwnd); } HWND wutil_AppWindow() { if (hAppWindow) { // In case of an assertion we might not receive a notification about the // closed window. So check it in-place. if (IsWindow(hAppWindow)) { // There is a chance that a new window might be opened with the // same handle. DWORD pid; GetWindowThreadProcessId(hAppWindow, &pid); if (pid != GetCurrentProcessId()) hAppWindow = 0; } else hAppWindow = 0; } return hAppWindow; } void wutil_EnableHiDPIOnWindows() { // We build with VS using XP toolkit which doesn't support DPI awareness. // It was introduced in 8.1. // https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-process_dpi_awareness typedef enum PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE, PROCESS_SYSTEM_DPI_AWARE, PROCESS_PER_MONITOR_DPI_AWARE }; // https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness using SetProcessDpiAwarenessFunc = HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS); void* shcoreDLL = SDL_LoadObject("SHCORE.DLL"); if (!shcoreDLL) return; SetProcessDpiAwarenessFunc SetProcessDpiAwareness = reinterpret_cast(SDL_LoadFunction(shcoreDLL, "SetProcessDpiAwareness")); if (SetProcessDpiAwareness) SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); SDL_UnloadObject(shcoreDLL); } //----------------------------------------------------------------------------- -static Status wutil_Init() +Status wutil_Init() { InitLocks(); return INFO::OK; } -static Status wutil_Shutdown() +Status wutil_Shutdown() { ShutdownLocks(); return INFO::OK; } Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp (revision 27809) +++ ps/trunk/source/main.cpp (revision 27810) @@ -1,746 +1,766 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* This module drives the game when running without Atlas (our integrated map editor). It receives input and OS messages via SDL and feeds them into the input dispatcher, where they are passed on to the game GUI and simulation. It also contains main(), which either runs the above controller or that of Atlas depending on commandline parameters. */ // not for any PCH effort, but instead for the (common) definitions // included there. #define MINIMAL_PCH 2 #include "lib/precompiled.h" #include "lib/debug.h" #include "lib/status.h" #include "lib/secure_crt.h" #include "lib/frequency_filter.h" #include "lib/input.h" #include "lib/timer.h" #include "lib/external_libraries/libsdl.h" #include "ps/ArchiveBuilder.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Loader.h" #include "ps/Mod.h" #include "ps/ModInstaller.h" #include "ps/Profile.h" #include "ps/Profiler2.h" #include "ps/Pyrogenesis.h" #include "ps/Replay.h" #include "ps/TouchInput.h" #include "ps/UserReport.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/TaskManager.h" #include "ps/World.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" #include "ps/GameSetup/Paths.h" #include "ps/XML/Xeromyces.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "network/NetSession.h" #include "lobby/IXmppClient.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/TextureManager.h" #include "gui/GUIManager.h" #include "renderer/backend/IDevice.h" #include "renderer/Renderer.h" #include "rlinterface/RLInterface.h" #include "scriptinterface/ScriptContext.h" #include "scriptinterface/ScriptEngine.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/JSON.h" #include "simulation2/Simulation2.h" #include "simulation2/system/TurnManager.h" #include "soundmanager/ISoundManager.h" #if OS_UNIX #include #include // geteuid #endif // OS_UNIX #if OS_MACOSX #include "lib/sysdep/os/osx/osx_atlas.h" #endif #if MSC_VERSION #include #define getpid _getpid // Use the non-deprecated function name #endif #if OS_WIN +// Forward declarations to avoid including Windows dependent headers. +Status waio_Shutdown(); +Status wdir_watch_Init(); +Status wdir_watch_Shutdown(); +Status wutil_Init(); +Status wutil_Shutdown(); + // We don't want to include Windows.h as it might mess up the rest // of the file so we just define DWORD as done in Windef.h. #ifndef DWORD typedef unsigned long DWORD; #endif // !DWORD // Request the high performance GPU on Windows by default if no system override is specified. // See: // - https://github.com/supertuxkart/stk-code/pull/4693/commits/0a99c667ef513b2ce0f5755729a6e05df8aac48a // - https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm // - https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/ extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; } #endif #include extern CStrW g_UniqueLogPostfix; // Determines the lifetime of the mainloop enum ShutdownType { // The application shall continue the main loop. None, // The process shall terminate as soon as possible. Quit, // The engine should be restarted in the same process, for instance to activate different mods. Restart, // Atlas should be started in the same process. RestartAsAtlas }; static ShutdownType g_Shutdown = ShutdownType::None; // to avoid redundant and/or recursive resizing, we save the new // size after VIDEORESIZE messages and only update the video mode // once per frame. // these values are the latest resize message, and reset to 0 once we've // updated the video mode static int g_ResizedW; static int g_ResizedH; static std::chrono::high_resolution_clock::time_point lastFrameTime; bool IsQuitRequested() { return g_Shutdown == ShutdownType::Quit; } void QuitEngine() { g_Shutdown = ShutdownType::Quit; } void RestartEngine() { g_Shutdown = ShutdownType::Restart; } void StartAtlas() { g_Shutdown = ShutdownType::RestartAsAtlas; } // main app message handler static InReaction MainInputHandler(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_WINDOWEVENT: switch(ev->ev.window.event) { case SDL_WINDOWEVENT_RESIZED: g_ResizedW = ev->ev.window.data1; g_ResizedH = ev->ev.window.data2; break; case SDL_WINDOWEVENT_MOVED: g_VideoMode.UpdatePosition(ev->ev.window.data1, ev->ev.window.data2); } break; case SDL_QUIT: QuitEngine(); break; case SDL_DROPFILE: { char* dropped_filedir = ev->ev.drop.file; const Paths paths(g_CmdLineArgs); CModInstaller installer(paths.UserData() / "mods", paths.Cache()); installer.Install(std::string(dropped_filedir), g_ScriptContext, true); SDL_free(dropped_filedir); if (installer.GetInstalledMods().empty()) LOGERROR("Failed to install mod %s", dropped_filedir); else { LOGMESSAGE("Installed mod %s", installer.GetInstalledMods().front()); ScriptInterface modInterface("Engine", "Mod", g_ScriptContext); g_Mods.UpdateAvailableMods(modInterface); RestartEngine(); } break; } case SDL_HOTKEYPRESS: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "exit") { QuitEngine(); return IN_HANDLED; } else if (hotkey == "screenshot") { g_Renderer.MakeScreenShotOnNextFrame(CRenderer::ScreenShotType::DEFAULT); return IN_HANDLED; } else if (hotkey == "bigscreenshot") { g_Renderer.MakeScreenShotOnNextFrame(CRenderer::ScreenShotType::BIG); return IN_HANDLED; } else if (hotkey == "togglefullscreen") { g_VideoMode.ToggleFullscreen(); return IN_HANDLED; } else if (hotkey == "profile2.toggle") { g_Profiler2.Toggle(); return IN_HANDLED; } break; } return IN_PASS; } // dispatch all pending events to the various receivers. static void PumpEvents() { ScriptRequest rq(g_GUI->GetScriptInterface()); PROFILE3("dispatch events"); SDL_Event_ ev; while (in_poll_event(&ev)) { PROFILE2("event"); if (g_GUI) { JS::RootedValue tmpVal(rq.cx); Script::ToJSVal(rq, &tmpVal, ev); std::string data = Script::StringifyJSON(rq, &tmpVal); PROFILE2_ATTR("%s", data.c_str()); } in_dispatch_event(&ev); } g_TouchInput.Frame(); } /** * Optionally throttle the render frequency in order to * prevent 100% workload of the currently used CPU core. */ inline static void LimitFPS() { if (g_VideoMode.IsVSyncEnabled()) return; double fpsLimit = 0.0; CFG_GET_VAL(g_Game && g_Game->IsGameStarted() ? "adaptivefps.session" : "adaptivefps.menu", fpsLimit); // Keep in sync with options.json if (fpsLimit < 20.0 || fpsLimit >= 360.0) return; double wait = 1000.0 / fpsLimit - std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - lastFrameTime).count() / 1000.0; if (wait > 0.0) SDL_Delay(wait); lastFrameTime = std::chrono::high_resolution_clock::now(); } static int ProgressiveLoad() { PROFILE3("progressive load"); wchar_t description[100]; int progress_percent; try { Status ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent); switch(ret) { // no load active => no-op (skip code below) case INFO::OK: return 0; // current task didn't complete. we only care about this insofar as the // load process is therefore not yet finished. case ERR::TIMED_OUT: break; // just finished loading case INFO::ALL_COMPLETE: g_Game->ReallyStartGame(); wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting.."); // LDR_ProgressiveLoad returns L""; set to valid text to // avoid problems in converting to JSString break; // error! default: WARN_RETURN_STATUS_IF_ERR(ret); // can't do this above due to legit ERR::TIMED_OUT break; } } catch (PSERROR_Game_World_MapLoadFailed& e) { // Map loading failed // Call script function to do the actual work // (delete game data, switch GUI page, show error, etc.) CancelLoad(CStr(e.what()).FromUTF8()); } g_GUI->DisplayLoadProgress(progress_percent, description); return 0; } static void RendererIncrementalLoad() { PROFILE3("renderer incremental load"); const double maxTime = 0.1f; double startTime = timer_Time(); bool more; do { more = g_Renderer.GetTextureManager().MakeProgress(); } while (more && timer_Time() - startTime < maxTime); } static void Frame() { g_Profiler2.RecordFrameStart(); PROFILE2("frame"); g_Profiler2.IncrementFrameNumber(); PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber()); // get elapsed time const double time = timer_Time(); g_frequencyFilter->Update(time); // .. old method - "exact" but contains jumps #if 0 static double last_time; const double time = timer_Time(); const float TimeSinceLastFrame = (float)(time-last_time); last_time = time; ONCE(return); // first call: set last_time and return // .. new method - filtered and more smooth, but errors may accumulate #else const float realTimeSinceLastFrame = 1.0 / g_frequencyFilter->SmoothedFrequency(); #endif ENSURE(realTimeSinceLastFrame > 0.0f); // Decide if update is necessary bool need_update = true; // If we are not running a multiplayer game, disable updates when the game is // minimized or out of focus and relinquish the CPU a bit, in order to make // debugging easier. if (g_PauseOnFocusLoss && !g_NetClient && !g_app_has_focus) { PROFILE3("non-focus delay"); need_update = false; // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored SDL_Delay(10); } // this scans for changed files/directories and reloads them, thus // allowing hotloading (changes are immediately assimilated in-game). ReloadChangedFiles(); ProgressiveLoad(); RendererIncrementalLoad(); PumpEvents(); // if the user quit by closing the window, the GL context will be broken and // may crash when we call Render() on some drivers, so leave this loop // before rendering if (g_Shutdown != ShutdownType::None) return; // respond to pumped resize events if (g_ResizedW || g_ResizedH) { g_VideoMode.ResizeWindow(g_ResizedW, g_ResizedH); g_ResizedW = g_ResizedH = 0; } if (g_NetClient) g_NetClient->Poll(); g_GUI->TickObjects(); if (g_RLInterface) g_RLInterface->TryApplyMessage(); if (g_Game && g_Game->IsGameStarted() && need_update) { if (!g_RLInterface) g_Game->Update(realTimeSinceLastFrame); g_Game->GetView()->Update(float(realTimeSinceLastFrame)); } // Keep us connected to any XMPP servers if (g_XmppClient) g_XmppClient->recv(); g_UserReporter.Update(); g_Console->Update(realTimeSinceLastFrame); if (g_SoundManager) g_SoundManager->IdleTask(); g_Renderer.RenderFrame(true); g_Profiler.Frame(); LimitFPS(); } static void NonVisualFrame() { g_Profiler2.RecordFrameStart(); PROFILE2("frame"); g_Profiler2.IncrementFrameNumber(); PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber()); if (g_NetClient) g_NetClient->Poll(); static u32 turn = 0; if (g_Game && g_Game->IsGameStarted() && g_Game->GetTurnManager()) if (g_Game->GetTurnManager()->Update(DEFAULT_TURN_LENGTH, 1)) debug_printf("Turn %u (%u)...\n", turn++, DEFAULT_TURN_LENGTH); g_Profiler.Frame(); if (g_Game->IsGameFinished()) QuitEngine(); } static void MainControllerInit() { // add additional input handlers only needed by this controller: // must be registered after gui_handler. Should mayhap even be last. in_add_handler(MainInputHandler); } static void MainControllerShutdown() { in_reset_handlers(); } static void StartRLInterface(CmdLineArgs args) { std::string server_address; CFG_GET_VAL("rlinterface.address", server_address); if (!args.Get("rl-interface").empty()) server_address = args.Get("rl-interface"); g_RLInterface = std::make_unique(server_address.c_str()); debug_printf("RL interface listening on %s\n", server_address.c_str()); } // moved into a helper function to ensure args is destroyed before // exit(), which may result in a memory leak. static void RunGameOrAtlas(const PS::span argv) { const CmdLineArgs args(argv); g_CmdLineArgs = args; if (args.Has("version")) { debug_printf("Pyrogenesis %s\n", engine_version); return; } if (args.Has("autostart-nonvisual") && args.Get("autostart").empty() && !args.Has("rl-interface") && !args.Has("autostart-client")) { LOGERROR("-autostart-nonvisual cant be used alone. A map with -autostart=\"TYPEDIR/MAPNAME\" is needed."); return; } if (args.Has("unique-logs")) g_UniqueLogPostfix = L"_" + std::to_wstring(std::time(nullptr)) + L"_" + std::to_wstring(getpid()); const bool isVisualReplay = args.Has("replay-visual"); const bool isNonVisualReplay = args.Has("replay"); const bool isNonVisual = args.Has("autostart-nonvisual"); const bool isUsingRLInterface = args.Has("rl-interface"); const OsPath replayFile( isVisualReplay ? args.Get("replay-visual") : isNonVisualReplay ? args.Get("replay") : ""); if (isVisualReplay || isNonVisualReplay) { if (!FileExists(replayFile)) { debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.string8().c_str()); return; } if (DirectoryExists(replayFile)) { debug_printf("ERROR: The requested replay file '%s' is a directory!\n", replayFile.string8().c_str()); return; } } std::vector modsToInstall; for (const CStr& arg : args.GetArgsWithoutName()) { const OsPath modPath(arg); if (!CModInstaller::IsDefaultModExtension(modPath.Extension())) { debug_printf("Skipping file '%s' which does not have a mod file extension.\n", modPath.string8().c_str()); continue; } if (!FileExists(modPath)) { debug_printf("ERROR: The mod file '%s' does not exist!\n", modPath.string8().c_str()); continue; } if (DirectoryExists(modPath)) { debug_printf("ERROR: The mod file '%s' is a directory!\n", modPath.string8().c_str()); continue; } modsToInstall.emplace_back(std::move(modPath)); } // We need to initialize SpiderMonkey and libxml2 in the main thread before // any thread uses them. So initialize them here before we might run Atlas. ScriptEngine scriptEngine; CXeromyces::Startup(); // Initialise the global task manager at this point (JS & Profiler2 are set up). Threading::TaskManager::Initialise(); if (ATLAS_RunIfOnCmdLine(args, false)) { CXeromyces::Terminate(); return; } if (isNonVisualReplay) { Paths paths(args); g_VFS = CreateVfs(); // Mount with highest priority, we don't want mods overwriting this. g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE, VFS_MAX_PRIORITY); { CReplayPlayer replay; replay.Load(replayFile); replay.Replay( args.Has("serializationtest"), args.Has("rejointest") ? args.Get("rejointest").ToInt() : -1, args.Has("ooslog"), !args.Has("hashtest-full") || args.Get("hashtest-full") == "true", args.Has("hashtest-quick") && args.Get("hashtest-quick") == "true"); } g_VFS.reset(); CXeromyces::Terminate(); return; } // run in archive-building mode if requested if (args.Has("archivebuild")) { Paths paths(args); OsPath mod(args.Get("archivebuild")); OsPath zip; if (args.Has("archivebuild-output")) zip = args.Get("archivebuild-output"); else zip = mod.Filename().ChangeExtension(L".zip"); CArchiveBuilder builder(mod, paths.Cache()); // Add mods provided on the command line // NOTE: We do not handle mods in the user mod path here std::vector mods = args.GetMultiple("mod"); for (size_t i = 0; i < mods.size(); ++i) builder.AddBaseMod(paths.RData()/"mods"/mods[i]); builder.Build(zip, args.Has("archivebuild-compress")); CXeromyces::Terminate(); return; } const double res = timer_Resolution(); g_frequencyFilter = CreateFrequencyFilter(res, 30.0); // run the game int flags = INIT_MODS; do { g_Shutdown = ShutdownType::None; if (!Init(args, flags)) { flags &= ~INIT_MODS; Shutdown(SHUTDOWN_FROM_CONFIG); continue; } std::vector installedMods; if (!modsToInstall.empty()) { Paths paths(args); CModInstaller installer(paths.UserData() / "mods", paths.Cache()); // Install the mods without deleting the pyromod files for (const OsPath& modPath : modsToInstall) { CModInstaller::ModInstallationResult result = installer.Install(modPath, g_ScriptContext, true); if (result != CModInstaller::ModInstallationResult::SUCCESS) LOGERROR("Failed to install '%s'", modPath.string8().c_str()); } installedMods = installer.GetInstalledMods(); ScriptInterface modInterface("Engine", "Mod", g_ScriptContext); g_Mods.UpdateAvailableMods(modInterface); } if (isNonVisual) { if (!InitNonVisual(args)) g_Shutdown = ShutdownType::Quit; else if (isUsingRLInterface) StartRLInterface(args); while (g_Shutdown == ShutdownType::None) { if (isUsingRLInterface) g_RLInterface->TryApplyMessage(); else NonVisualFrame(); } } else { InitGraphics(args, 0, installedMods); MainControllerInit(); if (isUsingRLInterface) StartRLInterface(args); while (g_Shutdown == ShutdownType::None) Frame(); } // Do not install mods again in case of restart (typically from the mod selector) modsToInstall.clear(); Shutdown(0); MainControllerShutdown(); flags &= ~INIT_MODS; } while (g_Shutdown == ShutdownType::Restart); #if OS_MACOSX if (g_Shutdown == ShutdownType::RestartAsAtlas) startNewAtlasProcess(g_Mods.GetEnabledMods()); #else if (g_Shutdown == ShutdownType::RestartAsAtlas) ATLAS_RunIfOnCmdLine(args, true); #endif Threading::TaskManager::Instance().ClearQueue(); CXeromyces::Terminate(); } #if OS_ANDROID // In Android we compile the engine as a shared library, not an executable, // so rename main() to a different symbol that the wrapper library can load #undef main #define main pyrogenesis_main extern "C" __attribute__((visibility ("default"))) int main(int argc, char* argv[]); #endif extern "C" int main(int argc, char* argv[]) { #if OS_UNIX // Don't allow people to run the game with root permissions, // because bad things can happen, check before we do anything if (geteuid() == 0) { std::cerr << "********************************************************\n" << "WARNING: Attempted to run the game with root permission!\n" << "This is not allowed because it can alter home directory \n" << "permissions and opens your system to vulnerabilities. \n" << "(You received this message because you were either \n" <<" logged in as root or used e.g. the 'sudo' command.) \n" << "********************************************************\n\n"; return EXIT_FAILURE; } #endif // OS_UNIX +#if OS_WIN + wutil_Init(); + wdir_watch_Init(); +#endif + EarlyInit(); // must come at beginning of main // static_cast is ok, argc is never negative. RunGameOrAtlas({argv, static_cast(argc)}); // Shut down profiler initialised by EarlyInit g_Profiler2.Shutdown(); +#if OS_WIN + // All calls to Windows specific functions have to happen before the following + // shutdowns. + wdir_watch_Shutdown(); + waio_Shutdown(); + wutil_Shutdown(); +#endif + return EXIT_SUCCESS; }