Index: ps/trunk/binaries/system/mozjs45-ps-debug-vc120.pdb =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/binaries/system/mozjs45-ps-debug-vc120.pdb =================================================================== --- ps/trunk/binaries/system/mozjs45-ps-debug-vc120.pdb (revision 23381) +++ ps/trunk/binaries/system/mozjs45-ps-debug-vc120.pdb (nonexistent) Property changes on: ps/trunk/binaries/system/mozjs45-ps-debug-vc120.pdb ___________________________________________________________________ Deleted: svn:mime-type ## -1 +0,0 ## -application/octet-stream \ No newline at end of property Index: ps/trunk/binaries/system/mozjs45-ps-debug-vc120.dll =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/binaries/system/mozjs45-ps-debug-vc120.dll =================================================================== --- ps/trunk/binaries/system/mozjs45-ps-debug-vc120.dll (revision 23381) +++ ps/trunk/binaries/system/mozjs45-ps-debug-vc120.dll (nonexistent) Property changes on: ps/trunk/binaries/system/mozjs45-ps-debug-vc120.dll ___________________________________________________________________ Deleted: svn:mime-type ## -1 +0,0 ## -application/octet-stream \ No newline at end of property Index: ps/trunk/binaries/system/mozjs45-ps-release-vc120.pdb =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/binaries/system/mozjs45-ps-release-vc120.pdb =================================================================== --- ps/trunk/binaries/system/mozjs45-ps-release-vc120.pdb (revision 23381) +++ ps/trunk/binaries/system/mozjs45-ps-release-vc120.pdb (nonexistent) Property changes on: ps/trunk/binaries/system/mozjs45-ps-release-vc120.pdb ___________________________________________________________________ Deleted: svn:mime-type ## -1 +0,0 ## -application/octet-stream \ No newline at end of property Index: ps/trunk/binaries/system/mozjs45-ps-release-vc120.dll =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/binaries/system/mozjs45-ps-release-vc120.dll =================================================================== --- ps/trunk/binaries/system/mozjs45-ps-release-vc120.dll (revision 23381) +++ ps/trunk/binaries/system/mozjs45-ps-release-vc120.dll (nonexistent) Property changes on: ps/trunk/binaries/system/mozjs45-ps-release-vc120.dll ___________________________________________________________________ Deleted: svn:mime-type ## -1 +0,0 ## -application/octet-stream \ No newline at end of property Index: ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-release-vc120.lib =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-release-vc120.lib =================================================================== --- ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-release-vc120.lib (revision 23381) +++ ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-release-vc120.lib (nonexistent) Property changes on: ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-release-vc120.lib ___________________________________________________________________ Deleted: svn:mime-type ## -1 +0,0 ## -application/octet-stream \ No newline at end of property Index: ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-debug-vc120.lib =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-debug-vc120.lib =================================================================== --- ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-debug-vc120.lib (revision 23381) +++ ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-debug-vc120.lib (nonexistent) Property changes on: ps/trunk/libraries/source/spidermonkey/lib/mozjs45-ps-debug-vc120.lib ___________________________________________________________________ Deleted: svn:mime-type ## -1 +0,0 ## -application/octet-stream \ No newline at end of property Index: ps/trunk/build/premake/extern_libs5.lua =================================================================== --- ps/trunk/build/premake/extern_libs5.lua (revision 23381) +++ ps/trunk/build/premake/extern_libs5.lua (revision 23382) @@ -1,705 +1,701 @@ -- this file provides project_add_extern_libs, which takes care of the -- dirty details of adding the libraries' include and lib paths. -- -- TYPICAL TASK: add new library. Instructions: -- 1) add a new extern_lib_defs entry -- 2) add library name to extern_libs tables in premake.lua for all 'projects' that want to use it -- directory in which OS-specific library subdirectories reside. if os.istarget("macosx") then libraries_dir = rootdir.."/libraries/osx/" elseif os.istarget("windows") then libraries_dir = rootdir.."/libraries/win32/" else -- No Unix-specific libs yet (use source directory instead!) end -- directory for shared, bundled libraries libraries_source_dir = rootdir.."/libraries/source/" third_party_source_dir = rootdir.."/source/third_party/" local function add_default_lib_paths(extern_lib) libdirs { libraries_dir .. extern_lib .. "/lib" } end local function add_source_lib_paths(extern_lib) libdirs { libraries_source_dir .. extern_lib .. "/lib" } end local function add_default_include_paths(extern_lib) sysincludedirs { libraries_dir .. extern_lib .. "/include" } end local function add_source_include_paths(extern_lib) sysincludedirs { libraries_source_dir .. extern_lib .. "/include" } end local function add_third_party_include_paths(extern_lib) sysincludedirs { third_party_source_dir .. extern_lib .. "/include" } end pkgconfig = require "pkgconfig" local function add_delayload(name, suffix, def) if def["no_delayload"] then return end -- currently only supported by VC; nothing to do on other platforms. if not os.istarget("windows") then return end -- no extra debug version; use same library in all configs if suffix == "" then linkoptions { "/DELAYLOAD:"..name..".dll" } -- extra debug version available; use in debug config else local dbg_cmd = "/DELAYLOAD:" .. name .. suffix .. ".dll" local cmd = "/DELAYLOAD:" .. name .. ".dll" filter "Debug" linkoptions { dbg_cmd } filter "Release" linkoptions { cmd } filter { } end end local function add_default_links(def) -- careful: make sure to only use *_names when on the correct platform. local names = {} if os.istarget("windows") then if def.win_names then names = def.win_names end elseif _OPTIONS["android"] and def.android_names then names = def.android_names elseif os.istarget("linux") and def.linux_names then names = def.linux_names elseif os.istarget("macosx") and (def.osx_names or def.osx_frameworks) then if def.osx_names then names = def.osx_names end -- OS X "Frameworks" are added to the links as "name.framework" if def.osx_frameworks then for i,name in pairs(def.osx_frameworks) do links { name .. ".framework" } end end elseif os.istarget("bsd") and def.bsd_names then names = def.bsd_names elseif def.unix_names then names = def.unix_names end local suffix = "d" -- library is overriding default suffix (typically "" to indicate there is none) if def["dbg_suffix"] then suffix = def["dbg_suffix"] end -- non-Windows doesn't have the distinction of debug vs. release libraries -- (to be more specific, they do, but the two are binary compatible; -- usually only one type - debug or release - is installed at a time). if not os.istarget("windows") then suffix = "" end for i,name in pairs(names) do filter "Debug" links { name .. suffix } filter "Release" links { name } filter { } add_delayload(name, suffix, def) end end -- Library definitions -- In a perfect world, libraries would have a common installation template, -- i.e. location of include directory, naming convention for .lib, etc. -- this table provides a means of working around each library's differences. -- -- The basic approach is defining two functions per library: -- -- 1. compile_settings -- This function should set all settings requred during the compile-phase like -- includedirs, defines etc... -- -- 2. link_settings -- This function should set all settings required during the link-phase like -- libdirs, linkflag etc... -- -- The main reason for separating those settings is different linking behaviour -- on osx and xcode. For more details, read the comment in project_add_extern_libs. -- -- There are some helper functions for the most common tasks. You should use them -- if they can be used in your situation to make the definitions more consistent and -- use their default beviours as a guideline. -- -- -- add_default_lib_paths(extern_lib) -- Description: Add '//lib'to the libpaths -- Parameters: -- * extern_lib: to be used in the libpath. -- -- add_default_include_paths(extern_lib) -- Description: Add '//include' to the includepaths -- Parameters: -- * extern_lib: to be used in the libpath. -- -- add_default_links -- Description: Adds links to libraries and configures delayloading. -- If the *_names parameter for a plattform is missing, no linking will be done -- on that plattform. -- The default assumptions are: -- * debug import library and DLL are distinguished with a "d" suffix -- * the library should be marked for delay-loading. -- Parameters: -- * win_names: table of import library / DLL names (no extension) when -- running on Windows. -- * unix_names: as above; shared object names when running on non-Windows. -- * osx_names, osx_frameworks: for OS X specifically; if any of those is -- specified, unix_names is ignored. Using both is possible if needed. -- * osx_names: as above. -- * osx_frameworks: as above, for system libraries that need to be linked -- as "name.framework". -- * bsd_names: as above; for BSD specifically (overrides unix_names if both are -- specified) -- * linux_names: ditto for Linux (overrides unix_names if both given) -- * dbg_suffix: changes the debug suffix from the above default. -- can be "" to indicate the library doesn't have a debug build; -- in that case, the same library (without suffix) is used in -- all build configurations. -- * no_delayload: indicate the library is not to be delay-loaded. -- this is necessary for some libraries that do not support it, -- e.g. Xerces (which is so stupid as to export variables). extern_lib_defs = { boost = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("boost") elseif os.istarget("macosx") then -- Suppress all the Boost warnings on OS X by including it as a system directory buildoptions { "-isystem../" .. libraries_dir .. "boost/include" } end -- TODO: This actually applies to most libraries we use on BSDs, make this a global setting. if os.istarget("bsd") then sysincludedirs { "/usr/local/include" } end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("boost") end add_default_links({ -- The following are not strictly link dependencies on all systems, but -- are included for compatibility with different versions of Boost android_names = { "boost_filesystem-gcc-mt", "boost_system-gcc-mt" }, unix_names = { os.findlib("boost_filesystem-mt") and "boost_filesystem-mt" or "boost_filesystem", os.findlib("boost_system-mt") and "boost_system-mt" or "boost_system" }, osx_names = { "boost_filesystem-mt", "boost_system-mt" }, }) end, }, comsuppw = { link_settings = function() add_default_links({ win_names = { "comsuppw" }, dbg_suffix = "d", no_delayload = 1, }) end, }, cxxtest = { compile_settings = function() sysincludedirs { libraries_source_dir .. "cxxtest-4.4" } end, }, enet = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("enet") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("enet") end add_default_links({ win_names = { "enet" }, unix_names = { "enet" }, }) end, }, fcollada = { compile_settings = function() add_source_include_paths("fcollada") end, link_settings = function() add_source_lib_paths("fcollada") if os.istarget("windows") then filter "Debug" links { "FColladaD" } filter "Release" links { "FCollada" } filter { } else filter "Debug" links { "FColladaSD" } filter "Release" links { "FColladaSR" } filter { } end end, }, gloox = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("gloox") else -- Support GLOOX_CONFIG for overriding the default (pkg-config --cflags gloox) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("gloox", os.getenv("GLOOX_CONFIG")) end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("gloox") add_default_links({ win_names = { "gloox-1.0" }, no_delayload = 1, }) else pkgconfig.add_links("gloox", os.getenv("GLOOX_CONFIG")) if os.istarget("macosx") then -- Manually add gnutls dependencies, those are not present in gloox's pkg-config add_default_lib_paths("nettle") add_default_lib_paths("gmp") add_default_links({ osx_names = { "nettle", "hogweed", "gmp" }, }) end end end, }, iconv = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("iconv") defines { "HAVE_ICONV_CONST" } defines { "ICONV_CONST=const" } defines { "LIBICONV_STATIC" } elseif os.istarget("macosx") then add_default_include_paths("iconv") defines { "LIBICONV_STATIC" } elseif os.getversion().description == "FreeBSD" then defines { "HAVE_ICONV_CONST" } defines { "ICONV_CONST=const" } end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("iconv") end add_default_links({ win_names = { "libiconv" }, osx_names = { "iconv" }, dbg_suffix = "", no_delayload = 1, }) -- glibc (used on Linux and GNU/kFreeBSD) has iconv -- FreeBSD 10+ has iconv as a part of libc if os.istarget("bsd") and not (os.getversion().description == "FreeBSD" and os.getversion().majorversion >= 10 or os.getversion().description == "GNU/kFreeBSD") then add_default_links({ bsd_names = { "iconv" }, }) end end, }, icu = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("icu") else -- Support ICU_CONFIG for overriding the default (pkg-config --cflags icu-i18n) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("icu-i18n", os.getenv("ICU_CONFIG"), "--cppflags") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("icu") add_default_links({ win_names = { "icuuc", "icuin" }, dbg_suffix = "", no_delayload = 1, }) else pkgconfig.add_links("icu-i18n", os.getenv("ICU_CONFIG"), "--ldflags-searchpath --ldflags-libsonly --ldflags-system") end end, }, libcurl = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("libcurl") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("libcurl") end add_default_links({ win_names = { "libcurl" }, unix_names = { "curl" }, osx_names = { "curl", "z" }, osx_frameworks = { "Security" } }) end, }, libpng = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("libpng") end if os.getversion().description == "OpenBSD" then sysincludedirs { "/usr/local/include/libpng" } end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("libpng") end add_default_links({ win_names = { "libpng16" }, unix_names = { "png" }, -- Otherwise ld will sometimes pull in ancient 1.2 from the SDK, which breaks the build :/ -- TODO: Figure out why that happens osx_names = { "png16" }, }) end, }, libsodium = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("libsodium") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("libsodium") end add_default_links({ win_names = { "libsodium" }, unix_names = { "sodium" }, }) end, }, libxml2 = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("libxml2") else -- Support XML2_CONFIG for overriding the default (pkg-config --cflags libxml-2.0) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("libxml-2.0", os.getenv("XML2_CONFIG")) end if os.istarget("macosx") then -- libxml2 needs _REENTRANT or __MT__ for thread support; -- OS X doesn't get either set by default, so do it manually defines { "_REENTRANT" } end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("libxml2") filter "Debug" links { "libxml2" } filter "Release" links { "libxml2" } filter { } else pkgconfig.add_links("libxml-2.0", os.getenv("XML2_CONFIG")) end end, }, miniupnpc = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("miniupnpc") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("miniupnpc") end add_default_links({ win_names = { "miniupnpc" }, unix_names = { "miniupnpc" }, }) end, }, nvtt = { compile_settings = function() if not _OPTIONS["with-system-nvtt"] then add_source_include_paths("nvtt") end defines { "NVTT_SHARED=1" } end, link_settings = function() if not _OPTIONS["with-system-nvtt"] then add_source_lib_paths("nvtt") end add_default_links({ win_names = { "nvtt" }, unix_names = { "nvcore", "nvmath", "nvimage", "nvtt" }, osx_names = { "bc6h", "bc7", "nvcore", "nvimage", "nvmath", "nvthread", "nvtt", "squish" }, dbg_suffix = "", -- for performance we always use the release-mode version }) end, }, openal = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("openal") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("openal") end add_default_links({ win_names = { "openal32" }, unix_names = { "openal" }, osx_frameworks = { "OpenAL" }, dbg_suffix = "", no_delayload = 1, -- delayload seems to cause errors on startup }) end, }, opengl = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("opengl") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("opengl") end if _OPTIONS["gles"] then add_default_links({ unix_names = { "GLESv2" }, dbg_suffix = "", }) else add_default_links({ win_names = { "opengl32", "gdi32" }, unix_names = { "GL" }, osx_frameworks = { "OpenGL" }, dbg_suffix = "", no_delayload = 1, -- delayload seems to cause errors on startup }) end end, }, sdl = { compile_settings = function() if os.istarget("windows") then includedirs { libraries_dir .. "sdl2/include/SDL" } elseif not _OPTIONS["android"] then -- Support SDL2_CONFIG for overriding the default (pkg-config sdl2) -- i.e. on OSX where it gets set in update-workspaces.sh pkgconfig.add_includes("sdl2", os.getenv("SDL2_CONFIG")) end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("sdl2") elseif not _OPTIONS["android"] then pkgconfig.add_links("sdl2", os.getenv("SDL2_CONFIG")) end end, }, spidermonkey = { compile_settings = function() if _OPTIONS["with-system-mozjs45"] then if not _OPTIONS["android"] then pkgconfig.add_includes("mozjs-45") end else if os.istarget("windows") then include_dir = "include-win32" buildoptions { "/FI\"js/RequiredDefines.h\"" } else include_dir = "include-unix" end filter "Debug" sysincludedirs { libraries_source_dir.."spidermonkey/"..include_dir.."-debug" } defines { "DEBUG" } filter "Release" sysincludedirs { libraries_source_dir.."spidermonkey/"..include_dir.."-release" } filter { } end end, link_settings = function() if _OPTIONS["with-system-mozjs45"] then if _OPTIONS["android"] then links { "mozjs-45" } else pkgconfig.add_links("mozjs-45") end else - filter { "Debug", "action:vs2013" } - links { "mozjs45-ps-debug-vc120" } - filter { "Release", "action:vs2013" } - links { "mozjs45-ps-release-vc120" } filter { "Debug", "action:vs2015" } links { "mozjs45-ps-debug-vc140" } filter { "Release", "action:vs2015" } links { "mozjs45-ps-release-vc140" } filter { "Debug", "action:not vs*" } links { "mozjs45-ps-debug" } filter { "Release", "action:not vs*" } links { "mozjs45-ps-release" } filter { } add_source_lib_paths("spidermonkey") end end, }, tinygettext = { compile_settings = function() add_third_party_include_paths("tinygettext") end, }, valgrind = { compile_settings = function() add_source_include_paths("valgrind") end, }, vorbis = { compile_settings = function() if os.istarget("windows") then add_default_include_paths("vorbis") elseif os.istarget("macosx") then add_default_include_paths("libogg") add_default_include_paths("vorbis") end end, link_settings = function() if os.istarget("windows") then add_default_lib_paths("vorbis") elseif os.istarget("macosx") then add_default_lib_paths("libogg") add_default_lib_paths("vorbis") end -- TODO: We need to force linking with these as currently -- they need to be loaded explicitly on execution if os.getversion().description == "OpenBSD" then add_default_links({ unix_names = { "ogg", "vorbis" }, }) end add_default_links({ win_names = { "libvorbisfile" }, unix_names = { "vorbisfile" }, osx_names = { "vorbis", "vorbisenc", "vorbisfile", "ogg" }, }) end, }, wxwidgets = { compile_settings = function() if os.istarget("windows") then includedirs { libraries_dir.."wxwidgets/include/msvc" } add_default_include_paths("wxwidgets") else -- wxwidgets does not come with a definition file for pkg-config, -- so we have to use wxwidgets' own config tool wx_config_path = os.getenv("WX_CONFIG") or "wx-config" pkgconfig.add_includes(nil, wx_config_path, "--unicode=yes --cxxflags") end end, link_settings = function() if os.istarget("windows") then libdirs { libraries_dir.."wxwidgets/lib/vc_lib" } else wx_config_path = os.getenv("WX_CONFIG") or "wx-config" pkgconfig.add_links(nil, wx_config_path, "--unicode=yes --libs std,gl") end end, }, x11 = { compile_settings = function() if not os.istarget("windows") and not os.istarget("macosx") then pkgconfig.add_includes("x11") end end, link_settings = function() if not os.istarget("windows") and not os.istarget("macosx") then pkgconfig.add_links("x11") end end, }, xcursor = { link_settings = function() add_default_links({ unix_names = { "Xcursor" }, }) end, }, zlib = { compile_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_include_paths("zlib") end end, link_settings = function() if os.istarget("windows") or os.istarget("macosx") then add_default_lib_paths("zlib") end add_default_links({ win_names = { "zlib1" }, unix_names = { "z" }, no_delayload = 1, }) end, }, } -- add a set of external libraries to the project; takes care of -- include / lib path and linking against the import library. -- extern_libs: table of library names [string] -- target_type: String defining the projects kind [string] function project_add_extern_libs(extern_libs, target_type) for i,extern_lib in pairs(extern_libs) do local def = extern_lib_defs[extern_lib] assert(def, "external library " .. extern_lib .. " not defined") if def.compile_settings then def.compile_settings() end -- Linking to external libraries will only be done in the main executable and not in the -- static libraries. Premake would silently skip linking into static libraries for some -- actions anyway (e.g. vs2010). -- On osx using xcode, if this linking would be defined in the static libraries, it would fail to -- link if only dylibs are available. If both *.a and *.dylib are available, it would link statically. -- I couldn't find any problems with that approach. if target_type ~= "StaticLib" and def.link_settings then def.link_settings() end end end Index: ps/trunk/build/premake/premake5.lua =================================================================== --- ps/trunk/build/premake/premake5.lua (revision 23381) +++ ps/trunk/build/premake/premake5.lua (revision 23382) @@ -1,1448 +1,1446 @@ newoption { trigger = "android", description = "Use non-working Android cross-compiling mode" } newoption { trigger = "atlas", description = "Include Atlas scenario editor projects" } newoption { trigger = "coverage", description = "Enable code coverage data collection (GCC only)" } newoption { trigger = "gles", description = "Use non-working OpenGL ES 2.0 mode" } newoption { trigger = "icc", description = "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)" } newoption { trigger = "jenkins-tests", description = "Configure CxxTest to use the XmlPrinter runner which produces Jenkins-compatible output" } newoption { trigger = "minimal-flags", description = "Only set compiler/linker flags that are really needed. Has no effect on Windows builds" } newoption { trigger = "outpath", description = "Location for generated project files" } newoption { trigger = "with-system-mozjs45", description = "Search standard paths for libmozjs45, instead of using bundled copy" } newoption { trigger = "with-system-nvtt", description = "Search standard paths for nvidia-texture-tools library, instead of using bundled copy" } newoption { trigger = "without-audio", description = "Disable use of OpenAL/Ogg/Vorbis APIs" } newoption { trigger = "without-lobby", description = "Disable the use of gloox and the multiplayer lobby" } newoption { trigger = "without-miniupnpc", description = "Disable use of miniupnpc for port forwarding" } newoption { trigger = "without-nvtt", description = "Disable use of NVTT" } newoption { trigger = "without-pch", description = "Disable generation and usage of precompiled headers" } newoption { trigger = "without-tests", description = "Disable generation of test projects" } -- Linux/BSD specific options newoption { trigger = "prefer-local-libs", description = "Prefer locally built libs. Any local libraries used must also be listed within a file within /etc/ld.so.conf.d so the dynamic linker can find them at runtime." } -- OS X specific options newoption { trigger = "macosx-bundle", description = "Enable OSX bundle, the argument is the bundle identifier string (e.g. com.wildfiregames.0ad)" } newoption { trigger = "macosx-version-min", description = "Set minimum required version of the OS X API, the build will possibly fail if an older SDK is used, while newer API functions will be weakly linked (i.e. resolved at runtime)" } newoption { trigger = "sysroot", description = "Set compiler system root path, used for building against a non-system SDK. For example /usr/local becomes SYSROOT/user/local" } -- Windows specific options newoption { trigger = "build-shared-glooxwrapper", description = "Rebuild glooxwrapper DLL for Windows. Requires the same compiler version that gloox was built with" } newoption { trigger = "use-shared-glooxwrapper", description = "Use prebuilt glooxwrapper DLL for Windows" } newoption { trigger = "large-address-aware", description = "Make the executable large address aware. Do not use for development, in order to spot memory issues easily" } -- Install options newoption { trigger = "bindir", description = "Directory for executables (typically '/usr/games'); default is to be relocatable" } newoption { trigger = "datadir", description = "Directory for data files (typically '/usr/share/games/0ad'); default is ../data/ relative to executable" } newoption { trigger = "libdir", description = "Directory for libraries (typically '/usr/lib/games/0ad'); default is ./ relative to executable" } -- Root directory of project checkout relative to this .lua file rootdir = "../.." dofile("extern_libs5.lua") -- detect compiler for non-Windows if os.istarget("macosx") then cc = "clang" elseif os.istarget("linux") and _OPTIONS["icc"] then cc = "icc" elseif not os.istarget("windows") then cc = os.getenv("CC") if cc == nil or cc == "" then local hasgcc = os.execute("which gcc > .gccpath") local f = io.open(".gccpath", "r") local gccpath = f:read("*line") f:close() os.execute("rm .gccpath") if gccpath == nil then cc = "clang" else cc = "gcc" end end end -- detect CPU architecture (simplistic, currently only supports x86, amd64 and ARM) arch = "x86" if _OPTIONS["android"] then arch = "arm" elseif os.istarget("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" or arch == "amd64" then arch = "amd64" else os.execute(cc .. " -dumpmachine > .gccmachine.tmp") local f = io.open(".gccmachine.tmp", "r") local machine = f:read("*line") f:close() if string.find(machine, "x86_64") == 1 or string.find(machine, "amd64") == 1 then arch = "amd64" elseif string.find(machine, "i.86") == 1 then arch = "x86" elseif string.find(machine, "arm") == 1 then arch = "arm" elseif string.find(machine, "aarch64") == 1 then arch = "aarch64" else print("WARNING: Cannot determine architecture from GCC, assuming x86") end end end -- Set up the Workspace workspace "pyrogenesis" targetdir(rootdir.."/binaries/system") libdirs(rootdir.."/binaries/system") if not _OPTIONS["outpath"] then error("You must specify the 'outpath' parameter") end location(_OPTIONS["outpath"]) configurations { "Release", "Debug" } source_root = rootdir.."/source/" -- default for most projects - overridden by local in others -- Rationale: projects should not have any additional include paths except for -- those required by external libraries. Instead, we should always write the -- full relative path, e.g. #include "maths/Vector3d.h". This avoids confusion -- ("which file is meant?") and avoids enormous include path lists. -- projects: engine static libs, main exe, atlas, atlas frontends, test. -------------------------------------------------------------------------------- -- project helper functions -------------------------------------------------------------------------------- function project_set_target(project_name) -- Note: On Windows, ".exe" is added on the end, on unices the name is used directly local obj_dir_prefix = _OPTIONS["outpath"].."/obj/"..project_name.."_" filter "Debug" objdir(obj_dir_prefix.."Debug") targetsuffix("_dbg") filter "Release" objdir(obj_dir_prefix.."Release") filter { } end function project_set_build_flags() editandcontinue "Off" if not _OPTIONS["minimal-flags"] then symbols "On" end if cc ~= "icc" and (os.istarget("windows") or not _OPTIONS["minimal-flags"]) then -- adds the -Wall compiler flag warnings "Extra" -- this causes far too many warnings/remarks on ICC end -- disable Windows debug heap, since it makes malloc/free hugely slower when -- running inside a debugger if os.istarget("windows") then debugenvs { "_NO_DEBUG_HEAP=1" } end filter "Debug" defines { "DEBUG" } filter "Release" if os.istarget("windows") or not _OPTIONS["minimal-flags"] then optimize "Speed" end defines { "NDEBUG", "CONFIG_FINAL=1" } filter { } if _OPTIONS["gles"] then defines { "CONFIG2_GLES=1" } end if _OPTIONS["without-audio"] then defines { "CONFIG2_AUDIO=0" } end if _OPTIONS["without-nvtt"] then defines { "CONFIG2_NVTT=0" } end if _OPTIONS["without-lobby"] then defines { "CONFIG2_LOBBY=0" } end if _OPTIONS["without-miniupnpc"] then defines { "CONFIG2_MINIUPNPC=0" } end -- required for the lowlevel library. must be set from all projects that use it, otherwise it assumes it is -- being used as a DLL (which is currently not the case in 0ad) defines { "LIB_STATIC_LINK" } -- various platform-specific build flags if os.istarget("windows") then flags { "MultiProcessorCompile" } -- use native wchar_t type (not typedef to unsigned short) nativewchar "on" else -- *nix -- TODO, FIXME: This check is incorrect because it means that some additional flags will be added inside the "else" branch if the -- compiler is ICC and minimal-flags is specified (ticket: #2994) if cc == "icc" and not _OPTIONS["minimal-flags"] then buildoptions { "-w1", -- "-Wabi", -- "-Wp64", -- complains about OBJECT_TO_JSVAL which is annoying "-Wpointer-arith", "-Wreturn-type", -- "-Wshadow", "-Wuninitialized", "-Wunknown-pragmas", "-Wunused-function", "-wd1292" -- avoid lots of 'attribute "__nonnull__" ignored' } filter "Debug" buildoptions { "-O0" } -- ICC defaults to -O2 filter { } if os.istarget("macosx") then linkoptions { "-multiply_defined","suppress" } end else -- exclude most non-essential build options for minimal-flags if not _OPTIONS["minimal-flags"] then buildoptions { -- enable most of the standard warnings "-Wno-switch", -- enumeration value not handled in switch (this is sometimes useful, but results in lots of noise) "-Wno-reorder", -- order of initialization list in constructors (lots of noise) "-Wno-invalid-offsetof", -- offsetof on non-POD types (see comment in renderer/PatchRData.cpp) "-Wextra", "-Wno-missing-field-initializers", -- (this is common in external headers we can't fix) -- add some other useful warnings that need to be enabled explicitly "-Wunused-parameter", "-Wredundant-decls", -- (useful for finding some multiply-included header files) -- "-Wformat=2", -- (useful sometimes, but a bit noisy, so skip it by default) -- "-Wcast-qual", -- (useful for checking const-correctness, but a bit noisy, so skip it by default) "-Wnon-virtual-dtor", -- (sometimes noisy but finds real bugs) "-Wundef", -- (useful for finding macro name typos) -- enable security features (stack checking etc) that shouldn't have -- a significant effect on performance and can catch bugs "-fstack-protector-all", "-U_FORTIFY_SOURCE", -- (avoid redefinition warning if already defined) "-D_FORTIFY_SOURCE=2", -- always enable strict aliasing (useful in debug builds because of the warnings) "-fstrict-aliasing", -- don't omit frame pointers (for now), because performance will be impacted -- negatively by the way this breaks profilers more than it will be impacted -- positively by the optimisation "-fno-omit-frame-pointer" } if not _OPTIONS["without-pch"] then buildoptions { -- do something (?) so that ccache can handle compilation with PCH enabled -- (ccache 3.1+ also requires CCACHE_SLOPPINESS=time_macros for this to work) "-fpch-preprocess" } end if os.istarget("linux") or os.istarget("bsd") then buildoptions { "-fPIC" } linkoptions { "-Wl,--no-undefined", "-Wl,--as-needed", "-Wl,-z,relro" } end if arch == "x86" then buildoptions { -- To support intrinsics like __sync_bool_compare_and_swap on x86 -- we need to set -march to something that supports them (i686). -- We use pentium3 to also enable other features like mmx and sse, -- while tuning for generic to have good performance on every -- supported CPU. -- Note that all these features are already supported on amd64. "-march=pentium3 -mtune=generic" } end end buildoptions { -- Enable C++11 standard. "-std=c++0x" } if arch == "arm" then -- disable warnings about va_list ABI change and use -- compile-time flags for futher configuration. buildoptions { "-Wno-psabi" } if _OPTIONS["android"] then -- Android uses softfp, so we should too. buildoptions { "-mfloat-abi=softfp" } end end if _OPTIONS["coverage"] then buildoptions { "-fprofile-arcs", "-ftest-coverage" } links { "gcov" } end -- 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.istarget("macosx") then buildoptions { "-msse2" } end -- Check if SDK path should be used if _OPTIONS["sysroot"] then buildoptions { "-isysroot " .. _OPTIONS["sysroot"] } linkoptions { "-Wl,-syslibroot," .. _OPTIONS["sysroot"] } end -- On OS X, sometimes we need to specify the minimum API version to use if _OPTIONS["macosx-version-min"] then buildoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } -- clang and llvm-gcc look at mmacosx-version-min to determine link target -- and CRT version, and use it to set the macosx_version_min linker flag linkoptions { "-mmacosx-version-min=" .. _OPTIONS["macosx-version-min"] } end -- Check if we're building a bundle if _OPTIONS["macosx-bundle"] then defines { "BUNDLE_IDENTIFIER=" .. _OPTIONS["macosx-bundle"] } end -- On OS X, force using libc++ since it has better C++11 support, -- now required by the game if os.istarget("macosx") then buildoptions { "-stdlib=libc++" } linkoptions { "-stdlib=libc++" } end end buildoptions { -- Hide symbols in dynamic shared objects by default, for efficiency and for equivalence with -- Windows - they should be exported explicitly with __attribute__ ((visibility ("default"))) "-fvisibility=hidden" } if _OPTIONS["bindir"] then defines { "INSTALLED_BINDIR=" .. _OPTIONS["bindir"] } end if _OPTIONS["datadir"] then defines { "INSTALLED_DATADIR=" .. _OPTIONS["datadir"] } end if _OPTIONS["libdir"] then defines { "INSTALLED_LIBDIR=" .. _OPTIONS["libdir"] } end if os.istarget("linux") or os.istarget("bsd") then if _OPTIONS["prefer-local-libs"] then libdirs { "/usr/local/lib" } end -- To use our local shared libraries, they need to be found in the -- runtime dynamic linker path. Add their path to -rpath. if _OPTIONS["libdir"] then linkoptions {"-Wl,-rpath," .. _OPTIONS["libdir"] } else -- On FreeBSD we need to allow use of $ORIGIN if os.istarget("bsd") then linkoptions { "-Wl,-z,origin" } end -- Adding the executable path and taking care of correct escaping if _ACTION == "gmake" then linkoptions { "-Wl,-rpath,'$$ORIGIN'" } elseif _ACTION == "codeblocks" then linkoptions { "-Wl,-R\\\\$$$ORIGIN" } end end end end end -- create a project and set the attributes that are common to all projects. function project_create(project_name, target_type) project(project_name) language "C++" kind(target_type) - filter "action:vs2013" - toolset "v120_xp" filter "action:vs2015" toolset "v140_xp" filter {} project_set_target(project_name) project_set_build_flags() end -- OSX creates a .app bundle if the project type of the main application is set to "WindowedApp". -- We don't want this because this bundle would be broken (it lacks all the resources and external dependencies, Info.plist etc...) -- Windows opens a console in the background if it's set to ConsoleApp, which is not what we want. -- I didn't check if this setting matters for linux, but WindowedApp works there. function get_main_project_target_type() if _OPTIONS["android"] then return "SharedLib" elseif os.istarget("macosx") then return "ConsoleApp" else return "WindowedApp" end end -- source_root: rel_source_dirs and rel_include_dirs are relative to this directory -- rel_source_dirs: A table of subdirectories. All source files in these directories are added. -- rel_include_dirs: A table of subdirectories to be included. -- extra_params: table including zero or more of the following: -- * no_pch: If specified, no precompiled headers are used for this project. -- * pch_dir: If specified, this directory will be used for precompiled headers instead of the default -- /pch//. -- * extra_files: table of filenames (relative to source_root) to add to project -- * extra_links: table of library names to add to link step function project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) for i,v in pairs(rel_source_dirs) do local prefix = source_root..v.."/" files { prefix.."*.cpp", prefix.."*.h", prefix.."*.inl", prefix.."*.js", prefix.."*.asm", prefix.."*.mm" } end -- Put the project-specific PCH directory at the start of the -- include path, so '#include "precompiled.h"' will look in -- there first local pch_dir if not extra_params["pch_dir"] then pch_dir = source_root .. "pch/" .. project().name .. "/" else pch_dir = extra_params["pch_dir"] end includedirs { pch_dir } -- Precompiled Headers -- rationale: we need one PCH per static lib, since one global header would -- increase dependencies. To that end, we can either include them as -- "projectdir/precompiled.h", or add "source/PCH/projectdir" to the -- include path and put the PCH there. The latter is better because -- many projects contain several dirs and it's unclear where there the -- PCH should be stored. This way is also a bit easier to use in that -- source files always include "precompiled.h". -- Notes: -- * Visual Assist manages to use the project include path and can -- correctly open these files from the IDE. -- * precompiled.cpp (needed to "Create" the PCH) also goes in -- the abovementioned dir. if (not _OPTIONS["without-pch"] and not extra_params["no_pch"]) then filter "action:vs*" pchheader("precompiled.h") filter "action:xcode*" pchheader("../"..pch_dir.."precompiled.h") filter { "action:not vs*", "action:not xcode*" } pchheader(pch_dir.."precompiled.h") filter {} pchsource(pch_dir.."precompiled.cpp") defines { "CONFIG_ENABLE_PCH=1" } files { pch_dir.."precompiled.h", pch_dir.."precompiled.cpp" } else defines { "CONFIG_ENABLE_PCH=0" } flags { "NoPCH" } end -- next is source root dir, for absolute (nonrelative) includes -- (e.g. "lib/precompiled.h") includedirs { source_root } for i,v in pairs(rel_include_dirs) do includedirs { source_root .. v } end if extra_params["extra_files"] then for i,v in pairs(extra_params["extra_files"]) do -- .rc files are only needed on Windows if path.getextension(v) ~= ".rc" or os.istarget("windows") then files { source_root .. v } end end end if extra_params["extra_links"] then links { extra_params["extra_links"] } end end -- Add command-line options to set up the manifest dependencies for Windows -- (See lib/sysdep/os/win/manifest.cpp) function project_add_manifest() linkoptions { "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df'\"" } end -------------------------------------------------------------------------------- -- engine static libraries -------------------------------------------------------------------------------- -- the engine is split up into several static libraries. this eases separate -- distribution of those components, reduces dependencies a bit, and can -- also speed up builds. -- more to the point, it is necessary to efficiently support a separate -- test executable that also includes much of the game code. -- names of all static libs created. automatically added to the -- main app project later (see explanation at end of this file) static_lib_names = {} static_lib_names_debug = {} static_lib_names_release = {} -- set up one of the static libraries into which the main engine code is split. -- extra_params: -- no_default_link: If specified, linking won't be done by default. -- For the rest of extra_params, see project_add_contents(). -- note: rel_source_dirs and rel_include_dirs are relative to global source_root. function setup_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "StaticLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end if os.istarget("windows") then rtti "off" elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end function setup_third_party_static_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) setup_static_lib_project(project_name, rel_source_dirs, extern_libs, extra_params) includedirs { source_root .. "third_party/" .. project_name .. "/include/" } end function setup_shared_lib_project (project_name, rel_source_dirs, extern_libs, extra_params) local target_type = "SharedLib" project_create(project_name, target_type) project_add_contents(source_root, rel_source_dirs, {}, extra_params) project_add_extern_libs(extern_libs, target_type) if not extra_params["no_default_link"] then table.insert(static_lib_names, project_name) end if os.istarget("windows") then rtti "off" links { "delayimp" } elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end -- this is where the source tree is chopped up into static libs. -- can be changed very easily; just copy+paste a new setup_static_lib_project, -- or remove existing ones. static libs are automagically added to -- main_exe link step. function setup_all_libs () -- relative to global source_root. local source_dirs = {} -- names of external libraries used (see libraries_dir comment) local extern_libs = {} source_dirs = { "network", } extern_libs = { "spidermonkey", "enet", "boost", -- dragged in via server->simulation.h->random } if not _OPTIONS["without-miniupnpc"] then table.insert(extern_libs, "miniupnpc") end setup_static_lib_project("network", source_dirs, extern_libs, {}) source_dirs = { "third_party/tinygettext/src", } extern_libs = { "iconv", "boost", } setup_third_party_static_lib_project("tinygettext", source_dirs, extern_libs, { } ) -- it's an external library and we don't want to modify its source to fix warnings, so we just disable them to avoid noise in the compile output filter "action:vs*" buildoptions { "/wd4127", "/wd4309", "/wd4800", "/wd4100", "/wd4996", "/wd4099", "/wd4503" } filter {} if not _OPTIONS["without-lobby"] then source_dirs = { "lobby", "lobby/scripting", "i18n", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "enet", "gloox", "icu", "iconv", "libsodium", "tinygettext" } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) if _OPTIONS["use-shared-glooxwrapper"] and not _OPTIONS["build-shared-glooxwrapper"] then table.insert(static_lib_names_debug, "glooxwrapper_dbg") table.insert(static_lib_names_release, "glooxwrapper") else source_dirs = { "lobby/glooxwrapper", } extern_libs = { "boost", "gloox", } if _OPTIONS["build-shared-glooxwrapper"] then setup_shared_lib_project("glooxwrapper", source_dirs, extern_libs, {}) else setup_static_lib_project("glooxwrapper", source_dirs, extern_libs, {}) end end else source_dirs = { "lobby/scripting", "third_party/encryption" } extern_libs = { "spidermonkey", "boost", "libsodium" } setup_static_lib_project("lobby", source_dirs, extern_libs, {}) files { source_root.."lobby/Globals.cpp" } end source_dirs = { "simulation2", "simulation2/components", "simulation2/helpers", "simulation2/scripting", "simulation2/serialization", "simulation2/system", "simulation2/testcomponents", } extern_libs = { "boost", "opengl", "spidermonkey", } setup_static_lib_project("simulation2", source_dirs, extern_libs, {}) source_dirs = { "scriptinterface", "scriptinterface/third_party" } extern_libs = { "boost", "spidermonkey", "valgrind", "sdl", } setup_static_lib_project("scriptinterface", source_dirs, extern_libs, {}) source_dirs = { "ps", "ps/scripting", "network/scripting", "ps/GameSetup", "ps/XML", "soundmanager", "soundmanager/data", "soundmanager/items", "soundmanager/scripting", "maths", "maths/scripting", "i18n", "i18n/scripting", "third_party/cppformat", } extern_libs = { "spidermonkey", "sdl", -- key definitions "libxml2", "opengl", "zlib", "boost", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", } if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") table.insert(extern_libs, "vorbis") end setup_static_lib_project("engine", source_dirs, extern_libs, {}) source_dirs = { "graphics", "graphics/scripting", "renderer", "renderer/scripting", "third_party/mikktspace", "third_party/ogre3d_preprocessor" } extern_libs = { "opengl", "sdl", -- key definitions "spidermonkey", -- for graphics/scripting "boost" } if not _OPTIONS["without-nvtt"] then table.insert(extern_libs, "nvtt") end setup_static_lib_project("graphics", source_dirs, extern_libs, {}) source_dirs = { "tools/atlas/GameInterface", "tools/atlas/GameInterface/Handlers" } extern_libs = { "boost", "sdl", -- key definitions "opengl", "spidermonkey" } setup_static_lib_project("atlas", source_dirs, extern_libs, {}) source_dirs = { "gui", "gui/ObjectTypes", "gui/ObjectBases", "gui/Scripting", "gui/SettingTypes", "i18n" } extern_libs = { "spidermonkey", "sdl", -- key definitions "opengl", "boost", "enet", "tinygettext", "icu", "iconv", } if not _OPTIONS["without-audio"] then table.insert(extern_libs, "openal") end setup_static_lib_project("gui", source_dirs, extern_libs, {}) source_dirs = { "lib", "lib/adts", "lib/allocators", "lib/external_libraries", "lib/file", "lib/file/archive", "lib/file/common", "lib/file/io", "lib/file/vfs", "lib/pch", "lib/posix", "lib/res", "lib/res/graphics", "lib/sysdep", "lib/tex" } extern_libs = { "boost", "sdl", "openal", "opengl", "libpng", "zlib", "valgrind", "cxxtest", } -- CPU architecture-specific if arch == "amd64" then table.insert(source_dirs, "lib/sysdep/arch/amd64"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "x86" then table.insert(source_dirs, "lib/sysdep/arch/ia32"); table.insert(source_dirs, "lib/sysdep/arch/x86_x64"); elseif arch == "arm" then table.insert(source_dirs, "lib/sysdep/arch/arm"); elseif arch == "aarch64" then table.insert(source_dirs, "lib/sysdep/arch/aarch64"); end -- OS-specific sysdep_dirs = { linux = { "lib/sysdep/os/linux", "lib/sysdep/os/unix" }, -- note: RC file must be added to main_exe project. -- note: don't add "lib/sysdep/os/win/aken.cpp" because that must be compiled with the DDK. windows = { "lib/sysdep/os/win", "lib/sysdep/os/win/wposix", "lib/sysdep/os/win/whrt" }, macosx = { "lib/sysdep/os/osx", "lib/sysdep/os/unix" }, bsd = { "lib/sysdep/os/bsd", "lib/sysdep/os/unix", "lib/sysdep/os/unix/x" }, } for i,v in pairs(sysdep_dirs[os.target()]) do table.insert(source_dirs, v); end if os.istarget("linux") then if _OPTIONS["android"] then table.insert(source_dirs, "lib/sysdep/os/android") else table.insert(source_dirs, "lib/sysdep/os/unix/x") end end -- On OSX, disable precompiled headers because C++ files and Objective-C++ files are -- mixed in this project. To fix that, we would need per-file basis configuration which -- is not yet supported by the gmake action in premake. We should look into using gmake2. extra_params = {} if os.istarget("macosx") then extra_params = { no_pch = 1 } end -- runtime-library-specific - if _ACTION == "vs2013" or _ACTION == "vs2015" then + if _ACTION == "vs2015" then table.insert(source_dirs, "lib/sysdep/rtl/msc"); else table.insert(source_dirs, "lib/sysdep/rtl/gcc"); end setup_static_lib_project("lowlevel", source_dirs, extern_libs, extra_params) -- Third-party libraries that are built as part of the main project, -- not built externally and then linked source_dirs = { "third_party/mongoose", } extern_libs = { } setup_static_lib_project("mongoose", source_dirs, extern_libs, { no_pch = 1 }) -- CxxTest mock function support extern_libs = { "boost", "cxxtest", } -- 'real' implementations, to be linked against the main executable -- (files are added manually and not with setup_static_lib_project -- because not all files in the directory are included) setup_static_lib_project("mocks_real", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { "mocks/*.h", source_root.."mocks/*_real.cpp" } -- 'test' implementations, to be linked against the test executable setup_static_lib_project("mocks_test", {}, extern_libs, { no_default_link = 1, no_pch = 1 }) files { source_root.."mocks/*.h", source_root.."mocks/*_test.cpp" } end -------------------------------------------------------------------------------- -- main EXE -------------------------------------------------------------------------------- -- used for main EXE as well as test used_extern_libs = { "opengl", "sdl", "libpng", "zlib", "spidermonkey", "libxml2", "boost", "cxxtest", "comsuppw", "enet", "libcurl", "tinygettext", "icu", "iconv", "libsodium", "valgrind", } if not os.istarget("windows") and not _OPTIONS["android"] and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(used_extern_libs, "x11") table.insert(used_extern_libs, "xcursor") end if not _OPTIONS["without-audio"] then table.insert(used_extern_libs, "openal") table.insert(used_extern_libs, "vorbis") end if not _OPTIONS["without-nvtt"] then table.insert(used_extern_libs, "nvtt") end if not _OPTIONS["without-lobby"] then table.insert(used_extern_libs, "gloox") end if not _OPTIONS["without-miniupnpc"] then table.insert(used_extern_libs, "miniupnpc") end -- Bundles static libs together with main.cpp and builds game executable. function setup_main_exe () local target_type = get_main_project_target_type() project_create("pyrogenesis", target_type) filter "system:not macosx" linkgroups 'On' filter {} links { "mocks_real" } local extra_params = { extra_files = { "main.cpp" }, no_pch = 1 } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } -- Platform Specifics if os.istarget("windows") then files { source_root.."lib/sysdep/os/win/icon.rc" } -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } rtti "off" 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", -- allow manual unload of delay-loaded DLLs "/DELAY:UNLOAD", } -- allow the executable to use more than 2GB of RAM. -- this should not be enabled during development, so that memory issues are easily spotted. if _OPTIONS["large-address-aware"] then linkoptions { "/LARGEADDRESSAWARE" } end -- see manifest.cpp project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } links { "log" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } elseif os.istarget("bsd") then links { -- Needed for backtrace* on BSDs "execinfo", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } elseif os.istarget("macosx") then links { "pthread" } links { "ApplicationServices.framework", "Cocoa.framework", "CoreFoundation.framework" } if _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end end -------------------------------------------------------------------------------- -- atlas -------------------------------------------------------------------------------- -- setup a typical Atlas component project -- extra_params, rel_source_dirs and rel_include_dirs: as in project_add_contents; function setup_atlas_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) local source_root = rootdir.."/source/tools/atlas/" .. project_name .. "/" project_create(project_name, target_type) -- if not specified, the default for atlas pch files is in the project root. if not extra_params["pch_dir"] then extra_params["pch_dir"] = source_root end project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then -- Link to required libraries links { "winmm", "comctl32", "rpcrt4", "delayimp", "ws2_32" } elseif os.istarget("linux") or os.istarget("bsd") then buildoptions { "-rdynamic", "-fPIC" } linkoptions { "-fPIC", "-rdynamic" } -- warnings triggered by wxWidgets buildoptions { "-Wno-unused-local-typedefs" } elseif os.istarget("macosx") then -- install_name settings aren't really supported yet by premake, but there are plans for the future. -- we currently use this hack to work around some bugs with wrong install_names. if target_type == "SharedLib" then if _OPTIONS["macosx-bundle"] then -- If we're building a bundle, it will be in ../Frameworks filter "Debug" linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name.."_dbg.dylib" } filter "Release" linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name..".dylib" } filter { } else filter "Debug" linkoptions { "-install_name @executable_path/lib"..project_name.."_dbg.dylib" } filter "Release" linkoptions { "-install_name @executable_path/lib"..project_name..".dylib" } filter { } end end end end -- build all Atlas component projects function setup_atlas_projects() setup_atlas_project("AtlasObject", "StaticLib", { -- src ".", "../../../third_party/jsonspirit" },{ -- include "../../../third_party/jsonspirit" },{ -- extern_libs "boost", "iconv", "libxml2" },{ -- extra_params no_pch = 1 }) atlas_src = { "ActorEditor", "CustomControls/Buttons", "CustomControls/Canvas", "CustomControls/ColorDialog", "CustomControls/DraggableListCtrl", "CustomControls/EditableListCtrl", "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/MapDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", "General", "General/VideoRecorder", "Misc", "ScenarioEditor", "ScenarioEditor/Sections/Common", "ScenarioEditor/Sections/Cinema", "ScenarioEditor/Sections/Environment", "ScenarioEditor/Sections/Map", "ScenarioEditor/Sections/Object", "ScenarioEditor/Sections/Player", "ScenarioEditor/Sections/Terrain", "ScenarioEditor/Tools", "ScenarioEditor/Tools/Common", } atlas_extra_links = { "AtlasObject" } atlas_extern_libs = { "boost", "comsuppw", "iconv", "libxml2", "sdl", -- key definitions "wxwidgets", "zlib", } if not os.istarget("windows") and not os.istarget("macosx") then -- X11 should only be linked on *nix table.insert(atlas_extern_libs, "x11") end setup_atlas_project("AtlasUI", "SharedLib", atlas_src, { -- include "..", "CustomControls", "Misc" }, atlas_extern_libs, { -- extra_params pch_dir = rootdir.."/source/tools/atlas/AtlasUI/Misc/", no_pch = false, extra_links = atlas_extra_links, extra_files = { "Misc/atlas.rc" } }) end -- Atlas 'frontend' tool-launching projects function setup_atlas_frontend_project (project_name) local target_type = get_main_project_target_type() project_create(project_name, target_type) local source_root = rootdir.."/source/tools/atlas/AtlasFrontends/" files { source_root..project_name..".cpp" } if os.istarget("windows") then files { source_root..project_name..".rc" } end includedirs { source_root .. ".." } -- Platform Specifics if os.istarget("windows") then -- see manifest.cpp project_add_manifest() else -- Non-Windows, = Unix links { "AtlasObject" } end links { "AtlasUI" } end function setup_atlas_frontends() setup_atlas_frontend_project("ActorEditor") end -------------------------------------------------------------------------------- -- collada -------------------------------------------------------------------------------- function setup_collada_project(project_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) project_create(project_name, target_type) local source_root = source_root.."collada/" extra_params["pch_dir"] = source_root project_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) project_add_extern_libs(extern_libs, target_type) -- Platform Specifics if os.istarget("windows") then characterset "MBCS" elseif os.istarget("linux") then defines { "LINUX" } links { "dl", } -- FCollada is not aliasing-safe, so disallow dangerous optimisations -- (TODO: It'd be nice to fix FCollada, but that looks hard) buildoptions { "-fno-strict-aliasing" } buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } elseif os.istarget("bsd") then if os.getversion().description == "OpenBSD" then links { "c", } end if os.getversion().description == "GNU/kFreeBSD" then links { "dl", } end buildoptions { "-fno-strict-aliasing" } buildoptions { "-rdynamic" } linkoptions { "-rdynamic" } elseif os.istarget("macosx") then -- define MACOS-something? -- install_name settings aren't really supported yet by premake, but there are plans for the future. -- we currently use this hack to work around some bugs with wrong install_names. if target_type == "SharedLib" then if _OPTIONS["macosx-bundle"] then -- If we're building a bundle, it will be in ../Frameworks linkoptions { "-install_name @executable_path/../Frameworks/lib"..project_name..".dylib" } else linkoptions { "-install_name @executable_path/lib"..project_name..".dylib" } end end buildoptions { "-fno-strict-aliasing" } -- On OSX, fcollada uses a few utility functions from coreservices links { "CoreServices.framework" } end end -- build all Collada component projects function setup_collada_projects() setup_collada_project("Collada", "SharedLib", { -- src "." },{ -- include },{ -- extern_libs "fcollada", "iconv", "libxml2" },{ -- extra_params }) end -------------------------------------------------------------------------------- -- tests -------------------------------------------------------------------------------- function setup_tests() local cxxtest = require "cxxtest" if os.istarget("windows") then cxxtest.setpath(rootdir.."/build/bin/cxxtestgen.exe") else cxxtest.setpath(rootdir.."/libraries/source/cxxtest-4.4/bin/cxxtestgen") end local runner = "ErrorPrinter" if _OPTIONS["jenkins-tests"] then runner = "XmlPrinter" end local includefiles = { -- Precompiled headers - the header is added to all generated .cpp files -- note that the header isn't actually precompiled here, only #included -- so that the build stage can use it as a precompiled header. "precompiled.h", -- This is required to build against SDL 2.0.4 on Windows. "lib/external_libraries/libsdl.h", } cxxtest.init(source_root, true, runner, includefiles) local target_type = get_main_project_target_type() project_create("test", target_type) -- Find header files in 'test' subdirectories local all_files = os.matchfiles(source_root .. "**/tests/*.h") local test_files = {} for i,v in pairs(all_files) do -- Don't include sysdep tests on the wrong sys -- Don't include Atlas tests unless Atlas is being built if not (string.find(v, "/sysdep/os/win/") and not os.istarget("windows")) and not (string.find(v, "/tools/atlas/") and not _OPTIONS["atlas"]) and not (string.find(v, "/sysdep/arch/x86_x64/") and ((arch ~= "amd64") or (arch ~= "x86"))) then table.insert(test_files, v) end end cxxtest.configure_project(test_files) filter "system:not macosx" linkgroups 'On' filter {} links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } links { "mocks_test" } if _OPTIONS["atlas"] then links { "AtlasObject" } end extra_params = { extra_files = { "test_setup.cpp" }, } project_add_contents(source_root, {}, {}, extra_params) project_add_extern_libs(used_extern_libs, target_type) dependson { "Collada" } -- TODO: should fix the duplication between this OS-specific linking -- code, and the similar version in setup_main_exe if os.istarget("windows") then -- from "lowlevel" static lib; must be added here to be linked in files { source_root.."lib/sysdep/os/win/error_dialog.rc" } rtti "off" -- see wstartup.h linkoptions { "/INCLUDE:_wstartup_InitAndRegisterShutdown" } -- Enables console for the TEST project on Windows linkoptions { "/SUBSYSTEM:CONSOLE" } project_add_manifest() elseif os.istarget("linux") or os.istarget("bsd") then if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then links { "rt" } end if _OPTIONS["android"] then -- NDK's STANDALONE-TOOLCHAIN.html says this is required linkoptions { "-Wl,--fix-cortex-a8" } end if os.istarget("linux") or os.getversion().description == "GNU/kFreeBSD" then links { -- Dynamic libraries (needed for linking for gold) "dl", } elseif os.istarget("bsd") then links { -- Needed for backtrace* on BSDs "execinfo", } end -- Threading support buildoptions { "-pthread" } if not _OPTIONS["android"] then linkoptions { "-pthread" } end -- For debug_resolve_symbol filter "Debug" linkoptions { "-rdynamic" } filter { } includedirs { source_root .. "pch/test/" } elseif os.istarget("macosx") and _OPTIONS["macosx-version-min"] then xcodebuildsettings { MACOSX_DEPLOYMENT_TARGET = _OPTIONS["macosx-version-min"] } end end -- must come first, so that VC sets it as the default project and therefore -- allows running via F5 without the "where is the EXE" dialog. setup_main_exe() setup_all_libs() -- add the static libs to the main EXE project. only now (after -- setup_all_libs has run) are the lib names known. cannot move -- setup_main_exe to run after setup_all_libs (see comment above). -- we also don't want to hardcode the names - that would require more -- work when changing the static lib breakdown. project("pyrogenesis") -- Set the main project active links { static_lib_names } filter "Debug" links { static_lib_names_debug } filter "Release" links { static_lib_names_release } filter { } if _OPTIONS["atlas"] then setup_atlas_projects() setup_atlas_frontends() end setup_collada_projects() if not _OPTIONS["without-tests"] then setup_tests() end Index: ps/trunk/build/workspaces/update-workspaces.bat =================================================================== --- ps/trunk/build/workspaces/update-workspaces.bat (revision 23381) +++ ps/trunk/build/workspaces/update-workspaces.bat (revision 23382) @@ -1,7 +1,6 @@ @echo off rem ** Create Visual Studio Workspaces on Windows ** cd ..\premake -if not exist ..\workspaces\vc2013\SKIP_PREMAKE_HERE premake5\bin\release\premake5 --outpath="../workspaces/vc2013" --use-shared-glooxwrapper %* vs2013 if not exist ..\workspaces\vc2015\SKIP_PREMAKE_HERE premake5\bin\release\premake5 --outpath="../workspaces/vc2015" --use-shared-glooxwrapper %* vs2015 cd ..\workspaces Index: ps/trunk/build/workspaces =================================================================== --- ps/trunk/build/workspaces (revision 23381) +++ ps/trunk/build/workspaces (revision 23382) Property changes on: ps/trunk/build/workspaces ___________________________________________________________________ Modified: svn:ignore ## -1,2 +1 ## -vc2013 vc2015 Index: ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h =================================================================== --- ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h (revision 23381) +++ ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h (revision 23382) @@ -1,696 +1,696 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 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_GLOOXWRAPPER_H #define INCLUDED_GLOOXWRAPPER_H /* The gloox API uses various STL types (std::string, std::list, etc), and it has functions that acquire/release ownership of objects and expect the library's user's 'new'/'delete' functions to be compatible with the library's. These assumptions are invalid when the game and library are built with different compiler versions (or the same version with different build flags): the STL types have different layouts, and new/delete can use different heaps. We want to let people build the game on Windows with any compiler version -(VC2008, 2010, 2012, 2013, and debug vs release), without requiring them to -rebuild the gloox library themselves. And we don't want to provide ~8 different -prebuilt versions of the library. +(any supported Visual Studio version, and debug vs release), without requiring +them to rebuild the gloox library themselves. And we don't want to provide ~8 +different prebuilt versions of the library. glooxwrapper replaces the gloox API with a version that is safe to use across compiler versions. glooxwrapper and gloox must be compiled together with the same version, but the resulting library can be used with any other compiler. This is the small subset of the API that the game currently uses, with no attempt to be comprehensive. General design and rules: * There is a strict boundary between gloox+glooxwrapper.cpp, and the game code that includes glooxwrapper.h. Objects allocated with new/delete on one side of the boundary must be freed/allocated on the same side. Objects allocated with glooxwrapper_alloc()/glooxwrapper_delete() can be freely shared across the boundary. * glooxwrapper.h and users of glooxwrapper must not use any types from the gloox namespace, except for enums. * std::string is replaced with glooxwrapper::string, std::list with glooxwrapper::list * Most glooxwrapper classes are simple wrappers around gloox classes. Some always take ownership of their wrapped gloox object (i.e. their destructor will delete the wrapped object too); some never do; and some can be used either way (indicated by an m_Owned field). */ #if OS_WIN # include "lib/sysdep/os/win/win.h" // Prevent gloox pulling in windows.h # define _WINDOWS_ #endif #include #include #include #include #include #include #include #include #include #if OS_WIN #define GLOOXWRAPPER_API __declspec(dllexport) #else #define GLOOXWRAPPER_API #endif namespace glooxwrapper { class Client; class DataForm; class DelayedDelivery; class Disco; class IQ; class JID; class MUCRoom; class MUCRoomConfigHandler; class Message; class MessageSession; class OOB; class Presence; class StanzaError; class StanzaExtension; class Tag; class ClientImpl; class MUCRoomHandlerWrapper; class SessionHandlerWrapper; GLOOXWRAPPER_API void* glooxwrapper_alloc(size_t size); GLOOXWRAPPER_API void glooxwrapper_free(void* p); class string { private: size_t m_Size; char* m_Data; public: string() { m_Size = 0; m_Data = (char*)glooxwrapper_alloc(1); m_Data[0] = '\0'; } string(const string& str) { m_Size = str.m_Size; m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str.m_Data, m_Size + 1); } string(const std::string& str) : m_Data(NULL) { m_Size = str.size(); m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str.c_str(), m_Size + 1); } string(const char* str) { m_Size = strlen(str); m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str, m_Size + 1); } string& operator=(const string& str) { if (this != &str) { glooxwrapper_free(m_Data); m_Size = str.m_Size; m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str.m_Data, m_Size + 1); } return *this; } ~string() { glooxwrapper_free(m_Data); } /** * Gloox strings are UTF encoded, so don't forget to decode it before passing it to the GUI! */ std::string to_string() const { return std::string(m_Data, m_Size); } const char* c_str() const { return m_Data; } bool empty() const { return m_Size == 0; } bool operator==(const char* str) const { return strcmp(m_Data, str) == 0; } bool operator!=(const char* str) const { return strcmp(m_Data, str) != 0; } bool operator==(const string& str) const { return strcmp(m_Data, str.m_Data) == 0; } bool operator<(const string& str) const { return strcmp(m_Data, str.m_Data) < 0; } }; static inline std::ostream& operator<<(std::ostream& stream, const string& string) { return stream << string.c_str(); } template class list { private: struct node { node(const T& item) : m_Item(item), m_Next(NULL) {} T m_Item; node* m_Next; }; node* m_Head; node* m_Tail; public: struct const_iterator { const node* m_Node; const_iterator(const node* n) : m_Node(n) {} bool operator!=(const const_iterator& it) { return m_Node != it.m_Node; } const_iterator& operator++() { m_Node = m_Node->m_Next; return *this; } const T& operator*() { return m_Node->m_Item; } }; const_iterator begin() const { return const_iterator(m_Head); } const_iterator end() const { return const_iterator(NULL); } list() : m_Head(NULL), m_Tail(NULL) {} list(const list& src) : m_Head(NULL), m_Tail(NULL) { *this = src; } list& operator=(const list& src) { if (this != &src) { clear(); for (node* n = src.m_Head; n; n = n->m_Next) push_back(n->m_Item); } return *this; } ~list() { clear(); } void push_back(const T& item) { node* n = new (glooxwrapper_alloc(sizeof(node))) node(item); if (m_Tail) m_Tail->m_Next = n; m_Tail = n; if (!m_Head) m_Head = n; } void clear() { node* n = m_Head; while (n) { node* next = n->m_Next; glooxwrapper_free(n); n = next; } m_Head = m_Tail = NULL; } const T& front() const { return *begin(); } }; typedef glooxwrapper::list TagList; typedef glooxwrapper::list ConstTagList; struct CertInfo { int status; bool chain; string issuer; string server; int date_from; int date_to; string protocol; string cipher; string mac; string compression; }; struct RegistrationFields { string username; string nick; string password; string name; string first; string last; string email; string address; string city; string state; string zip; string phone; string url; string date; string misc; string text; }; struct MUCRoomParticipant { JID* nick; gloox::MUCRoomAffiliation affiliation; gloox::MUCRoomRole role; JID* jid; int flags; string reason; JID* actor; string newNick; string status; JID* alternate; }; class GLOOXWRAPPER_API ConnectionListener { public: virtual ~ConnectionListener() {} virtual void onConnect() = 0; virtual void onDisconnect(gloox::ConnectionError e) = 0; virtual bool onTLSConnect(const CertInfo& info) = 0; }; class GLOOXWRAPPER_API IqHandler { public: virtual ~IqHandler() {} virtual bool handleIq(const IQ& iq) = 0; virtual void handleIqID(const IQ& iq, int context) = 0; }; class GLOOXWRAPPER_API MessageHandler { public: virtual ~MessageHandler() {} virtual void handleMessage(const Message& msg, MessageSession* session = 0) = 0; // MessageSession not supported }; class GLOOXWRAPPER_API MUCRoomHandler { public: virtual ~MUCRoomHandler() {} virtual void handleMUCParticipantPresence(MUCRoom& room, const MUCRoomParticipant participant, const Presence& presence) = 0; virtual void handleMUCMessage(MUCRoom& room, const Message& msg, bool priv) = 0; virtual void handleMUCError(MUCRoom& room, gloox::StanzaError error) = 0; virtual void handleMUCSubject(MUCRoom& room, const string& nick, const string& subject) = 0; }; class GLOOXWRAPPER_API RegistrationHandler { public: virtual ~RegistrationHandler() {} virtual void handleRegistrationFields(const JID& from, int fields, string instructions) = 0; virtual void handleAlreadyRegistered(const JID& from) = 0; virtual void handleRegistrationResult(const JID& from, gloox::RegistrationResult regResult) = 0; virtual void handleDataForm(const JID& from, const DataForm& form) = 0; // DataForm not supported virtual void handleOOB(const JID& from, const OOB& oob) = 0; // OOB not supported }; class GLOOXWRAPPER_API StanzaExtension { public: StanzaExtension(int type) : m_extensionType(type) {} virtual ~StanzaExtension() {} virtual const string& filterString() const = 0; virtual StanzaExtension* newInstance(const Tag* tag) const = 0; virtual glooxwrapper::Tag* tag() const = 0; virtual StanzaExtension* clone() const = 0; int extensionType() const { return m_extensionType; } private: int m_extensionType; }; class GLOOXWRAPPER_API Client { NONCOPYABLE(Client); private: gloox::Client* m_Wrapped; ClientImpl* m_Impl; Disco* m_DiscoWrapper; public: gloox::Client* getWrapped() { return m_Wrapped; } bool connect(bool block = true); gloox::ConnectionError recv(int timeout = -1); const string getID() const; void send(const IQ& iq); void setTls(gloox::TLSPolicy tls); void setCompression(bool compression); void setSASLMechanisms(int mechanisms); void registerStanzaExtension(StanzaExtension* ext); void registerConnectionListener(ConnectionListener* cl); void registerIqHandler(IqHandler* ih, int exttype); void registerMessageHandler(MessageHandler* mh); bool removePresenceExtension(int type); Disco* disco() const { return m_DiscoWrapper; } Client(const string& server); Client(const JID& jid, const string& password, int port = -1); ~Client(); void setPresence(gloox::Presence::PresenceType pres, int priority, const string& status = ""); void disconnect(); }; class GLOOXWRAPPER_API DelayedDelivery { NONCOPYABLE(DelayedDelivery); private: const gloox::DelayedDelivery* m_Wrapped; public: DelayedDelivery(const gloox::DelayedDelivery* wrapped); const string stamp() const; }; class GLOOXWRAPPER_API Disco { NONCOPYABLE(Disco); private: gloox::Disco* m_Wrapped; public: Disco(gloox::Disco* wrapped); void setVersion(const string& name, const string& version, const string& os = ""); void setIdentity(const string& category, const string& type, const string& name = ""); }; class GLOOXWRAPPER_API IQ { NONCOPYABLE(IQ); private: gloox::IQ* m_Wrapped; bool m_Owned; public: const gloox::IQ& getWrapped() const { return *m_Wrapped; } IQ(const gloox::IQ& iq) : m_Wrapped(const_cast(&iq)), m_Owned(false) { } IQ(gloox::IQ::IqType type, const JID& to, const string& id); ~IQ(); void addExtension(const StanzaExtension* se); const StanzaExtension* findExtension(int type) const; template inline const T* findExtension(int type) const { return static_cast(findExtension(type)); } gloox::IQ::IqType subtype() const; const string id() const; const gloox::JID& from() const; gloox::StanzaError error_error() const; // wrapper for ->error()->error() Tag* tag() const; }; class GLOOXWRAPPER_API JID { NONCOPYABLE(JID); private: gloox::JID* m_Wrapped; bool m_Owned; void init(const char* data, size_t len); public: const gloox::JID& getWrapped() const { return *m_Wrapped; } JID(const gloox::JID& jid) : m_Wrapped(const_cast(&jid)), m_Owned(false) { } JID(); JID(const string& jid); JID(const std::string& jid) { init(jid.c_str(), jid.size()); } ~JID(); string username() const; string resource() const; }; class GLOOXWRAPPER_API Message { NONCOPYABLE(Message); private: gloox::Message* m_Wrapped; bool m_Owned; glooxwrapper::JID m_From; glooxwrapper::DelayedDelivery* m_DelayedDelivery; public: Message(gloox::Message* wrapped, bool owned); ~Message(); gloox::Message::MessageType subtype() const; const JID& from() const; string body() const; string subject(const string& lang = "default") const; string thread() const; const glooxwrapper::DelayedDelivery* when() const; }; class GLOOXWRAPPER_API MUCRoom { NONCOPYABLE(MUCRoom); private: gloox::MUCRoom* m_Wrapped; MUCRoomHandlerWrapper* m_HandlerWrapper; bool m_Owned; public: MUCRoom(gloox::MUCRoom* room, bool owned); MUCRoom(Client* parent, const JID& nick, MUCRoomHandler* mrh, MUCRoomConfigHandler* mrch = 0); ~MUCRoom(); const string nick() const; const string name() const; const string service() const; void join(gloox::Presence::PresenceType type = gloox::Presence::Available, const string& status = "", int priority = 0); void leave(const string& msg = ""); void send(const string& message); void setNick(const string& nick); void setPresence(gloox::Presence::PresenceType presence, const string& msg = ""); void setRequestHistory(int value, gloox::MUCRoom::HistoryRequestType type); void kick(const string& nick, const string& reason); void ban(const string& nick, const string& reason); }; class GLOOXWRAPPER_API Presence { gloox::Presence::PresenceType m_Presence; public: Presence(gloox::Presence::PresenceType presence) : m_Presence(presence) {} gloox::Presence::PresenceType presence() const { return m_Presence; } }; class GLOOXWRAPPER_API Registration { NONCOPYABLE(Registration); private: gloox::Registration* m_Wrapped; std::list > m_RegistrationHandlers; public: Registration(Client* parent); ~Registration(); void fetchRegistrationFields(); bool createAccount(int fields, const RegistrationFields& values); void registerRegistrationHandler(RegistrationHandler* rh); }; class GLOOXWRAPPER_API Tag { NONCOPYABLE(Tag); private: gloox::Tag* m_Wrapped; bool m_Owned; Tag(const string& name); Tag(const string& name, const string& cdata); Tag(gloox::Tag* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {} ~Tag(); public: // Internal use: gloox::Tag* getWrapped() { return m_Wrapped; } gloox::Tag* stealWrapped() { m_Owned = false; return m_Wrapped; } static Tag* allocate(gloox::Tag* wrapped, bool owned); // Instead of using new/delete, Tags must be allocated/freed with these functions static Tag* allocate(const string& name); static Tag* allocate(const string& name, const string& cdata); static void free(const Tag* tag); bool addAttribute(const string& name, const string& value); string findAttribute(const string& name) const; Tag* clone() const; string xmlns() const; bool setXmlns(const string& xmlns); string xml() const; void addChild(Tag* child); string name() const; string cdata() const; const Tag* findTag_clone(const string& expression) const; // like findTag but must be Tag::free()d ConstTagList findTagList_clone(const string& expression) const; // like findTagList but each tag must be Tag::free()d }; /** * See XEP-0166: Jingle and https://camaya.net/api/gloox/namespacegloox_1_1Jingle.html. */ namespace Jingle { class GLOOXWRAPPER_API Plugin { protected: const gloox::Jingle::Plugin* m_Wrapped; bool m_Owned; public: Plugin(const gloox::Jingle::Plugin* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {} virtual ~Plugin(); const Plugin findPlugin(int type) const; const gloox::Jingle::Plugin* getWrapped() const { return m_Wrapped; } }; typedef list PluginList; /** * See XEP-0176: Jingle ICE-UDP Transport Method */ class GLOOXWRAPPER_API ICEUDP { public: struct Candidate { string ip; int port; }; private: // Class not implemented as it is not used. ICEUDP() = delete; }; class GLOOXWRAPPER_API Session { protected: gloox::Jingle::Session* m_Wrapped; bool m_Owned; public: class GLOOXWRAPPER_API Jingle { private: const gloox::Jingle::Session::Jingle* m_Wrapped; bool m_Owned; public: Jingle(const gloox::Jingle::Session::Jingle* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {} ~Jingle() { if (m_Owned) delete m_Wrapped; } ICEUDP::Candidate getCandidate() const; }; Session(gloox::Jingle::Session* wrapped, bool owned); ~Session(); bool sessionInitiate(char* ipStr, uint16_t port); }; class GLOOXWRAPPER_API SessionHandler { public: virtual ~SessionHandler() {} virtual void handleSessionAction(gloox::Jingle::Action action, Session& session, const Session::Jingle& jingle) = 0; }; } class GLOOXWRAPPER_API SessionManager { private: gloox::Jingle::SessionManager* m_Wrapped; SessionHandlerWrapper* m_HandlerWrapper; public: SessionManager(Client* parent, Jingle::SessionHandler* sh); ~SessionManager(); void registerPlugins(); Jingle::Session createSession(const JID& callee); }; } #endif // INCLUDED_GLOOXWRAPPER_H Index: ps/trunk/source/scriptinterface/NativeWrapperDefns.h =================================================================== --- ps/trunk/source/scriptinterface/NativeWrapperDefns.h (revision 23381) +++ ps/trunk/source/scriptinterface/NativeWrapperDefns.h (revision 23382) @@ -1,240 +1,240 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 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 . */ // Use the macro below to define types that will be passed by value to C++ functions. // NOTE: References are used just to avoid superfluous copy constructor calls // in the script wrapper code. They cannot be used as out-parameters. // They are const T& by default to avoid confusion about this, especially // because sometimes the function is not just exposed to scripts, but also // called from C++ code. template struct ScriptInterface::MaybeRef { typedef const T& Type; }; #define PASS_BY_VALUE_IN_NATIVE_WRAPPER(T) \ template <> struct ScriptInterface::MaybeRef \ { \ typedef T Type; \ }; \ PASS_BY_VALUE_IN_NATIVE_WRAPPER(JS::HandleValue) PASS_BY_VALUE_IN_NATIVE_WRAPPER(bool) PASS_BY_VALUE_IN_NATIVE_WRAPPER(int) PASS_BY_VALUE_IN_NATIVE_WRAPPER(uint8_t) PASS_BY_VALUE_IN_NATIVE_WRAPPER(uint16_t) PASS_BY_VALUE_IN_NATIVE_WRAPPER(uint32_t) PASS_BY_VALUE_IN_NATIVE_WRAPPER(fixed) PASS_BY_VALUE_IN_NATIVE_WRAPPER(float) PASS_BY_VALUE_IN_NATIVE_WRAPPER(double) #undef PASS_BY_VALUE_IN_NATIVE_WRAPPER -// This works around a bug in Visual Studio 2013 (error C2244 if ScriptInterface:: is included in the +// This works around a bug in Visual Studio (error C2244 if ScriptInterface:: is included in the // type specifier of MaybeRef::Type for parameters inside the member function declaration). // It's probably the bug described here, but I'm not quite sure (at least the example there still // cause error C2244): // https://connect.microsoft.com/VisualStudio/feedback/details/611863/vs2010-c-fails-with-error-c2244-gcc-4-3-4-compiles-ok // -// TODO: When dropping support for VS 2013, check if this bug is still present in the supported +// TODO: When dropping support for VS 2015, check if this bug is still present in the supported // Visual Studio versions (replace the macro definitions in NativeWrapperDecls.h with these ones, // remove them from here and check if this causes error C2244 when compiling. #undef NUMBERED_LIST_TAIL_MAYBE_REF #undef NUMBERED_LIST_BALANCED_MAYBE_REF #define NUMBERED_LIST_TAIL_MAYBE_REF(z, i, data) , typename ScriptInterface::MaybeRef::Type #define NUMBERED_LIST_BALANCED_MAYBE_REF(z, i, data) BOOST_PP_COMMA_IF(i) typename ScriptInterface::MaybeRef::Type // (NativeWrapperDecls.h set up a lot of the macros we use here) // ScriptInterface_NativeWrapper::call(cx, rval, fptr, args...) will call fptr(cbdata, args...), // and if T != void then it will store the result in rval: // Templated on the return type so void can be handled separately template struct ScriptInterface_NativeWrapper { template static void call(JSContext* cx, JS::MutableHandleValue rval, F fptr, Ts... params) { ScriptInterface::AssignOrToJSValUnrooted(cx, rval, fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx), params...)); } }; // Overloaded to ignore the return value from void functions template <> struct ScriptInterface_NativeWrapper { template static void call(JSContext* cx, JS::MutableHandleValue UNUSED(rval), F fptr, Ts... params) { fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx), params...); } }; // Same idea but for method calls: template struct ScriptInterface_NativeMethodWrapper { template static void call(JSContext* cx, JS::MutableHandleValue rval, TC* c, F fptr, Ts... params) { ScriptInterface::AssignOrToJSValUnrooted(cx, rval, (c->*fptr)(params...)); } }; template struct ScriptInterface_NativeMethodWrapper { template static void call(JSContext* UNUSED(cx), JS::MutableHandleValue UNUSED(rval), TC* c, F fptr, Ts... params) { (c->*fptr)(params...); } }; // JSFastNative-compatible function that wraps the function identified in the template argument list #define OVERLOADS(z, i, data) \ template \ bool ScriptInterface::call(JSContext* cx, uint argc, JS::Value* vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JSAutoRequest rq(cx); \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ JS::RootedValue rval(cx); \ ScriptInterface_NativeWrapper::template call(cx, &rval, fptr A0_TAIL(z,i)); \ args.rval().set(rval); \ return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // Same idea but for methods #define OVERLOADS(z, i, data) \ template \ bool ScriptInterface::callMethod(JSContext* cx, uint argc, JS::Value* vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JSAutoRequest rq(cx); \ TC* c = ScriptInterface::GetPrivate(cx, args, CLS); \ if (! c) return false; \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ JS::RootedValue rval(cx); \ ScriptInterface_NativeMethodWrapper::template call(cx, &rval, c, fptr A0_TAIL(z,i)); \ args.rval().set(rval); \ return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // const methods #define OVERLOADS(z, i, data) \ template \ bool ScriptInterface::callMethodConst(JSContext* cx, uint argc, JS::Value* vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JSAutoRequest rq(cx); \ TC* c = ScriptInterface::GetPrivate(cx, args, CLS); \ if (! c) return false; \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ JS::RootedValue rval(cx); \ ScriptInterface_NativeMethodWrapper::template call(cx, &rval, c, fptr A0_TAIL(z,i)); \ args.rval().set(rval); \ return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS template static void AssignOrToJSValHelper(JSContext* cx, JS::AutoValueVector& argv, const T& a, const Ts&... params) { ScriptInterface::AssignOrToJSVal(cx, argv[i], a); AssignOrToJSValHelper(cx, argv, params...); } template static void AssignOrToJSValHelper(JSContext* UNUSED(cx), JS::AutoValueVector& UNUSED(argv)) { cassert(sizeof...(Ts) == 0); // Nop, for terminating the template recursion. } template bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, R& ret, const Ts&... params) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::RootedValue jsRet(cx); JS::AutoValueVector argv(cx); argv.resize(sizeof...(Ts)); AssignOrToJSValHelper<0>(cx, argv, params...); bool ok = CallFunction_(val, name, argv, &jsRet); if (!ok) return false; return FromJSVal(cx, jsRet, ret); } template bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::Rooted* ret, const Ts&... params) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::MutableHandle jsRet(ret); JS::AutoValueVector argv(cx); argv.resize(sizeof...(Ts)); AssignOrToJSValHelper<0>(cx, argv, params...); return CallFunction_(val, name, argv, jsRet); } template bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle ret, const Ts&... params) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::AutoValueVector argv(cx); argv.resize(sizeof...(Ts)); AssignOrToJSValHelper<0>(cx, argv, params...); return CallFunction_(val, name, argv, ret); } // Call the named property on the given object, with void return type template bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name, const Ts&... params) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::RootedValue jsRet(cx); JS::AutoValueVector argv(cx); argv.resize(sizeof...(Ts)); AssignOrToJSValHelper<0>(cx, argv, params...); return CallFunction_(val, name, argv, &jsRet); } // Clean up our mess #undef NUMBERED_LIST_HEAD #undef NUMBERED_LIST_TAIL #undef NUMBERED_LIST_TAIL_MAYBE_REF #undef NUMBERED_LIST_BALANCED #undef NUMBERED_LIST_BALANCED_MAYBE_REF #undef CONVERT_ARG #undef TYPENAME_T0_HEAD #undef T0 #undef T0_MAYBE_REF #undef T0_TAIL #undef T0_TAIL_MAYBE_REF #undef A0_TAIL Index: ps/trunk/source/scriptinterface/ScriptInterface.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.h (revision 23381) +++ ps/trunk/source/scriptinterface/ScriptInterface.h (revision 23382) @@ -1,624 +1,605 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 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 "lib/file/vfs/vfs_path.h" #include "maths/Fixed.h" #include "ScriptTypes.h" #include "ps/Errors.h" #include #include ERROR_GROUP(Scripting); ERROR_TYPE(Scripting, SetupFailed); ERROR_SUBGROUP(Scripting, LoadFile); ERROR_TYPE(Scripting_LoadFile, OpenFailed); ERROR_TYPE(Scripting_LoadFile, EvalErrors); ERROR_TYPE(Scripting, CallFunctionFailed); ERROR_TYPE(Scripting, RegisterFunctionFailed); ERROR_TYPE(Scripting, DefineConstantFailed); ERROR_TYPE(Scripting, CreateObjectFailed); ERROR_TYPE(Scripting, TypeDoesNotExist); ERROR_SUBGROUP(Scripting, DefineType); ERROR_TYPE(Scripting_DefineType, AlreadyExists); ERROR_TYPE(Scripting_DefineType, CreationFailed); // 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 8 // TODO: what's a good default? #define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024 #define DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER 2 * 1024 *1024 struct ScriptInterface_impl; class ScriptRuntime; extern shared_ptr g_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 { NONCOPYABLE(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. * @param runtimeSize Maximum size in bytes of the new runtime */ static shared_ptr CreateRuntime(shared_ptr parentRuntime = shared_ptr(), int runtimeSize = DEFAULT_RUNTIME_SIZE, int heapGrowthBytesGCTrigger = DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER); /** * Constructor. * @param nativeScopeName Name of global object that functions (via RegisterFunction) will * be placed into, as a scoping mechanism; typically "Engine" * @param debugName Name of this interface for CScriptStats purposes. * @param runtime ScriptRuntime to use when initializing this interface. */ ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime); ~ScriptInterface(); struct CxPrivate { ScriptInterface* pScriptInterface; // the ScriptInterface object the current context belongs to void* pCBData; // meant to be used as the "this" object for callback functions } m_CxPrivate; void SetCallbackData(void* pCBData); static CxPrivate* GetScriptInterfaceAndCBData(JSContext* cx); JSContext* GetContext() const; JSRuntime* GetJSRuntime() const; shared_ptr GetRuntime() const; /** * Load global scripts that most script contexts need, * located in the /globalscripts directory. VFS must be initialized. */ bool LoadGlobalScripts(); /** * Replace the default JS random number geenrator with a seeded, network-sync'd one. */ bool ReplaceNondeterministicRNG(boost::rand48& rng); /** * Call a constructor function, equivalent to JS "new ctor(arg)". * @param ctor An object that can be used as constructor * @param argv Constructor arguments * @param out The new object; On error an error message gets logged and out is Null (out.isNull() == true). */ void CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) const; JSObject* CreateCustomObject(const std::string & typeName) const; void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs); /** * Sets the given value to a new plain JS::Object, converts the arguments to JS::Values and sets them as properties. * This is static so that callers like ToJSVal can use it with the JSContext directly instead of having to obtain the instance using GetScriptInterfaceAndCBData. * Can throw an exception. */ template static bool CreateObject(JSContext* cx, JS::MutableHandleValue objectValue, Args const&... args) { JSAutoRequest rq(cx); JS::RootedObject obj(cx); if (!CreateObject_(cx, &obj, args...)) return false; objectValue.setObject(*obj); return true; } /** * Sets the given value to a new JS object or Null Value in case of out-of-memory. */ static void CreateArray(JSContext* cx, JS::MutableHandleValue objectValue, size_t length = 0); JS::Value GetGlobalObject() const; /** * Set the named property on the global object. * Optionally makes it {ReadOnly, DontEnum}. We do not allow to make it DontDelete, so that it can be hotloaded * by deleting it and re-creating it, which is done by setting @p replace to true. */ template bool SetGlobal(const char* name, const T& value, bool replace = false, bool constant = true, bool enumerate = true); /** * Set the named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template bool SetProperty(JS::HandleValue obj, const char* name, const T& value, bool constant = false, bool enumerate = true) const; /** * Set the named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template bool SetProperty(JS::HandleValue obj, const wchar_t* name, const T& value, bool constant = false, bool enumerate = true) const; /** * Set the integer-named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template bool SetPropertyInt(JS::HandleValue obj, int name, const T& value, bool constant = false, bool enumerate = true) const; /** * Get the named property on the given object. */ template bool GetProperty(JS::HandleValue obj, const char* name, T& out) const; /** * Get the named property of the given object. */ bool GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const; bool GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleObject out) const; /** * Get the integer-named property on the given object. */ template bool GetPropertyInt(JS::HandleValue obj, int name, T& out) const; /** * Get the named property of the given object. */ bool GetPropertyInt(JS::HandleValue obj, int name, JS::MutableHandleValue out) const; /** * Check the named property has been defined on the given object. */ bool HasProperty(JS::HandleValue obj, const char* name) const; bool EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal, const char* prefix, std::vector& out) const; bool SetPrototype(JS::HandleValue obj, JS::HandleValue proto); bool FreezeObject(JS::HandleValue objVal, bool deep) const; bool Eval(const char* code) const; template bool Eval(const CHAR* code, JS::MutableHandleValue out) const; template bool Eval(const CHAR* code, T& out) const; /** * Convert an object to a UTF-8 encoded string, either with JSON * (if pretty == true and there is no JSON error) or with toSource(). * * We have to use a mutable handle because JS_Stringify requires that for unknown reasons. */ std::string ToString(JS::MutableHandleValue obj, bool pretty = false) const; /** * Parse a UTF-8-encoded JSON string. Returns the unmodified value on error * and prints an error message. * @return true on success; false otherwise */ bool ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) const; /** * Read a JSON file. Returns the unmodified value on error and prints an error message. */ void ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) const; /** * Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error. */ std::string StringifyJSON(JS::MutableHandleValue obj, bool indent = true) const; /** * Report the given error message through the JS error reporting mechanism, * and throw a JS exception. (Callers can check IsPendingException, and must * return false in that case to propagate the exception.) */ void ReportError(const char* msg) const; /** * 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 VfsPath& filename, const std::string& code) const; /** * Load and execute the given script in the global 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 LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const; /** * Load and execute the given script in the global scope. * @return true on successful compilation and execution; false otherwise */ bool LoadGlobalScriptFile(const VfsPath& path) const; /** * 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. */ JS::Value CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const; /** * Convert a JS::Value to a C++ type. (This might trigger GC.) */ template static bool FromJSVal(JSContext* cx, const JS::HandleValue val, T& ret); /** * Convert a C++ type to a JS::Value. (This might trigger GC. The return * value must be rooted if you don't want it to be collected.) * NOTE: We are passing the JS::Value by reference instead of returning it by value. * The reason is a memory corruption problem that appears to be caused by a bug in Visual Studio. * Details here: http://www.wildfiregames.com/forum/index.php?showtopic=17289&p=285921 */ template static void ToJSVal(JSContext* cx, JS::MutableHandleValue ret, T const& val); /** * Convert a named property of an object to a C++ type. */ template static bool FromJSProperty(JSContext* cx, const JS::HandleValue val, const char* name, T& ret); /** * MathRandom (this function) calls the random number generator assigned to this ScriptInterface instance and * returns the generated number. * Math_random (with underscore, not this function) is a global function, but different random number generators can be * stored per ScriptInterface. It calls MathRandom of the current ScriptInterface instance. */ bool MathRandom(double& nbr); /** * 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(); u64* m_Data; size_t m_Size; }; shared_ptr WriteStructuredClone(JS::HandleValue v) const; void ReadStructuredClone(const shared_ptr& ptr, JS::MutableHandleValue ret) const; /** * Retrieve the private data field of a JSObject that is an instance of the given JSClass. */ template static T* GetPrivate(JSContext* cx, JS::HandleObject thisobj, JSClass* jsClass) { JSAutoRequest rq(cx); T* value = static_cast(JS_GetInstancePrivate(cx, thisobj, jsClass, nullptr)); if (value == nullptr && !JS_IsExceptionPending(cx)) JS_ReportError(cx, "Private data of the given object is null!"); return value; } /** * Retrieve the private data field of a JS Object that is an instance of the given JSClass. * If an error occurs, GetPrivate will report it with the according stack. */ template static T* GetPrivate(JSContext* cx, JS::CallArgs& callArgs, JSClass* jsClass) { JSAutoRequest rq(cx); if (!callArgs.thisv().isObject()) { JS_ReportError(cx, "Cannot retrieve private JS class data because from a non-object value!"); return nullptr; } JS::RootedObject thisObj(cx, &callArgs.thisv().toObject()); T* value = static_cast(JS_GetInstancePrivate(cx, thisObj, jsClass, &callArgs)); if (value == nullptr && !JS_IsExceptionPending(cx)) JS_ReportError(cx, "Private data of the given object is null!"); return value; } /** * Converts |a| if needed and assigns it to |handle|. * This is meant for use in other templates where we want to use the same code for JS::RootedValue&/JS::HandleValue and * other types. Note that functions are meant to take JS::HandleValue instead of JS::RootedValue&, but this implicit * conversion does not work for templates (exact type matches required for type deduction). * A similar functionality could also be implemented as a ToJSVal specialization. The current approach was preferred * because "conversions" from JS::HandleValue to JS::MutableHandleValue are unusual and should not happen "by accident". */ template static void AssignOrToJSVal(JSContext* cx, JS::MutableHandleValue handle, const T& a); /** * The same as AssignOrToJSVal, but also allows JS::Value for T. * In most cases it's not safe to use the plain (unrooted) JS::Value type, but this can happen quite * easily with template functions. The idea is that the linker prints an error if AssignOrToJSVal is * used with JS::Value. If the specialization for JS::Value should be allowed, you can use this * "unrooted" version of AssignOrToJSVal. */ template static void AssignOrToJSValUnrooted(JSContext* cx, JS::MutableHandleValue handle, const T& a) { AssignOrToJSVal(cx, handle, a); } /** * Converts |val| to T if needed or just returns it if it's a handle. * This is meant for use in other templates where we want to use the same code for JS::HandleValue and * other types. */ template static T AssignOrFromJSVal(JSContext* cx, const JS::HandleValue& val, bool& ret); private: /** * Careful, the CreateObject_ helpers avoid creation of the JSAutoRequest! */ static bool CreateObject_(JSContext* cx, JS::MutableHandleObject obj); template static bool CreateObject_(JSContext* cx, JS::MutableHandleObject obj, const char* propertyName, const T& propertyValue, Args const&... args) { // JSAutoRequest is the responsibility of the caller JS::RootedValue val(cx); AssignOrToJSVal(cx, &val, propertyValue); return CreateObject_(cx, obj, args...) && JS_DefineProperty(cx, obj, propertyName, val, JSPROP_ENUMERATE); } bool CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const; bool Eval_(const char* code, JS::MutableHandleValue ret) const; bool Eval_(const wchar_t* code, JS::MutableHandleValue ret) const; bool SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate); bool SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) const; bool SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) const; bool SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool constant, bool enumerate) const; bool GetProperty_(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) const; bool GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue value) const; static bool IsExceptionPending(JSContext* cx); struct CustomType { - // TODO: Move assignment operator and move constructor only have to be - // explicitly defined for Visual Studio. VS2013 is still behind on C++11 support - // What's missing is what they call "Rvalue references v3.0", see - // https://msdn.microsoft.com/en-us/library/hh567368.aspx#rvref - CustomType() {} - CustomType& operator=(CustomType&& other) - { - m_Prototype = std::move(other.m_Prototype); - m_Class = std::move(other.m_Class); - m_Constructor = std::move(other.m_Constructor); - return *this; - } - CustomType(CustomType&& other) - { - m_Prototype = std::move(other.m_Prototype); - m_Class = std::move(other.m_Class); - m_Constructor = std::move(other.m_Constructor); - } - JS::PersistentRootedObject m_Prototype; JSClass* m_Class; JSNative m_Constructor; }; void Register(const char* name, JSNative fptr, size_t nargs) const; // Take care to keep this declaration before heap rooted members. Destructors of heap rooted // members have to be called before the runtime destructor. std::unique_ptr m; boost::rand48* m_rng; std::map m_CustomObjectTypes; // 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) const; // // template // static JSNative call; // // template // static JSNative callMethod; // // template // static JSNative callMethodConst; // // template // static size_t nargs(); // // template // bool CallFunction(JS::HandleValue val, const char* name, R& ret, const T0&...) const; // // template // bool CallFunction(JS::HandleValue val, const char* name, JS::Rooted* ret, const T0&...) const; // // template // bool CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle ret, const T0&...) const; // // template // bool CallFunctionVoid(JS::HandleValue val, const char* name, const T0&...) const; }; // Implement those declared functions #include "NativeWrapperDefns.h" template inline void ScriptInterface::AssignOrToJSVal(JSContext* cx, JS::MutableHandleValue handle, const T& a) { ToJSVal(cx, handle, a); } template<> inline void ScriptInterface::AssignOrToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::PersistentRootedValue& a) { handle.set(a); } template<> inline void ScriptInterface::AssignOrToJSVal >(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::Heap& a) { handle.set(a); } template<> inline void ScriptInterface::AssignOrToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::RootedValue& a) { handle.set(a); } template <> inline void ScriptInterface::AssignOrToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::HandleValue& a) { handle.set(a); } template <> inline void ScriptInterface::AssignOrToJSValUnrooted(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::Value& a) { handle.set(a); } template inline T ScriptInterface::AssignOrFromJSVal(JSContext* cx, const JS::HandleValue& val, bool& ret) { T retVal; ret = FromJSVal(cx, val, retVal); return retVal; } template<> inline JS::HandleValue ScriptInterface::AssignOrFromJSVal(JSContext* UNUSED(cx), const JS::HandleValue& val, bool& ret) { ret = true; return val; } template bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace, bool constant, bool enumerate) { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetGlobal_(name, val, replace, constant, enumerate); } template bool ScriptInterface::SetProperty(JS::HandleValue obj, const char* name, const T& value, bool constant, bool enumerate) const { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetProperty_(obj, name, val, constant, enumerate); } template bool ScriptInterface::SetProperty(JS::HandleValue obj, const wchar_t* name, const T& value, bool constant, bool enumerate) const { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetProperty_(obj, name, val, constant, enumerate); } template bool ScriptInterface::SetPropertyInt(JS::HandleValue obj, int name, const T& value, bool constant, bool enumerate) const { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetPropertyInt_(obj, name, val, constant, enumerate); } template bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, T& out) const { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::RootedValue val(cx); if (!GetProperty_(obj, name, &val)) return false; return FromJSVal(cx, val, out); } template bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, T& out) const { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); if (!GetPropertyInt_(obj, name, &val)) return false; return FromJSVal(GetContext(), val, out); } template bool ScriptInterface::Eval(const CHAR* code, JS::MutableHandleValue ret) const { if (!Eval_(code, ret)) return false; return true; } template bool ScriptInterface::Eval(const CHAR* code, T& ret) const { JSAutoRequest rq(GetContext()); JS::RootedValue rval(GetContext()); if (!Eval_(code, &rval)) return false; return FromJSVal(GetContext(), rval, ret); } #endif // INCLUDED_SCRIPTINTERFACE Index: ps/trunk/source/scriptinterface/ScriptVal.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptVal.h (revision 23381) +++ ps/trunk/source/scriptinterface/ScriptVal.h (revision 23382) @@ -1,100 +1,85 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2020 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_SCRIPTVAL #define INCLUDED_SCRIPTVAL #include "ScriptTypes.h" /** * A default constructible wrapper around JS::PersistentRootedValue * * It's a very common case that we need to store JS::Values on the heap as * class members and only need them conditionally or want to initialize * them after the constructor because we don't have the runtime available yet. * Use it in these cases, but prefer to use JS::PersistentRootedValue directly * if initializing it with a runtime/context in the constructor isn't a problem. */ template class DefPersistentRooted { public: DefPersistentRooted() { } DefPersistentRooted(JSRuntime* rt) { m_Val.reset(new JS::PersistentRooted(rt)); } DefPersistentRooted(JSRuntime* rt, JS::HandleValue val) { m_Val.reset(new JS::PersistentRooted(rt, val)); } DefPersistentRooted(JSContext* cx, JS::Handle val) { m_Val.reset(new JS::PersistentRooted(cx, val)); } void clear() { m_Val = nullptr; } inline bool uninitialized() { return m_Val == nullptr; } inline JS::PersistentRooted& get() const { ENSURE(m_Val); return *m_Val; } inline void set(JSRuntime* rt, T val) { m_Val.reset(new JS::PersistentRooted(rt, val)); } inline void set(JSContext* cx, T val) { m_Val.reset(new JS::PersistentRooted(cx, val)); } - // TODO: Move assignment operator and move constructor only have to be - // explicitly defined for Visual Studio. VS2013 is still behind on C++11 support - // What's missing is what they call "Rvalue references v3.0", see - // https://msdn.microsoft.com/en-us/library/hh567368.aspx#rvref - DefPersistentRooted& operator=(DefPersistentRooted&& other) - { - m_Val = std::move(other.m_Val); - return *this; - } - - DefPersistentRooted(DefPersistentRooted&& other) - { - m_Val = std::move(other.m_Val); - } - private: std::unique_ptr > m_Val; }; #endif // INCLUDED_SCRIPTVAL Index: ps/trunk/source/simulation2/system/ComponentManager.cpp =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 23381) +++ ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 23382) @@ -1,1173 +1,1173 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 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 "ComponentManager.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/scripting/JSInterface_VFS.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/DynamicSubscription.h" #include "simulation2/system/IComponent.h" #include "simulation2/system/ParamNode.h" #include "simulation2/system/SimContext.h" /** * Used for script-only message types. */ class CMessageScripted : public CMessage { public: virtual int GetType() const { return mtid; } virtual const char* GetScriptHandlerName() const { return handlerName.c_str(); } virtual const char* GetScriptGlobalHandlerName() const { return globalHandlerName.c_str(); } virtual JS::Value ToJSVal(const ScriptInterface& UNUSED(scriptInterface)) const { return msg.get(); } CMessageScripted(const ScriptInterface& scriptInterface, int mtid, const std::string& name, JS::HandleValue msg) : mtid(mtid), handlerName("On" + name), globalHandlerName("OnGlobal" + name), msg(scriptInterface.GetJSRuntime(), msg) { } int mtid; std::string handlerName; std::string globalHandlerName; JS::PersistentRootedValue msg; }; CComponentManager::CComponentManager(CSimContext& context, shared_ptr rt, bool skipScriptFunctions) : m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine", "Simulation", rt), m_SimContext(context), m_CurrentlyHotloading(false) { context.SetComponentManager(this); m_ScriptInterface.SetCallbackData(static_cast (this)); m_ScriptInterface.ReplaceNondeterministicRNG(m_RNG); // For component script tests, the test system sets up its own scripted implementation of // these functions, so we skip registering them here in those cases if (!skipScriptFunctions) { JSI_VFS::RegisterScriptFunctions_Simulation(m_ScriptInterface); m_ScriptInterface.RegisterFunction ("RegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterSystemComponentType"); m_ScriptInterface.RegisterFunction ("ReRegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterInterface"); m_ScriptInterface.RegisterFunction ("RegisterMessageType"); m_ScriptInterface.RegisterFunction ("RegisterGlobal"); m_ScriptInterface.RegisterFunction ("QueryInterface"); m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface"); m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetComponentsWithInterface> ("GetComponentsWithInterface"); m_ScriptInterface.RegisterFunction ("PostMessage"); m_ScriptInterface.RegisterFunction ("BroadcastMessage"); m_ScriptInterface.RegisterFunction ("AddEntity"); m_ScriptInterface.RegisterFunction ("AddLocalEntity"); m_ScriptInterface.RegisterFunction ("DestroyEntity"); m_ScriptInterface.RegisterFunction ("FlushDestroyedEntities"); } // Globalscripts may use VFS script functions m_ScriptInterface.LoadGlobalScripts(); // Define MT_*, IID_* as script globals, and store their names #define MESSAGE(name) m_ScriptInterface.SetGlobal("MT_" #name, (int)MT_##name); #define INTERFACE(name) \ m_ScriptInterface.SetGlobal("IID_" #name, (int)IID_##name); \ m_InterfaceIdsByName[#name] = IID_##name; #define COMPONENT(name) #include "simulation2/TypeList.h" #undef MESSAGE #undef INTERFACE #undef COMPONENT m_ScriptInterface.SetGlobal("INVALID_ENTITY", (int)INVALID_ENTITY); m_ScriptInterface.SetGlobal("INVALID_PLAYER", (int)INVALID_PLAYER); m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); m_ComponentsByInterface.resize(IID__LastNative); ResetState(); } CComponentManager::~CComponentManager() { ResetState(); } void CComponentManager::LoadComponentTypes() { #define MESSAGE(name) \ RegisterMessageType(MT_##name, #name); #define INTERFACE(name) \ extern void RegisterComponentInterface_##name(ScriptInterface&); \ RegisterComponentInterface_##name(m_ScriptInterface); #define COMPONENT(name) \ extern void RegisterComponentType_##name(CComponentManager&); \ m_CurrentComponent = CID_##name; \ RegisterComponentType_##name(*this); #include "simulation2/TypeList.h" m_CurrentComponent = CID__Invalid; #undef MESSAGE #undef INTERFACE #undef COMPONENT } bool CComponentManager::LoadScript(const VfsPath& filename, bool hotload) { m_CurrentlyHotloading = hotload; CVFSFile file; PSRETURN loadOk = file.Load(g_VFS, filename); if (loadOk != PSRETURN_OK) // VFS will log the failed file and the reason return false; std::string content = file.DecodeUTF8(); // assume it's UTF-8 bool ok = m_ScriptInterface.LoadScript(filename, content); m_CurrentlyHotloading = false; return ok; } void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); JSContext* cx = componentManager->m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); // Find the C++ component that wraps the interface int cidWrapper = componentManager->GetScriptWrapper(iid); if (cidWrapper == CID__Invalid) { componentManager->m_ScriptInterface.ReportError("Invalid interface id"); return; } const ComponentType& ctWrapper = componentManager->m_ComponentTypesById[cidWrapper]; bool mustReloadComponents = false; // for hotloading ComponentTypeId cid = componentManager->LookupCID(cname); if (cid == CID__Invalid) { if (reRegister) { componentManager->m_ScriptInterface.ReportError(("ReRegistering component type that was not registered before '" + cname + "'").c_str()); return; } // Allocate a new cid number cid = componentManager->m_NextScriptComponentTypeId++; componentManager->m_ComponentTypeIdsByName[cname] = cid; if (systemComponent) componentManager->MarkScriptedComponentForSystemEntity(cid); } else { // Component type is already loaded, so do hotloading: if (!componentManager->m_CurrentlyHotloading && !reRegister) { componentManager->m_ScriptInterface.ReportError(("Registering component type with already-registered name '" + cname + "'").c_str()); return; } const ComponentType& ctPrevious = componentManager->m_ComponentTypesById[cid]; // We can only replace scripted component types, not native ones if (ctPrevious.type != CT_Script) { componentManager->m_ScriptInterface.ReportError(("Loading script component type with same name '" + cname + "' as native component").c_str()); return; } // We don't support changing the IID of a component type (it would require fiddling // around with m_ComponentsByInterface and being careful to guarantee uniqueness per entity) if (ctPrevious.iid != iid) { // ...though it only matters if any components exist with this type if (!componentManager->m_ComponentsByTypeId[cid].empty()) { componentManager->m_ScriptInterface.ReportError("Hotloading script component type mustn't change interface ID"); return; } } // Remove the old component type's message subscriptions std::map >::iterator it; for (it = componentManager->m_LocalMessageSubscriptions.begin(); it != componentManager->m_LocalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } for (it = componentManager->m_GlobalMessageSubscriptions.begin(); it != componentManager->m_GlobalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } mustReloadComponents = true; } JS::RootedValue protoVal(cx); if (!componentManager->m_ScriptInterface.GetProperty(ctor, "prototype", &protoVal)) { componentManager->m_ScriptInterface.ReportError("Failed to get property 'prototype'"); return; } if (!protoVal.isObject()) { componentManager->m_ScriptInterface.ReportError("Component has no constructor"); return; } std::string schema = ""; if (componentManager->m_ScriptInterface.HasProperty(protoVal, "Schema")) componentManager->m_ScriptInterface.GetProperty(protoVal, "Schema", schema); // Construct a new ComponentType, using the wrapper's alloc functions - ComponentType ct( + ComponentType ct{ CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, schema, DefPersistentRooted(cx, ctor) - ); + }; componentManager->m_ComponentTypesById[cid] = std::move(ct); componentManager->m_CurrentComponent = cid; // needed by Subscribe // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: std::vector methods; if (!componentManager->m_ScriptInterface.EnumeratePropertyNamesWithPrefix(protoVal, "On", methods)) { componentManager->m_ScriptInterface.ReportError("Failed to enumerate 'On' messages"); return; } for (std::vector::const_iterator it = methods.begin(); it != methods.end(); ++it) { std::string name = (*it).substr(2); // strip the "On" prefix // Handle "OnGlobalFoo" functions specially bool isGlobal = false; if (name.substr(0, 6) == "Global") { isGlobal = true; name = name.substr(6); } std::map::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name); if (mit == componentManager->m_MessageTypeIdsByName.end()) { componentManager->m_ScriptInterface.ReportError(("Registered component has unrecognized '" + *it + "' message handler method").c_str()); return; } if (isGlobal) componentManager->SubscribeGloballyToMessageType(mit->second); else componentManager->SubscribeToMessageType(mit->second); } componentManager->m_CurrentComponent = CID__Invalid; if (mustReloadComponents) { // For every script component with this cid, we need to switch its // prototype from the old constructor's prototype property to the new one's const std::map& comps = componentManager->m_ComponentsByTypeId[cid]; std::map::const_iterator eit = comps.begin(); for (; eit != comps.end(); ++eit) { JS::RootedValue instance(cx, eit->second->GetJSInstance()); if (!instance.isNull()) componentManager->m_ScriptInterface.SetPrototype(instance, protoVal); } } } void CComponentManager::Script_RegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, false); componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); } void CComponentManager::Script_RegisterSystemComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, true); componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); } void CComponentManager::Script_ReRegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor) { Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, true, false); } void CComponentManager::Script_RegisterInterface(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::map::iterator it = componentManager->m_InterfaceIdsByName.find(name); if (it != componentManager->m_InterfaceIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!componentManager->m_CurrentlyHotloading) componentManager->m_ScriptInterface.ReportError(("Registering interface with already-registered name '" + name + "'").c_str()); return; } // IIDs start at 1, so size+1 is the next unused one size_t id = componentManager->m_InterfaceIdsByName.size() + 1; componentManager->m_InterfaceIdsByName[name] = (InterfaceId)id; componentManager->m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterMessageType(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::map::iterator it = componentManager->m_MessageTypeIdsByName.find(name); if (it != componentManager->m_MessageTypeIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!componentManager->m_CurrentlyHotloading) componentManager->m_ScriptInterface.ReportError(("Registering message type with already-registered name '" + name + "'").c_str()); return; } // MTIDs start at 1, so size+1 is the next unused one size_t id = componentManager->m_MessageTypeIdsByName.size() + 1; componentManager->RegisterMessageType((MessageTypeId)id, name.c_str()); componentManager->m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterGlobal(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name, JS::HandleValue value) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, componentManager->m_CurrentlyHotloading); } IComponent* CComponentManager::Script_QueryInterface(ScriptInterface::CxPrivate* pCxPrivate, int ent, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); IComponent* component = componentManager->QueryInterface((entity_id_t)ent, iid); return component; } std::vector CComponentManager::Script_GetEntitiesWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::vector ret; const InterfaceListUnordered& ents = componentManager->GetEntitiesWithInterfaceUnordered(iid); for (InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) if (!ENTITY_IS_LOCAL(it->first)) ret.push_back(it->first); std::sort(ret.begin(), ret.end()); return ret; } std::vector CComponentManager::Script_GetComponentsWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::vector ret; InterfaceList ents = componentManager->GetEntitiesWithInterface(iid); for (InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) ret.push_back(it->second); // TODO: maybe we should exclude local entities return ret; } CMessage* CComponentManager::ConstructMessage(int mtid, JS::HandleValue data) { if (mtid == MT__Invalid || mtid > (int)m_MessageTypeIdsByName.size()) // (IDs start at 1 so use '>' here) LOGERROR("PostMessage with invalid message type ID '%d'", mtid); if (mtid < MT__LastNative) { return CMessageFromJSVal(mtid, m_ScriptInterface, data); } else { return new CMessageScripted(m_ScriptInterface, mtid, m_MessageTypeNamesById[mtid], data); } } void CComponentManager::Script_PostMessage(ScriptInterface::CxPrivate* pCxPrivate, int ent, int mtid, JS::HandleValue data) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->PostMessage(ent, *msg); delete msg; } void CComponentManager::Script_BroadcastMessage(ScriptInterface::CxPrivate* pCxPrivate, int mtid, JS::HandleValue data) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->BroadcastMessage(*msg); delete msg; } int CComponentManager::Script_AddEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewEntity()); return (int)ent; } int CComponentManager::Script_AddLocalEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewLocalEntity()); return (int)ent; } void CComponentManager::Script_DestroyEntity(ScriptInterface::CxPrivate* pCxPrivate, int ent) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->DestroyComponentsSoon(ent); } void CComponentManager::Script_FlushDestroyedEntities(ScriptInterface::CxPrivate *pCxPrivate) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->FlushDestroyedComponents(); } void CComponentManager::ResetState() { // Delete all dynamic message subscriptions m_DynamicMessageSubscriptionsNonsync.clear(); m_DynamicMessageSubscriptionsNonsyncByComponent.clear(); // Delete all IComponents std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.begin(); for (; eit != iit->second.end(); ++eit) { eit->second->Deinit(); m_ComponentTypesById[iit->first].dealloc(eit->second); } } std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) ifcit->clear(); m_ComponentsByTypeId.clear(); // Delete all SEntityComponentCaches std::unordered_map::iterator ccit = m_ComponentCaches.begin(); for (; ccit != m_ComponentCaches.end(); ++ccit) free(ccit->second); m_ComponentCaches.clear(); m_SystemEntity = CEntityHandle(); m_DestructionQueue.clear(); // Reset IDs m_NextEntityId = SYSTEM_ENTITY + 1; m_NextLocalEntityId = FIRST_LOCAL_ENTITY; } void CComponentManager::SetRNGSeed(u32 seed) { m_RNG.seed(seed); } void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { - ComponentType c(CT_Native, iid, alloc, dealloc, name, schema, DefPersistentRooted()); + ComponentType c{ CT_Native, iid, alloc, dealloc, name, schema, DefPersistentRooted() }; m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; } void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { - ComponentType c(CT_ScriptWrapper, iid, alloc, dealloc, name, schema, DefPersistentRooted()); + ComponentType c{ CT_ScriptWrapper, iid, alloc, dealloc, name, schema, DefPersistentRooted() }; m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; // TODO: merge with RegisterComponentType } void CComponentManager::MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid) { m_ScriptedSystemComponents.push_back(cid); } void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name) { m_MessageTypeIdsByName[name] = mtid; m_MessageTypeNamesById[mtid] = name; } void CComponentManager::SubscribeToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_LocalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::SubscribeGloballyToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_GlobalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::FlattenDynamicSubscriptions() { std::map::iterator it; for (it = m_DynamicMessageSubscriptionsNonsync.begin(); it != m_DynamicMessageSubscriptionsNonsync.end(); ++it) { it->second.Flatten(); } } void CComponentManager::DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enable) { if (enable) { bool newlyInserted = m_DynamicMessageSubscriptionsNonsyncByComponent[component].insert(mtid).second; if (newlyInserted) m_DynamicMessageSubscriptionsNonsync[mtid].Add(component); } else { size_t numRemoved = m_DynamicMessageSubscriptionsNonsyncByComponent[component].erase(mtid); if (numRemoved) m_DynamicMessageSubscriptionsNonsync[mtid].Remove(component); } } void CComponentManager::RemoveComponentDynamicSubscriptions(IComponent* component) { std::map >::iterator it = m_DynamicMessageSubscriptionsNonsyncByComponent.find(component); if (it == m_DynamicMessageSubscriptionsNonsyncByComponent.end()) return; std::set::iterator mtit; for (mtit = it->second.begin(); mtit != it->second.end(); ++mtit) { m_DynamicMessageSubscriptionsNonsync[*mtit].Remove(component); // Need to flatten the subscription lists immediately to avoid dangling IComponent* references m_DynamicMessageSubscriptionsNonsync[*mtit].Flatten(); } m_DynamicMessageSubscriptionsNonsyncByComponent.erase(it); } CComponentManager::ComponentTypeId CComponentManager::LookupCID(const std::string& cname) const { std::map::const_iterator it = m_ComponentTypeIdsByName.find(cname); if (it == m_ComponentTypeIdsByName.end()) return CID__Invalid; return it->second; } std::string CComponentManager::LookupComponentTypeName(ComponentTypeId cid) const { std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) return ""; return it->second.name; } CComponentManager::ComponentTypeId CComponentManager::GetScriptWrapper(InterfaceId iid) { if (iid >= IID__LastNative && iid <= (int)m_InterfaceIdsByName.size()) // use <= since IDs start at 1 return CID_UnknownScript; std::map::const_iterator it = m_ComponentTypesById.begin(); for (; it != m_ComponentTypesById.end(); ++it) if (it->second.iid == iid && it->second.type == CT_ScriptWrapper) return it->first; std::map::const_iterator iiit = m_InterfaceIdsByName.begin(); for (; iiit != m_InterfaceIdsByName.end(); ++iiit) if (iiit->second == iid) { LOGERROR("No script wrapper found for interface id %d '%s'", iid, iiit->first.c_str()); return CID__Invalid; } LOGERROR("No script wrapper found for interface id %d", iid); return CID__Invalid; } entity_id_t CComponentManager::AllocateNewEntity() { entity_id_t id = m_NextEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewLocalEntity() { entity_id_t id = m_NextLocalEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewEntity(entity_id_t preferredId) { // TODO: ensure this ID hasn't been allocated before // (this might occur with broken map files) // Trying to actually add two entities with the same id will fail in AddEntitiy entity_id_t id = preferredId; // Ensure this ID won't be allocated again if (id >= m_NextEntityId) m_NextEntityId = id+1; // TODO: check for overflow return id; } bool CComponentManager::AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode) { IComponent* component = ConstructComponent(ent, cid); if (!component) return false; component->Init(paramNode); return true; } void CComponentManager::AddSystemComponents(bool skipScriptedComponents, bool skipAI) { CParamNode noParam; AddComponent(m_SystemEntity, CID_TemplateManager, noParam); AddComponent(m_SystemEntity, CID_CinemaManager, noParam); AddComponent(m_SystemEntity, CID_CommandQueue, noParam); AddComponent(m_SystemEntity, CID_ObstructionManager, noParam); AddComponent(m_SystemEntity, CID_ParticleManager, noParam); AddComponent(m_SystemEntity, CID_Pathfinder, noParam); AddComponent(m_SystemEntity, CID_ProjectileManager, noParam); AddComponent(m_SystemEntity, CID_RangeManager, noParam); AddComponent(m_SystemEntity, CID_SoundManager, noParam); AddComponent(m_SystemEntity, CID_Terrain, noParam); AddComponent(m_SystemEntity, CID_TerritoryManager, noParam); AddComponent(m_SystemEntity, CID_UnitRenderer, noParam); AddComponent(m_SystemEntity, CID_WaterManager, noParam); // Add scripted system components: if (!skipScriptedComponents) { for (uint32_t i = 0; i < m_ScriptedSystemComponents.size(); ++i) AddComponent(m_SystemEntity, m_ScriptedSystemComponents[i], noParam); if (!skipAI) AddComponent(m_SystemEntity, CID_AIManager, noParam); } } IComponent* CComponentManager::ConstructComponent(CEntityHandle ent, ComponentTypeId cid) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) { LOGERROR("Invalid component id %d", cid); return NULL; } const ComponentType& ct = it->second; ENSURE((size_t)ct.iid < m_ComponentsByInterface.size()); std::unordered_map& emap1 = m_ComponentsByInterface[ct.iid]; if (emap1.find(ent.GetId()) != emap1.end()) { LOGERROR("Multiple components for interface %d", ct.iid); return NULL; } std::map& emap2 = m_ComponentsByTypeId[cid]; // If this is a scripted component, construct the appropriate JS object first JS::RootedValue obj(cx); if (ct.type == CT_Script) { m_ScriptInterface.CallConstructor(ct.ctor.get(), JS::HandleValueArray::empty(), &obj); if (obj.isNull()) { LOGERROR("Script component constructor failed"); return NULL; } } // Construct the new component IComponent* component = ct.alloc(m_ScriptInterface, obj); ENSURE(component); component->SetEntityHandle(ent); component->SetSimContext(m_SimContext); // Store a reference to the new component emap1.insert(std::make_pair(ent.GetId(), component)); emap2.insert(std::make_pair(ent.GetId(), component)); // TODO: We need to more careful about this - if an entity is constructed by a component // while we're iterating over all components, this will invalidate the iterators and everything // will break. // We probably need some kind of delayed addition, so they get pushed onto a queue and then // inserted into the world later on. (Be careful about immediation deletion in that case, too.) SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && ct.iid < (int)cache->numInterfaces && cache->interfaces[ct.iid] == NULL); cache->interfaces[ct.iid] = component; return component; } void CComponentManager::AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component) { // Just add it into the by-interface map, not the by-component-type map, // so it won't be considered for messages or deletion etc std::unordered_map& emap1 = m_ComponentsByInterface.at(iid); if (emap1.find(ent.GetId()) != emap1.end()) debug_warn(L"Multiple components for interface"); emap1.insert(std::make_pair(ent.GetId(), &component)); SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && iid < (int)cache->numInterfaces && cache->interfaces[iid] == NULL); cache->interfaces[iid] = &component; } CEntityHandle CComponentManager::AllocateEntityHandle(entity_id_t ent) { ENSURE(!EntityExists(ent)); // Interface IDs start at 1, and SEntityComponentCache is defined with a 1-sized array, // so we need space for an extra m_InterfaceIdsByName.size() items SEntityComponentCache* cache = (SEntityComponentCache*)calloc(1, sizeof(SEntityComponentCache) + sizeof(IComponent*) * m_InterfaceIdsByName.size()); ENSURE(cache != NULL); cache->numInterfaces = m_InterfaceIdsByName.size() + 1; m_ComponentCaches[ent] = cache; return CEntityHandle(ent, cache); } CEntityHandle CComponentManager::LookupEntityHandle(entity_id_t ent, bool allowCreate) { std::unordered_map::iterator it; it = m_ComponentCaches.find(ent); if (it == m_ComponentCaches.end()) { if (allowCreate) return AllocateEntityHandle(ent); else return CEntityHandle(ent, NULL); } else return CEntityHandle(ent, it->second); } void CComponentManager::InitSystemEntity() { ENSURE(m_SystemEntity.GetId() == INVALID_ENTITY); m_SystemEntity = AllocateEntityHandle(SYSTEM_ENTITY); m_SimContext.SetSystemEntity(m_SystemEntity); } entity_id_t CComponentManager::AddEntity(const std::wstring& templateName, entity_id_t ent) { ICmpTemplateManager *cmpTemplateManager = static_cast (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager)); if (!cmpTemplateManager) { debug_warn(L"No ICmpTemplateManager loaded"); return INVALID_ENTITY; } const CParamNode* tmpl = cmpTemplateManager->LoadTemplate(ent, utf8_from_wstring(templateName)); if (!tmpl) return INVALID_ENTITY; // LoadTemplate will have reported the error // This also ensures that ent does not exist CEntityHandle handle = AllocateEntityHandle(ent); // Construct a component for each child of the root element const CParamNode::ChildrenMap& tmplChilds = tmpl->GetChildren(); for (CParamNode::ChildrenMap::const_iterator it = tmplChilds.begin(); it != tmplChilds.end(); ++it) { // Ignore attributes on the root element if (it->first.length() && it->first[0] == '@') continue; CComponentManager::ComponentTypeId cid = LookupCID(it->first); if (cid == CID__Invalid) { LOGERROR("Unrecognized component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } if (!AddComponent(handle, cid, it->second)) { LOGERROR("Failed to construct component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } // TODO: maybe we should delete already-constructed components if one of them fails? } CMessageCreate msg(ent); PostMessage(ent, msg); return ent; } bool CComponentManager::EntityExists(entity_id_t ent) const { return m_ComponentCaches.find(ent) != m_ComponentCaches.end(); } void CComponentManager::DestroyComponentsSoon(entity_id_t ent) { m_DestructionQueue.push_back(ent); } void CComponentManager::FlushDestroyedComponents() { PROFILE2("Flush Destroyed Components"); while (!m_DestructionQueue.empty()) { // Make a copy of the destruction queue, so that the iterators won't be invalidated if the // CMessageDestroy handlers try to destroy more entities themselves std::vector queue; queue.swap(m_DestructionQueue); for (std::vector::iterator it = queue.begin(); it != queue.end(); ++it) { entity_id_t ent = *it; // Do nothing if invalid, destroyed, etc. if (!EntityExists(ent)) continue; CEntityHandle handle = LookupEntityHandle(ent); CMessageDestroy msg(ent); PostMessage(ent, msg); // Flatten all the dynamic subscriptions to ensure there are no dangling // references in the 'removed' lists to components we're going to delete // Some components may have dynamically unsubscribed following the Destroy message FlattenDynamicSubscriptions(); // Destroy the components, and remove from m_ComponentsByTypeId: std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.find(ent); if (eit != iit->second.end()) { eit->second->Deinit(); RemoveComponentDynamicSubscriptions(eit->second); m_ComponentTypesById[iit->first].dealloc(eit->second); iit->second.erase(ent); handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = NULL; } } free(handle.GetComponentCache()); m_ComponentCaches.erase(ent); // Remove from m_ComponentsByInterface std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) { ifcit->erase(ent); } } } } IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return NULL; } std::unordered_map::const_iterator eit = m_ComponentsByInterface[iid].find(ent); if (eit == m_ComponentsByInterface[iid].end()) { // This entity doesn't implement this interface return NULL; } return eit->second; } CComponentManager::InterfaceList CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const { std::vector > ret; if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return ret; } ret.reserve(m_ComponentsByInterface[iid].size()); std::unordered_map::const_iterator it = m_ComponentsByInterface[iid].begin(); for (; it != m_ComponentsByInterface[iid].end(); ++it) ret.push_back(*it); std::sort(ret.begin(), ret.end()); // lexicographic pair comparison means this'll sort by entity ID return ret; } static CComponentManager::InterfaceListUnordered g_EmptyEntityMap; const CComponentManager::InterfaceListUnordered& CComponentManager::GetEntitiesWithInterfaceUnordered(InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return g_EmptyEntityMap; } return m_ComponentsByInterface[iid]; } void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) { PROFILE2_IFSPIKE("Post Message", 0.0005); PROFILE2_ATTR("%s", msg.GetScriptHandlerName()); // Send the message to components of ent, that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.find(ent); if (eit != emap->second.end()) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(ent, msg); } void CComponentManager::BroadcastMessage(const CMessage& msg) { // Send the message to components of all entities that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(INVALID_ENTITY, msg); } void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg) { PROFILE2_IFSPIKE("SendGlobalMessage", 0.001); PROFILE2_ATTR("%s", msg.GetScriptHandlerName()); // (Common functionality for PostMessage and BroadcastMessage) // Send the message to components of all entities that subscribed globally to this message std::map >::const_iterator it; it = m_GlobalMessageSubscriptions.find(msg.GetType()); if (it != m_GlobalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Special case: Messages for local entities shouldn't be sent to script // components that subscribed globally, so that we don't have to worry about // them accidentally picking up non-network-synchronised data. if (ENTITY_IS_LOCAL(ent)) { std::map::const_iterator it = m_ComponentTypesById.find(*ctit); if (it != m_ComponentTypesById.end() && it->second.type == CT_Script) continue; } // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, true); } } // Send the message to component instances that dynamically subscribed to this message std::map::iterator dit = m_DynamicMessageSubscriptionsNonsync.find(msg.GetType()); if (dit != m_DynamicMessageSubscriptionsNonsync.end()) { dit->second.Flatten(); const std::vector& dynamic = dit->second.GetComponents(); for (size_t i = 0; i < dynamic.size(); i++) dynamic[i]->HandleMessage(msg, false); } } std::string CComponentManager::GenerateSchema() const { std::string schema = "" "" "" "" "" "0" "" "" "0" "" "" "" "" "" "" "" "" "" "" "" "" ""; std::map > interfaceComponentTypes; std::vector componentTypes; for (std::map::const_iterator it = m_ComponentTypesById.begin(); it != m_ComponentTypesById.end(); ++it) { schema += "" "" "" + it->second.schema + "" "" ""; interfaceComponentTypes[it->second.iid].push_back(it->second.name); componentTypes.push_back(it->second.name); } // Declare the implementation of each interface, for documentation for (std::map::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it) { schema += ""; std::vector& cts = interfaceComponentTypes[it->second]; for (size_t i = 0; i < cts.size(); ++i) schema += ""; schema += ""; } // List all the component types, in alphabetical order (to match the reordering performed by CParamNode). // (We do it this way, rather than ing all the interface definitions (which would additionally perform // a check that we don't use multiple component types of the same interface in one file), because libxml2 gives // useless error messages in the latter case; this way lets it report the real error.) std::sort(componentTypes.begin(), componentTypes.end()); schema += "" "" ""; for (std::vector::const_iterator it = componentTypes.begin(); it != componentTypes.end(); ++it) schema += ""; schema += "" ""; schema += ""; return schema; } Index: ps/trunk/source/simulation2/system/ComponentManager.h =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.h (revision 23381) +++ ps/trunk/source/simulation2/system/ComponentManager.h (revision 23382) @@ -1,383 +1,343 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 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_COMPONENTMANAGER #define INCLUDED_COMPONENTMANAGER #include "ps/Filesystem.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptVal.h" #include "simulation2/helpers/Player.h" #include "simulation2/system/Components.h" #include "simulation2/system/Entity.h" #include #include #include #include class IComponent; class CParamNode; class CMessage; class CSimContext; class CDynamicSubscription; class CComponentManager { NONCOPYABLE(CComponentManager); public: // We can't use EInterfaceId/etc directly, since scripts dynamically generate new IDs // and casting arbitrary ints to enums is undefined behaviour, so use 'int' typedefs typedef int InterfaceId; typedef int ComponentTypeId; typedef int MessageTypeId; private: // Component allocation types typedef IComponent* (*AllocFunc)(const ScriptInterface& scriptInterface, JS::HandleValue ctor); typedef void (*DeallocFunc)(IComponent*); // ComponentTypes come in three types: // Native: normal C++ component // ScriptWrapper: C++ component that wraps a JS component implementation // Script: a ScriptWrapper linked to a specific JS component implementation enum EComponentTypeType { CT_Native, CT_ScriptWrapper, CT_Script }; // Representation of a component type, to be used when instantiating components struct ComponentType { EComponentTypeType type; InterfaceId iid; AllocFunc alloc; DeallocFunc dealloc; std::string name; std::string schema; // RelaxNG fragment DefPersistentRooted ctor; // only valid if type == CT_Script - - // TODO: Constructor, move assignment operator and move constructor only have to be - // explicitly defined for Visual Studio. VS2013 is still behind on C++11 support - // What's missing is what they call "Rvalue references v3.0", see - // https://msdn.microsoft.com/en-us/library/hh567368.aspx#rvref - ComponentType() {} - ComponentType (EComponentTypeType type, InterfaceId iid, AllocFunc alloc, - DeallocFunc dealloc, std::string name, std::string schema, DefPersistentRooted ctor) : - type(type), - iid(iid), - alloc(alloc), - dealloc(dealloc), - name(name), - schema(schema), - ctor(std::move(ctor)) - { - } - - ComponentType& operator= (ComponentType&& other) - { - type = std::move(other.type); - iid = std::move(other.iid); - alloc = std::move(other.alloc); - dealloc = std::move(other.dealloc); - name = std::move(other.name); - schema = std::move(other.schema); - ctor = std::move(other.ctor); - return *this; - } - - ComponentType(ComponentType&& other) - { - type = std::move(other.type); - iid = std::move(other.iid); - alloc = std::move(other.alloc); - dealloc = std::move(other.dealloc); - name = std::move(other.name); - schema = std::move(other.schema); - ctor = std::move(other.ctor); - } }; public: CComponentManager(CSimContext&, shared_ptr rt, bool skipScriptFunctions = false); ~CComponentManager(); void LoadComponentTypes(); /** * Load a script and execute it in a new function scope. * @param filename VFS path to load * @param hotload set to true if this script has been loaded before, and redefinitions of * existing components should not be considered errors */ bool LoadScript(const VfsPath& filename, bool hotload = false); void RegisterMessageType(MessageTypeId mtid, const char* name); void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); void MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid); /** * Subscribe the current component type to the given message type. * Each component's HandleMessage will be called on any BroadcastMessage of this message type, * or on any PostMessage of this type targeted at the component's entity. * Must only be called by a component type's ClassInit. */ void SubscribeToMessageType(MessageTypeId mtid); /** * Subscribe the current component type to all messages of the given message type. * Each component's HandleMessage will be called on any BroadcastMessage or PostMessage of this message type, * regardless of the entity. * Must only be called by a component type's ClassInit. */ void SubscribeGloballyToMessageType(MessageTypeId mtid); /** * Subscribe the given component instance to all messages of the given message type. * The component's HandleMessage will be called on any BroadcastMessage or PostMessage of * this message type, regardless of the entity. * * This can be called at any time (including inside the HandleMessage callback for this message type). * * The component type must not have statically subscribed to this message type in its ClassInit. * * The subscription status is not saved or network-synchronised. Components must remember to * resubscribe in their Deserialize methods if they still want the message. * * This is primarily intended for Interpolate and RenderSubmit messages, to avoid the cost of * sending the message to components that do not currently need to do any rendering. */ void DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enabled); /** * @param cname Requested component type name (not including any "CID" or "CCmp" prefix) * @return The component type id, or CID__Invalid if not found */ ComponentTypeId LookupCID(const std::string& cname) const; /** * @return The name of the given component type, or "" if not found */ std::string LookupComponentTypeName(ComponentTypeId cid) const; /** * Set up an empty SYSTEM_ENTITY. Must be called after ResetState() and before GetSystemEntity(). */ void InitSystemEntity(); /** * Returns a CEntityHandle with id SYSTEM_ENTITY. */ CEntityHandle GetSystemEntity() { ASSERT(m_SystemEntity.GetId() == SYSTEM_ENTITY); return m_SystemEntity; } /** * Returns a CEntityHandle with id @p ent. * If @p allowCreate is true and there is no existing CEntityHandle, a new handle will be allocated. */ CEntityHandle LookupEntityHandle(entity_id_t ent, bool allowCreate = false); /** * Returns true if the entity with id @p ent exists. */ bool EntityExists(entity_id_t ent) const; /** * Returns a new entity ID that has never been used before. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(); /** * Returns a new local entity ID that has never been used before. * This entity will not be synchronised over the network, stored in saved games, etc. */ entity_id_t AllocateNewLocalEntity(); /** * Returns a new entity ID that has never been used before. * If possible, returns preferredId, and ensures this ID won't be allocated again. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(entity_id_t preferredId); /** * Constructs a component of type 'cid', initialised with data 'paramNode', * and attaches it to entity 'ent'. * * @return true on success; false on failure, and logs an error message */ bool AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode); /** * Add all system components to the system entity (skip the scripted components or the AI components on demand) */ void AddSystemComponents(bool skipScriptedComponents, bool skipAI); /** * Adds an externally-created component, so that it is returned by QueryInterface * but does not get destroyed and does not receive messages from the component manager. * (This is intended for unit tests that need to add mock objects the tested components * expect to exist.) */ void AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component); /** * Allocates a component object of type 'cid', and attaches it to entity 'ent'. * (The component's Init is not called here - either Init or Deserialize must be called * before using the returned object.) */ IComponent* ConstructComponent(CEntityHandle ent, ComponentTypeId cid); /** * Constructs an entity based on the given template, and adds it the world with * entity ID @p ent. There should not be any existing components with that entity ID. * @return ent, or INVALID_ENTITY on error */ entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent); /** * Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called. * Has no effect if the entity does not exist, or has already been added to the destruction queue. */ void DestroyComponentsSoon(entity_id_t ent); /** * Does the actual destruction of components from DestroyComponentsSoon. * This must not be called if the component manager is on the call stack (since it * will break internal iterators). */ void FlushDestroyedComponents(); IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const; using InterfacePair = std::pair; using InterfaceList = std::vector; using InterfaceListUnordered = std::unordered_map; InterfaceList GetEntitiesWithInterface(InterfaceId iid) const; const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(InterfaceId iid) const; /** * Send a message, targeted at a particular entity. The message will be received by any * components of that entity which subscribed to the message type, and by any other components * that subscribed globally to the message type. */ void PostMessage(entity_id_t ent, const CMessage& msg); /** * Send a message, not targeted at any particular entity. The message will be received by any * components that subscribed (either globally or not) to the message type. */ void BroadcastMessage(const CMessage& msg); /** * Resets the dynamic simulation state (deletes all entities, resets entity ID counters; * doesn't unload/reload component scripts). */ void ResetState(); /** * Initializes the random number generator with a seed determined by the host. */ void SetRNGSeed(u32 seed); // Various state serialization functions: bool ComputeStateHash(std::string& outHash, bool quick) const; bool DumpDebugState(std::ostream& stream, bool includeDebugInfo) const; // FlushDestroyedComponents must be called before SerializeState (since the destruction queue // won't get serialized) bool SerializeState(std::ostream& stream) const; bool DeserializeState(std::istream& stream); std::string GenerateSchema() const; ScriptInterface& GetScriptInterface() { return m_ScriptInterface; } private: // Implementations of functions exposed to scripts static void Script_RegisterComponentType_Common(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent); static void Script_RegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_RegisterSystemComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_ReRegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_RegisterInterface(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); static void Script_RegisterMessageType(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); static void Script_RegisterGlobal(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name, JS::HandleValue value); static IComponent* Script_QueryInterface(ScriptInterface::CxPrivate* pCxPrivate, int ent, int iid); static std::vector Script_GetEntitiesWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid); static std::vector Script_GetComponentsWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid); static void Script_PostMessage(ScriptInterface::CxPrivate* pCxPrivate, int ent, int mtid, JS::HandleValue data); static void Script_BroadcastMessage(ScriptInterface::CxPrivate* pCxPrivate, int mtid, JS::HandleValue data); static int Script_AddEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static int Script_AddLocalEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static void Script_DestroyEntity(ScriptInterface::CxPrivate* pCxPrivate, int ent); static void Script_FlushDestroyedEntities(ScriptInterface::CxPrivate* pCxPrivate); CMessage* ConstructMessage(int mtid, JS::HandleValue data); void SendGlobalMessage(entity_id_t ent, const CMessage& msg); void FlattenDynamicSubscriptions(); void RemoveComponentDynamicSubscriptions(IComponent* component); ComponentTypeId GetScriptWrapper(InterfaceId iid); CEntityHandle AllocateEntityHandle(entity_id_t ent); ScriptInterface m_ScriptInterface; CSimContext& m_SimContext; CEntityHandle m_SystemEntity; ComponentTypeId m_CurrentComponent; // used when loading component types bool m_CurrentlyHotloading; // TODO: some of these should be vectors std::map m_ComponentTypesById; std::vector m_ScriptedSystemComponents; std::vector > m_ComponentsByInterface; // indexed by InterfaceId std::map > m_ComponentsByTypeId; std::map > m_LocalMessageSubscriptions; std::map > m_GlobalMessageSubscriptions; std::map m_ComponentTypeIdsByName; std::map m_MessageTypeIdsByName; std::map m_MessageTypeNamesById; std::map m_InterfaceIdsByName; std::map m_DynamicMessageSubscriptionsNonsync; std::map > m_DynamicMessageSubscriptionsNonsyncByComponent; std::unordered_map m_ComponentCaches; // TODO: maintaining both ComponentsBy* is nasty; can we get rid of one, // while keeping QueryInterface and PostMessage sufficiently efficient? std::vector m_DestructionQueue; ComponentTypeId m_NextScriptComponentTypeId; entity_id_t m_NextEntityId; entity_id_t m_NextLocalEntityId; boost::rand48 m_RNG; friend class TestComponentManager; }; #endif // INCLUDED_COMPONENTMANAGER