Index: ps/trunk/build/premake/premake.lua =================================================================== --- ps/trunk/build/premake/premake.lua (revision 8924) +++ ps/trunk/build/premake/premake.lua (revision 8925) @@ -1,1141 +1,1143 @@ addoption("atlas", "Include Atlas scenario editor packages") addoption("collada", "Include COLLADA packages (requires FCollada library)") addoption("coverage", "Enable code coverage data collection (GCC only)") addoption("aoe3ed", "Include AoE3Ed") addoption("icc", "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)") addoption("outpath", "Location for generated project files") addoption("without-tests", "Disable generation of test projects") addoption("without-pch", "Disable generation and usage of precompiled headers") addoption("with-system-nvtt", "Search standard paths for nvidia-texture-tools library, instead of using bundled copy") addoption("bindir", "Directory for executables (typically '/usr/games'); default is to be relocatable") addoption("datadir", "Directory for data files (typically '/usr/share/games/0ad'); default is ../data/ relative to executable") addoption("libdir", "Directory for libraries (typically '/usr/lib/games/0ad'); default is ./ relative to executable") dofile("functions.lua") dofile("extern_libs.lua") -- detect CPU architecture (simplistic, currently only supports x86 and amd64) arch = "x86" if OS == "windows" then if os.getenv("PROCESSOR_ARCHITECTURE") == "amd64" or os.getenv("PROCESSOR_ARCHITEW6432") == "amd64" then arch = "amd64" end else arch = os.getenv("HOSTTYPE") if arch == "x86_64" then arch = "amd64" else os.execute("gcc -dumpmachine > .gccmachine.tmp") local f = io.open(".gccmachine.tmp", "r") local machine = f:read("*line") f:close() if string.find(machine, "x86_64") == 1 then arch = "amd64" elseif string.find(machine, "i.86") == 1 then arch = "x86" else print("WARNING: Cannot determine architecture from GCC, assuming x86") end end end -- Set up the Project project.name = "pyrogenesis" project.bindir = "../../binaries/system" project.libdir = "../../binaries/system" project.debugdir = "../../binaries/system" if not options["outpath"] then error("You must specify the 'outpath' parameter") end project.path = options["outpath"] project.configs = { "Debug", "Release", "Testing" } if OS == "windows" then project.nasmpath = "../../build/bin/nasm.exe" project.cxxtestpath = "../../build/bin/cxxtestgen.exe" has_broken_pch = false else project.cxxtestpath = "../../build/bin/cxxtestgen.pl" if OS == "linux" and arch == "amd64" then -- Hack for amd64 linux - tell nasm to product 64-bit elf. project.nasmformat = "elf64" elseif OS == "macosx" and arch == "amd64" then project.nasmformat = "macho64" end -- GCC bug (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10591) - PCH breaks anonymous namespaces -- Fixed in 4.2.0, but we have to disable PCH for earlier versions, else -- it conflicts annoyingly with wx 2.8 headers. -- It's too late to do this test by the time we start compiling the PCH file, so -- do the test in this build script instead (which is kind of ugly - please fix if -- you have a better idea) if not options["icc"] then os.execute("gcc -dumpversion > .gccver.tmp") local f = io.open(".gccver.tmp", "r") major, dot, minor = f:read(1, 1, 1) f:close() major = 0+major -- coerce to number minor = 0+minor has_broken_pch = (major < 4 or (major == 4 and minor < 2)) if has_broken_pch then print("WARNING: Detected GCC <4.2 -- disabling PCH for Atlas (will increase build times)") end end end source_root = "../../../source/" -- default for most projects - overridden by local in others -- Rationale: packages 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. -- packages: engine static libs, main exe, atlas, atlas frontends, test. -------------------------------------------------------------------------------- -- package helper functions -------------------------------------------------------------------------------- function package_set_target(package_name) -- Note: On Windows, ".exe" is added on the end, on unices the name is used directly package.config["Debug" ].target = package_name.."_dbg" package.config["Testing"].target = package_name.."_test" package.config["Release"].target = package_name local obj_dir_prefix = "obj/"..package_name.."_" package.config["Debug" ].objdir = obj_dir_prefix.."Debug" package.config["Testing"].objdir = obj_dir_prefix.."Test" package.config["Release"].objdir = obj_dir_prefix.."Release" end function package_set_build_flags() package.buildflags = { "with-symbols", "no-edit-and-continue" } if not options["icc"] then -- adds the -Wall compiler flag tinsert(package.buildflags, "extra-warnings") -- this causes far too many warnings/remarks on ICC end -- PremakeWiki says with-symbols and optimize are automatically set for -- Debug and Release builds, respectively. doesn't happen though, so do it manually. package.config["Debug"].defines = { "DEBUG" } package.config["Testing"].buildflags = { "no-runtime-checks" } package.config["Testing"].defines = { "TESTING" } package.config["Release"].buildflags = { "no-runtime-checks", "optimize-speed" } package.config["Release"].defines = { "NDEBUG", "CONFIG_FINAL=1" } -- required for the lowlevel library. must be set from all packages that use it, otherwise it assumes it is -- being used as a DLL (which is currently not the case in 0ad) tinsert(package.defines, "LIB_STATIC_LINK") -- various platform-specific build flags if OS == "windows" then -- use native wchar_t type (not typedef to unsigned short) tinsert(package.buildflags, "native-wchar_t") else -- *nix if options["icc"] then tinsert(package.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' }) tinsert(package.config["Debug"].buildoptions, { "-O0", -- ICC defaults to -O2 }) if OS == "macosx" then tinsert(package.linkoptions, {"-multiply_defined","suppress"}) end else tinsert(package.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", "-D_FORTIFY_SOURCE=2", -- always enable strict aliasing (useful in debug builds because of the warnings) "-fstrict-aliasing", -- do something (?) so that ccache can handle compilation with PCH enabled "-fpch-preprocess", -- enable SSE intrinsics "-msse", -- 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 OS == "linux" then tinsert(package.linkoptions, { "-Wl,--no-undefined", "-Wl,--as-needed", }) end if options["coverage"] then tinsert(package.buildoptions, {"-fprofile-arcs", "-ftest-coverage"}) tinsert(package.links, "gcov") end -- To support intrinsics like __sync_bool_compare_and_swap on x86 -- we need to set -march to something that supports them if arch == "x86" then tinsert(package.buildoptions, "-march=i686") end -- We don't want to require SSE2 everywhere yet, but OS X headers do -- require it (and Intel Macs always have it) so enable it here if OS == "macosx" then tinsert(package.buildoptions, "-msse2") end end tinsert(package.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", }) -- X11 includes may be installed in one of a gadzillion of three places -- Famous last words: "You can't include too much! ;-)" package.includepaths = { "/usr/X11R6/include/X11", "/usr/X11R6/include", "/usr/include/X11" } package.libpaths = { "/usr/X11R6/lib" } if options["bindir"] then tinsert(package.defines, "INSTALLED_BINDIR=" .. options["bindir"]) end if options["datadir"] then tinsert(package.defines, "INSTALLED_DATADIR=" .. options["datadir"]) end if options["libdir"] then tinsert(package.defines, "INSTALLED_LIBDIR=" .. options["libdir"]) end if OS == "linux" then -- To use our local SpiderMonkey library, it needs to be part of the -- runtime dynamic linker path. Add it with -rpath to make sure it gets found. if options["libdir"] then tinsert(package.linkoptions, {"-Wl,-rpath=" .. options["libdir"]}) else -- Add the executable path: tinsert(package.linkoptions, {"-Wl,-rpath='$$ORIGIN'"}) -- use Makefile escaping of '$' end end end end -- create a package and set the attributes that are common to all packages. function package_create(package_name, target_type) -- Note: don't store in local variable. A global variable needs to -- be set for Premake's use; it is implicitly used in e.g. matchfiles() package = newpackage() package.path = project.path package.language = "c++" package.name = package_name package.kind = target_type package.includepaths = {} package_set_target(package_name) package_set_build_flags() return package end -- extra_params: table including zero or more of the following: -- * no_default_pch: (any type) prevents adding the PCH include dir. -- see setup_static_lib_package() for explanation of this scheme and rationale. -- * 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 package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) -- We don't want the VC project to be deeply nested (once for each -- folder in source_root). Therefore, remove the source_root -- directory from the filenames (where those -- names are used by Premake to construct the project tree), but set -- 'trimprefix' (with Premake altered to recognise that) so the project -- will still point to the correct filenames. package.trimprefix = source_root package.files = sourcesfromdirs(source_root, rel_source_dirs) -- Put the project-specific PCH directory at the start of the -- include path, so '#include "precompiled.h"' will look in -- there first if not extra_params["no_default_pch"] then tinsert(package.includepaths, source_root .. "pch/" .. package.name) end -- next is source root dir, for absolute (nonrelative) includes -- (e.g. "lib/precompiled.h") tinsert(package.includepaths, source_root) for i,v in pairs(rel_include_dirs) do tinsert(package.includepaths, source_root .. v) end if extra_params["extra_files"] then for i,v in pairs(extra_params["extra_files"]) do tinsert(package.files, source_root .. v) end end if extra_params["extra_links"] then listconcat(package.links, extra_params["extra_links"]) end end -- Detect and set up PCH for the current package function package_setup_pch(pch_dir, header, source) if OS == "windows" or not options["without-pch"] then package.pchheader = header -- "precompiled.h" package.pchsource = source -- "precompiled.cpp" if pch_dir then tinsert(package.files, { pch_dir..header, pch_dir..source }) end for i,v in pairs(project.configs) do tinsert(package.config[v].defines, "USING_PCH") end end 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 = {} -- set up one of the static libraries into which the main engine code is split. -- extra_params: see package_add_contents(). -- note: rel_source_dirs and rel_include_dirs are relative to global source_root. function setup_static_lib_package (package_name, rel_source_dirs, extern_libs, extra_params) package_create(package_name, "lib") package_add_contents(source_root, rel_source_dirs, {}, extra_params) package_add_extern_libs(extern_libs) if not extra_params["no_default_link"] then tinsert(static_lib_names, package_name) end if OS == "windows" then tinsert(package.buildflags, "no-rtti") end -- 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 -- "packagedir/precompiled.h", or add "source/PCH/packagedir" to the -- include path and put the PCH there. The latter is better because -- many packages 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 extra_params["no_default_pch"] then pch_dir = source_root.."pch/"..package_name.."/" package_setup_pch(pch_dir, "precompiled.h", "precompiled.cpp") 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_package, -- 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", "boost", -- dragged in via server->simulation.h->random } setup_static_lib_package("network", source_dirs, extern_libs, {}) source_dirs = { "simulation2", "simulation2/components", "simulation2/helpers", "simulation2/scripting", "simulation2/serialization", "simulation2/system", "simulation2/testcomponents", } extern_libs = { "boost", "opengl", "spidermonkey", } setup_static_lib_package("simulation2", source_dirs, extern_libs, {}) source_dirs = { "scriptinterface", } extern_libs = { "boost", "spidermonkey", "valgrind", } setup_static_lib_package("scriptinterface", source_dirs, extern_libs, {}) source_dirs = { "ps", "ps/scripting", "ps/Network", "ps/GameSetup", "ps/XML", "sound", "scripting", "maths", "maths/scripting", } extern_libs = { "spidermonkey", "sdl", -- key definitions "libxml2", "opengl", "zlib", "boost", "enet", + "libcurl", } setup_static_lib_package("engine", source_dirs, extern_libs, {}) source_dirs = { "graphics", "graphics/scripting", "renderer" } extern_libs = { "nvtt", "opengl", "sdl", -- key definitions "spidermonkey", -- for graphics/scripting "boost" } setup_static_lib_package("graphics", source_dirs, extern_libs, {}) source_dirs = { "tools/atlas/GameInterface", "tools/atlas/GameInterface/Handlers" } extern_libs = { "boost", "sdl", -- key definitions "opengl", "spidermonkey" } setup_static_lib_package("atlas", source_dirs, extern_libs, {}) source_dirs = { "gui", "gui/scripting" } if OS == "windows" then -- add JS files to allow VS find-in-solution and VA open-file-in-workspace tinsert(source_dirs, "../binaries/data/mods/public/gui/test") end extern_libs = { "spidermonkey", "sdl", -- key definitions "opengl", "boost" } setup_static_lib_package("gui", source_dirs, extern_libs, {}) source_dirs = { "lib", "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/res/sound", "lib/sysdep", "lib/tex" } extern_libs = { "boost", "sdl", "opengl", "libpng", "zlib", "openal", "vorbis", "libjpg", "valgrind", "cxxtest", } -- CPU architecture-specific if arch == "amd64" then tinsert(source_dirs, "lib/sysdep/arch/amd64"); tinsert(source_dirs, "lib/sysdep/arch/x86_x64"); else tinsert(source_dirs, "lib/sysdep/arch/ia32"); tinsert(source_dirs, "lib/sysdep/arch/x86_x64"); end -- OS-specific sysdep_dirs = { linux = { "lib/sysdep/os/linux", "lib/sysdep/os/unix", "lib/sysdep/os/unix/x" }, -- note: RC file must be added to main_exe package. -- 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" }, } for i,v in pairs(sysdep_dirs[OS]) do tinsert(source_dirs, v); end -- runtime-library-specific if options["target"] == "gnu" then tinsert(source_dirs, "lib/sysdep/rtl/gcc"); else tinsert(source_dirs, "lib/sysdep/rtl/msc"); end setup_static_lib_package("lowlevel", source_dirs, extern_libs, {}) -- CxxTest mock function support extern_libs = { "boost", "cxxtest", } -- 'real' implementations, to be linked against the main executable setup_static_lib_package("mocks_real", {}, extern_libs, { no_default_link = 1, no_default_pch = 1 }) listconcat(package.files, matchfiles(source_root.."mocks/*.h", source_root.."mocks/*_real.cpp")) -- 'test' implementations, to be linked against the test executable setup_static_lib_package("mocks_test", {}, extern_libs, { no_default_link = 1, no_default_pch = 1 }) listconcat(package.files, matchfiles(source_root.."mocks/*.h", source_root.."mocks/*_test.cpp")) end -------------------------------------------------------------------------------- -- main EXE -------------------------------------------------------------------------------- -- used for main EXE as well as test used_extern_libs = { "opengl", "sdl", "libjpg", "libpng", "zlib", "spidermonkey", "libxml2", "openal", "vorbis", "boost", "cxxtest", "comsuppw", "enet", + "libcurl", "valgrind", "nvtt", } -- Bundles static libs together with main.cpp and builds game executable. function setup_main_exe () package_create("pyrogenesis", "winexe") -- For VS2005, tell the linker to use the libraries' .obj files instead of -- the .lib, to allow incremental linking. -- (Reduces re-link time from ~20 seconds to ~2 secs) tinsert(package.buildflags, "use-library-dep-inputs") local extra_params = { extra_files = { "main.cpp" }, } package_add_contents(source_root, {}, {}, extra_params) package_add_extern_libs(used_extern_libs) tinsert(package.links, "mocks_real") -- Platform Specifics if OS == "windows" then tinsert(package.files, source_root.."lib/sysdep/os/win/icon.rc") -- from "lowlevel" static lib; must be added here to be linked in tinsert(package.files, source_root.."lib/sysdep/os/win/error_dialog.rc") -- VS2005 generates its own manifest, but earlier ones need us to add it manually if (options["target"] == "vs2002" or options["target"] == "vs2003") then tinsert(package.files, source_root.."lib/sysdep/os/win/manifest.rc") end tinsert(package.buildflags, "no-rtti") -- this seems to be required when using vcbuild but not the IDE (?!) tinsert(package.links, "ws2_32") package.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", -- delay loading of various Windows DLLs (not specific to any of the -- external libraries; those are handled separately) "/DELAYLOAD:ws2_32.dll", -- allow manual unload of delay-loaded DLLs "/DELAY:UNLOAD" } elseif OS == "linux" then -- Libraries tinsert(package.links, { "fam", -- Utilities "rt", -- Dynamic libraries (needed for linking for gold) "dl", }) -- Threading support tinsert(package.buildoptions, "-pthread") tinsert(package.linkoptions, "-pthread") -- For debug_resolve_symbol package.config["Debug"].linkoptions = { "-rdynamic" } package.config["Testing"].linkoptions = { "-rdynamic" } elseif OS == "macosx" then -- Libraries tinsert(package.links, { -- Utilities "pthread" }) end end -------------------------------------------------------------------------------- -- atlas -------------------------------------------------------------------------------- -- setup a typical Atlas component package -- extra_params: as in package_add_contents; also zero or more of the following: -- * pch: (any type) set precompiled.h and .cpp as PCH function setup_atlas_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) local source_root = "../../../source/tools/atlas/" .. package_name .. "/" package_create(package_name, target_type) -- Don't add the default 'sourceroot/pch/projectname' for finding PCH files extra_params["no_default_pch"] = 1 package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) package_add_extern_libs(extern_libs) if extra_params["pch"] then package_setup_pch(nil, "precompiled.h", "precompiled.cpp"); end if options["aoe3ed"] then tinsert(package.defines, "USE_AOE3ED") end -- Platform Specifics if OS == "windows" then tinsert(package.defines, "_UNICODE") -- Link to required libraries package.links = { "winmm", "comctl32", "rpcrt4", "delayimp", "ws2_32" } -- required to use WinMain() on Windows, otherwise will default to main() tinsert(package.buildflags, "no-main") if extra_params["extra_links"] then listconcat(package.links, extra_params["extra_links"]) end elseif OS == "linux" then tinsert(package.buildoptions, "-rdynamic") tinsert(package.buildoptions, "-fPIC") tinsert(package.linkoptions, "-fPIC") tinsert(package.linkoptions, "-rdynamic") if extra_params["no_unused_warnings"] then if not options["icc"] then tinsert(package.buildoptions, "-Wno-unused-parameter") end end end end -- build all Atlas component packages function setup_atlas_packages() setup_atlas_package("AtlasObject", "lib", { -- src "" },{ -- include },{ -- extern_libs "libxml2", "wxwidgets" },{ -- extra_params }) setup_atlas_package("AtlasScript", "lib", { -- src "" },{ -- include ".." },{ -- extern_libs "boost", "spidermonkey", "valgrind", "wxwidgets", },{ -- extra_params }) setup_atlas_package("wxJS", "lib", { -- src "", "common", "ext", "gui", "gui/control", "gui/event", "gui/misc", "io", },{ -- include },{ -- extern_libs "spidermonkey", "wxwidgets" },{ -- extra_params pch = (not has_broken_pch), no_unused_warnings = 1, -- wxJS has far too many and we're never going to fix them, so just ignore them }) atlas_src = { "ActorEditor", "ColourTester", "CustomControls/Buttons", "CustomControls/Canvas", "CustomControls/ColourDialog", "CustomControls/DraggableListCtrl", "CustomControls/EditableListCtrl", "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", "ErrorReporter", "General", "General/VideoRecorder", "Misc", "ScenarioEditor", "ScenarioEditor/Sections/Common", "ScenarioEditor/Sections/Cinematic", "ScenarioEditor/Sections/Environment", "ScenarioEditor/Sections/Map", "ScenarioEditor/Sections/Object", "ScenarioEditor/Sections/Terrain", "ScenarioEditor/Sections/Trigger", "ScenarioEditor/Tools", "ScenarioEditor/Tools/Common", } atlas_extra_links = { "AtlasObject", "AtlasScript", "wxJS", } if options["aoe3ed"] then tinsert(atlas_src, "ArchiveViewer") tinsert(atlas_src, "FileConverter") tinsert(atlas_extra_links, "DatafileIO") tinsert(atlas_extra_links, "xerces-c") end setup_atlas_package("AtlasUI", "dll", atlas_src, { -- include "..", "CustomControls", "Misc" },{ -- extern_libs "boost", "comsuppw", "devil", --"ffmpeg", -- disabled for now because it causes too many build difficulties "libxml2", "sdl", -- key definitions "spidermonkey", "wxwidgets", "x11", "zlib", },{ -- extra_params pch = (not has_broken_pch), extra_links = atlas_extra_links, extra_files = { "Misc/atlas.rc" } }) if options["aoe3ed"] then setup_atlas_package("DatafileIO", "lib", { -- src "", "BAR", "DDT", "SCN", "Stream", "XMB" },{ -- include },{ -- extern_libs "devil", "xerces", "zlib" },{ -- extra_params pch = 1, }) end end -- Atlas 'frontend' tool-launching packages function setup_atlas_frontend_package (package_name) package_create(package_name, "winexe") package_add_extern_libs({ "spidermonkey", }) local source_root = "../../../source/tools/atlas/AtlasFrontends/" package.files = { source_root..package_name..".cpp", source_root..package_name..".rc" } package.trimprefix = source_root package.includepaths = { source_root .. ".." } -- Platform Specifics if OS == "windows" then tinsert(package.defines, "_UNICODE") -- required to use WinMain() on Windows, otherwise will default to main() tinsert(package.buildflags, "no-main") else -- Non-Windows, = Unix if options["aoe3ed"] then tinsert(package.links, "DatafileIO") end tinsert(package.links, "AtlasObject") end tinsert(package.links, "AtlasUI") end function setup_atlas_frontends() setup_atlas_frontend_package("ActorEditor") setup_atlas_frontend_package("ColourTester") if options["aoe3ed"] then setup_atlas_frontend_package("ArchiveViewer") setup_atlas_frontend_package("FileConverter") end end -------------------------------------------------------------------------------- -- collada -------------------------------------------------------------------------------- function setup_collada_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) package_create(package_name, target_type) -- Don't add the default 'sourceroot/pch/projectname' for finding PCH files extra_params["no_default_pch"] = 1 package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) package_add_extern_libs(extern_libs) if extra_params["pch"] then package_setup_pch(nil, "precompiled.h", "precompiled.cpp"); end -- Platform Specifics if OS == "windows" then -- required to use WinMain() on Windows, otherwise will default to main() tinsert(package.buildflags, "no-main") if extra_params["extra_links"] then listconcat(package.links, extra_params["extra_links"]) end end if OS == "linux" then tinsert(package.defines, "LINUX"); -- FCollada is not aliasing-safe, so disallow dangerous optimisations -- (TODO: It'd be nice to fix FCollada, but that looks hard) tinsert(package.buildoptions, "-fno-strict-aliasing") tinsert(package.buildoptions, "-rdynamic") tinsert(package.linkoptions, "-rdynamic") elseif OS == "macosx" then -- define MACOS-something? -- On OSX, fcollada uses a few utility functions from coreservices tinsert(package.linkoptions, "-framework CoreServices") end end -- build all Collada component packages function setup_collada_packages() setup_collada_package("Collada", "dll", { -- src "collada" },{ -- include },{ -- extern_libs "fcollada", "dl", "libxml2", },{ -- extra_params pch = 1, }) end -------------------------------------------------------------------------------- -- tests -------------------------------------------------------------------------------- function get_all_test_files(root, src_files, hdr_files) -- note: lua doesn't have any directory handling functions at all, ugh. -- premake's matchrecursive on patterns like *tests*.h doesn't work - -- apparently it applies them to filenames, not the complete path. -- our workaround is to enumerate all files and manually filter out the -- desired */tests/* files. this is a bit slow, but hey. local all_files = matchrecursive(root .. "*.h") for i,v in pairs(all_files) do -- header file in subdirectory test if string.sub(v, -2) == ".h" and string.find(v, "/tests/") then -- 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 OS ~= "windows") and not (string.find(v, "/tools/atlas/") and not options["atlas"]) then tinsert(hdr_files, v) -- add the corresponding source file immediately, instead of -- waiting for it to appear after cxxtestgen. this avoids -- having to recreate workspace 2x after adding a test. tinsert(src_files, string.sub(v, 1, -3) .. ".cpp") end end end end function setup_tests() local src_files = {} local hdr_files = {} get_all_test_files(source_root, src_files, hdr_files) package_create("test_gen", "cxxtestgen") package.files = hdr_files package.rootfile = source_root .. "test_root.cpp" package.testoptions = "--have-std" package.rootoptions = "--have-std" if OS == "windows" then package.rootoptions = package.rootoptions .. " --gui=PsTestWrapper --runner=Win32ODSPrinter" else package.rootoptions = package.rootoptions .. " --gui=PsTestWrapper --runner=ErrorPrinter" end -- 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. include = " --include=precompiled.h" package.rootoptions = package.rootoptions .. include package.testoptions = package.testoptions .. include tinsert(package.buildflags, "no-manifest") package_create("test", "winexe") links = static_lib_names tinsert(links, "test_gen") if options["atlas"] then tinsert(links, "AtlasObject") end extra_params = { extra_files = { "test_root.cpp", "test_setup.cpp" }, extra_links = links, } package_add_contents(source_root, {}, {}, extra_params) -- note: these are not relative to source_root and therefore can't be included via package_add_contents. listconcat(package.files, src_files) package_add_extern_libs(used_extern_libs) tinsert(package.links, "mocks_test") if OS == "windows" then -- from "lowlevel" static lib; must be added here to be linked in tinsert(package.files, source_root.."lib/sysdep/os/win/error_dialog.rc") tinsert(package.buildflags, "no-rtti") -- this seems to be required when using vcbuild but not the IDE (?!) tinsert(package.links, "ws2_32") -- see wstartup.h tinsert(package.linkoptions, "/INCLUDE:_wstartup_InitAndRegisterShutdown") elseif OS == "linux" then tinsert(package.links, { "fam", -- Utilities "rt", -- Dynamic libraries (needed for linking for gold) "dl", }) -- Threading support tinsert(package.buildoptions, "-pthread") tinsert(package.linkoptions, "-pthread") -- For debug_resolve_symbol package.config["Debug"].linkoptions = { "-rdynamic" } package.config["Testing"].linkoptions = { "-rdynamic" } tinsert(package.includepaths, source_root .. "pch/test/") end package_setup_pch( source_root .. "pch/test/", "precompiled.h", "precompiled.cpp"); tinsert(package.buildflags, "use-library-dep-inputs") 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() -- save package global variable for later (will be overwritten by setup_all_libs) main_exe_package = package setup_all_libs() -- HACK: 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. listconcat(main_exe_package.links, static_lib_names) if options["atlas"] then setup_atlas_packages() setup_atlas_frontends() end if options["collada"] then setup_collada_packages() end if not options["without-tests"] then setup_tests() end Index: ps/trunk/source/ps/ConfigDB.cpp =================================================================== --- ps/trunk/source/ps/ConfigDB.cpp (revision 8924) +++ ps/trunk/source/ps/ConfigDB.cpp (revision 8925) @@ -1,418 +1,423 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Pyrogenesis.h" #include "Parser.h" #include "ConfigDB.h" #include "CLogger.h" #include "Filesystem.h" #include "scripting/ScriptingHost.h" #include "scriptinterface/ScriptInterface.h" #include typedef std::map TConfigMap; TConfigMap CConfigDB::m_Map[CFG_LAST]; -CStrW CConfigDB::m_ConfigFile[CFG_LAST]; -bool CConfigDB::m_UseVFS[CFG_LAST]; +VfsPath CConfigDB::m_ConfigFile[CFG_LAST]; #define GET_NS_PRIVATE(cx, obj) (EConfigNamespace)((intptr_t)JS_GetPrivate(cx, obj) >> 1) namespace ConfigNamespace_JS { JSBool GetProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp) { EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; jsval idval; if (!JS_IdToValue(cx, id, &idval)) return JS_FALSE; std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) return JS_FALSE; CConfigValue *val = g_ConfigDB.GetValue(cfgNs, propName); if (val) { JSString *js_str = JS_NewStringCopyN(cx, val->m_String.c_str(), val->m_String.size()); *vp = STRING_TO_JSVAL(js_str); } return JS_TRUE; } JSBool SetProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp) { EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; jsval idval; if (!JS_IdToValue(cx, id, &idval)) return JS_FALSE; std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) return JS_FALSE; CConfigValue *val = g_ConfigDB.CreateValue(cfgNs, propName); if (!ScriptInterface::FromJSVal(cx, *vp, val->m_String)) return JS_FALSE; return JS_TRUE; } JSClass Class = { "ConfigNamespace", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, GetProperty, SetProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; JSBool Construct(JSContext* cx, uintN argc, jsval* vp) { UNUSED2(argc); JSObject *newObj = JS_NewObject(cx, &Class, NULL, NULL); JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObj)); return JS_TRUE; } void SetNamespace(JSContext *cx, JSObject *obj, EConfigNamespace cfgNs) { JS_SetPrivate(cx, obj, (void *)((uintptr_t)cfgNs << 1)); // JS requires bottom bit = 0 } JSBool WriteFile(JSContext* cx, uintN argc, jsval* vp) { EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, JS_THIS_OBJECT(cx, vp)); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; - if (argc != 2) - return JS_FALSE; - - bool useVFS; - if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], useVFS)) + if (argc != 1) return JS_FALSE; std::wstring path; - if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[1], path)) + if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], path)) return JS_FALSE; - bool res = g_ConfigDB.WriteFile(cfgNs, useVFS, path); + bool res = g_ConfigDB.WriteFile(cfgNs, path); JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(res)); return JS_TRUE; } JSBool Reload(JSContext* cx, uintN argc, jsval* vp) { if (argc != 0) return JS_FALSE; EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, JS_THIS_OBJECT(cx, vp)); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; JSBool ret = g_ConfigDB.Reload(cfgNs); JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(ret)); return JS_TRUE; } JSBool SetFile(JSContext* cx, uintN argc, jsval* vp) { EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, JS_THIS_OBJECT(cx, vp)); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; - if (argc != 2) - return JS_FALSE; - - bool useVFS; - if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], useVFS)) + if (argc != 1) return JS_FALSE; std::wstring path; - if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[1], path)) + if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], path)) return JS_FALSE; - g_ConfigDB.SetConfigFile(cfgNs, useVFS, path); + g_ConfigDB.SetConfigFile(cfgNs, path); JS_SET_RVAL(cx, vp, JSVAL_VOID); return JS_TRUE; } JSFunctionSpec Funcs[] = { { "writeFile", WriteFile, 2, 0 }, { "reload", Reload, 0, 0 }, { "setFile", SetFile, 2, 0 }, {0} }; }; namespace ConfigDB_JS { JSClass Class = { "ConfigDB", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; JSPropertySpec Props[] = { {0} }; JSFunctionSpec Funcs[] = { {0} }; JSBool Construct(JSContext* cx, uintN argc, jsval* vp) { UNUSED2(argc); JSObject *newObj = JS_NewObject(cx, &Class, NULL, NULL); JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObj)); int flags=JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT; #define cfg_ns(_propname, _enum) STMT (\ JSObject *nsobj=g_ScriptingHost.CreateCustomObject("ConfigNamespace"); \ debug_assert(nsobj); \ ConfigNamespace_JS::SetNamespace(cx, nsobj, _enum); \ debug_assert(JS_DefineProperty(cx, newObj, _propname, OBJECT_TO_JSVAL(nsobj), NULL, NULL, flags)); ) cfg_ns("default", CFG_DEFAULT); cfg_ns("system", CFG_SYSTEM); cfg_ns("user", CFG_USER); cfg_ns("mod", CFG_MOD); #undef cfg_ns return JS_TRUE; } }; CConfigDB::CConfigDB() { g_ScriptingHost.DefineCustomObjectType(&ConfigDB_JS::Class, ConfigDB_JS::Construct, 0, ConfigDB_JS::Props, ConfigDB_JS::Funcs, NULL, NULL); g_ScriptingHost.DefineCustomObjectType(&ConfigNamespace_JS::Class, ConfigNamespace_JS::Construct, 0, NULL, ConfigNamespace_JS::Funcs, NULL, NULL); JSObject *js_ConfigDB = g_ScriptingHost.CreateCustomObject("ConfigDB"); g_ScriptingHost.SetGlobal("g_ConfigDB", OBJECT_TO_JSVAL(js_ConfigDB)); } CConfigValue *CConfigDB::GetValue(EConfigNamespace ns, const CStr& name) { CConfigValueSet* values = GetValues(ns, name); if (!values) return (NULL); return &((*values)[0]); } CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, const CStr& name) { if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return NULL; } TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); if (it != m_Map[CFG_COMMAND].end()) return &(it->second); for (int search_ns = ns; search_ns >= 0; search_ns--) { TConfigMap::iterator it = m_Map[search_ns].find(name); if (it != m_Map[search_ns].end()) return &(it->second); } return NULL; } std::vector > CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) { std::vector > ret; if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return ret; } for (TConfigMap::iterator it = m_Map[CFG_COMMAND].begin(); it != m_Map[CFG_COMMAND].end(); ++it) { if (boost::algorithm::starts_with(it->first, prefix)) ret.push_back(std::make_pair(it->first, it->second)); } for (int search_ns = ns; search_ns >= 0; search_ns--) { for (TConfigMap::iterator it = m_Map[search_ns].begin(); it != m_Map[search_ns].end(); ++it) { if (boost::algorithm::starts_with(it->first, prefix)) ret.push_back(std::make_pair(it->first, it->second)); } } return ret; } CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, const CStr& name) { if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return NULL; } CConfigValue *ret=GetValue(ns, name); if (ret) return ret; TConfigMap::iterator it=m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet( 1 ))); return &(it->second[0]); } -void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path) +void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path) { if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return; } m_ConfigFile[ns]=path; - m_UseVFS[ns]=useVFS; } bool CConfigDB::Reload(EConfigNamespace ns) { + if (ns < 0 || ns >= CFG_LAST) + { + debug_warn(L"CConfigDB: Invalid ns value"); + return false; + } + // Set up CParser CParser parser; CParserLine parserLine; parser.InputTaskType("Assignment", "_$ident_=<_[-$arg(_minus)]_$value_,>_[-$arg(_minus)]_$value[[;]$rest]"); parser.InputTaskType("CommentOrBlank", "_[;[$rest]]"); // Open file with VFS shared_ptr buffer; size_t buflen; { // Handle missing files quietly if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0) { - LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].c_str()); + LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].string().c_str()); return false; } else { - LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].c_str()); + LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].string().c_str()); LibError ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen); if (ret != INFO::OK) { - LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %ld", m_ConfigFile[ns].c_str(), ret); + LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %ld", m_ConfigFile[ns].string().c_str(), ret); return false; } } } TConfigMap newMap; char *filebuf=(char *)buffer.get(); char *filebufend=filebuf+buflen; // Read file line by line char *next=filebuf-1; do { char *pos=next+1; next=(char *)memchr(pos, '\n', filebufend-pos); if (!next) next=filebufend; char *lend=next; if (lend > filebuf && *(lend-1) == '\r') lend--; // Send line to parser bool parseOk=parserLine.ParseString(parser, std::string(pos, lend)); // Get name and value from parser std::string name; std::string value; if (parseOk && parserLine.GetArgCount()>=2 && parserLine.GetArgString(0, name) && parserLine.GetArgString(1, value)) { // Add name and value to the map size_t argCount = parserLine.GetArgCount(); newMap[name].clear(); for( size_t t = 0; t < argCount; t++ ) { if( !parserLine.GetArgString( (int)t + 1, value ) ) continue; CConfigValue argument; argument.m_String = value; newMap[name].push_back( argument ); LOGMESSAGE(L"Loaded config string \"%hs\" = \"%hs\"", name.c_str(), value.c_str()); } } } while (next < filebufend); m_Map[ns].swap(newMap); return true; } -bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path) +bool CConfigDB::WriteFile(EConfigNamespace ns) { - debug_assert(useVFS); + if (ns < 0 || ns >= CFG_LAST) + { + debug_warn(L"CConfigDB: Invalid ns value"); + return false; + } + + return WriteFile(ns, m_ConfigFile[ns]); +} +bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) +{ if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return false; } shared_ptr buf = io_Allocate(1*MiB); char* pos = (char*)buf.get(); TConfigMap &map=m_Map[ns]; for(TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it) { pos += sprintf(pos, "%s = \"%s\"\n", it->first.c_str(), it->second[0].m_String.c_str()); } const size_t len = pos - (char*)buf.get(); LibError ret = g_VFS->CreateFile(path, buf, len); if(ret < 0) { - LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.c_str(), (int)ret); + LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.string().c_str(), (int)ret); return false; } return true; } Index: ps/trunk/source/ps/GameSetup/Config.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/Config.cpp (revision 8924) +++ ps/trunk/source/ps/GameSetup/Config.cpp (revision 8925) @@ -1,199 +1,186 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ps/ConfigDB.h" #include "ps/CConsole.h" #include "ps/GameSetup/CmdLineArgs.h" #include "lib/timer.h" #include "lib/utf8.h" #include "lib/res/sound/snd_mgr.h" #include "Config.h" // (these variables are documented in the header.) CStrW g_CursorName = L"test"; -CStr g_ActiveProfile = "default"; bool g_NoGLS3TC = false; bool g_NoGLAutoMipmap = false; bool g_NoGLVBO = false; bool g_NoGLFramebufferObject = false; bool g_Shadows = false; bool g_FancyWater = false; float g_LodBias = 0.0f; float g_Gamma = 1.0f; bool g_EntGraph = false; CStr g_RenderPath = "default"; int g_xres, g_yres; bool g_VSync = false; bool g_Quickstart = false; bool g_DisableAudio = false; // flag to switch on drawing terrain overlays bool g_ShowPathfindingOverlay = false; // flag to switch on triangulation pathfinding bool g_TriPathfind = false; // If non-empty, specified map will be automatically loaded CStr g_AutostartMap = ""; //---------------------------------------------------------------------------- -// config and profile +// config //---------------------------------------------------------------------------- -static void LoadProfile( const CStr& profile ) -{ - VfsPath path = VfsPath(L"config/profiles") / wstring_from_utf8(profile); - - VfsPath configFilename = path / L"settings/user.cfg"; - g_ConfigDB.SetConfigFile(CFG_USER, true, configFilename.string().c_str()); - g_ConfigDB.Reload(CFG_USER); - - int max_history_lines = 200; - CFG_GET_USER_VAL("console.history.size", Int, max_history_lines); - g_Console->UseHistoryFile(path / L"settings/history", max_history_lines); -} - - // Fill in the globals from the config files. static void LoadGlobals() { - CFG_GET_SYS_VAL("profile", String, g_ActiveProfile); - - // Now load the profile before trying to retrieve the values of the rest of these. - - LoadProfile( g_ActiveProfile ); - CFG_GET_USER_VAL("vsync", Bool, g_VSync); CFG_GET_USER_VAL("nos3tc", Bool, g_NoGLS3TC); CFG_GET_USER_VAL("noautomipmap", Bool, g_NoGLAutoMipmap); CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO); CFG_GET_USER_VAL("noframebufferobject", Bool, g_NoGLFramebufferObject); CFG_GET_USER_VAL("shadows", Bool, g_Shadows); CFG_GET_USER_VAL("fancywater", Bool, g_FancyWater); CFG_GET_USER_VAL("renderpath", String, g_RenderPath); CFG_GET_USER_VAL("lodbias", Float, g_LodBias); float gain = -1.0f; CFG_GET_USER_VAL("sound.mastergain", Float, gain); if(gain >= 0.0f) WARN_ERR(snd_set_master_gain(gain)); } static void ProcessCommandLineArgs(const CmdLineArgs& args) { // TODO: all these options (and the ones processed elsewhere) should // be documented somewhere for users. if (args.Has("buildarchive")) { // note: VFS init is sure to have been completed by now // (since CONFIG_Init reads from file); therefore, // it is safe to call this from here directly. // vfs_opt_rebuild_main_archive("mods/official/official1.zip", "../logs/trace.txt"); } // Handle "-conf=key:value" (potentially multiple times) std::vector conf = args.GetMultiple("conf"); for (size_t i = 0; i < conf.size(); ++i) { CStr name_value = conf[i]; if (name_value.Find(':') != -1) { CStr name = name_value.BeforeFirst(":"); CStr value = name_value.AfterFirst(":"); g_ConfigDB.CreateValue(CFG_COMMAND, name)->m_String = value; } } if (args.Has("entgraph")) g_EntGraph = true; if (args.Has("g")) { g_Gamma = (float)atof(args.Get("g")); if (g_Gamma == 0.0f) g_Gamma = 1.0f; } // if (args.Has("listfiles")) // trace_enable(true); if (args.Has("profile")) g_ConfigDB.CreateValue(CFG_COMMAND, "profile")->m_String = args.Get("profile"); if (args.Has("quickstart")) { g_Quickstart = true; g_DisableAudio = true; // do this for backward-compatibility with user expectations } if (args.Has("nosound")) g_DisableAudio = true; if (args.Has("shadows")) g_ConfigDB.CreateValue(CFG_COMMAND, "shadows")->m_String = "true"; if (args.Has("xres")) g_ConfigDB.CreateValue(CFG_COMMAND, "xres")->m_String = args.Get("xres"); if (args.Has("yres")) g_ConfigDB.CreateValue(CFG_COMMAND, "yres")->m_String = args.Get("yres"); if (args.Has("vsync")) g_ConfigDB.CreateValue(CFG_COMMAND, "vsync")->m_String = "true"; } void CONFIG_Init(const CmdLineArgs& args) { TIMER(L"CONFIG_Init"); new CConfigDB; // Load the global, default config file - g_ConfigDB.SetConfigFile(CFG_DEFAULT, false, L"config/default.cfg"); + g_ConfigDB.SetConfigFile(CFG_DEFAULT, L"config/default.cfg"); g_ConfigDB.Reload(CFG_DEFAULT); // 216ms // Try loading the local system config file (which doesn't exist by // default) - this is designed as a way of letting developers edit the // system config without accidentally committing their changes back to SVN. - g_ConfigDB.SetConfigFile(CFG_SYSTEM, false, L"config/local.cfg"); + g_ConfigDB.SetConfigFile(CFG_SYSTEM, L"config/local.cfg"); g_ConfigDB.Reload(CFG_SYSTEM); - g_ConfigDB.SetConfigFile(CFG_MOD, true, L"config/mod.cfg"); + g_ConfigDB.SetConfigFile(CFG_USER, L"config/user.cfg"); + g_ConfigDB.Reload(CFG_USER); + + g_ConfigDB.SetConfigFile(CFG_MOD, L"config/mod.cfg"); // No point in reloading mod.cfg here - we haven't mounted mods yet ProcessCommandLineArgs(args); + // Initialise console history file + int max_history_lines = 200; + CFG_GET_USER_VAL("console.history.size", Int, max_history_lines); + g_Console->UseHistoryFile(L"config/console.txt", max_history_lines); + // Collect information from system.cfg, the profile file, // and any command-line overrides to fill in the globals. LoadGlobals(); // 64ms } Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 8924) +++ ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 8925) @@ -1,103 +1,258 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "scripting/ScriptingHost.h" #include "scriptinterface/ScriptInterface.h" #include "lib/ogl.h" +#include "lib/svn_revision.h" +#include "lib/timer.h" #include "lib/utf8.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/sysdep/os_cpu.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/UserReport.h" #include "ps/VideoMode.h" #include "ps/GameSetup/Config.h" void SetDisableAudio(void* UNUSED(cbdata), bool disabled) { g_DisableAudio = disabled; } +static void ReportGLLimits(ScriptInterface& scriptInterface, CScriptValRooted settings); + void RunHardwareDetection() { + TIMER(L"RunHardwareDetection"); + ScriptInterface& scriptInterface = g_ScriptingHost.GetScriptInterface(); scriptInterface.RegisterFunction("SetDisableAudio"); // Load the detection script: const wchar_t* scriptName = L"hwdetect/hwdetect.js"; CVFSFile file; if (file.Load(g_VFS, scriptName) != PSRETURN_OK) { LOGERROR(L"Failed to load hardware detection script"); return; } LibError err; // ignore encoding errors std::wstring code = wstring_from_utf8(file.GetAsString(), &err); scriptInterface.LoadScript(scriptName, code); // Collect all the settings we'll pass to the script: + // (We'll use this same data for the opt-in online reporting system, so it + // includes some fields that aren't directly useful for the hwdetect script) CScriptValRooted settings; scriptInterface.Eval("({})", settings); - scriptInterface.SetProperty(settings.get(), "os_unix", OS_UNIX, false); - scriptInterface.SetProperty(settings.get(), "os_linux", OS_LINUX, false); - scriptInterface.SetProperty(settings.get(), "os_macosx", OS_MACOSX, false); - scriptInterface.SetProperty(settings.get(), "os_win", OS_WIN, false); - - scriptInterface.SetProperty(settings.get(), "gfx_card", std::wstring(gfx_card), false); - scriptInterface.SetProperty(settings.get(), "gfx_drv_ver", std::wstring(gfx_drv_ver), false); - scriptInterface.SetProperty(settings.get(), "gfx_mem", gfx_mem, false); - - scriptInterface.SetProperty(settings.get(), "gl_vendor", std::string((const char*)glGetString(GL_VENDOR)), false); - scriptInterface.SetProperty(settings.get(), "gl_renderer", std::string((const char*)glGetString(GL_RENDERER)), false); - scriptInterface.SetProperty(settings.get(), "gl_version", std::string((const char*)glGetString(GL_VERSION)), false); - - const char* exts = ogl_ExtensionString(); - if (!exts) exts = ""; - scriptInterface.SetProperty(settings.get(), "gl_extensions", std::string(exts), false); - scriptInterface.SetProperty(settings.get(), "gl_max_tex_size", (int)ogl_max_tex_size, false); - scriptInterface.SetProperty(settings.get(), "gl_max_tex_units", (int)ogl_max_tex_units, false); - - scriptInterface.SetProperty(settings.get(), "video_xres", g_VideoMode.GetXRes(), false); - scriptInterface.SetProperty(settings.get(), "video_yres", g_VideoMode.GetYRes(), false); - scriptInterface.SetProperty(settings.get(), "video_bpp", g_VideoMode.GetBPP(), false); + scriptInterface.SetProperty(settings.get(), "os_unix", OS_UNIX); + scriptInterface.SetProperty(settings.get(), "os_linux", OS_LINUX); + scriptInterface.SetProperty(settings.get(), "os_macosx", OS_MACOSX); + scriptInterface.SetProperty(settings.get(), "os_win", OS_WIN); + + scriptInterface.SetProperty(settings.get(), "arch_ia32", ARCH_IA32); + scriptInterface.SetProperty(settings.get(), "arch_amd64", ARCH_AMD64); + +#ifdef NDEBUG + scriptInterface.SetProperty(settings.get(), "build_debug", 0); +#else + scriptInterface.SetProperty(settings.get(), "build_debug", 1); +#endif + scriptInterface.SetProperty(settings.get(), "build_datetime", std::string(__DATE__ " " __TIME__)); + scriptInterface.SetProperty(settings.get(), "build_revision", std::wstring(svn_revision)); + + scriptInterface.SetProperty(settings.get(), "gfx_card", std::wstring(gfx_card)); + scriptInterface.SetProperty(settings.get(), "gfx_drv_ver", std::wstring(gfx_drv_ver)); + scriptInterface.SetProperty(settings.get(), "gfx_mem", gfx_mem); + + ReportGLLimits(scriptInterface, settings); + + scriptInterface.SetProperty(settings.get(), "video_xres", g_VideoMode.GetXRes()); + scriptInterface.SetProperty(settings.get(), "video_yres", g_VideoMode.GetYRes()); + scriptInterface.SetProperty(settings.get(), "video_bpp", g_VideoMode.GetBPP()); struct utsname un; uname(&un); - scriptInterface.SetProperty(settings.get(), "uname_sysname", std::string(un.sysname), false); - scriptInterface.SetProperty(settings.get(), "uname_release", std::string(un.release), false); - scriptInterface.SetProperty(settings.get(), "uname_version", std::string(un.version), false); - scriptInterface.SetProperty(settings.get(), "uname_machine", std::string(un.machine), false); + scriptInterface.SetProperty(settings.get(), "uname_sysname", std::string(un.sysname)); + scriptInterface.SetProperty(settings.get(), "uname_release", std::string(un.release)); + scriptInterface.SetProperty(settings.get(), "uname_version", std::string(un.version)); + scriptInterface.SetProperty(settings.get(), "uname_machine", std::string(un.machine)); + + scriptInterface.SetProperty(settings.get(), "cpu_identifier", std::string(cpu_IdentifierString())); + scriptInterface.SetProperty(settings.get(), "cpu_frequency", os_cpu_ClockFrequency()); - scriptInterface.SetProperty(settings.get(), "cpu_identifier", std::string(cpu_IdentifierString()), false); - scriptInterface.SetProperty(settings.get(), "cpu_frequency", os_cpu_ClockFrequency(), false); + scriptInterface.SetProperty(settings.get(), "ram_total", (int)os_cpu_MemorySize()); + scriptInterface.SetProperty(settings.get(), "ram_free", (int)os_cpu_MemoryAvailable()); - scriptInterface.SetProperty(settings.get(), "ram_total", (int)os_cpu_MemorySize(), false); - scriptInterface.SetProperty(settings.get(), "ram_free", (int)os_cpu_MemoryAvailable(), false); + // Send the same data to the reporting system + g_UserReporter.SubmitReport("hwdetect", 1, scriptInterface.StringifyJSON(settings.get(), false)); // Run the detection script: scriptInterface.CallFunctionVoid(scriptInterface.GetGlobalObject(), "RunHardwareDetection", settings); } + + +static void ReportGLLimits(ScriptInterface& scriptInterface, CScriptValRooted settings) +{ +#define INTEGER(id) do { \ + GLint i = -1; \ + glGetIntegerv(GL_##id, &i); \ + ogl_WarnIfError(); \ + scriptInterface.SetProperty(settings.get(), "GL_" #id, i); \ + } while (false) + +#define INTEGER2(id) do { \ + GLint i[2] = { -1, -1 }; \ + glGetIntegerv(GL_##id, i); \ + ogl_WarnIfError(); \ + scriptInterface.SetProperty(settings.get(), "GL_" #id "[0]", i[0]); \ + scriptInterface.SetProperty(settings.get(), "GL_" #id "[1]", i[1]); \ + } while (false) + +#define FLOAT(id) do { \ + GLfloat f = std::numeric_limits::quiet_NaN(); \ + glGetFloatv(GL_##id, &f); \ + ogl_WarnIfError(); \ + scriptInterface.SetProperty(settings.get(), "GL_" #id, f); \ + } while (false) + +#define FLOAT2(id) do { \ + GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \ + glGetFloatv(GL_##id, f); \ + ogl_WarnIfError(); \ + scriptInterface.SetProperty(settings.get(), "GL_" #id "[0]", f[0]); \ + scriptInterface.SetProperty(settings.get(), "GL_" #id "[1]", f[1]); \ + } while (false) + +#define STRING(id) do { \ + const char* c = (const char*)glGetString(GL_##id); \ + ogl_WarnIfError(); \ + if (!c) c = ""; \ + scriptInterface.SetProperty(settings.get(), "GL_" #id, std::string(c)); \ + } while (false) + +#define BOOL(id) INTEGER(id) + + // Core OpenGL 1.3: + // (We don't bother checking extension strings for anything older than 1.3; + // it'll just produce harmless warnings) + STRING(VERSION); + STRING(VENDOR); + STRING(RENDERER); + STRING(EXTENSIONS); + INTEGER(MAX_LIGHTS); + INTEGER(MAX_CLIP_PLANES); + if (ogl_HaveExtension("GL_ARB_imaging")) // only in imaging subset + INTEGER(MAX_COLOR_MATRIX_STACK_DEPTH); + INTEGER(MAX_MODELVIEW_STACK_DEPTH); + INTEGER(MAX_PROJECTION_STACK_DEPTH); + INTEGER(MAX_TEXTURE_STACK_DEPTH); + INTEGER(SUBPIXEL_BITS); + INTEGER(MAX_3D_TEXTURE_SIZE); + INTEGER(MAX_TEXTURE_SIZE); + INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE); + INTEGER(MAX_PIXEL_MAP_TABLE); + INTEGER(MAX_NAME_STACK_DEPTH); + INTEGER(MAX_LIST_NESTING); + INTEGER(MAX_EVAL_ORDER); + INTEGER2(MAX_VIEWPORT_DIMS); + INTEGER(MAX_ATTRIB_STACK_DEPTH); + INTEGER(MAX_CLIENT_ATTRIB_STACK_DEPTH); + INTEGER(AUX_BUFFERS); + BOOL(RGBA_MODE); + BOOL(INDEX_MODE); + BOOL(DOUBLEBUFFER); + BOOL(STEREO); + FLOAT2(ALIASED_POINT_SIZE_RANGE); + FLOAT2(SMOOTH_POINT_SIZE_RANGE); + FLOAT(SMOOTH_POINT_SIZE_GRANULARITY); + FLOAT2(ALIASED_LINE_WIDTH_RANGE); + FLOAT2(SMOOTH_LINE_WIDTH_RANGE); + FLOAT(SMOOTH_LINE_WIDTH_GRANULARITY); + // Skip MAX_CONVOLUTION_WIDTH, MAX_CONVOLUTION_HEIGHT since they'd need GetConvolutionParameteriv + INTEGER(MAX_ELEMENTS_INDICES); + INTEGER(MAX_ELEMENTS_VERTICES); + INTEGER(MAX_TEXTURE_UNITS); + INTEGER(SAMPLE_BUFFERS); + INTEGER(SAMPLES); + // TODO: compressed texture formats + INTEGER(RED_BITS); + INTEGER(GREEN_BITS); + INTEGER(BLUE_BITS); + INTEGER(ALPHA_BITS); + INTEGER(INDEX_BITS); + INTEGER(DEPTH_BITS); + INTEGER(STENCIL_BITS); + INTEGER(ACCUM_RED_BITS); + INTEGER(ACCUM_GREEN_BITS); + INTEGER(ACCUM_BLUE_BITS); + INTEGER(ACCUM_ALPHA_BITS); + + // Core OpenGL 2.0 (treated as extensions): + + if (ogl_HaveExtension("GL_EXT_texture_lod_bias")) + FLOAT(MAX_TEXTURE_LOD_BIAS_EXT); + + // Skip GL_ARB_occlusion_query's QUERY_COUNTER_BITS_ARB since it'd need GetQueryiv + + if (ogl_HaveExtension("GL_ARB_shading_language_100")) + STRING(SHADING_LANGUAGE_VERSION_ARB); + + if (ogl_HaveExtension("GL_ARB_vertex_shader")) + { + INTEGER(MAX_VERTEX_ATTRIBS_ARB); + INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB); + INTEGER(MAX_VARYING_FLOATS_ARB); + INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB); + INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB); + } + + if (ogl_HaveExtension("GL_ARB_fragment_shader")) + INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB); + + if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader")) + { + INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB); + INTEGER(MAX_TEXTURE_COORDS_ARB); + } + + if (ogl_HaveExtension("GL_ARB_draw_buffers")) + INTEGER(MAX_DRAW_BUFFERS_ARB); + + // Other interesting extensions: + + if (ogl_HaveExtension("GL_EXT_framebuffer_object")) + { + INTEGER(MAX_COLOR_ATTACHMENTS_EXT); + INTEGER(MAX_RENDERBUFFER_SIZE_EXT); + } + + if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic")) + FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT); +} Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8924) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8925) @@ -1,1053 +1,1061 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/app_hooks.h" #include "lib/input.h" #include "lib/lockfree.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/utf8.h" #include "lib/external_libraries/sdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" #include "lib/res/graphics/cursor.h" #include "lib/res/sound/snd_mgr.h" #include "lib/sysdep/cursor.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/tex/tex.h" #if OS_WIN #include "lib/sysdep/os/win/wversion.h" #endif #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" +#include "ps/UserReport.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapReader.h" #include "graphics/MaterialManager.h" #include "graphics/ParticleEngine.h" #include "graphics/TerrainTextureManager.h" #include "renderer/Renderer.h" #include "renderer/VertexBufferManager.h" #include "maths/MathUtil.h" #include "simulation2/Simulation2.h" #include "scripting/ScriptingHost.h" #include "scripting/ScriptGlue.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptStats.h" #include "maths/scripting/JSInterface_Vector3D.h" #include "ps/scripting/JSInterface_Console.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/ScriptFunctions.h" #include "sound/JSI_Sound.h" #include "network/NetServer.h" #include "network/NetClient.h" #include "ps/Pyrogenesis.h" // psSetLogDir #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Paths.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" #include "ps/GameSetup/HWDetect.h" #if !(OS_WIN || OS_MACOSX) // assume all other platforms use X11 for wxWidgets #define MUST_INIT_X11 1 #include #else #define MUST_INIT_X11 0 #endif #include ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); bool g_DoRenderGui = true; bool g_DoRenderLogger = true; bool g_DoRenderCursor = true; static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code static void SetTextureQuality(int quality) { int q_flags; GLint filter; retry: // keep this in sync with SANE_TEX_QUALITY_DEFAULT switch(quality) { // worst quality case 0: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_NEAREST; break; // [perf] add bilinear filtering case 1: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] no longer reduce resolution case 2: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] add mipmaps case 3: q_flags = OGL_TEX_HALF_BPP; filter = GL_NEAREST_MIPMAP_LINEAR; break; // [perf] better filtering case 4: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [vmem] no longer reduce bpp case SANE_TEX_QUALITY_DEFAULT: q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [perf] add anisotropy case 6: // TODO: add anisotropic filtering q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // invalid default: debug_warn(L"SetTextureQuality: invalid quality"); quality = SANE_TEX_QUALITY_DEFAULT; // careful: recursion doesn't work and we don't want to duplicate // the "sane" default values. goto retry; } ogl_tex_set_defaults(q_flags, filter); } //---------------------------------------------------------------------------- // GUI integration //---------------------------------------------------------------------------- // display progress / description in loading screen void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) { g_ScriptingHost.GetScriptInterface().SetGlobal("g_Progress", percent, true); g_ScriptingHost.GetScriptInterface().SetGlobal("g_LoadDescription", pending_task, true); g_GUI->SendEventToAll("progress"); } void Render() { ogl_WarnIfError(); CStr skystring = "255 0 255"; CFG_GET_USER_VAL("skycolor", String, skystring); CColor skycol; GUI::ParseString(skystring, skycol); g_Renderer.SetClearColor(skycol.AsSColor4ub()); // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->Render(); } ogl_WarnIfError(); // set up overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); ogl_WarnIfError(); g_Renderer.RenderTextOverlays(); // Temp GUI message GeeTODO PROFILE_START("render gui"); if(g_DoRenderGui) g_GUI->Draw(); PROFILE_END("render gui"); ogl_WarnIfError(); // Particle Engine Updating CParticleEngine::GetInstance()->UpdateEmitters(); ogl_WarnIfError(); // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Added -- glEnable(GL_TEXTURE_2D); // -- GL glLoadIdentity(); PROFILE_START("render console"); g_Console->Render(); PROFILE_END("render console"); ogl_WarnIfError(); PROFILE_START("render logger"); if(g_DoRenderLogger) g_Logger->Render(); PROFILE_END("render logger"); ogl_WarnIfError(); // Profile information PROFILE_START("render profiling"); g_ProfileViewer.RenderProfile(); PROFILE_END("render profiling"); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) if (g_DoRenderCursor) { CStrW cursorName = g_CursorName; if (cursorName.empty()) { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y); } else { if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y) < 0) LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str()); } } // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); g_Renderer.EndFrame(); ogl_WarnIfError(); } static void RegisterJavascriptInterfaces() { // maths JSI_Vector3D::init(); // graphics CGameView::ScriptingInit(); // renderer CRenderer::ScriptingInit(); // sound JSI_Sound::ScriptingInit(); // ps JSI_Console::init(); // GUI CGUI::ScriptingInit(); GuiScriptingInit(g_ScriptingHost.GetScriptInterface()); } static void InitScripting() { TIMER(L"InitScripting"); // Create the scripting host. This needs to be done before the GUI is created. // [7ms] new ScriptingHost; RegisterJavascriptInterfaces(); } #if 0 // disabled because the file cache doesn't work (http://trac.wildfiregames.com/ticket/611) static size_t OperatingSystemFootprint() { #if OS_WIN switch(wversion_Number()) { case WVERSION_2K: case WVERSION_XP: return 150; case WVERSION_XP64: return 200; default: // don't warn about newer Windows versions case WVERSION_VISTA: return 300; case WVERSION_7: return 250; } #else return 200; #endif } static size_t ChooseCacheSize() { // (all sizes in MiB) const size_t total = os_cpu_MemorySize(); const size_t available = os_cpu_MemoryAvailable(); debug_assert(total >= available); const size_t inUse = total-available; size_t os = OperatingSystemFootprint(); debug_assert(total >= os/2); size_t apps = (inUse > os)? (inUse - os) : 0; size_t game = 300; size_t cache = 200; // plenty of memory if(os + apps + game + cache < total) { cache = total - os - apps - game; } else // below min-spec { // assume kernel+other apps will be swapped out os = 9*os/10; apps = 2*apps/3; game = 3*game/4; if(os + apps + game + cache < total) cache = total - os - apps - game; else { cache = 50; debug_printf(L"Warning: memory size (%d MiB, %d used) is rather low\n", total, available); } } // total data is currently about 500 MiB, and not all of it // is used at once, so don't use more than that. // (this also ensures the byte count will fit in size_t and // avoids using up too much address space) cache = std::min(cache, (size_t)500); debug_printf(L"Cache: %d (total: %d; available: %d)\n", cache, total, available); return cache*MiB; } #else static size_t ChooseCacheSize() { return 32*MiB; } #endif ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags)) { // If we're fullscreen, then sometimes (at least on some particular drivers on Linux) // displaying the error dialog hangs the desktop since the dialog box is behind the // fullscreen window. So we just force the game to windowed mode before displaying the dialog. // (But only if we're in the main thread, and not if we're being reentrant.) if (ThreadUtil::IsMainThread()) { static bool reentering = false; if (!reentering) { reentering = true; g_VideoMode.SetFullscreen(false); reentering = false; } } // We don't actually implement the error display here, so return appropriately return ERI_NOT_IMPLEMENTED; } static void InitVfs(const CmdLineArgs& args) { TIMER(L"InitVfs"); const Paths paths(args); fs::wpath logs(paths.Logs()); CreateDirectories(logs, 0700); psSetLogDir(logs); // desired location for crashlog is now known. update AppHooks ASAP // (particularly before the following error-prone operations): AppHooks hooks = {0}; hooks.bundle_logs = psBundleLogs; hooks.get_log_dir = psLogDir; hooks.display_error = psDisplayError; app_hooks_update(&hooks); const size_t cacheSize = ChooseCacheSize(); g_VFS = CreateVfs(cacheSize); g_VFS->Mount(L"screenshots/", paths.Data()/L"screenshots/"); const fs::wpath readonlyConfig = paths.RData()/L"config/"; g_VFS->Mount(L"config/", readonlyConfig); if(readonlyConfig != paths.Config()) g_VFS->Mount(L"config/", paths.Config()); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads) std::vector mods = args.GetMultiple("mod"); mods.push_back("public"); if(!args.Has("onlyPublicFiles")) mods.push_back("internal"); fs::wpath modArchivePath(paths.Cache()/L"mods"); fs::wpath modLoosePath(paths.RData()/L"mods"); for (size_t i = 0; i < mods.size(); ++i) { size_t priority = i; size_t flags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_MUST_EXIST; std::wstring modName (wstring_from_utf8(mods[i])); g_VFS->Mount(L"", AddSlash(modLoosePath/modName), flags, priority); g_VFS->Mount(L"", AddSlash(modArchivePath/modName), flags, priority); } // note: don't bother with g_VFS->TextRepresentation - directories // haven't yet been populated and are empty. } static void InitPs(bool setup_gui, const CStrW& gui_page, CScriptVal initData) { { // console TIMER(L"ps_console"); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font(CONSOLE_FONT); g_Console->m_iFontHeight = font.GetLineSpacing(); g_Console->m_iFontWidth = font.GetCharacterWidth(L'C'); g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 7; } // hotkeys { TIMER(L"ps_lang_hotkeys"); LoadHotkeys(); } if (!setup_gui) { // We do actually need *some* kind of GUI loaded, so use the // (currently empty) Atlas one g_GUI->SwitchPage(L"page_atlas.xml", initData); return; } // GUI uses VFS, so this must come after VFS init. g_GUI->SwitchPage(gui_page, initData); // Warn nicely about missing S3TC support if (!ogl_tex_has_s3tc()) { #if !(OS_WIN || OS_MACOSX) bool isMesa = true; #else bool isMesa = false; #endif g_GUI->GetScriptInterface().CallFunctionVoid(OBJECT_TO_JSVAL(g_GUI->GetScriptObject()), "s3tcWarning", isMesa); } } static void InitInput() { SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); g_Joystick.Initialise(); // register input handlers // This stack is constructed so the first added, will be the last // one called. This is important, because each of the handlers // has the potential to block events to go further down // in the chain. I.e. the last one in the list added, is the // only handler that can block all messages before they are // processed. in_add_handler(game_view_handler); in_add_handler(conInputHandler); in_add_handler(CProfileViewer::InputThunk); in_add_handler(HotkeyInputHandler); // gui_handler needs to be registered after (i.e. called before!) the // hotkey handler so that input boxes can be typed in without // setting off hotkeys. in_add_handler(gui_handler); // must be registered after (called before) the GUI which relies on these globals in_add_handler(GlobalsInputHandler); } static void ShutdownPs() { SAFE_DELETE(g_GUI); SAFE_DELETE(g_Console); // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y); } static void InitRenderer() { TIMER(L"InitRenderer"); if(g_NoGLS3TC) ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE); if(g_NoGLAutoMipmap) ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_NOFRAMEBUFFEROBJECT,g_NoGLFramebufferObject); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_FANCYWATER,g_FancyWater); g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff new CTerrainTextureManager; // create the material manager new CMaterialManager; g_Renderer.Open(g_xres,g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. g_Renderer.SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; g_Renderer.SetViewport(vp); ColorActivateFastImpl(); } static void InitSDL() { if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOGERROR(L"SDL library initialization failed: %hs", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); } static void ShutdownSDL() { SDL_Quit(); sys_cursor_reset(); } void EndGame() { SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } void Shutdown(int UNUSED(flags)) { EndGame(); ShutdownPs(); // Must delete g_GUI before g_ScriptingHost in_reset_handlers(); // destroy actor related stuff TIMER_BEGIN(L"shutdown actor stuff"); delete &g_MaterialManager; TIMER_END(L"shutdown actor stuff"); // destroy terrain related stuff TIMER_BEGIN(L"shutdown TexMan"); delete &g_TexMan; TIMER_END(L"shutdown TexMan"); // destroy renderer TIMER_BEGIN(L"shutdown Renderer"); delete &g_Renderer; g_VBMan.Shutdown(); TIMER_END(L"shutdown Renderer"); tex_codec_unregister_all(); TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); g_VideoMode.Shutdown(); + TIMER_BEGIN(L"shutdown UserReporter"); + g_UserReporter.Deinitialize(); + TIMER_END(L"shutdown UserReporter"); + TIMER_BEGIN(L"shutdown ScriptingHost"); delete &g_ScriptingHost; TIMER_END(L"shutdown ScriptingHost"); TIMER_BEGIN(L"shutdown ConfigDB"); delete &g_ConfigDB; TIMER_END(L"shutdown ConfigDB"); // resource // first shut down all resource owners, and then the handle manager. TIMER_BEGIN(L"resource modules"); snd_shutdown(); g_VFS.reset(); // this forcibly frees all open handles (thus preventing real leaks), // and makes further access to h_mgr impossible. h_mgr_shutdown(); file_stats_dump(); TIMER_END(L"resource modules"); TIMER_BEGIN(L"shutdown misc"); timer_DisplayClientTotals(); CNetHost::Deinitialize(); SAFE_DELETE(g_ScriptStatsTable); // should be last, since the above use them SAFE_DELETE(g_Logger); delete &g_Profiler; delete &g_ProfileViewer; TIMER_END(L"shutdown misc"); } #if OS_UNIX void SetDefaultIfLocaleInvalid() { // On misconfigured systems with incorrect locale settings, we'll die // with a C++ exception when some code tries to use locales. // To avoid death, we'll detect the problem here and warn the user and // reset to the default C locale. // For informing the user of the problem, use the list of env vars that // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.) const char* const LocaleEnvVars[] = { "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", "LANG" }; try { // this constructor is similar to setlocale(LC_ALL, ""), // but instead of returning NULL, it throws runtime_error // when the first locale env variable found contains an invalid value std::locale(""); } catch (std::runtime_error&) { LOGWARNING(L"Invalid locale settings"); for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++) { if (char* envval = getenv(LocaleEnvVars[i])) LOGWARNING(L" %hs=\"%hs\"", LocaleEnvVars[i], envval); else LOGWARNING(L" %hs=\"(unset)\"", LocaleEnvVars[i]); } // We should set LC_ALL since it overrides LANG if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1)) debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable."); else LOGWARNING(L"Setting LC_ALL env variable to: %hs", getenv("LC_ALL")); } } #else void SetDefaultIfLocaleInvalid() { // Do nothing on Windows } #endif void EarlyInit() { // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(232647); ThreadUtil::SetMainThread(); debug_SetThreadName("main"); // add all debug_printf "tags" that we are interested in: debug_filter_add(L"TIMER"); debug_filter_add(L"HRT"); SetDefaultIfLocaleInvalid(); cpu_ConfigureFloatingPoint(); timer_LatchStartTime(); // Because we do GL calls from a secondary thread, Xlib needs to // be told to support multiple threads safely. // This is needed for Atlas, but we have to call it before any other // Xlib functions (e.g. the ones used when drawing the main menu // before launching Atlas) #if MUST_INIT_X11 int status = XInitThreads(); if (status == 0) debug_printf(L"Error enabling thread-safety via XInitThreads\n"); #endif // Initialise the low-quality rand function srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! } static bool Autostart(const CmdLineArgs& args); void Init(const CmdLineArgs& args, int UNUSED(flags)) { h_mgr_init(); // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. InitVfs(args); // This must come after VFS init, which sets the current directory // (required for finding our output log files). g_Logger = new CLogger; // Special command-line mode to dump the entity schemas instead of running the game. // (This must be done after loading VFS etc, but should be done before wasting time // on anything else.) if (args.Has("dumpSchema")) { CSimulation2 sim(NULL, NULL); sim.LoadDefaultScripts(); std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc); f << sim.GenerateSchema(); std::cout << "Generated entity.rng\n"; exit(0); } // override ah_translate with our i18n code. AppHooks hooks = {0}; hooks.translate = psTranslate; hooks.translate_free = psTranslateFree; app_hooks_update(&hooks); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); CNetHost::Initialize(); new CProfileViewer; new CProfileManager; // before any script code g_ScriptStatsTable = new CScriptStatsTable; g_ProfileViewer.AddRootTable(g_ScriptStatsTable); InitScripting(); // before GUI // g_ConfigDB, command line args, globals CONFIG_Init(args); + + if (!g_Quickstart) + g_UserReporter.Initialize(); // after config } void InitGraphics(const CmdLineArgs& args, int flags) { const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0; if(setup_vmode) { InitSDL(); if (!g_VideoMode.InitSDL()) throw PSERROR_System_VmodeFailed(); // abort startup SDL_WM_SetCaption("0 A.D.", "0 A.D."); } // needed by ogl_tex to detect broken gfx card/driver combos, // but takes a while due to WMI startup, so make it optional. if(!g_Quickstart) gfx_detect(); RunHardwareDetection(); tex_codec_register_all(); const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file SetTextureQuality(quality); ogl_WarnIfError(); if(!g_Quickstart) { WriteSystemInfo(); // note: no longer vfs_display here. it's dog-slow due to unbuffered // file output and very rarely needed. } if(g_DisableAudio) { // speed up startup by disabling all sound // (OpenAL init will be skipped). // must be called before first snd_open. snd_disable(true); } g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface()); // (must come after SetVideoMode, since it calls ogl_Init) const char* missing = ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", NULL); if(missing) { wchar_t buf[500]; swprintf_s(buf, ARRAY_SIZE(buf), L"The %hs extension doesn't appear to be available on your computer." L" The game may still work, though - you are welcome to try at your own risk." L" If not or it doesn't look right, upgrade your graphics card.", missing ); DEBUG_DISPLAY_ERROR(buf); // TODO: i18n } if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar")) { DEBUG_DISPLAY_ERROR( L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer." L" Shadows are not available and overall graphics quality might suffer." L" You are advised to try installing newer drivers and/or upgrade your graphics card."); g_Shadows = false; } ogl_WarnIfError(); InitRenderer(); InitInput(); ogl_WarnIfError(); if (!Autostart(args)) { const bool setup_gui = ((flags & INIT_NO_GUI) == 0); InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID); } } void RenderGui(bool RenderingState) { g_DoRenderGui = RenderingState; } void RenderLogger(bool RenderingState) { g_DoRenderLogger = RenderingState; } void RenderCursor(bool RenderingState) { g_DoRenderCursor = RenderingState; } static bool Autostart(const CmdLineArgs& args) { /* * Handle various command-line options, for quick testing of various features: * -autostart=mapname -- single-player * -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players * -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1 */ CStr autostartMap = args.Get("autostart"); if (autostartMap.empty()) return false; g_Game = new CGame(); ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); CScriptValRooted attrs; scriptInterface.Eval("({})", attrs); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false); scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap), false); // Set attrs.settings = { AIs: [...] }: /* * Handle command-line options for AI: * -autostart-ai=1:dummybot -autostart-ai=2:dummybot -- adds the dummybot AI to players 1 and 2 */ CScriptValRooted ais; scriptInterface.Eval("([])", ais); if (args.Has("autostart-ai")) { std::vector aiArgs = args.GetMultiple("autostart-ai"); for (size_t i = 0; i < aiArgs.size(); ++i) { int player = aiArgs[i].BeforeFirst(":").ToInt(); CStr name = aiArgs[i].AfterFirst(":"); scriptInterface.SetPropertyInt(ais.get(), player, std::string(name), false); } } CScriptValRooted settings; scriptInterface.Eval("({})", settings); scriptInterface.SetProperty(settings.get(), "AIs", ais, false); scriptInterface.SetProperty(attrs.get(), "settings", settings, false); CScriptValRooted mpInitData; g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData); g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs", CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())), false); if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml", mpInitData.get()); size_t maxPlayers = 2; if (args.Has("autostart-players")) maxPlayers = args.Get("autostart-players").ToUInt(); g_NetServer = new CNetServer(maxPlayers); g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface); bool ok = g_NetServer->SetupConnection(); debug_assert(ok); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml", mpInitData.get()); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc CStr ip = "127.0.0.1"; if (args.Has("autostart-ip")) ip = args.Get("autostart-ip"); bool ok = g_NetClient->SetupConnection(ip); debug_assert(ok); } else { g_Game->SetPlayerID(1); g_Game->StartGame(attrs); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); debug_assert(ret == PSRETURN_OK); InitPs(true, L"page_session.xml", JSVAL_VOID); } return true; } Index: ps/trunk/source/ps/UserReport.cpp =================================================================== --- ps/trunk/source/ps/UserReport.cpp (nonexistent) +++ ps/trunk/source/ps/UserReport.cpp (revision 8925) @@ -0,0 +1,601 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "UserReport.h" + +#include "lib/timer.h" +#include "lib/external_libraries/curl.h" +#include "lib/external_libraries/sdl.h" +#include "lib/external_libraries/zlib.h" +#include "lib/file/archive/stream.h" +#include "lib/sysdep/sysdep.h" +#include "ps/ConfigDB.h" +#include "ps/Filesystem.h" +#include "ps/ThreadUtil.h" + +#define DEBUG_UPLOADS 0 + +/* + * The basic idea is that the game submits reports to us, which we send over + * HTTP to a server for storage and analysis. + * + * We can't use libcurl's asynchronous 'multi' API, because DNS resolution can + * be synchronous and slow (which would make the game pause). + * So we use the 'easy' API in a background thread. + * The main thread submits reports, toggles whether uploading is enabled, + * and polls for the current status (typically to display in the GUI); + * the worker thread does all of the uploading. + * + * It'd be nice to extend this in the future to handle things like crash reports. + * The game should store the crashlogs (suitably anonymised) in a directory, and + * we should detect those files and upload them when we're restarted and online. + */ + + +/** + * Version number stored in config file when the user agrees to the reporting. + * Reporting will be disabled if the config value is missing or is less than + * this value. If we start reporting a lot more data, we should increase this + * value and get the user to re-confirm. + */ +static const int REPORTER_VERSION = 1; + +/** + * Time interval (seconds) at which the worker thread will check its reconnection + * timers. (This should be relatively high so the thread doesn't waste much time + * continually waking up.) + */ +static const double TIMER_CHECK_INTERVAL = 10.0; + +/** + * Seconds we should wait before reconnecting to the server after a failure. + */ +static const double RECONNECT_INVERVAL = 60.0; + +CUserReporter g_UserReporter; + +struct CUserReport +{ + time_t m_Time; + std::string m_Type; + int m_Version; + std::string m_Data; +}; + +class CUserReporterWorker +{ +public: + CUserReporterWorker(const std::string& userID, const std::string& url) : + m_UserID(userID), m_Enabled(false), m_Shutdown(false), m_Status("disabled"), + m_PauseUntilTime(timer_Time()), m_LastUpdateTime(timer_Time()) + { + // Set up libcurl: + + m_Curl = curl_easy_init(); + debug_assert(m_Curl); + +#if DEBUG_UPLOADS + curl_easy_setopt(m_Curl, CURLOPT_VERBOSE, 1L); +#endif + + // Capture error messages + curl_easy_setopt(m_Curl, CURLOPT_ERRORBUFFER, m_ErrorBuffer); + + // Disable signal handlers (required for multithreaded applications) + curl_easy_setopt(m_Curl, CURLOPT_NOSIGNAL, 1L); + + // To minimise security risks, don't support redirects + curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 0L); + + // Set IO callbacks + curl_easy_setopt(m_Curl, CURLOPT_WRITEFUNCTION, ReceiveCallback); + curl_easy_setopt(m_Curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(m_Curl, CURLOPT_READFUNCTION, SendCallback); + curl_easy_setopt(m_Curl, CURLOPT_READDATA, this); + + // Set URL to POST to + curl_easy_setopt(m_Curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_Curl, CURLOPT_POST, 1L); + + // Set up HTTP headers + m_Headers = NULL; + // Set the UA string + std::string ua = "User-Agent: 0ad "; + ua += curl_version(); + ua += " (http://wildfiregames.com/0ad/)"; + m_Headers = curl_slist_append(m_Headers, ua.c_str()); + // Override the default application/x-www-form-urlencoded type since we're not using that type + m_Headers = curl_slist_append(m_Headers, "Content-Type: application/octet-stream"); + // Disable the Accept header because it's a waste of a dozen bytes + m_Headers = curl_slist_append(m_Headers, "Accept: "); + curl_easy_setopt(m_Curl, CURLOPT_HTTPHEADER, m_Headers); + + + // Set up the worker thread: + + // Use SDL semaphores since OS X doesn't implement sem_init + m_WorkerSem = SDL_CreateSemaphore(0); + debug_assert(m_WorkerSem); + + int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this); + debug_assert(ret == 0); + } + + ~CUserReporterWorker() + { + // Clean up resources + + SDL_DestroySemaphore(m_WorkerSem); + + curl_slist_free_all(m_Headers); + curl_easy_cleanup(m_Curl); + } + + /** + * Called by main thread, when the online reporting is enabled/disabled. + */ + void SetEnabled(bool enabled) + { + CScopeLock lock(m_WorkerMutex); + if (enabled != m_Enabled) + { + m_Enabled = enabled; + + // Wake up the worker thread + SDL_SemPost(m_WorkerSem); + } + } + + /** + * Called by main thread to request shutdown. + * Returns true if we've shut down successfully. + * Returns false if shutdown is taking too long (we might be blocked on a + * sync network operation) - you mustn't destroy this object, just leak it + * and terminate. + */ + bool Shutdown() + { + { + CScopeLock lock(m_WorkerMutex); + m_Shutdown = true; + } + + // Wake up the worker thread + SDL_SemPost(m_WorkerSem); + + // Wait for it to shut down cleanly + // TODO: should have a timeout in case of network hangs + pthread_join(m_WorkerThread, NULL); + + return true; + } + + /** + * Called by main thread to determine the current status of the uploader. + */ + std::string GetStatus() + { + CScopeLock lock(m_WorkerMutex); + return m_Status; + } + + /** + * Called by main thread to add a new report to the queue. + */ + void Submit(const shared_ptr& report) + { + { + CScopeLock lock(m_WorkerMutex); + m_ReportQueue.push_back(report); + } + + // Wake up the worker thread + SDL_SemPost(m_WorkerSem); + } + + /** + * Called by the main thread every frame, so we can check + * retransmission timers. + */ + void Update() + { + double now = timer_Time(); + if (now > m_LastUpdateTime + TIMER_CHECK_INTERVAL) + { + // Wake up the worker thread + SDL_SemPost(m_WorkerSem); + + m_LastUpdateTime = now; + } + } + +private: + static void* RunThread(void* data) + { + debug_SetThreadName("CUserReportWorker"); + + static_cast(data)->Run(); + + return NULL; + } + + void Run() + { + /* + * We use a semaphore to let the thread be woken up when it has + * work to do. Various actions from the main thread can wake it: + * * SetEnabled() + * * Shutdown() + * * Submit() + * * Retransmission timeouts, once every several seconds + * + * If multiple actions have triggered wakeups, we might respond to + * all of those actions after the first wakeup, which is okay (we'll do + * nothing during the subsequent wakeups). We should never hang due to + * processing fewer actions than wakeups. + * + * Retransmission timeouts are triggered via the main thread - we can't simply + * use SDL_SemWaitTimeout because on Linux it's implemented as an inefficient + * busy-wait loop, and we can't use a manual busy-wait with a long delay time + * because we'd lose responsiveness. So the main thread pings the worker + * occasionally so it can check its timer. + */ + + // Wait until the main thread wakes us up + while (SDL_SemWait(m_WorkerSem) == 0) + { + // Handle shutdown requests as soon as possible + if (GetShutdown()) + return; + + // If we're not enabled, ignore this wakeup + if (!GetEnabled()) + continue; + + // If we're still pausing due to a failed connection, + // go back to sleep again + if (timer_Time() < m_PauseUntilTime) + continue; + + // We're enabled, so process as many reports as possible + while (ProcessReport()) + { + // Handle shutdowns while we were sending the report + if (GetShutdown()) + return; + } + } + } + + bool GetEnabled() + { + CScopeLock lock(m_WorkerMutex); + return m_Enabled; + } + + bool GetShutdown() + { + CScopeLock lock(m_WorkerMutex); + return m_Shutdown; + } + + void SetStatus(const std::string& status) + { + CScopeLock lock(m_WorkerMutex); + m_Status = status; +#if DEBUG_UPLOADS + debug_printf(L">>> CUserReporterWorker status: %hs\n", status.c_str()); +#endif + } + + bool ProcessReport() + { + shared_ptr report; + + { + CScopeLock lock(m_WorkerMutex); + if (m_ReportQueue.empty()) + return false; + report = m_ReportQueue.front(); + m_ReportQueue.pop_front(); + } + + ConstructRequestData(*report); + m_RequestDataOffset = 0; + m_ResponseData.clear(); + + curl_easy_setopt(m_Curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)m_RequestData.size()); + + SetStatus("connecting"); + +#if DEBUG_UPLOADS + TIMER(L"CUserReporterWorker request"); +#endif + + CURLcode err = curl_easy_perform(m_Curl); + +#if DEBUG_UPLOADS + printf(">>>\n%s\n<<<\n", m_ResponseData.c_str()); +#endif + + if (err == CURLE_OK) + { + long code = -1; + curl_easy_getinfo(m_Curl, CURLINFO_RESPONSE_CODE, &code); + SetStatus("completed:" + CStr(code)); + + // Check for success code + if (code == 200) + return true; + + // If the server returns the 410 Gone status, interpret that as meaning + // it no longer supports uploads (at least from this version of the game), + // so shut down and stop talking to it (to avoid wasting bandwidth) + if (code == 410) + { + CScopeLock lock(m_WorkerMutex); + m_Shutdown = true; + return false; + } + } + else + { + SetStatus("failed:" + CStr(err) + ":" + m_ErrorBuffer); + } + + // We got an unhandled return code or a connection failure; + // push this report back onto the queue and try again after + // a long interval + + { + CScopeLock lock(m_WorkerMutex); + m_ReportQueue.push_front(report); + } + + m_PauseUntilTime = timer_Time() + RECONNECT_INVERVAL; + return false; + } + + void ConstructRequestData(const CUserReport& report) + { + // Construct the POST request data in the application/x-www-form-urlencoded format + + std::string r; + + r += "user_id="; + AppendEscaped(r, m_UserID); + + r += "&time=" + CStr(report.m_Time); + + r += "&type="; + AppendEscaped(r, report.m_Type); + + r += "&version=" + CStr(report.m_Version); + + r += "&data="; + AppendEscaped(r, report.m_Data); + + // Compress the content with zlib to save bandwidth. + // (Note that we send a request with unlabelled compressed data instead + // of using Content-Encoding, because Content-Encoding is a mess and causes + // problems with servers and breaks Content-Length and this is much easier.) + std::string compressed; + compressed.resize(compressBound(r.size())); + uLongf destLen = compressed.size(); + int ok = compress((Bytef*)compressed.c_str(), &destLen, (const Bytef*)r.c_str(), r.size()); + debug_assert(ok == Z_OK); + compressed.resize(destLen); + + m_RequestData.swap(compressed); + } + + void AppendEscaped(std::string& buffer, const std::string& str) + { + char* escaped = curl_easy_escape(m_Curl, str.c_str(), str.size()); + buffer += escaped; + curl_free(escaped); + } + + static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp) + { + CUserReporterWorker* self = static_cast(userp); + + if (self->GetShutdown()) + return 0; // signals an error + + self->m_ResponseData += std::string((char*)buffer, (char*)buffer+size*nmemb); + + return size*nmemb; + } + + static size_t SendCallback(char* bufptr, size_t size, size_t nmemb, void* userp) + { + CUserReporterWorker* self = static_cast(userp); + + if (self->GetShutdown()) + return CURL_READFUNC_ABORT; // signals an error + + // We can return as much data as available, up to the buffer size + size_t amount = std::min(self->m_RequestData.size() - self->m_RequestDataOffset, size*nmemb); + + // ...But restrict to sending a small amount at once, so that we remain + // responsive to shutdown requests even if the network is pretty slow + amount = std::min((size_t)1024, amount); + + memcpy(bufptr, &self->m_RequestData[self->m_RequestDataOffset], amount); + self->m_RequestDataOffset += amount; + + self->SetStatus("sending:" + CStr((float)self->m_RequestDataOffset / self->m_RequestData.size())); + + return amount; + } + +private: + // Thread-related members: + pthread_t m_WorkerThread; + CMutex m_WorkerMutex; + SDL_sem* m_WorkerSem; + + // Shared by main thread and worker thread: + // These variables are all protected by m_WorkerMutex + std::deque > m_ReportQueue; + bool m_Enabled; + bool m_Shutdown; + std::string m_Status; + + // Initialised in constructor by main thread; otherwise used only by worker thread: + std::string m_UserID; + CURL* m_Curl; + curl_slist* m_Headers; + double m_PauseUntilTime; + + // Only used by worker thread: + std::string m_ResponseData; + std::string m_RequestData; + size_t m_RequestDataOffset; + char m_ErrorBuffer[CURL_ERROR_SIZE]; + + // Only used by main thread: + double m_LastUpdateTime; +}; + + + +CUserReporter::CUserReporter() : + m_Worker(NULL) +{ +} + +CUserReporter::~CUserReporter() +{ + debug_assert(!m_Worker); // Deinitialize should have been called before shutdown +} + +std::string CUserReporter::LoadUserID() +{ + std::string userID; + + // Read the user ID from user.cfg (if there is one) + CFG_GET_USER_VAL("userreport.id", String, userID); + + // If we don't have a validly-formatted user ID, generate a new one + if (userID.length() != 16) + { + u8 bytes[8] = {0}; + sys_generate_random_bytes(bytes, ARRAY_SIZE(bytes)); + // ignore failures - there's not much we can do about it + + userID = ""; + for (size_t i = 0; i < ARRAY_SIZE(bytes); ++i) + { + char hex[3]; + sprintf_s(hex, ARRAY_SIZE(hex), "%02x", bytes[i]); + userID += hex; + } + + g_ConfigDB.CreateValue(CFG_USER, "userreport.id")->m_String = userID; + g_ConfigDB.WriteFile(CFG_USER); + } + + return userID; +} + +bool CUserReporter::IsReportingEnabled() +{ + int version = -1; + CFG_GET_USER_VAL("userreport.enabledversion", Int, version); + return (version >= REPORTER_VERSION); +} + +void CUserReporter::SetReportingEnabled(bool enabled) +{ + CStr val(enabled ? REPORTER_VERSION : 0); + g_ConfigDB.CreateValue(CFG_USER, "userreport.enabledversion")->m_String = val; + g_ConfigDB.WriteFile(CFG_USER); + + if (m_Worker) + m_Worker->SetEnabled(enabled); +} + +std::string CUserReporter::GetStatus() +{ + if (!m_Worker) + return "disabled"; + + return m_Worker->GetStatus(); +} + + +void CUserReporter::Initialize() +{ + debug_assert(!m_Worker); // must only be called once + + std::string userID = LoadUserID(); + std::string url; + CFG_GET_SYS_VAL("userreport.url", String, url); + + // Initialise everything except Win32 sockets (because our networking + // system already inits those) + curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32); + + m_Worker = new CUserReporterWorker(userID, url); + + m_Worker->SetEnabled(IsReportingEnabled()); +} + +void CUserReporter::Deinitialize() +{ + if (!m_Worker) + return; + + if (m_Worker->Shutdown()) + { + // Worker was shut down cleanly + + SAFE_DELETE(m_Worker); + curl_global_cleanup(); + } + else + { + // Worker failed to shut down in a reasonable time + // Leak the resources (since that's better than hanging or crashing) + m_Worker = NULL; + } +} + +void CUserReporter::Update() +{ + if (m_Worker) + m_Worker->Update(); +} + +void CUserReporter::SubmitReport(const char* type, int version, const std::string& data) +{ + // If not initialised, discard the report + if (!m_Worker) + return; + + shared_ptr report(new CUserReport); + report->m_Time = time(NULL); + report->m_Type = type; + report->m_Version = version; + report->m_Data = data; + + m_Worker->Submit(report); +} Property changes on: ps/trunk/source/ps/UserReport.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/ps/UserReport.h =================================================================== --- ps/trunk/source/ps/UserReport.h (nonexistent) +++ ps/trunk/source/ps/UserReport.h (revision 8925) @@ -0,0 +1,62 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_USERREPORT +#define INCLUDED_USERREPORT + +class CUserReporterWorker; + +class CUserReporter +{ +public: + CUserReporter(); + ~CUserReporter(); + + void Initialize(); + void Deinitialize(); + + /** + * Must be called frequently (preferably every frame), to update some + * internal reconnection timers. + */ + void Update(); + + // Functions for the GUI to control the reporting: + bool IsReportingEnabled(); + void SetReportingEnabled(bool enabled); + std::string GetStatus(); + + /** + * Submit a report to be transmitted to the online server. + * Nothing will be transmitted until reporting is enabled by the user, so + * you don't need to check for that first. + * @param type short string identifying the type of data ("hwdetect", "message", etc) + * @param version positive integer that should be incremented if the data is changed in + * a non-compatible way and the server will have to distinguish old and new formats + * @param data the actual data (typically UTF-8-encoded text, or JSON, but could be binary) + */ + void SubmitReport(const char* type, int version, const std::string& data); + +private: + std::string LoadUserID(); + + CUserReporterWorker* m_Worker; +}; + +extern CUserReporter g_UserReporter; + +#endif // INCLUDED_USERREPORT Property changes on: ps/trunk/source/ps/UserReport.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/ps/ConfigDB.h =================================================================== --- ps/trunk/source/ps/ConfigDB.h (revision 8924) +++ ps/trunk/source/ps/ConfigDB.h (revision 8925) @@ -1,171 +1,179 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * 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 . */ /* CConfigDB - Load, access and store configuration variables TDD : http://forums.wildfiregames.com/0ad/index.php?showtopic=1125 OVERVIEW: JavaScript: All the javascript interfaces are provided through the global object g_ConfigDB. g_ConfigDB Properties: system: All CFG_SYSTEM values are linked to properties of this object. a=g_ConfigDB.system.foo; is equivalent to C++ code g_ConfigDB.GetValue(CFG_SYSTEM, "foo"); mod: Ditto, but linked to CFG_MOD user: Ditto, but linked to CFG_USER default: Ditto, but linked to CFG_DEFAULT g_ConfigDB Functions: None so far ConfigNamespace Functions: (applicable to the system, mod or user properties of g_ConfigDB) boolean WriteFile(boolean useVFS, string path): JS interface to g_ConfigDB.WriteFile - should work exactly the same. boolean Reload() => g_ConfigDB.Reload() void SetFile() => g_ConfigDB.SetConfigFile() */ #ifndef INCLUDED_CONFIGDB #define INCLUDED_CONFIGDB #include "Parser.h" #include "CStr.h" #include "Singleton.h" +#include "lib/file/vfs/vfs_path.h" + // Namespace priorities: User supersedes mod supersedes system. // Command-line arguments override everything. enum EConfigNamespace { CFG_DEFAULT, CFG_SYSTEM, CFG_MOD, CFG_USER, CFG_COMMAND, CFG_LAST }; typedef CParserValue CConfigValue; typedef std::vector CConfigValueSet; #define g_ConfigDB CConfigDB::GetSingleton() class CConfigDB: public Singleton { static std::map m_Map[]; - static CStrW m_ConfigFile[]; - static bool m_UseVFS[]; + static VfsPath m_ConfigFile[]; public: // NOTE: Construct the Singleton Object *after* JavaScript init, so that // the JS interface can be registered. CConfigDB(); /** * Attempt to find a config variable with the given name; will search all * namespaces from system up to the specified namespace. * * Returns a pointer to the config value structure for the variable, or * NULL if such a variable could not be found */ CConfigValue *GetValue(EConfigNamespace ns, const CStr& name); /** * Attempt to retrieve a vector of values corresponding to the given setting; * will search all namespaces from system up to the specified namespace. * * Returns a pointer to the vector, or NULL if the setting could not be found. */ CConfigValueSet *GetValues(EConfigNamespace ns, const CStr& name); /** * Retrieve a vector of values corresponding to settings whose names begin * with the given prefix; * will search all namespaces from system up to the specified namespace. */ std::vector > GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix); /** * Create a new config value in the specified namespace. If such a * variable already exists, the old value is returned and the effect is * exactly the same as that of GetValue() * * Returns a pointer to the value of the newly created config variable, or * that of the already existing config variable. */ CConfigValue *CreateValue(EConfigNamespace ns, const CStr& name); /** * Set the path to the config file used to populate the specified namespace * Note that this function does not actually load the config file. Use * the Reload() method if you want to read the config file at the same time. * * 'path': The path to the config file. - * VFS: relative to VFS root - * non-VFS: relative to current working directory (binaries/data/) - * 'useVFS': true if the path is a VFS path, false if it is a real path */ - void SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path); + void SetConfigFile(EConfigNamespace ns, const VfsPath& path); /** * Reload the config file associated with the specified config namespace * (the last config file path set with SetConfigFile) * * Returns: * true: if the reload succeeded, * false: if the reload failed */ bool Reload(EConfigNamespace); /** * Write the current state of the specified config namespace to the file * specified by 'path' * * Returns: * true: if the config namespace was successfully written to the file * false: if an error occurred */ - bool WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path); + bool WriteFile(EConfigNamespace ns, const VfsPath& path); + + /** + * Write the current state of the specified config namespace to the file + * it was originally loaded from. + * + * Returns: + * true: if the config namespace was successfully written to the file + * false: if an error occurred + */ + bool WriteFile(EConfigNamespace ns); }; // stores the value of the given key into . this quasi-template // convenience wrapper on top of CConfigValue::Get* simplifies user code and // avoids "assignment within condition expression" warnings. #define CFG_GET_SYS_VAL(name, type, destination)\ STMT(\ CConfigValue* val = g_ConfigDB.GetValue(CFG_SYSTEM, name);\ if(val)\ val->Get##type(destination);\ ) #define CFG_GET_USER_VAL(name, type, destination)\ STMT(\ CConfigValue* val = g_ConfigDB.GetValue(CFG_USER, name);\ if(val)\ val->Get##type(destination);\ ) #endif Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 8924) +++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 8925) @@ -1,649 +1,649 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "JSInterface_IGUIObject.h" #include "JSInterface_GUITypes.h" #include "gui/IGUIObject.h" #include "gui/CGUI.h" #include "gui/IGUIScrollBar.h" #include "gui/CList.h" #include "gui/GUIManager.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptInterface.h" JSClass JSI_IGUIObject::JSI_class = { "GUIObject", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, NULL, NULL, NULL, JSI_IGUIObject::construct }; JSPropertySpec JSI_IGUIObject::JSI_props[] = { { 0 } }; JSFunctionSpec JSI_IGUIObject::JSI_methods[] = { { "toString", JSI_IGUIObject::toString, 0, 0 }, { "focus", JSI_IGUIObject::focus, 0, 0 }, { "blur", JSI_IGUIObject::blur, 0, 0 }, { 0 } }; JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp) { IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return JS_FALSE; jsval idval; if (!JS_IdToValue(cx, id, &idval)) return JS_FALSE; std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) return JS_FALSE; // Skip some things which are known to be functions rather than properties. // ("constructor" *must* be here, else it'll try to GetSettingType before // the private IGUIObject* has been set (and thus crash). The others are // partly for efficiency, and also to allow correct reporting of attempts to // access nonexistent properties.) if (propName == "constructor" || propName == "prototype" || propName == "toString" || propName == "focus" || propName == "blur" ) return JS_TRUE; // Use onWhatever to access event handlers if (propName.substr(0, 2) == "on") { CStr eventName (CStr(propName.substr(2)).LowerCase()); std::map::iterator it = e->m_ScriptHandlers.find(eventName); if (it == e->m_ScriptHandlers.end()) *vp = JSVAL_NULL; else *vp = OBJECT_TO_JSVAL(*(it->second)); return JS_TRUE; } // Handle the "parent" property specially if (propName == "parent") { IGUIObject* parent = e->GetParent(); if (parent) { // If the object isn't parentless, return a new object *vp = OBJECT_TO_JSVAL(parent->GetJSObject()); } else { // Return null if there's no parent *vp = JSVAL_NULL; } return JS_TRUE; } // Also handle "name" specially else if (propName == "name") { *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, e->GetName())); return JS_TRUE; } // Handle all other properties else { // Retrieve the setting's type (and make sure it actually exists) EGUISettingType Type; if (e->GetSettingType(propName, Type) != PSRETURN_OK) { JS_ReportError(cx, "Invalid GUIObject property '%s'", propName.c_str()); return JS_FALSE; } // (All the cases are in {...} to avoid scoping problems) switch (Type) { case GUIST_bool: { bool value; GUI::GetSetting(e, propName, value); *vp = value ? JSVAL_TRUE : JSVAL_FALSE; break; } case GUIST_int: { int value; GUI::GetSetting(e, propName, value); *vp = INT_TO_JSVAL(value); break; } case GUIST_float: { float value; GUI::GetSetting(e, propName, value); // Create a garbage-collectable double return JS_NewNumberValue(cx, value, vp); } case GUIST_CColor: { CColor colour; GUI::GetSetting(e, propName, colour); JSObject* obj = JS_NewObject(cx, &JSI_GUIColor::JSI_class, NULL, NULL); *vp = OBJECT_TO_JSVAL(obj); // root it jsval c; // Attempt to minimise ugliness through macrosity #define P(x) if (!JS_NewNumberValue(cx, colour.x, &c)) return JS_FALSE; JS_SetProperty(cx, obj, #x, &c) P(r); P(g); P(b); P(a); #undef P break; } case GUIST_CClientArea: { CClientArea area; GUI::GetSetting(e, propName, area); JSObject* obj = JS_NewObject(cx, &JSI_GUISize::JSI_class, NULL, NULL); *vp = OBJECT_TO_JSVAL(obj); // root it try { #define P(x, y, z) g_ScriptingHost.SetObjectProperty_Double(obj, #z, area.x.y) P(pixel, left, left); P(pixel, top, top); P(pixel, right, right); P(pixel, bottom, bottom); P(percent, left, rleft); P(percent, top, rtop); P(percent, right, rright); P(percent, bottom, rbottom); #undef P } catch (PSERROR_Scripting_ConversionFailed) { debug_warn(L"Error creating size object!"); break; } break; } case GUIST_CGUIString: { CGUIString value; GUI::GetSetting(e, propName, value); - *vp = ScriptInterface::ToJSVal(cx, value.GetRawString()); + *vp = ScriptInterface::ToJSVal(cx, value.GetOriginalString()); break; } case GUIST_CStr: { CStr value; GUI::GetSetting(e, propName, value); *vp = ScriptInterface::ToJSVal(cx, value); break; } case GUIST_CStrW: { CStrW value; GUI::GetSetting(e, propName, value); *vp = ScriptInterface::ToJSVal(cx, value); break; } case GUIST_CGUISpriteInstance: { CGUISpriteInstance *value; GUI::GetSettingPointer(e, propName, value); *vp = ScriptInterface::ToJSVal(cx, value->GetName()); break; } case GUIST_EAlign: { EAlign value; GUI::GetSetting(e, propName, value); CStr word; switch (value) { case EAlign_Left: word = "left"; break; case EAlign_Right: word = "right"; break; case EAlign_Center: word = "center"; break; default: debug_warn(L"Invalid EAlign!"); word = "error"; break; } *vp = ScriptInterface::ToJSVal(cx, word); break; } case GUIST_EVAlign: { EVAlign value; GUI::GetSetting(e, propName, value); CStr word; switch (value) { case EVAlign_Top: word = "top"; break; case EVAlign_Bottom: word = "bottom"; break; case EVAlign_Center: word = "center"; break; default: debug_warn(L"Invalid EVAlign!"); word = "error"; break; } *vp = ScriptInterface::ToJSVal(cx, word); break; } case GUIST_CGUIList: { CGUIList value; GUI::GetSetting(e, propName, value); JSObject *obj = JS_NewArrayObject(cx, 0, NULL); *vp = OBJECT_TO_JSVAL(obj); // root it for (size_t i = 0; i < value.m_Items.size(); ++i) { - jsval val = ScriptInterface::ToJSVal(cx, value.m_Items[i].GetRawString()); + jsval val = ScriptInterface::ToJSVal(cx, value.m_Items[i].GetOriginalString()); JS_SetElement(cx, obj, (jsint)i, &val); } break; } default: JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str()); debug_assert(0); return JS_FALSE; } return JS_TRUE; } } JSBool JSI_IGUIObject::setProperty(JSContext* cx, JSObject* obj, jsid id, jsval* vp) { IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return JS_FALSE; jsval idval; if (!JS_IdToValue(cx, id, &idval)) return JS_FALSE; std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) return JS_FALSE; if (propName == "name") { std::string value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; e->SetName(value); return JS_TRUE; } // Use onWhatever to set event handlers if (propName.substr(0, 2) == "on") { if (!JSVAL_IS_OBJECT(*vp) || !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(*vp))) { JS_ReportError(cx, "on- event-handlers must be functions"); return JS_FALSE; } CStr eventName (CStr(propName.substr(2)).LowerCase()); e->SetScriptHandler(eventName, JSVAL_TO_OBJECT(*vp)); return JS_TRUE; } // Retrieve the setting's type (and make sure it actually exists) EGUISettingType Type; if (e->GetSettingType(propName, Type) != PSRETURN_OK) { JS_ReportError(cx, "Invalid setting '%s'", propName.c_str()); return JS_TRUE; } switch (Type) { case GUIST_CStr: { std::string value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; GUI::SetSetting(e, propName, value); break; } case GUIST_CStrW: { std::wstring value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; GUI::SetSetting(e, propName, value); break; } case GUIST_CGUISpriteInstance: { std::string value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; GUI::SetSetting(e, propName, CGUISpriteInstance(value)); break; } case GUIST_CGUIString: { std::wstring value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; CGUIString str; str.SetValue(value); GUI::SetSetting(e, propName, str); break; } case GUIST_EAlign: { std::string value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; EAlign a; if (value == "left") a = EAlign_Left; else if (value == "right") a = EAlign_Right; else if (value == "center" || value == "centre") a = EAlign_Center; else { JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')"); return JS_FALSE; } GUI::SetSetting(e, propName, a); break; } case GUIST_EVAlign: { std::string value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; EVAlign a; if (value == "top") a = EVAlign_Top; else if (value == "bottom") a = EVAlign_Bottom; else if (value == "center" || value == "centre") a = EVAlign_Center; else { JS_ReportError(cx, "Invalid alignment (should be 'top', 'bottom' or 'center')"); return JS_FALSE; } GUI::SetSetting(e, propName, a); break; } case GUIST_int: { int32 value; if (JS_ValueToInt32(cx, *vp, &value) == JS_TRUE) GUI::SetSetting(e, propName, value); else { JS_ReportError(cx, "Cannot convert value to int"); return JS_FALSE; } break; } case GUIST_float: { jsdouble value; if (JS_ValueToNumber(cx, *vp, &value) == JS_TRUE) GUI::SetSetting(e, propName, (float)value); else { JS_ReportError(cx, "Cannot convert value to float"); return JS_FALSE; } break; } case GUIST_bool: { JSBool value; if (JS_ValueToBoolean(cx, *vp, &value) == JS_TRUE) GUI::SetSetting(e, propName, value||0); // ||0 to avoid int-to-bool compiler warnings else { JS_ReportError(cx, "Cannot convert value to bool"); return JS_FALSE; } break; } case GUIST_CClientArea: { if (JSVAL_IS_STRING(*vp)) { std::wstring value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; if (e->SetSetting(propName, value) != PSRETURN_OK) { JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str()); return JS_FALSE; } } else if (JSVAL_IS_OBJECT(*vp) && JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &JSI_GUISize::JSI_class, NULL)) { CClientArea area; GUI::GetSetting(e, propName, area); JSObject* obj = JSVAL_TO_OBJECT(*vp); #define P(x, y, z) area.x.y = (float)g_ScriptingHost.GetObjectProperty_Double(obj, #z) P(pixel, left, left); P(pixel, top, top); P(pixel, right, right); P(pixel, bottom, bottom); P(percent, left, rleft); P(percent, top, rtop); P(percent, right, rright); P(percent, bottom, rbottom); #undef P GUI::SetSetting(e, propName, area); } else { JS_ReportError(cx, "Size only accepts strings or GUISize objects"); return JS_FALSE; } break; } case GUIST_CColor: { if (JSVAL_IS_STRING(*vp)) { std::wstring value; if (!ScriptInterface::FromJSVal(cx, *vp, value)) return JS_FALSE; if (e->SetSetting(propName, value) != PSRETURN_OK) { JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str()); return JS_FALSE; } } else if (JSVAL_IS_OBJECT(*vp) && JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &JSI_GUIColor::JSI_class, NULL)) { CColor colour; JSObject* obj = JSVAL_TO_OBJECT(*vp); jsval t; double s; #define PROP(x) JS_GetProperty(cx, obj, #x, &t); \ JS_ValueToNumber(cx, t, &s); \ colour.x = (float)s PROP(r); PROP(g); PROP(b); PROP(a); #undef PROP GUI::SetSetting(e, propName, colour); } else { JS_ReportError(cx, "Color only accepts strings or GUIColor objects"); return JS_FALSE; } break; } case GUIST_CGUIList: { JSObject* obj = JSVAL_TO_OBJECT(*vp); jsuint length; if (JSVAL_IS_OBJECT(*vp) && JS_GetArrayLength(cx, obj, &length) == JS_TRUE) { CGUIList list; for (int i=0; i<(int)length; ++i) { jsval element; if (! JS_GetElement(cx, obj, i, &element)) { JS_ReportError(cx, "Failed to get list element"); return JS_FALSE; } std::wstring value; if (!ScriptInterface::FromJSVal(cx, element, value)) return JS_FALSE; CGUIString str; str.SetValue(value); list.m_Items.push_back(str); } GUI::SetSetting(e, propName, list); } else { JS_ReportError(cx, "List only accepts a GUIList object"); return JS_FALSE; } break; } // TODO Gee: (2004-09-01) EAlign and EVAlign too. default: JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str()); break; } return JS_TRUE; } JSBool JSI_IGUIObject::construct(JSContext* cx, uintN argc, jsval* vp) { if (argc == 0) { JS_ReportError(cx, "GUIObject has no default constructor"); return JS_FALSE; } JSObject* obj = JS_NewObject(cx, &JSI_IGUIObject::JSI_class, NULL, NULL); // Store the IGUIObject in the JS object's 'private' area IGUIObject* guiObject = (IGUIObject*)JSVAL_TO_PRIVATE(JS_ARGV(cx, vp)[0]); JS_SetPrivate(cx, obj, guiObject); JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); return JS_TRUE; } void JSI_IGUIObject::init() { g_ScriptingHost.DefineCustomObjectType(&JSI_class, construct, 1, JSI_props, JSI_methods, NULL, NULL); } JSBool JSI_IGUIObject::toString(JSContext* cx, uintN argc, jsval* vp) { UNUSED2(argc); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL); if (!e) return JS_FALSE; char buffer[256]; snprintf(buffer, 256, "[GUIObject: %s]", e->GetName().c_str()); buffer[255] = 0; JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, buffer))); return JS_TRUE; } JSBool JSI_IGUIObject::focus(JSContext* cx, uintN argc, jsval* vp) { UNUSED2(argc); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL); if (!e) return JS_FALSE; e->GetGUI()->SetFocusedObject(e); JS_SET_RVAL(cx, vp, JSVAL_VOID); return JS_TRUE; } JSBool JSI_IGUIObject::blur(JSContext* cx, uintN argc, jsval* vp) { UNUSED2(argc); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, JS_THIS_OBJECT(cx, vp), &JSI_IGUIObject::JSI_class, NULL); if (!e) return JS_FALSE; e->GetGUI()->SetFocusedObject(NULL); JS_SET_RVAL(cx, vp, JSVAL_VOID); return JS_TRUE; } Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp =================================================================== --- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 8924) +++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 8925) @@ -1,462 +1,493 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "scriptinterface/ScriptInterface.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/MapReader.h" #include "gui/GUIManager.h" #include "lib/timer.h" +#include "lib/utf8.h" #include "lib/sysdep/sysdep.h" #include "maths/FixedVector3D.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "network/NetTurnManager.h" #include "ps/CLogger.h" #include "ps/CConsole.h" #include "ps/Errors.h" #include "ps/Game.h" #include "ps/Hotkey.h" #include "ps/Overlay.h" #include "ps/Pyrogenesis.h" +#include "ps/UserReport.h" #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/Config.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpAIManager.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpGuiInterface.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/helpers/Selection.h" #include "js/jsapi.h" /* * This file defines a set of functions that are available to GUI scripts, to allow * interaction with the rest of the engine. * Functions are exposed to scripts within the global object 'Engine', so * scripts should call "Engine.FunctionName(...)" etc. */ extern void restart_mainloop_in_atlas(); // from main.cpp namespace { CScriptVal GetActiveGui(void* UNUSED(cbdata)) { return OBJECT_TO_JSVAL(g_GUI->GetScriptObject()); } void PushGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData) { g_GUI->PushPage(name, initData); } void SwitchGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData) { g_GUI->SwitchPage(name, initData); } void PopGuiPage(void* UNUSED(cbdata)) { g_GUI->PopPage(); } CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data) { CGUIManager* guiManager = static_cast (cbdata); if (!g_Game) return JSVAL_VOID; CSimulation2* sim = g_Game->GetSimulation2(); debug_assert(sim); CmpPtr gui(*sim, SYSTEM_ENTITY); if (gui.null()) return JSVAL_VOID; int player = -1; if (g_Game) player = g_Game->GetPlayerID(); CScriptValRooted arg (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), data.get())); CScriptVal ret (gui->ScriptCall(player, name, arg.get())); return guiManager->GetScriptInterface().CloneValueFromOtherContext(sim->GetScriptInterface(), ret.get()); } void PostNetworkCommand(void* cbdata, CScriptVal cmd) { CGUIManager* guiManager = static_cast (cbdata); if (!g_Game) return; CSimulation2* sim = g_Game->GetSimulation2(); debug_assert(sim); CmpPtr queue(*sim, SYSTEM_ENTITY); if (queue.null()) return; jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), cmd.get()); queue->PostNetworkCommand(cmd2); } std::vector PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y) { return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID()); } std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0, int y0, int x1, int y1, int player) { return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player); } std::vector PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool onScreenOnly) { return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), onScreenOnly); } CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y) { CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true); return CFixedVector3D(fixed::FromFloat(pos.X), fixed::FromFloat(pos.Y), fixed::FromFloat(pos.Z)); } std::wstring SetCursor(void* UNUSED(cbdata), std::wstring name) { std::wstring old = g_CursorName; g_CursorName = name; return old; } int GetPlayerID(void* UNUSED(cbdata)) { if (g_Game) return g_Game->GetPlayerID(); return -1; } std::wstring GetDefaultPlayerName(void* UNUSED(cbdata)) { // TODO: this should come from a config file or something std::wstring name = sys_get_user_name(); if (name.empty()) name = L"anonymous"; return name; } void StartNetworkGame(void* UNUSED(cbdata)) { debug_assert(g_NetServer); g_NetServer->StartGame(); } void StartGame(void* cbdata, CScriptVal attribs, int playerID) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(!g_NetServer); debug_assert(!g_NetClient); debug_assert(!g_Game); g_Game = new CGame(); // Convert from GUI script context to sim script context CSimulation2* sim = g_Game->GetSimulation2(); CScriptValRooted gameAttribs (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get())); g_Game->SetPlayerID(playerID); g_Game->StartGame(gameAttribs); } void SetNetworkGameAttributes(void* cbdata, CScriptVal attribs) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(g_NetServer); g_NetServer->UpdateGameAttributes(attribs, guiManager->GetScriptInterface()); } void StartNetworkHost(void* cbdata, std::wstring playerName) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(!g_NetClient); debug_assert(!g_NetServer); debug_assert(!g_Game); g_NetServer = new CNetServer(); if (!g_NetServer->SetupConnection()) { guiManager->GetScriptInterface().ReportError("Failed to start server"); SAFE_DELETE(g_NetServer); return; } g_Game = new CGame(); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); if (!g_NetClient->SetupConnection("127.0.0.1")) { guiManager->GetScriptInterface().ReportError("Failed to connect to server"); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } } void StartNetworkJoin(void* cbdata, std::wstring playerName, std::string serverAddress) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(!g_NetClient); debug_assert(!g_NetServer); debug_assert(!g_Game); g_Game = new CGame(); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); if (!g_NetClient->SetupConnection(serverAddress)) { guiManager->GetScriptInterface().ReportError("Failed to connect to server"); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } } void DisconnectNetworkGame(void* UNUSED(cbdata)) { // TODO: we ought to do async reliable disconnections SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } CScriptVal PollNetworkClient(void* cbdata) { CGUIManager* guiManager = static_cast (cbdata); if (!g_NetClient) return CScriptVal(); CScriptValRooted poll = g_NetClient->GuiPoll(); // Convert from net client context to GUI script context return guiManager->GetScriptInterface().CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), poll.get()); } void AssignNetworkPlayer(void* UNUSED(cbdata), int playerID, std::string guid) { debug_assert(g_NetServer); g_NetServer->AssignPlayer(playerID, guid); } void SendNetworkChat(void* UNUSED(cbdata), std::wstring message) { debug_assert(g_NetClient); g_NetClient->SendChatMessage(message); } std::vector GetAIs(void* cbdata) { CGUIManager* guiManager = static_cast (cbdata); return ICmpAIManager::GetAIs(guiManager->GetScriptInterface()); } void OpenURL(void* UNUSED(cbdata), std::string url) { sys_open_url(url); } void RestartInAtlas(void* UNUSED(cbdata)) { restart_mainloop_in_atlas(); } bool AtlasIsAvailable(void* UNUSED(cbdata)) { return ATLAS_IsAvailable(); } CScriptVal LoadMapSettings(void* cbdata, std::wstring pathname) { CGUIManager* guiManager = static_cast (cbdata); CMapSummaryReader reader; if (reader.LoadMap(VfsPath(pathname + L".xml")) != PSRETURN_OK) return CScriptVal(); return reader.GetMapSettings(guiManager->GetScriptInterface()).get(); } /** * Start / stop camera following mode * @param entityid unit id to follow. If zero, stop following mode */ void CameraFollow(void* UNUSED(cbdata), entity_id_t entityid) { if (g_Game && g_Game->GetView()) g_Game->GetView()->CameraFollow(entityid, false); } void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid) { if (g_Game && g_Game->GetView()) g_Game->GetView()->CameraFollow(entityid, true); } bool HotkeyIsPressed_(void* UNUSED(cbdata), std::string hotkeyName) { return HotkeyIsPressed(hotkeyName); } void DisplayErrorDialog(void* UNUSED(cbdata), std::wstring msg) { debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL); } + +bool IsUserReportEnabled(void* UNUSED(cbdata)) +{ + return g_UserReporter.IsReportingEnabled(); +} + +void SetUserReportEnabled(void* UNUSED(cbdata), bool enabled) +{ + g_UserReporter.SetReportingEnabled(enabled); +} + +std::string GetUserReportStatus(void* UNUSED(cbdata)) +{ + return g_UserReporter.GetStatus(); +} + +void SubmitUserReport(void* UNUSED(cbdata), std::string type, int version, std::wstring data) +{ + g_UserReporter.SubmitReport(type.c_str(), version, utf8_from_wstring(data)); +} + + + void SetSimRate(void* UNUSED(cbdata), float rate) { g_Game->SetSimRate(rate); } void SetTurnLength(void* UNUSED(cbdata), int length) { if (g_NetServer) g_NetServer->SetTurnLength(length); else LOGERROR(L"Only network host can change turn length"); } // Focus the game camera on a given position. void SetCameraTarget(void* UNUSED(cbdata), float x, float y, float z) { g_Game->GetView()->ResetCameraTarget(CVector3D(x, y, z)); } // Deliberately cause the game to crash. // Currently implemented via access violation (read of address 0). // Useful for testing the crashlog/stack trace code. int Crash(void* UNUSED(cbdata)) { debug_printf(L"Crashing at user's request.\n"); return *(int*)0; } void DebugWarn(void* UNUSED(cbdata)) { debug_warn(L"Warning at user's request."); } // Force a JS garbage collection cycle to take place immediately. // Writes an indication of how long this took to the console. void ForceGC(void* cbdata) { CGUIManager* guiManager = static_cast (cbdata); double time = timer_Time(); JS_GC(guiManager->GetScriptInterface().GetContext()); time = timer_Time() - time; g_Console->InsertMessage(L"Garbage collection completed in: %f", time); } void DumpSimState(void* UNUSED(cbdata)) { fs::wpath path (psLogDir()/L"sim_dump.txt"); std::ofstream file (path.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc); g_Game->GetSimulation2()->DumpDebugState(file); } void EnableTimeWarpRecording(void* UNUSED(cbdata), unsigned int numTurns) { g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns); } void RewindTimeWarp(void* UNUSED(cbdata)) { g_Game->GetTurnManager()->RewindTimeWarp(); } } // namespace void GuiScriptingInit(ScriptInterface& scriptInterface) { // GUI manager functions: scriptInterface.RegisterFunction("GetActiveGui"); scriptInterface.RegisterFunction("PushGuiPage"); scriptInterface.RegisterFunction("SwitchGuiPage"); scriptInterface.RegisterFunction("PopGuiPage"); // Simulation<->GUI interface functions: scriptInterface.RegisterFunction("GuiInterfaceCall"); scriptInterface.RegisterFunction("PostNetworkCommand"); // Entity picking scriptInterface.RegisterFunction, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint"); scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect"); scriptInterface.RegisterFunction, std::string, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities"); scriptInterface.RegisterFunction("GetTerrainAtPoint"); // Network / game setup functions scriptInterface.RegisterFunction("StartNetworkGame"); scriptInterface.RegisterFunction("StartGame"); scriptInterface.RegisterFunction("StartNetworkHost"); scriptInterface.RegisterFunction("StartNetworkJoin"); scriptInterface.RegisterFunction("DisconnectNetworkGame"); scriptInterface.RegisterFunction("PollNetworkClient"); scriptInterface.RegisterFunction("SetNetworkGameAttributes"); scriptInterface.RegisterFunction("AssignNetworkPlayer"); scriptInterface.RegisterFunction("SendNetworkChat"); scriptInterface.RegisterFunction, &GetAIs>("GetAIs"); // Misc functions scriptInterface.RegisterFunction("SetCursor"); scriptInterface.RegisterFunction("GetPlayerID"); scriptInterface.RegisterFunction("GetDefaultPlayerName"); scriptInterface.RegisterFunction("OpenURL"); scriptInterface.RegisterFunction("RestartInAtlas"); scriptInterface.RegisterFunction("AtlasIsAvailable"); scriptInterface.RegisterFunction("LoadMapSettings"); scriptInterface.RegisterFunction("CameraFollow"); scriptInterface.RegisterFunction("CameraFollowFPS"); scriptInterface.RegisterFunction("HotkeyIsPressed"); scriptInterface.RegisterFunction("DisplayErrorDialog"); + // User report functions + scriptInterface.RegisterFunction("IsUserReportEnabled"); + scriptInterface.RegisterFunction("SetUserReportEnabled"); + scriptInterface.RegisterFunction("GetUserReportStatus"); + scriptInterface.RegisterFunction("SubmitUserReport"); + // Development/debugging functions scriptInterface.RegisterFunction("SetSimRate"); scriptInterface.RegisterFunction("SetTurnLength"); scriptInterface.RegisterFunction("SetCameraTarget"); scriptInterface.RegisterFunction("Crash"); scriptInterface.RegisterFunction("DebugWarn"); scriptInterface.RegisterFunction("ForceGC"); scriptInterface.RegisterFunction("DumpSimState"); scriptInterface.RegisterFunction("EnableTimeWarpRecording"); scriptInterface.RegisterFunction("RewindTimeWarp"); } Index: ps/trunk/source/gui/GUItext.h =================================================================== --- ps/trunk/source/gui/GUItext.h (revision 8924) +++ ps/trunk/source/gui/GUItext.h (revision 8925) @@ -1,338 +1,348 @@ /* Copyright (C) 2009 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 . */ /* GUI text, handles text stuff --Overview-- Mainly contains struct SGUIText and friends. Actual text processing is made in CGUI::GenerateText() --More info-- Check GUI.h */ #ifndef INCLUDED_GUITEXT #define INCLUDED_GUITEXT //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include #include "CGUISprite.h" //-------------------------------------------------------- // Declarations //-------------------------------------------------------- /** * An SGUIText object is a parsed string, divided into * text-rendering components. Each component, being a * call to the Renderer. For instance, if you by tags * change the color, then the GUI will have to make * individual calls saying it want that color on the * text. * * For instance: * "Hello [b]there[/b] bunny!" * * That without word-wrapping would mean 3 components. * i.e. 3 calls to CRenderer. One drawing "Hello", * one drawing "there" in bold, and one drawing "bunny!". */ struct SGUIText { /** * A sprite call to the CRenderer */ struct SSpriteCall { SSpriteCall() : m_CellID(0) {} /** * Size and position of sprite */ CRect m_Area; /** * Sprite from global GUI sprite database. */ CGUISpriteInstance m_Sprite; int m_CellID; /** * Tooltip text */ CStrW m_Tooltip; /** * Tooltip style */ CStrW m_TooltipStyle; }; /** * A text call to the CRenderer */ struct STextCall { STextCall() : m_UseCustomColor(false), m_Bold(false), m_Italic(false), m_Underlined(false), m_pSpriteCall(NULL) {} /** * Position */ CPos m_Pos; /** * Size */ CSize m_Size; /** * The string that is suppose to be rendered. */ CStrW m_String; /** * Use custom color? If true then m_Color is used, * else the color inputted will be used. */ bool m_UseCustomColor; /** * Color setup */ CColor m_Color; /** * Font name */ CStrW m_Font; /** * Settings */ bool m_Bold, m_Italic, m_Underlined; /** * *IF* an icon, then this is not NULL. */ std::list::pointer m_pSpriteCall; }; /** * List of TextCalls, for instance "Hello", "there!" */ std::vector m_TextCalls; /** * List of sprites, or "icons" that should be rendered * along with the text. */ std::list m_SpriteCalls; // list for consistent mem addresses // so that we can point to elements. /** * Width and height of the whole output, used when setting up * scrollbars and such. */ CSize m_Size; }; /** * String class, substitute for CStr, but that parses * the tags and builds up a list of all text that will * be different when outputted. * * The difference between CGUIString and SGUIText is that * CGUIString is a string-class that parses the tags * when the value is set. The SGUIText is just a container * which stores the positions and settings of all text-calls * that will have to be made to the Renderer. */ class CGUIString { public: /** * A chunk of text that represents one call to the renderer. * In other words, all text in one chunk, will be drawn * exactly with the same settings. */ struct TextChunk { /** * A tag looks like this "Hello [B]there[/B] little" */ struct Tag { /** * Tag Type */ enum TagType { TAG_B, TAG_I, TAG_FONT, TAG_SIZE, TAG_COLOR, TAG_IMGLEFT, TAG_IMGRIGHT, TAG_ICON }; struct TagAttribute { std::string attrib; std::string value; }; /** * Set tag from string * * @param tagtype TagType by string, like 'IMG' for [IMG] * @return True if m_TagType was set. */ bool SetTagType(const CStr& tagtype); /** * In [B=Hello][/B] * m_TagType is TAG_B */ TagType m_TagType; /** * In [B=Hello][/B] * m_TagValue is 'Hello' */ std::string m_TagValue; /** * Some tags need an additional attributes */ std::vector m_TagAttributes; }; /** * m_From and m_To is the range of the string */ int m_From, m_To; /** * Tags that are present. [A][B] */ std::vector m_Tags; }; /** * All data generated in GenerateTextCall() */ struct SFeedback { // Constants static const int Left=0; static const int Right=1; /** * Reset all member data. */ void Reset(); /** * Image stacks, for left and right floating images. */ std::vector m_Images[2]; // left and right /** * Text and Sprite Calls. */ std::vector m_TextCalls; std::list m_SpriteCalls; // list for consistent mem addresses // so that we can point to elements. /** * Width and Height *feedback* */ CSize m_Size; /** * If the word inputted was a new line. */ bool m_NewLine; }; /** * Set the value, the string will automatically * be parsed when set. */ void SetValue(const CStrW& str); /** * Get String, without tags */ const CStrW& GetRawString() const { return m_RawString; } /** + * Get String, with tags + */ + const CStrW& GetOriginalString() const { return m_OriginalString; } + + /** * Generate Text Call from specified range. The range * must span only within ONE TextChunk though. Otherwise * it can't be fit into a single Text Call * * Notice it won't make it complete, you will have to add * X/Y values and such. * * @param Feedback contains all info that is generated. * @param DefaultFont Default Font * @param from From character n, * @param to to character n. * @param FirstLine Whether this is the first line of text, to calculate its height correctly * @param pObject Only for Error outputting, optional! If NULL * then no Errors will be reported! Useful when you need * to make several GenerateTextCall in different phases, * it avoids duplicates. */ void GenerateTextCall(SFeedback &Feedback, const CStr& DefaultFont, const int &from, const int &to, const bool FirstLine, const IGUIObject *pObject=NULL) const; /** * Words */ std::vector m_Words; /** * TextChunks */ std::vector m_TextChunks; private: /** * The full raw string. Stripped of tags. */ CStrW m_RawString; + + /** + * The original string value passed to SetValue. + */ + CStrW m_OriginalString; }; #endif Index: ps/trunk/source/gui/GUItext.cpp =================================================================== --- ps/trunk/source/gui/GUItext.cpp (revision 8924) +++ ps/trunk/source/gui/GUItext.cpp (revision 8925) @@ -1,613 +1,615 @@ /* Copyright (C) 2009 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 . */ /* GUI text */ #include "precompiled.h" #include "GUI.h" #include "GUIManager.h" #include "ps/CLogger.h" #include "ps/Parser.h" #include #include "ps/Font.h" static const wchar_t TagStart = '['; static const wchar_t TagEnd = ']'; void CGUIString::SFeedback::Reset() { m_Images[Left].clear(); m_Images[Right].clear(); m_TextCalls.clear(); m_SpriteCalls.clear(); m_Size = CSize(); m_NewLine=false; } void CGUIString::GenerateTextCall(SFeedback &Feedback, const CStr& DefaultFont, const int &from, const int &to, const bool FirstLine, const IGUIObject *pObject) const { // Reset width and height, because they will be determined with incrementation // or comparisons. Feedback.Reset(); // Check out which text chunk this is within. //bool match_found = false; std::vector::const_iterator itTextChunk; for (itTextChunk=m_TextChunks.begin(); itTextChunk!=m_TextChunks.end(); ++itTextChunk) { // - GL - Temp TextChunk tc = *itTextChunk; // -- GL // Get the area that is overlapped by both the TextChunk and // by the from/to inputted. int _from, _to; _from = std::max(from, itTextChunk->m_From); _to = std::min(to, itTextChunk->m_To); // If from is larger than to, than they are not overlapping if (_to == _from && itTextChunk->m_From == itTextChunk->m_To) { // These should never be able to have more than one tag. debug_assert(itTextChunk->m_Tags.size()==1); // Now do second check // because icons and images are placed on exactly one position // in the words-list, it can be counted twice if placed on an // edge. But there is always only one logical preference that // we want. This check filters the unwanted. // it's in the end of one word, and the icon // should really belong to the beginning of the next one if (_to == to && to >= 1) { if (GetRawString()[to-1] == ' ' || GetRawString()[to-1] == '-' || GetRawString()[to-1] == '\n') continue; } // This std::string is just a break if (_from == from && from >= 1) { if (GetRawString()[from] == '\n' && GetRawString()[from-1] != '\n' && GetRawString()[from-1] != ' ' && GetRawString()[from-1] != '-') continue; } // Single tags if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT) { // Only add the image if the icon exists. if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue)) { Feedback.m_Images[SFeedback::Left].push_back(itTextChunk->m_Tags[0].m_TagValue); } else if (pObject) { LOGERROR(L"Trying to use an [imgleft]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str()); } } else if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT) { // Only add the image if the icon exists. if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue)) { Feedback.m_Images[SFeedback::Right].push_back(itTextChunk->m_Tags[0].m_TagValue); } else if (pObject) { LOGERROR(L"Trying to use an [imgright]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str()); } } else if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_ICON) { // Only add the image if the icon exists. if (g_GUI->IconExists(itTextChunk->m_Tags[0].m_TagValue)) { // We'll need to setup a text-call that will point // to the icon, this is to be able to iterate // through the text-calls without having to // complex the structure virtually for nothing more. SGUIText::STextCall TextCall; // Also add it to the sprites being rendered. SGUIText::SSpriteCall SpriteCall; // Get Icon from icon database in g_GUI SGUIIcon icon = g_GUI->GetIcon(itTextChunk->m_Tags[0].m_TagValue); CSize size = icon.m_Size; // append width, and make maximum height the height. Feedback.m_Size.cx += size.cx; Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); // These are also needed later TextCall.m_Size = size; SpriteCall.m_Area = size; // Handle additional attributes std::vector::const_iterator att_it; for(att_it = itTextChunk->m_Tags[0].m_TagAttributes.begin(); att_it != itTextChunk->m_Tags[0].m_TagAttributes.end(); ++att_it) { TextChunk::Tag::TagAttribute tagAttrib = (TextChunk::Tag::TagAttribute)(*att_it); if (tagAttrib.attrib == "displace" && !tagAttrib.value.empty()) { //Displace the sprite CSize displacement; // Parse the value if (!GUI::ParseString(CStrW(tagAttrib.value), displacement)) LOGERROR(L"Error parsing 'displace' value for tag [ICON]"); else SpriteCall.m_Area += displacement; } else if(tagAttrib.attrib == "tooltip") { SpriteCall.m_Tooltip = CStrW(tagAttrib.value); } else if(tagAttrib.attrib == "tooltip_style") { SpriteCall.m_TooltipStyle = CStrW(tagAttrib.value); } } SpriteCall.m_Sprite = icon.m_SpriteName; SpriteCall.m_CellID = icon.m_CellID; // Add sprite call Feedback.m_SpriteCalls.push_back(SpriteCall); // Finalize text call TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back(); // Add text call Feedback.m_TextCalls.push_back(TextCall); } else if (pObject) { LOGERROR(L"Trying to use an [icon]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str()); } } } else if (_to > _from && !Feedback.m_NewLine) { SGUIText::STextCall TextCall; // Set defaults TextCall.m_Font = DefaultFont; TextCall.m_UseCustomColor = false; // Extract substd::string from RawString. TextCall.m_String = GetRawString().substr(_from, _to-_from); // Go through tags and apply changes. std::vector::const_iterator it2; for (it2 = itTextChunk->m_Tags.begin(); it2 != itTextChunk->m_Tags.end(); ++it2) { if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_COLOR) { // Set custom color TextCall.m_UseCustomColor = true; // Try parsing the color std::string if (!GUI::ParseString(CStrW(it2->m_TagValue), TextCall.m_Color)) { if (pObject) LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str()); } } else if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_FONT) { // TODO Gee: (2004-08-15) Check if Font exists? TextCall.m_Font = CStrW(it2->m_TagValue); } } // Calculate the size of the font CSize size; int cx, cy; CFont font (TextCall.m_Font); font.CalculateStringSize(TextCall.m_String, cx, cy); // For anything other than the first line, the line spacing // needs to be considered rather than just the height of the text if (! FirstLine) cy = font.GetLineSpacing(); size.cx = (float)cx; size.cy = (float)cy; // Append width, and make maximum height the height. Feedback.m_Size.cx += size.cx; Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); // These are also needed later TextCall.m_Size = size; if (! TextCall.m_String.empty()) { if (TextCall.m_String[0] == '\n') { Feedback.m_NewLine = true; } } // Add text-chunk Feedback.m_TextCalls.push_back(TextCall); } } } bool CGUIString::TextChunk::Tag::SetTagType(const CStr& tagtype) { CStr _tagtype = tagtype.UpperCase(); if (_tagtype == CStr("B")) { m_TagType = TAG_B; return true; } else if (_tagtype == CStr("I")) { m_TagType = TAG_I; return true; } else if (_tagtype == CStr("COLOR")) { m_TagType = TAG_COLOR; return true; } else if (_tagtype == CStr("FONT")) { m_TagType = TAG_FONT; return true; } else if (_tagtype == CStr("ICON")) { m_TagType = TAG_ICON; return true; } else if (_tagtype == CStr("IMGLEFT")) { m_TagType = TAG_IMGLEFT; return true; } else if (_tagtype == CStr("IMGRIGHT")) { m_TagType = TAG_IMGRIGHT; return true; } return false; } void CGUIString::SetValue(const CStrW& str) { + m_OriginalString = str; + // clear m_TextChunks.clear(); m_Words.clear(); m_RawString = CStrW(); // Setup parser // TODO Gee: (2004-08-16) Create and store this parser object somewhere to save loading time. // TODO PT: Extended CParserCache so that the above is possible (since it currently only // likes one-task parsers) CParser Parser; // I've added the option of an additional parameter. Only used for icons when writing this. Parser.InputTaskType("start", "$ident[_=_$value_[$ident_=_$value_]]"); Parser.InputTaskType("end", "/$ident"); long position = 0; long from=0; // the position in the raw std::string where the last tag ended long from_nonraw=0; // like from only in position of the REAL std::string, with tags. long curpos = 0; // Current Text Chunk CGUIString::TextChunk CurrentTextChunk; for (;;position = curpos+1) { // Find next TagStart character curpos = str.Find(position, TagStart); if (curpos == -1) { m_RawString += str.substr(position); if (from != (long)m_RawString.length()) { CurrentTextChunk.m_From = from; CurrentTextChunk.m_To = (int)m_RawString.length(); m_TextChunks.push_back(CurrentTextChunk); } break; } else { // First check if there is another TagStart before a TagEnd, // in that case it's just a regular TagStart and we can continue. long pos_left = str.Find(curpos+1, TagStart); long pos_right = str.Find(curpos+1, TagEnd); if (pos_right == -1) { m_RawString += str.substr(position, curpos-position+1); continue; } else if (pos_left != -1 && pos_left < pos_right) { m_RawString += str.substr(position, pos_left-position); continue; } else { m_RawString += str.substr(position, curpos-position); // Okay we've found a TagStart and TagEnd, positioned // at pos and pos_right. Now let's extract the // interior and try parsing. CStr tagstr (str.substr(curpos+1, pos_right-curpos-1)); CParserLine Line; Line.ParseString(Parser, (const char*)tagstr); // Set to true if the tag is just text. bool justtext = false; if (Line.m_ParseOK) { if (Line.m_TaskTypeName == "start") { // The tag TextChunk::Tag tag; std::string Str_TagType; Line.GetArgString(0, Str_TagType); if (!tag.SetTagType(Str_TagType)) { justtext = true; } else { // Check for possible value-std::strings if (Line.GetArgCount() >= 2) Line.GetArgString(1, tag.m_TagValue); //Handle arbitrary number of additional parameters size_t argn; for(argn = 2; argn < Line.GetArgCount(); argn += 2) { TextChunk::Tag::TagAttribute a; Line.GetArgString(argn, a.attrib); Line.GetArgString(argn+1, a.value); tag.m_TagAttributes.push_back(a); } // Finalize last if (curpos != from_nonraw) { CurrentTextChunk.m_From = from; CurrentTextChunk.m_To = from + curpos - from_nonraw; m_TextChunks.push_back(CurrentTextChunk); from = CurrentTextChunk.m_To; } from_nonraw = pos_right+1; // Some tags does not have a closure, and should be // stored without text. Like a in XML. if (tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT || tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT || tag.m_TagType == TextChunk::Tag::TAG_ICON) { // We need to use a fresh text chunk // because 'tag' should be the *only* tag. TextChunk FreshTextChunk; // They does not work with the text system. FreshTextChunk.m_From = from + pos_right+1 - from_nonraw; FreshTextChunk.m_To = from + pos_right+1 - from_nonraw; FreshTextChunk.m_Tags.push_back(tag); m_TextChunks.push_back(FreshTextChunk); } else { // Add that tag, but first, erase previous occurences of the // same tag. std::vector::iterator it; for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it) { if (it->m_TagType == tag.m_TagType) { CurrentTextChunk.m_Tags.erase(it); break; } } // Add! CurrentTextChunk.m_Tags.push_back(tag); } } } else if (Line.m_TaskTypeName == "end") { // The tag TextChunk::Tag tag; std::string Str_TagType; Line.GetArgString(0, Str_TagType); if (!tag.SetTagType(Str_TagType)) { justtext = true; } else { // Finalize the previous chunk if (curpos != from_nonraw) { CurrentTextChunk.m_From = from; CurrentTextChunk.m_To = from + curpos - from_nonraw; m_TextChunks.push_back(CurrentTextChunk); from = CurrentTextChunk.m_To; } from_nonraw = pos_right+1; // Search for the tag, if it's not added, then // pass it as plain text. std::vector::iterator it; for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it) { if (it->m_TagType == tag.m_TagType) { CurrentTextChunk.m_Tags.erase(it); break; } } } } } else justtext = true; if (justtext) { // What was within the tags could not be interpreted // so we'll assume it's just text. m_RawString += str.substr(curpos, pos_right-curpos+1); } curpos = pos_right; continue; } } } // Add a delimiter at start and at end, it helps when // processing later, because we don't have make exceptions for // those cases. // We'll sort later. m_Words.push_back(0); m_Words.push_back((int)m_RawString.length()); // Space: ' ' for (position=0, curpos=0;;position = curpos+1) { // Find the next word-delimiter. long dl = m_RawString.Find(position, ' '); if (dl == -1) break; curpos = dl; m_Words.push_back((int)dl+1); } // Dash: '-' for (position=0, curpos=0;;position = curpos+1) { // Find the next word-delimiter. long dl = m_RawString.Find(position, '-'); if (dl == -1) break; curpos = dl; m_Words.push_back((int)dl+1); } // New Line: '\n' for (position=0, curpos=0;;position = curpos+1) { // Find the next word-delimiter. long dl = m_RawString.Find(position, '\n'); if (dl == -1) break; curpos = dl; // Add before and m_Words.push_back((int)dl); m_Words.push_back((int)dl+1); } sort(m_Words.begin(), m_Words.end()); // Remove duplicates (only if larger than 2) if (m_Words.size() > 2) { std::vector::iterator it; int last_word = -1; for (it = m_Words.begin(); it != m_Words.end(); ) { if (last_word == *it) { it = m_Words.erase(it); } else { last_word = *it; ++it; } } } #if 0 for (int i=0; i<(int)m_Words.size(); ++i) { LOGMESSAGE(L"m_Words[%d] = %d", i, m_Words[i]); } for (int i=0; i<(int)m_TextChunks.size(); ++i) { LOGMESSAGE(L"m_TextChunk[%d] = [%d,%d]", i, m_TextChunks[i].m_From, m_TextChunks[i].m_To); for (int j=0; j<(int)m_TextChunks[i].m_Tags.size(); ++j) { LOGMESSAGE(L"--Tag: %d \"%hs\"", (int)m_TextChunks[i].m_Tags[j].m_TagType, m_TextChunks[i].m_Tags[j].m_TagValue.c_str()); } } #endif } Index: ps/trunk/source/scriptinterface/ScriptInterface.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.h (revision 8924) +++ ps/trunk/source/scriptinterface/ScriptInterface.h (revision 8925) @@ -1,461 +1,461 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_SCRIPTINTERFACE #define INCLUDED_SCRIPTINTERFACE #include #include #include #include "ScriptTypes.h" #include "ScriptVal.h" #include "js/jsapi.h" #include "lib/file/vfs/vfs_path.h" #include "ps/Profile.h" #include "ps/utf16string.h" class AutoGCRooter; namespace boost { class rand48; } // Set the maximum number of function arguments that can be handled // (This should be as small as possible (for compiler efficiency), // but as large as necessary for all wrapped functions) #define SCRIPT_INTERFACE_MAX_ARGS 6 #ifdef NDEBUG #define ENABLE_SCRIPT_PROFILING 0 #else #define ENABLE_SCRIPT_PROFILING 1 #endif struct ScriptInterface_impl; class ScriptRuntime; /** * Abstraction around a SpiderMonkey JSContext. * * Thread-safety: * - May be used in non-main threads. * - Each ScriptInterface must be created, used, and destroyed, all in a single thread * (it must never be shared between threads). */ class ScriptInterface { public: /** * Returns a runtime, which can used to initialise any number of * ScriptInterfaces contexts. Values created in one context may be used * in any other context from the same runtime (but not any other runtime). * Each runtime should only ever be used on a single thread. */ static shared_ptr CreateRuntime(); /** * Constructor. * @param nativeScopeName Name of global object that functions (via RegisterFunction) will * be placed into, as a scoping mechanism; typically "Engine" */ ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime); ~ScriptInterface(); /** * Shut down the JS system to clean up memory. Must only be called when there * are no ScriptInterfaces alive. */ static void ShutDown(); void SetCallbackData(void* cbdata); static void* GetCallbackData(JSContext* cx); JSContext* GetContext() const; JSRuntime* GetRuntime() const; void ReplaceNondeterministicFunctions(boost::rand48& rng); /** * Call a constructor function, equivalent to JS "new ctor(arg)". * @return The new object; or JSVAL_VOID on failure, and logs an error message */ jsval CallConstructor(jsval ctor, jsval arg); /** * Create an object as with CallConstructor except don't actually execute the * constructor function. * @return The new object; or JSVAL_VOID on failure, and logs an error message */ jsval NewObjectFromConstructor(jsval ctor); /** * Call the named property on the given object, with void return type and 0 arguments */ bool CallFunctionVoid(jsval val, const char* name); /** * Call the named property on the given object, with void return type and 1 argument */ template bool CallFunctionVoid(jsval val, const char* name, const T0& a0); /** * Call the named property on the given object, with void return type and 2 arguments */ template bool CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1); /** * Call the named property on the given object, with void return type and 3 arguments */ template bool CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2); /** * Call the named property on the given object, with return type R and 0 arguments */ template bool CallFunction(jsval val, const char* name, R& ret); /** * Call the named property on the given object, with return type R and 1 argument */ template bool CallFunction(jsval val, const char* name, const T0& a0, R& ret); /** * Call the named property on the given object, with return type R and 2 arguments */ template bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, R& ret); /** * Call the named property on the given object, with return type R and 3 arguments */ template bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, R& ret); /** * Call the named property on the given object, with return type R and 4 arguments */ template bool CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, const T3& a3, R& ret); jsval GetGlobalObject(); JSClass* GetGlobalClass(); /** * Set the named property on the global object. * If @p replace is true, an existing property will be overwritten; otherwise attempts * to set an already-defined value will fail. */ template bool SetGlobal(const char* name, const T& value, bool replace = false); /** * Set the named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template - bool SetProperty(jsval obj, const char* name, const T& value, bool constant, bool enumerate = true); + bool SetProperty(jsval obj, const char* name, const T& value, bool constant = false, bool enumerate = true); /** * Set the integer-named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template - bool SetPropertyInt(jsval obj, int name, const T& value, bool constant, bool enumerate = true); + bool SetPropertyInt(jsval obj, int name, const T& value, bool constant = false, bool enumerate = true); template bool GetProperty(jsval obj, const char* name, T& out); bool HasProperty(jsval obj, const char* name); bool EnumeratePropertyNamesWithPrefix(jsval obj, const char* prefix, std::vector& out); bool SetPrototype(jsval obj, jsval proto); bool FreezeObject(jsval obj, bool deep); bool Eval(const char* code); template bool Eval(const CHAR* code, T& out); std::wstring ToString(jsval obj); /** * Parse a JSON string. Returns the undefined value on error. */ CScriptValRooted ParseJSON(const utf16string& string); /** * Parse a UTF-8-encoded JSON string. Returns the undefined value on error. */ CScriptValRooted ParseJSON(const std::string& string_utf8); /** * Read a JSON file. Returns the undefined value on error. */ CScriptValRooted ReadJSONFile(const VfsPath& path); /** * Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error. */ std::string StringifyJSON(jsval obj, bool indent = true); /** * Report the given error message through the JS error reporting mechanism, * and throw a JS exception. (Callers can check IsPendingException, and must * return JS_FALSE in that case to propagate the exception.) */ void ReportError(const char* msg); /** * Load and execute the given script in a new function scope. * @param filename Name for debugging purposes (not used to load the file) * @param code JS code to execute * @return true on successful compilation and execution; false otherwise */ bool LoadScript(const std::wstring& filename, const std::wstring& code); /** * Load and execute the given script in the global scope. * @return true on successful compilation and execution; false otherwise */ bool LoadGlobalScriptFile(const VfsPath& path); /** * Construct a new value (usable in this ScriptInterface's context) by cloning * a value from a different context. * Complex values (functions, XML, etc) won't be cloned correctly, but basic * types and cyclic references should be fine. */ jsval CloneValueFromOtherContext(ScriptInterface& otherContext, jsval val); /** * Convert a jsval to a C++ type. (This might trigger GC.) */ template static bool FromJSVal(JSContext* cx, jsval val, T& ret); /** * Convert a C++ type to a jsval. (This might trigger GC. The return * value must be rooted if you don't want it to be collected.) */ template static jsval ToJSVal(JSContext* cx, T const& val); AutoGCRooter* ReplaceAutoGCRooter(AutoGCRooter* rooter); /** * Dump some memory heap debugging information to stderr. */ void DumpHeap(); void MaybeGC(); /** * Structured clones are a way to serialize 'simple' JS values into a buffer * that can safely be passed between contexts and runtimes and threads. * A StructuredClone can be stored and read multiple times if desired. * We wrap them in shared_ptr so memory management is automatic and * thread-safe. */ class StructuredClone { NONCOPYABLE(StructuredClone); public: StructuredClone(); ~StructuredClone(); JSContext* m_Context; uint64* m_Data; size_t m_Size; }; shared_ptr WriteStructuredClone(jsval v); jsval ReadStructuredClone(const shared_ptr& ptr); private: bool CallFunction_(jsval val, const char* name, size_t argc, jsval* argv, jsval& ret); bool Eval_(const char* code, jsval& ret); bool Eval_(const wchar_t* code, jsval& ret); bool SetGlobal_(const char* name, jsval value, bool replace); bool SetProperty_(jsval obj, const char* name, jsval value, bool readonly, bool enumerate); bool SetPropertyInt_(jsval obj, int name, jsval value, bool readonly, bool enumerate); bool GetProperty_(jsval obj, const char* name, jsval& value); static bool IsExceptionPending(JSContext* cx); static JSClass* GetClass(JSContext* cx, JSObject* obj); static void* GetPrivate(JSContext* cx, JSObject* obj); void Register(const char* name, JSNative fptr, size_t nargs); std::auto_ptr m; // The nasty macro/template bits are split into a separate file so you don't have to look at them public: #include "NativeWrapperDecls.h" // This declares: // // template // void RegisterFunction(const char* functionName); // // template // static JSNative call; // // template // static JSNative callMethod; // // template // static size_t nargs(); }; // Implement those declared functions #include "NativeWrapperDefns.h" template bool ScriptInterface::CallFunction(jsval val, const char* name, R& ret) { jsval jsRet; bool ok = CallFunction_(val, name, 0, NULL, jsRet); if (!ok) return false; return FromJSVal(GetContext(), jsRet, ret); } template bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0) { jsval jsRet; jsval argv[1]; argv[0] = ToJSVal(GetContext(), a0); return CallFunction_(val, name, 1, argv, jsRet); } template bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1) { jsval jsRet; jsval argv[2]; argv[0] = ToJSVal(GetContext(), a0); argv[1] = ToJSVal(GetContext(), a1); return CallFunction_(val, name, 2, argv, jsRet); } template bool ScriptInterface::CallFunctionVoid(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2) { jsval jsRet; jsval argv[3]; argv[0] = ToJSVal(GetContext(), a0); argv[1] = ToJSVal(GetContext(), a1); argv[2] = ToJSVal(GetContext(), a2); return CallFunction_(val, name, 3, argv, jsRet); } template bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, R& ret) { jsval jsRet; jsval argv[1]; argv[0] = ToJSVal(GetContext(), a0); bool ok = CallFunction_(val, name, 1, argv, jsRet); if (!ok) return false; return FromJSVal(GetContext(), jsRet, ret); } template bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, R& ret) { jsval jsRet; jsval argv[2]; argv[0] = ToJSVal(GetContext(), a0); argv[1] = ToJSVal(GetContext(), a1); bool ok = CallFunction_(val, name, 2, argv, jsRet); if (!ok) return false; return FromJSVal(GetContext(), jsRet, ret); } template bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, R& ret) { jsval jsRet; jsval argv[3]; argv[0] = ToJSVal(GetContext(), a0); argv[1] = ToJSVal(GetContext(), a1); argv[2] = ToJSVal(GetContext(), a2); bool ok = CallFunction_(val, name, 3, argv, jsRet); if (!ok) return false; return FromJSVal(GetContext(), jsRet, ret); } template bool ScriptInterface::CallFunction(jsval val, const char* name, const T0& a0, const T1& a1, const T2& a2, const T3& a3, R& ret) { jsval jsRet; jsval argv[4]; argv[0] = ToJSVal(GetContext(), a0); argv[1] = ToJSVal(GetContext(), a1); argv[2] = ToJSVal(GetContext(), a2); argv[3] = ToJSVal(GetContext(), a3); bool ok = CallFunction_(val, name, 4, argv, jsRet); if (!ok) return false; return FromJSVal(GetContext(), jsRet, ret); } template bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace) { return SetGlobal_(name, ToJSVal(GetContext(), value), replace); } template bool ScriptInterface::SetProperty(jsval obj, const char* name, const T& value, bool readonly, bool enumerate) { return SetProperty_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate); } template bool ScriptInterface::SetPropertyInt(jsval obj, int name, const T& value, bool readonly, bool enumerate) { return SetPropertyInt_(obj, name, ToJSVal(GetContext(), value), readonly, enumerate); } template bool ScriptInterface::GetProperty(jsval obj, const char* name, T& out) { jsval val; if (! GetProperty_(obj, name, val)) return false; return FromJSVal(GetContext(), val, out); } template bool ScriptInterface::Eval(const CHAR* code, T& ret) { jsval rval; if (! Eval_(code, rval)) return false; return FromJSVal(GetContext(), rval, ret); } #endif // INCLUDED_SCRIPTINTERFACE Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp (revision 8924) +++ ps/trunk/source/main.cpp (revision 8925) @@ -1,506 +1,509 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* 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/lib_errors.h" #include "lib/secure_crt.h" #include "lib/frequency_filter.h" #include "lib/input.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/utf8.h" #include "lib/external_libraries/sdl.h" #include "lib/res/sound/snd_mgr.h" #include "ps/ArchiveBuilder.h" #include "ps/CConsole.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Loader.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/Replay.h" +#include "ps/UserReport.h" #include "ps/Util.h" #include "ps/VideoMode.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 "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/TextureManager.h" #include "gui/GUIManager.h" #include "renderer/Renderer.h" #include "scripting/ScriptingHost.h" #include "simulation2/Simulation2.h" extern bool g_GameRestarted; void kill_mainloop(); // 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; // main app message handler static InReaction MainInputHandler(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_ACTIVEEVENT: if (ev->ev.active.state == SDL_APPMOUSEFOCUS) { // Tell renderer not to render cursor if mouse focus is lost // this restores system cursor, until/if focus is regained if (!ev->ev.active.gain) RenderCursor(false); else RenderCursor(true); } break; case SDL_QUIT: kill_mainloop(); break; case SDL_VIDEORESIZE: g_ResizedW = ev->ev.resize.w; g_ResizedH = ev->ev.resize.h; break; case SDL_HOTKEYDOWN: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "exit") { kill_mainloop(); return IN_HANDLED; } else if (hotkey == "screenshot") { WriteScreenshot(L".png"); return IN_HANDLED; } else if (hotkey == "bigscreenshot") { WriteBigScreenshot(L".bmp", 10); return IN_HANDLED; } else if (hotkey == "togglefullscreen") { g_VideoMode.ToggleFullscreen(); return IN_HANDLED; } break; } return IN_PASS; } // dispatch all pending events to the various receivers. static void PumpEvents() { PROFILE( "dispatch events" ); in_dispatch_recorded_events(); SDL_Event_ ev; while(SDL_PollEvent(&ev.ev)) in_dispatch_event(&ev); } // return indication of whether archive is currently being built; this is // used to prevent reloading during that time (see call site). static bool ProgressiveBuildArchive() { ONCE(g_GUI->SendEventToAll("archivebuildercomplete")); return false; #if 0 int ret = vfs_opt_auto_build("../logs/trace.txt", "mods/official/official%02d.zip", "mods/official/mini%02d.zip"); if(ret == INFO::ALL_COMPLETE) { // nothing to do; will return false below } else if(ret < 0) DEBUG_DISPLAY_ERROR(L"Archive build failed"); else if(ret == INFO::OK) g_GUI.SendEventToAll("archivebuildercomplete"); // in progress else { int percent = (int)ret; g_ScriptingHost.SetGlobal("g_ArchiveBuilderProgress", INT_TO_JSVAL(percent)); g_GUI.SendEventToAll("archivebuilderprogress"); return true; } return false; #endif } static int ProgressiveLoad() { wchar_t description[100]; int progress_percent; LibError 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: CHECK_ERR(ret); // can't do this above due to legit ERR::TIMED_OUT break; } GUI_DisplayLoadProgress(progress_percent, description); return 0; } static void RendererIncrementalLoad() { const double maxTime = 0.1f; double startTime = timer_Time(); bool more; do { more = g_Renderer.GetTextureManager().MakeProgress(); } while (more && timer_Time() - startTime < maxTime); } static bool quit = false; // break out of main loop static void Frame() { ogl_WarnIfError(); // 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 TimeSinceLastFrame = 1.0 / g_frequencyFilter->SmoothedFrequency(); #endif debug_assert(TimeSinceLastFrame > 0.0f); // decide if update/render is necessary bool need_render = !g_app_minimized; 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_NetClient && !g_app_has_focus ) { need_update = false; // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored SDL_Delay(10); } // TODO: throttling: limit update and render frequency to the minimum. // this is mostly relevant for "inactive" state, so that other windows // get enough CPU time, but it's always nice for power+thermal management. bool is_building_archive; // must come before PROFILE_START's { PROFILE_START("build archive"); is_building_archive = ProgressiveBuildArchive(); PROFILE_END( "build archive"); // this scans for changed files/directories and reloads them, thus // allowing hotloading (changes are immediately assimilated in-game). // must not be done during archive building because it changes the // archive file each iteration, but keeps it locked; reloading // would trigger a warning because the file can't be opened. if(!is_building_archive) { PROFILE_START("reload changed files"); ReloadChangedFiles(); PROFILE_END( "reload changed files"); } PROFILE_START("progressive load"); ProgressiveLoad(); PROFILE_END("progressive load"); PROFILE_START("renderer incremental load"); RendererIncrementalLoad(); PROFILE_END("renderer incremental load"); PROFILE_START("input"); PumpEvents(); PROFILE_END("input"); // 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 (quit) return; // respond to pumped resize events if (g_ResizedW || g_ResizedH) { g_VideoMode.ResizeWindow(g_ResizedW, g_ResizedH); g_ResizedW = g_ResizedH = 0; } PROFILE_START("network poll"); if (g_NetClient) g_NetClient->Poll(); PROFILE_END("network poll"); ogl_WarnIfError(); PROFILE_START("gui tick"); g_GUI->TickObjects(); PROFILE_END("gui tick"); ogl_WarnIfError(); PROFILE_START( "game logic" ); if (g_Game && g_Game->IsGameStarted() && need_update) { PROFILE_START( "simulation update" ); g_Game->Update(TimeSinceLastFrame); PROFILE_END( "simulation update" ); PROFILE_START( "camera update" ); g_Game->GetView()->Update(float(TimeSinceLastFrame)); PROFILE_END( "camera update" ); PROFILE_START( "sound update" ); CCamera* camera = g_Game->GetView()->GetCamera(); CMatrix3D& orientation = camera->m_Orientation; float* pos = &orientation._data[12]; float* dir = &orientation._data[8]; float* up = &orientation._data[4]; // HACK: otherwise sound effects are L/R flipped. No idea what else // is going wrong, because the listener and camera are supposed to // coincide in position and orientation. float down[3] = { -up[0], -up[1], -up[2] }; if(snd_update(pos, dir, down) < 0) debug_printf(L"snd_update failed\n"); PROFILE_END( "sound update" ); } else { if(snd_update(0, 0, 0) < 0) debug_printf(L"snd_update (pos=0 version) failed\n"); } PROFILE_END( "game logic" ); // Immediately flush any messages produced by simulation code PROFILE_START("network flush"); if (g_NetClient) g_NetClient->Flush(); PROFILE_END("network flush"); + g_UserReporter.Update(); + PROFILE_START( "update console" ); g_Console->Update(TimeSinceLastFrame); PROFILE_END( "update console" ); PROFILE_START("render"); ogl_WarnIfError(); if(need_render) { Render(); PROFILE_START( "swap buffers" ); SDL_GL_SwapBuffers(); PROFILE_END( "swap buffers" ); } ogl_WarnIfError(); PROFILE_END("render"); g_Profiler.Frame(); g_GameRestarted = false; } 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() { } // stop the main loop and trigger orderly shutdown. called from several // places: the event handler (SDL_QUIT and hotkey) and JS exitProgram. void kill_mainloop() { quit = true; } static bool restart_in_atlas = false; // called by game code to indicate main() should restart in Atlas mode // instead of terminating void restart_mainloop_in_atlas() { quit = true; restart_in_atlas = true; } // moved into a helper function to ensure args is destroyed before // exit(), which may result in a memory leak. static void RunGameOrAtlas(int argc, const char* argv[]) { CmdLineArgs args(argc, argv); // We need to initialise libxml2 in the main thread before // any thread uses it. So initialise it here before we // might run Atlas. CXeromyces::Startup(); // run Atlas (if requested via args) bool ran_atlas = ATLAS_RunIfOnCmdLine(args, false); // Atlas handles the whole init/shutdown/etc sequence by itself; // when we get here, it has exited and we're done. if(ran_atlas) return; // run non-visual simulation replay if requested if (args.Has("replay")) { snd_disable(true); Paths paths(args); g_VFS = CreateVfs(20 * MiB); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); g_VFS->Mount(L"", paths.RData()/L"mods/public", VFS_MOUNT_MUST_EXIST); { CReplayPlayer replay; replay.Load(args.Get("replay")); replay.Replay(); } g_VFS.reset(); CXeromyces::Terminate(); return; } // run in archive-building mode if requested if (args.Has("archivebuild")) { Paths paths(args); fs::wpath mod = wstring_from_utf8(args.Get("archivebuild")); fs::wpath zip; if (args.Has("archivebuild-output")) zip = wstring_from_utf8(args.Get("archivebuild-output")); else zip = mod.leaf()+L".zip"; CArchiveBuilder builder(mod, paths.Cache()); builder.Build(zip); CXeromyces::Terminate(); return; } const double res = timer_Resolution(); g_frequencyFilter = CreateFrequencyFilter(res, 30.0); // run the game Init(args, 0); InitGraphics(args, 0); MainControllerInit(); while(!quit) Frame(); Shutdown(0); ScriptingHost::FinalShutdown(); // this can't go in Shutdown() because that could be called multiple times per process, so stick it here instead MainControllerShutdown(); if (restart_in_atlas) { ATLAS_RunIfOnCmdLine(args, true); return; } // Shut down libxml2 (done here to match the Startup call) CXeromyces::Terminate(); } int main(int argc, char* argv[]) { EarlyInit(); // must come at beginning of main RunGameOrAtlas(argc, const_cast(argv)); return EXIT_SUCCESS; } Index: ps/trunk/source/lib/external_libraries/curl.h =================================================================== --- ps/trunk/source/lib/external_libraries/curl.h (revision 8924) +++ ps/trunk/source/lib/external_libraries/curl.h (revision 8925) @@ -1,48 +1,52 @@ /* Copyright (c) 2011 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * bring in libcurl header, with compatibility fixes */ #ifndef INCLUDED_CURL #define INCLUDED_CURL +#if OS_WIN + // curl.h wants to include winsock2.h which causes conflicts. // provide some required definitions from winsock.h, then pretend // we already included winsock.h typedef uintptr_t SOCKET; struct sockaddr { unsigned short sa_family; char sa_data[14]; }; struct fd_set; #define _WINSOCKAPI_ // winsock.h include guard +#endif // OS_WIN + #include #endif // #ifndef INCLUDED_CURL Index: ps/trunk/binaries/data/mods/public/gui/pregame/styles.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/pregame/styles.xml (revision 8924) +++ ps/trunk/binaries/data/mods/public/gui/pregame/styles.xml (revision 8925) @@ -1,10 +1,21 @@