From 8f67b628ef8e4854eac13a5df1bbe37104335ca4 Mon Sep 17 00:00:00 2001 From: Dmitry Tsarevich Date: Mon, 25 Mar 2024 14:47:17 +0300 Subject: [PATCH] Initial commit --- .clang-format | 37 + .github/workflows/build.yml | 43 + .gitignore | 398 ++ LICENSE | 55 + devtools/base.xcconfig | 28 + devtools/debug.xcconfig | 2 + devtools/makefile_base_posix.mak | 352 ++ devtools/release.xcconfig | 1 + interfaces/interfaces.cpp | 251 + public/appframework/iappsystem.h | 92 + public/color.h | 83 + public/datamap.h | 651 ++ public/filesystem.h | 1034 +++ public/filesystem_passthru.h | 643 ++ public/icvar.h | 189 + public/ilaunchabledll.h | 16 + public/interfaces/interfaces.h | 264 + public/mathlib/fltx4.h | 94 + public/mathlib/math_pfns.h | 238 + public/mathlib/mathlib.h | 2296 +++++++ public/mathlib/vector.h | 2329 +++++++ public/mathlib/vector2d.h | 579 ++ public/p4lib/ip4.h | 184 + public/tier0/annotations.h | 84 + public/tier0/basetypes.h | 597 ++ public/tier0/commonmacros.h | 40 + public/tier0/dbg.h | 736 +++ public/tier0/dbgflag.h | 69 + public/tier0/etwprof.h | 155 + public/tier0/eventmasks.h | 392 ++ public/tier0/eventmodes.h | 1094 ++++ public/tier0/fasttimer.h | 515 ++ public/tier0/ia32detect.h | 273 + public/tier0/icommandline.h | 55 + public/tier0/ioctlcodes.h | 25 + public/tier0/k8performancecounters.h | 1447 +++++ public/tier0/l2cache.h | 34 + public/tier0/logging.h | 700 ++ public/tier0/mem.h | 35 + public/tier0/memalloc.h | 684 ++ public/tier0/memdbgoff.h | 22 + public/tier0/memdbgon.h | 279 + public/tier0/memvirt.h | 37 + public/tier0/minidump.h | 74 + public/tier0/p4performancecounters.h | 290 + public/tier0/p5p6performancecounters.h | 213 + public/tier0/platform.h | 1932 ++++++ public/tier0/pmelib.h | 169 + public/tier0/stackstats.h | 1141 ++++ public/tier0/stacktools.h | 191 + public/tier0/threadtools.h | 2576 ++++++++ public/tier0/threadtools.inl | 579 ++ public/tier0/tslist.h | 729 +++ public/tier0/validator.h | 61 + public/tier0/valobject.h | 66 + public/tier0/valve_off.h | 22 + public/tier0/valve_on.h | 28 + public/tier0/vprof.h | 1421 +++++ public/tier0/vprof_sn.h | 28 + public/tier0/wchartypes.h | 94 + public/tier0/win32consoleio.h | 30 + public/tier0/xbox_codeline_defines.h | 9 + public/tier1/byteswap.h | 255 + public/tier1/characterset.h | 27 + public/tier1/checksum_crc.h | 27 + public/tier1/checksum_md5.h | 27 + public/tier1/convar.h | 916 +++ public/tier1/convar_serverbounded.h | 45 + public/tier1/exprevaluator.h | 69 + public/tier1/fmtstr.h | 370 ++ public/tier1/functors.h | 1574 +++++ public/tier1/generichash.h | 93 + public/tier1/iconvar.h | 139 + public/tier1/interface.h | 219 + public/tier1/keyvalues.h | 570 ++ public/tier1/mempool.h | 624 ++ public/tier1/memstack.h | 247 + public/tier1/netadr.h | 78 + public/tier1/refcount.h | 326 + public/tier1/stringpool.h | 83 + public/tier1/strtools.h | 600 ++ public/tier1/tier1.h | 55 + public/tier1/utlblockmemory.h | 321 + public/tier1/utlbuffer.h | 1237 ++++ public/tier1/utldict.h | 296 + public/tier1/utlenvelope.h | 184 + public/tier1/utlfixedmemory.h | 323 + public/tier1/utlgraph.h | 591 ++ public/tier1/utlhash.h | 1231 ++++ public/tier1/utllinkedlist.h | 956 +++ public/tier1/utlmap.h | 209 + public/tier1/utlmemory.h | 1044 +++ public/tier1/utlmultilist.h | 676 ++ public/tier1/utlqueue.h | 152 + public/tier1/utlrbtree.h | 1380 ++++ public/tier1/utlsortvector.h | 305 + public/tier1/utlstack.h | 291 + public/tier1/utlstring.h | 372 ++ public/tier1/utlsymbol.h | 304 + public/tier1/utlvector.h | 1099 ++++ public/tier2/tier2.h | 88 + public/unitlib/unitlib.h | 238 + public/vstdlib/cvar.h | 12 + public/vstdlib/ikeyvaluessystem.h | 54 + public/vstdlib/pch_vstdlib.h | 34 + public/vstdlib/random.h | 90 + public/vstdlib/vstdlib.h | 28 + public/vstdlib/vstrtools.h | 250 + public/winlite.h | 25 + tier0/ValveETWProviderEvents.h | 1700 +++++ tier0/assert_dialog.cpp | 507 ++ tier0/commandline.cpp | 568 ++ tier0/cpu.cpp | 479 ++ tier0/cpu_posix.cpp | 129 + tier0/cputopology.cpp | 834 +++ tier0/cputopology.h | 36 + tier0/dbg.cpp | 574 ++ tier0/dlmalloc/malloc-2.8.3.h | 545 ++ tier0/dlmalloc/malloc.cpp | 6307 +++++++++++++++++++ tier0/etwprof.cpp | 355 ++ tier0/fasttimer.cpp | 17 + tier0/logging.cpp | 667 ++ tier0/mem.cpp | 62 + tier0/mem_helpers.cpp | 156 + tier0/mem_helpers.h | 33 + tier0/mem_impl_type.h | 8 + tier0/memdbg.cpp | 2718 ++++++++ tier0/memstd.cpp | 2275 +++++++ tier0/memstd.h | 448 ++ tier0/memvalidate.cpp | 498 ++ tier0/minidump.cpp | 291 + tier0/pch_tier0.cpp | 3 + tier0/pch_tier0.h | 38 + tier0/platform.cpp | 486 ++ tier0/platform_posix.cpp | 421 ++ tier0/pme.cpp | 136 + tier0/pme_posix.cpp | 35 + tier0/pmelib.cpp | 574 ++ tier0/resource.h | 30 + tier0/stacktools.cpp | 1699 +++++ tier0/threadtools.cpp | 3075 +++++++++ tier0/tier0_strtools.cpp | 41 + tier0/tier0_strtools.h | 8 + tier0/valobject.cpp | 106 + tier0/vprof.cpp | 1593 +++++ tier0/win32consoleio.cpp | 168 + tier1/characterset.cpp | 31 + tier1/checksum_crc.cpp | 145 + tier1/checksum_md5.cpp | 276 + tier1/convar.cpp | 1278 ++++ tier1/exprevaluator.cpp | 429 ++ tier1/generichash.cpp | 404 ++ tier1/interface.cpp | 625 ++ tier1/keyvalues.cpp | 2989 +++++++++ tier1/mempool.cpp | 289 + tier1/memstack.cpp | 408 ++ tier1/splitstring.cpp | 76 + tier1/stringpool.cpp | 364 ++ tier1/strtools.cpp | 2221 +++++++ tier1/tier1.cpp | 22 + tier1/utlbuffer.cpp | 1445 +++++ tier1/utlstring.cpp | 454 ++ tier1/utlsymbol.cpp | 457 ++ utils/vpc/Makefile | 299 + utils/vpc/baseprojectdatacollector.cpp | 341 + utils/vpc/baseprojectdatacollector.h | 135 + utils/vpc/conditionals.cpp | 210 + utils/vpc/config_general.cpp | 178 + utils/vpc/configuration.cpp | 431 ++ utils/vpc/dependencies.cpp | 1140 ++++ utils/vpc/dependencies.h | 220 + utils/vpc/exprsimplifier.cpp | 266 + utils/vpc/generatordefinition.cpp | 352 ++ utils/vpc/generatordefinition.h | 114 + utils/vpc/groupscript.cpp | 369 ++ utils/vpc/ibaseprojectgenerator.h | 75 + utils/vpc/ibasesolutiongenerator.h | 15 + utils/vpc/macros.cpp | 163 + utils/vpc/main.cpp | 209 + utils/vpc/memory_reservation_x64.cpp | 124 + utils/vpc/memory_reservation_x64.h | 16 + utils/vpc/p4sln.cpp | 240 + utils/vpc/p4sln.h | 10 + utils/vpc/projectgenerator_codelite.cpp | 188 + utils/vpc/projectgenerator_codelite.h | 40 + utils/vpc/projectgenerator_makefile.cpp | 1152 ++++ utils/vpc/projectgenerator_ps3.cpp | 1242 ++++ utils/vpc/projectgenerator_ps3.h | 41 + utils/vpc/projectgenerator_ps3.inc | 137 + utils/vpc/projectgenerator_vcproj.cpp | 1773 ++++++ utils/vpc/projectgenerator_vcproj.h | 375 ++ utils/vpc/projectgenerator_win32.cpp | 326 + utils/vpc/projectgenerator_win32.h | 37 + utils/vpc/projectgenerator_win32.inc | 253 + utils/vpc/projectgenerator_win32_2010.cpp | 641 ++ utils/vpc/projectgenerator_win32_2010.h | 59 + utils/vpc/projectgenerator_win32_2010.inc | 300 + utils/vpc/projectgenerator_xbox360.cpp | 317 + utils/vpc/projectgenerator_xbox360.h | 36 + utils/vpc/projectgenerator_xbox360.inc | 192 + utils/vpc/projectgenerator_xbox360_2010.cpp | 611 ++ utils/vpc/projectgenerator_xbox360_2010.h | 59 + utils/vpc/projectgenerator_xbox360_2010.inc | 210 + utils/vpc/projectscript.cpp | 2286 +++++++ utils/vpc/scriptsource.cpp | 475 ++ utils/vpc/scriptsource.h | 90 + utils/vpc/solutiongenerator_codelite.cpp | 317 + utils/vpc/solutiongenerator_makefile.cpp | 374 ++ utils/vpc/solutiongenerator_win32.cpp | 387 ++ utils/vpc/solutiongenerator_xcode.cpp | 3436 ++++++++++ utils/vpc/sys_utils.cpp | 675 ++ utils/vpc/sys_utils.h | 140 + utils/vpc/vpc.cpp | 2519 ++++++++ utils/vpc/vpc.h | 518 ++ utils/vpc/vpc.sln | 22 + utils/vpc/vpc.vcxproj | 237 + utils/vpc/vpc.vcxproj.filters | 393 ++ utils/vpc/vpc.xcodeproj/project.pbxproj | 530 ++ utils/vpccrccheck/crccheck_shared.cpp | 601 ++ utils/vpccrccheck/crccheck_shared.h | 38 + utils/vpccrccheck/vpccrccheck.cpp | 16 + utils/vpccrccheck/vpccrccheck.vpc | 33 + vpc.sln | 31 + vpc.vcxproj | 434 ++ vpc.vcxproj.filters | 702 +++ vstdlib/concommandhash.h | 204 + vstdlib/cvar.cpp | 1159 ++++ vstdlib/keyvaluessystem.cpp | 576 ++ vstdlib/random.cpp | 223 + vstdlib/vstrtools.cpp | 307 + 230 files changed, 114071 insertions(+) create mode 100644 .clang-format create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 devtools/base.xcconfig create mode 100644 devtools/debug.xcconfig create mode 100644 devtools/makefile_base_posix.mak create mode 100644 devtools/release.xcconfig create mode 100644 interfaces/interfaces.cpp create mode 100644 public/appframework/iappsystem.h create mode 100644 public/color.h create mode 100644 public/datamap.h create mode 100644 public/filesystem.h create mode 100644 public/filesystem_passthru.h create mode 100644 public/icvar.h create mode 100644 public/ilaunchabledll.h create mode 100644 public/interfaces/interfaces.h create mode 100644 public/mathlib/fltx4.h create mode 100644 public/mathlib/math_pfns.h create mode 100644 public/mathlib/mathlib.h create mode 100644 public/mathlib/vector.h create mode 100644 public/mathlib/vector2d.h create mode 100644 public/p4lib/ip4.h create mode 100644 public/tier0/annotations.h create mode 100644 public/tier0/basetypes.h create mode 100644 public/tier0/commonmacros.h create mode 100644 public/tier0/dbg.h create mode 100644 public/tier0/dbgflag.h create mode 100644 public/tier0/etwprof.h create mode 100644 public/tier0/eventmasks.h create mode 100644 public/tier0/eventmodes.h create mode 100644 public/tier0/fasttimer.h create mode 100644 public/tier0/ia32detect.h create mode 100644 public/tier0/icommandline.h create mode 100644 public/tier0/ioctlcodes.h create mode 100644 public/tier0/k8performancecounters.h create mode 100644 public/tier0/l2cache.h create mode 100644 public/tier0/logging.h create mode 100644 public/tier0/mem.h create mode 100644 public/tier0/memalloc.h create mode 100644 public/tier0/memdbgoff.h create mode 100644 public/tier0/memdbgon.h create mode 100644 public/tier0/memvirt.h create mode 100644 public/tier0/minidump.h create mode 100644 public/tier0/p4performancecounters.h create mode 100644 public/tier0/p5p6performancecounters.h create mode 100644 public/tier0/platform.h create mode 100644 public/tier0/pmelib.h create mode 100644 public/tier0/stackstats.h create mode 100644 public/tier0/stacktools.h create mode 100644 public/tier0/threadtools.h create mode 100644 public/tier0/threadtools.inl create mode 100644 public/tier0/tslist.h create mode 100644 public/tier0/validator.h create mode 100644 public/tier0/valobject.h create mode 100644 public/tier0/valve_off.h create mode 100644 public/tier0/valve_on.h create mode 100644 public/tier0/vprof.h create mode 100644 public/tier0/vprof_sn.h create mode 100644 public/tier0/wchartypes.h create mode 100644 public/tier0/win32consoleio.h create mode 100644 public/tier0/xbox_codeline_defines.h create mode 100644 public/tier1/byteswap.h create mode 100644 public/tier1/characterset.h create mode 100644 public/tier1/checksum_crc.h create mode 100644 public/tier1/checksum_md5.h create mode 100644 public/tier1/convar.h create mode 100644 public/tier1/convar_serverbounded.h create mode 100644 public/tier1/exprevaluator.h create mode 100644 public/tier1/fmtstr.h create mode 100644 public/tier1/functors.h create mode 100644 public/tier1/generichash.h create mode 100644 public/tier1/iconvar.h create mode 100644 public/tier1/interface.h create mode 100644 public/tier1/keyvalues.h create mode 100644 public/tier1/mempool.h create mode 100644 public/tier1/memstack.h create mode 100644 public/tier1/netadr.h create mode 100644 public/tier1/refcount.h create mode 100644 public/tier1/stringpool.h create mode 100644 public/tier1/strtools.h create mode 100644 public/tier1/tier1.h create mode 100644 public/tier1/utlblockmemory.h create mode 100644 public/tier1/utlbuffer.h create mode 100644 public/tier1/utldict.h create mode 100644 public/tier1/utlenvelope.h create mode 100644 public/tier1/utlfixedmemory.h create mode 100644 public/tier1/utlgraph.h create mode 100644 public/tier1/utlhash.h create mode 100644 public/tier1/utllinkedlist.h create mode 100644 public/tier1/utlmap.h create mode 100644 public/tier1/utlmemory.h create mode 100644 public/tier1/utlmultilist.h create mode 100644 public/tier1/utlqueue.h create mode 100644 public/tier1/utlrbtree.h create mode 100644 public/tier1/utlsortvector.h create mode 100644 public/tier1/utlstack.h create mode 100644 public/tier1/utlstring.h create mode 100644 public/tier1/utlsymbol.h create mode 100644 public/tier1/utlvector.h create mode 100644 public/tier2/tier2.h create mode 100644 public/unitlib/unitlib.h create mode 100644 public/vstdlib/cvar.h create mode 100644 public/vstdlib/ikeyvaluessystem.h create mode 100644 public/vstdlib/pch_vstdlib.h create mode 100644 public/vstdlib/random.h create mode 100644 public/vstdlib/vstdlib.h create mode 100644 public/vstdlib/vstrtools.h create mode 100644 public/winlite.h create mode 100644 tier0/ValveETWProviderEvents.h create mode 100644 tier0/assert_dialog.cpp create mode 100644 tier0/commandline.cpp create mode 100644 tier0/cpu.cpp create mode 100644 tier0/cpu_posix.cpp create mode 100644 tier0/cputopology.cpp create mode 100644 tier0/cputopology.h create mode 100644 tier0/dbg.cpp create mode 100644 tier0/dlmalloc/malloc-2.8.3.h create mode 100644 tier0/dlmalloc/malloc.cpp create mode 100644 tier0/etwprof.cpp create mode 100644 tier0/fasttimer.cpp create mode 100644 tier0/logging.cpp create mode 100644 tier0/mem.cpp create mode 100644 tier0/mem_helpers.cpp create mode 100644 tier0/mem_helpers.h create mode 100644 tier0/mem_impl_type.h create mode 100644 tier0/memdbg.cpp create mode 100644 tier0/memstd.cpp create mode 100644 tier0/memstd.h create mode 100644 tier0/memvalidate.cpp create mode 100644 tier0/minidump.cpp create mode 100644 tier0/pch_tier0.cpp create mode 100644 tier0/pch_tier0.h create mode 100644 tier0/platform.cpp create mode 100644 tier0/platform_posix.cpp create mode 100644 tier0/pme.cpp create mode 100644 tier0/pme_posix.cpp create mode 100644 tier0/pmelib.cpp create mode 100644 tier0/resource.h create mode 100644 tier0/stacktools.cpp create mode 100644 tier0/threadtools.cpp create mode 100644 tier0/tier0_strtools.cpp create mode 100644 tier0/tier0_strtools.h create mode 100644 tier0/valobject.cpp create mode 100644 tier0/vprof.cpp create mode 100644 tier0/win32consoleio.cpp create mode 100644 tier1/characterset.cpp create mode 100644 tier1/checksum_crc.cpp create mode 100644 tier1/checksum_md5.cpp create mode 100644 tier1/convar.cpp create mode 100644 tier1/exprevaluator.cpp create mode 100644 tier1/generichash.cpp create mode 100644 tier1/interface.cpp create mode 100644 tier1/keyvalues.cpp create mode 100644 tier1/mempool.cpp create mode 100644 tier1/memstack.cpp create mode 100644 tier1/splitstring.cpp create mode 100644 tier1/stringpool.cpp create mode 100644 tier1/strtools.cpp create mode 100644 tier1/tier1.cpp create mode 100644 tier1/utlbuffer.cpp create mode 100644 tier1/utlstring.cpp create mode 100644 tier1/utlsymbol.cpp create mode 100644 utils/vpc/Makefile create mode 100644 utils/vpc/baseprojectdatacollector.cpp create mode 100644 utils/vpc/baseprojectdatacollector.h create mode 100644 utils/vpc/conditionals.cpp create mode 100644 utils/vpc/config_general.cpp create mode 100644 utils/vpc/configuration.cpp create mode 100644 utils/vpc/dependencies.cpp create mode 100644 utils/vpc/dependencies.h create mode 100644 utils/vpc/exprsimplifier.cpp create mode 100644 utils/vpc/generatordefinition.cpp create mode 100644 utils/vpc/generatordefinition.h create mode 100644 utils/vpc/groupscript.cpp create mode 100644 utils/vpc/ibaseprojectgenerator.h create mode 100644 utils/vpc/ibasesolutiongenerator.h create mode 100644 utils/vpc/macros.cpp create mode 100644 utils/vpc/main.cpp create mode 100644 utils/vpc/memory_reservation_x64.cpp create mode 100644 utils/vpc/memory_reservation_x64.h create mode 100644 utils/vpc/p4sln.cpp create mode 100644 utils/vpc/p4sln.h create mode 100644 utils/vpc/projectgenerator_codelite.cpp create mode 100644 utils/vpc/projectgenerator_codelite.h create mode 100644 utils/vpc/projectgenerator_makefile.cpp create mode 100644 utils/vpc/projectgenerator_ps3.cpp create mode 100644 utils/vpc/projectgenerator_ps3.h create mode 100644 utils/vpc/projectgenerator_ps3.inc create mode 100644 utils/vpc/projectgenerator_vcproj.cpp create mode 100644 utils/vpc/projectgenerator_vcproj.h create mode 100644 utils/vpc/projectgenerator_win32.cpp create mode 100644 utils/vpc/projectgenerator_win32.h create mode 100644 utils/vpc/projectgenerator_win32.inc create mode 100644 utils/vpc/projectgenerator_win32_2010.cpp create mode 100644 utils/vpc/projectgenerator_win32_2010.h create mode 100644 utils/vpc/projectgenerator_win32_2010.inc create mode 100644 utils/vpc/projectgenerator_xbox360.cpp create mode 100644 utils/vpc/projectgenerator_xbox360.h create mode 100644 utils/vpc/projectgenerator_xbox360.inc create mode 100644 utils/vpc/projectgenerator_xbox360_2010.cpp create mode 100644 utils/vpc/projectgenerator_xbox360_2010.h create mode 100644 utils/vpc/projectgenerator_xbox360_2010.inc create mode 100644 utils/vpc/projectscript.cpp create mode 100644 utils/vpc/scriptsource.cpp create mode 100644 utils/vpc/scriptsource.h create mode 100644 utils/vpc/solutiongenerator_codelite.cpp create mode 100644 utils/vpc/solutiongenerator_makefile.cpp create mode 100644 utils/vpc/solutiongenerator_win32.cpp create mode 100644 utils/vpc/solutiongenerator_xcode.cpp create mode 100644 utils/vpc/sys_utils.cpp create mode 100644 utils/vpc/sys_utils.h create mode 100644 utils/vpc/vpc.cpp create mode 100644 utils/vpc/vpc.h create mode 100644 utils/vpc/vpc.sln create mode 100644 utils/vpc/vpc.vcxproj create mode 100644 utils/vpc/vpc.vcxproj.filters create mode 100644 utils/vpc/vpc.xcodeproj/project.pbxproj create mode 100644 utils/vpccrccheck/crccheck_shared.cpp create mode 100644 utils/vpccrccheck/crccheck_shared.h create mode 100644 utils/vpccrccheck/vpccrccheck.cpp create mode 100644 utils/vpccrccheck/vpccrccheck.vpc create mode 100644 vpc.sln create mode 100644 vpc.vcxproj create mode 100644 vpc.vcxproj.filters create mode 100644 vstdlib/concommandhash.h create mode 100644 vstdlib/cvar.cpp create mode 100644 vstdlib/keyvaluessystem.cpp create mode 100644 vstdlib/random.cpp create mode 100644 vstdlib/vstrtools.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8bf78cc --- /dev/null +++ b/.clang-format @@ -0,0 +1,37 @@ +# Copyright Valve Corporation, All rights reserved. + +# Defines the Chromium style for automatic reformatting. +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Google +# This defaults to 'Auto'. Explicitly set it for a while, so that +# 'vector >' in existing files gets formatted to +# 'vector>'. ('Auto' means that clang-format will only use +# 'int>>' if the file already contains at least one such instance.) +Standard: Cpp11 + +# Includes order matters +SortIncludes: false + +# Make sure code like: +# IPC_BEGIN_MESSAGE_MAP() +# IPC_MESSAGE_HANDLER(WidgetHostViewHost_Update, OnUpdate) +# IPC_END_MESSAGE_MAP() +# gets correctly indented. +MacroBlockBegin: "^\ +BEGIN_BITFIELD|\ +BEGIN_BYTESWAP_DATADESC|\ +BEGIN_DATADESC_GUTS|\ +BEGIN_DATADESC_GUTS_NAMESPACE +BEGIN_DATADESC_NO_BASE|\ +BEGIN_DEFINE_LOGGING_CHANNEL|\ +BEGIN_SIMPLE_DATADESC|\ +BEGIN_SIMPLE_DATADESC_$" +MacroBlockEnd: "^\ +END_BITFIELD|\ +END_BYTESWAP_DATADESC|\ +END_DATADESC|\ +END_DATADESC|\ +END_DATADESC|\ +END_DEFINE_LOGGING_CHANNEL|\ +END_DATADESC|\ +END_DATADESC$" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8e44b3b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Path to the solution file relative to the root of the project. + SOLUTION_FILE_PATH: vpc.sln + + # Configuration type to build. + BUILD_CONFIGURATION: Release + + # Build architecture + BUILD_PLATFORM: x64 + +permissions: + contents: read + +jobs: + build: + name: Build VPC + runs-on: windows-latest + + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Add msbuild / cmake / nmake to PATH + uses: ilammy/msvc-dev-cmd@v1 + + - name: Build the app + working-directory: ${{env.GITHUB_WORKSPACE}} + run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:Platform=${{env.BUILD_PLATFORM}} ${{env.SOLUTION_FILE_PATH}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a30d25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..70094c9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,55 @@ +Valve, the Valve logo, Half-Life, the Half-Life logo, the Lambda logo, Steam, +the Steam logo, Team Fortress, the Team Fortress logo, Opposing Force, Day of +Defeat, the Day of Defeat logo, Counter-Strike, the Counter-Strike logo, Source, +the Source logo, Counter-Strike: Condition Zero, Portal, the Portal logo, Dota, +the Dota 2 logo, and Defense of the Ancients are trademarks and/or registered +trademarks of Valve Corporation. All other trademarks are property of their +respective owners. See https://store.steampowered.com/legal for Valve Corporation legal details. + +The Source 1 license applies to all parts of repository that are not explicitly +marked with other licenses or externally maintained libraries. + +""" + SOURCE 1 SDK LICENSE + + Source SDK Copyright(c) Valve Corp. + + THIS DOCUMENT DESCRIBES A CONTRACT BETWEEN YOU AND VALVE + CORPORATION ("Valve"). PLEASE READ IT BEFORE DOWNLOADING OR USING + THE SOURCE ENGINE SDK ("SDK"). BY DOWNLOADING AND/OR USING THE + SOURCE ENGINE SDK YOU ACCEPT THIS LICENSE. IF YOU DO NOT AGREE TO + THE TERMS OF THIS LICENSE PLEASE DON'T DOWNLOAD OR USE THE SDK. + + You may, free of charge, download and use the SDK to develop a modified Valve game + running on the Source engine. You may distribute your modified Valve game in source and + object code form, but only for free. Terms of use for Valve games are found in the Steam + Subscriber Agreement located here: https://store.steampowered.com/subscriber_agreement/ + + You may copy, modify, and distribute the SDK and any modifications you make to the + SDK in source and object code form, but only for free. Any distribution of this SDK must + include this LICENSE file and thirdpartylegalnotices.txt. + + Any distribution of the SDK or a substantial portion of the SDK must include the above + copyright notice and the following: + + DISCLAIMER OF WARRANTIES. THE SOURCE SDK AND ANY + OTHER MATERIAL DOWNLOADED BY LICENSEE IS PROVIDED + "AS IS". VALVE AND ITS SUPPLIERS DISCLAIM ALL + WARRANTIES WITH RESPECT TO THE SDK, EITHER EXPRESS + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED + WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, + TITLE AND FITNESS FOR A PARTICULAR PURPOSE. + + LIMITATION OF LIABILITY. IN NO EVENT SHALL VALVE OR + ITS SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER + (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF + BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF + BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS) + ARISING OUT OF THE USE OF OR INABILITY TO USE THE + ENGINE AND/OR THE SDK, EVEN IF VALVE HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + If you would like to use the SDK for a commercial purpose, please contact Valve at + sourceengine@valvesoftware.com. +""" diff --git a/devtools/base.xcconfig b/devtools/base.xcconfig new file mode 100644 index 0000000..da9d9e5 --- /dev/null +++ b/devtools/base.xcconfig @@ -0,0 +1,28 @@ +ALWAYS_SEARCH_USER_PATHS = YES +HEADER_SEARCH_PATHS = $(HEADER_SEARCH_PATHS) $(SDKROOT)/usr/include/malloc + +ARCHS = i386 +ONLY_ACTIVE_ARCH = NO +COPY_PHASE_STRIP = NO +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym + +DEAD_CODE_STRIPPING = YES +PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES + +GCC_C_LANGUAGE_STANDARD = gnu99 +GCC_ENABLE_OBJC_EXCEPTIONS = YES +GCC_PREPROCESSOR_DEFINITIONS = _DLL_EXT=.dylib +GCC_SYMBOLS_PRIVATE_EXTERN = YES + +GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO + +// CLANG - and use the ccache wrapper +GCC_VERSION = com.apple.compilers.llvm.clang.1_0 +CC = $(SOURCE_ROOT)/devtools/bin/osx32/xcode_ccache_wrapper +LDPLUSPLUS = $(DT_TOOLCHAIN_DIR)/usr/bin/clang++ +WARNING_CFLAGS = -Wno-deprecated-writable-strings +CLANG_WARN_CXX0X_EXTENSIONS = NO + +// this combination of sdk and deploy target allows building on 10.7 (lion) targeting 10.5 +SDKROOT = macosx10.6 +MACOSX_DEPLOYMENT_TARGET=10.5 diff --git a/devtools/debug.xcconfig b/devtools/debug.xcconfig new file mode 100644 index 0000000..98883ea --- /dev/null +++ b/devtools/debug.xcconfig @@ -0,0 +1,2 @@ +#include "base.xcconfig" +GCC_OPTIMIZATION_LEVEL = 0 diff --git a/devtools/makefile_base_posix.mak b/devtools/makefile_base_posix.mak new file mode 100644 index 0000000..8e50e66 --- /dev/null +++ b/devtools/makefile_base_posix.mak @@ -0,0 +1,352 @@ +# +# Base makefile for Linux and OSX +# +# !!!!! Note to future editors !!!!! +# +# before you make changes, make sure you grok: +# 1. the difference between =, :=, +=, and ?= +# 2. how and when this base makefile gets included in the generated makefile(s) +# + +OS := $(shell uname) +HOSTNAME := $(shell hostname) + +-include $(SRCROOT)/devtools/steam_def.mak + +ifeq ($(CFG), release) + OptimizerLevel_CompilerSpecific = -O3 -fno-strict-aliasing +else + OptimizerLevel_CompilerSpecific = -O0 + #-O1 -finline-functions +endif + +# CPPFLAGS == "c/c++ *preprocessor* flags" - not "cee-plus-plus flags" +ARCH_FLAGS = +BUILDING_MULTI_ARCH = 0 +CPPFLAGS = $(DEFINES) $(addprefix -I, $(abspath $(INCLUDEDIRS) )) +CFLAGS = $(ARCH_FLAGS) $(CPPFLAGS) $(WARN_FLAGS) -fvisibility=$(SymbolVisibility) $(OptimizerLevel) -fPIC -pipe $(GCC_ExtraCompilerFlags) -Usprintf -Ustrncpy -UPROTECTED_THINGS_ENABLE +CXXFLAGS = $(CFLAGS) +DEFINES += -DVPROF_LEVEL=1 -DGNUC +LDFLAGS = $(CFLAGS) $(GCC_ExtraLinkerFlags) $(OptimizerLevel) +GENDEP_CXXFLAGS = -MD -MP -MF $(@:.o=.P) + +ifeq ($(STEAM_BRANCH),1) + WARN_FLAGS = -Wall -Wextra -Wshadow -Wno-invalid-offsetof +else + WARN_FLAGS = -Wno-write-strings +endif + +WARN_FLAGS += -Wno-unknown-pragmas -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-sign-compare -Wno-reorder -Wno-invalid-offsetof -Wno-float-equal -fdiagnostics-show-option + + +ifeq ($(OS),Linux) + CCACHE := $(SRCROOT)/devtools/bin/linux/ccache + + GCC_VER=4.5 + ifeq ($(origin AR), default) + AR = $(VALVE_BINDIR)/ar rs + endif + ifeq ($(origin CC),default) + CC = $(CCACHE) $(VALVE_BINDIR)/gcc-$(GCC_VER) + endif + ifeq ($(origin CXX), default) + CXX = $(CCACHE) $(VALVE_BINDIR)/g++-$(GCC_VER) + endif + LINK ?= $(CC) + + ifeq ($(TARGET_PLATFORM),linux64) + VALVE_BINDIR = /valve/bin64 + # nocona = pentium4 + 64bit + MMX, SSE, SSE2, SSE3 - no SSSE3 (that's three s's - added in core2) + ARCH_FLAGS += -march=nocona + LD_SO = ld-linux-x86_64.so.2 + LIBSTDCXX := $(shell $(CXX) -print-file-name=libstdc++.a) + LIBSTDCXXPIC := $(shell $(CXX) -print-file-name=libstdc++-pic.a) + else + VALVE_BINDIR = /valve/bin + # pentium4 = MMX, SSE, SSE2 - no SSE3 (added in prescott) + ARCH_FLAGS += -m32 -march=pentium4 + LD_SO = ld-linux.so.2 + LIBSTDCXX := $(shell $(CXX) -print-file-name=libstdc++.so) + LIBSTDCXXPIC := $(shell $(CXX) -print-file-name=libstdc++.so) + endif + + GEN_SYM ?= $(SRCROOT)/devtools/gendbg.sh + ifeq ($(CFG),release) + STRIP ?= strip -x -S + # CFLAGS += -ffunction-sections -fdata-sections + # LDFLAGS += -Wl,--gc-sections -Wl,--print-gc-sections + else + STRIP ?= true + endif + VSIGN ?= true + + SHLIBLDFLAGS = -shared $(LDFLAGS) -Wl,--no-undefined + + ifeq ($(STEAM_BRANCH),1) + _WRAP := -Xlinker --wrap= + PATHWRAP = $(_WRAP)fopen $(_WRAP)freopen $(_WRAP)open $(_WRAP)creat $(_WRAP)access $(_WRAP)__xstat \ + $(_WRAP)stat $(_WRAP)lstat $(_WRAP)fopen64 $(_WRAP)open64 $(_WRAP)opendir $(_WRAP)__lxstat \ + $(_WRAP)chmod $(_WRAP)chown $(_WRAP)lchown $(_WRAP)symlink $(_WRAP)link $(_WRAP)__lxstat64 \ + $(_WRAP)mknod $(_WRAP)utimes $(_WRAP)unlink $(_WRAP)rename $(_WRAP)utime $(_WRAP)__xstat64 \ + $(_WRAP)mount $(_WRAP)mkfifo $(_WRAP)mkdir $(_WRAP)rmdir $(_WRAP)scandir $(_WRAP)realpath + endif + + LIB_START_EXE = $(PATHWRAP) -static-libgcc -Wl,--start-group + LIB_END_EXE = -Wl,--end-group -lm -ldl $(LIBSTDCXX) -lpthread + + LIB_START_SHLIB = $(PATHWRAP) -static-libgcc -Wl,--start-group + LIB_END_SHLIB = -Wl,--end-group -lm -ldl $(LIBSTDCXXPIC) -lpthread -l:$(LD_SO) -Wl,--version-script=$(SRCROOT)/devtools/version_script.linux.txt + +endif + +ifeq ($(OS),Darwin) + OSXVER := $(shell sw_vers -productVersion) + CCACHE := $(SRCROOT)/devtools/bin/osx32/ccache + DEVELOPER_DIR := $(shell /usr/bin/xcode-select -print-path) + + ifeq (,$(findstring 10.7, $(OSXVER))) + BUILDING_ON_LION := 0 + COMPILER_BIN_DIR := $(DEVELOPER_DIR)/usr/bin + SDK_DIR := $(DEVELOPER_DIR)/SDKs + else + BUILDING_ON_LION := 1 + COMPILER_BIN_DIR := $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/bin + SDK_DIR := $(DEVELOPER_DIR)/Platforms/MacOSX.platform/Developer/SDKs + endif + + SDKROOT ?= $(SDK_DIR)/MacOSX10.6.sdk + + ifeq ($(origin AR), default) + AR = libtool -static -o + endif + ifeq ($(origin CC), default) + CC = $(COMPILER_BIN_DIR)/clang -Qunused-arguments + endif + ifeq ($(origin CXX), default) + CXX = $(COMPILER_BIN_DIR)/clang++ -Qunused-arguments + endif + LINK ?= $(CXX) + + ifeq (($TARGET_PLATFORM),osx64) + ARCH_FLAGS += -arch x86_64 -m64 -march=core2 + else ifeq (,$(findstring -arch x86_64,$(GCC_ExtraCompilerFlags))) + ARCH_FLAGS += -arch i386 -m32 -march=prescott + else + # dirty hack to build a universal binary - don't specify the architecture + ARCH_FLAGS += -arch i386 -Xarch_i386 -march=prescott -Xarch_x86_64 -march=core2 + BUILDING_MULTI_ARCH=1 + endif + + #FIXME: NOTE:Full path specified because the xcode 4.0 preview has a terribly broken dsymutil, so ref the 3.2 one + GEN_SYM ?= $(DEVELOPER_DIR)/usr/bin/dsymutil + ifeq ($(CFG),release) + STRIP ?= strip -S + else + STRIP ?= true + endif + VSIGN ?= $(SRCROOT)/devtools/bin/vsign + + SDKROOT ?= $(SDK_DIR)/MacOSX10.6.sdk + + CPPFLAGS += -I$(SDKROOT)/usr/include/malloc + CFLAGS += -isysroot $(SDKROOT) -mmacosx-version-min=10.5 -fasm-blocks + + LIB_START_EXE = -lm -ldl -lpthread + LIB_END_EXE = + + LIB_START_SHLIB = + LIB_END_SHLIB = + + SHLIBLDFLAGS = $(LDFLAGS) -bundle -flat_namespace -undefined suppress -Wl,-dead_strip -Wl,-no_dead_strip_inits_and_terms + + ifeq (lib,$(findstring lib,$(GAMEOUTPUTFILE))) + SHLIBLDFLAGS = $(LDFLAGS) -dynamiclib -current_version 1.0 -compatibility_version 1.0 -install_name @loader_path/$(basename $(notdir $(GAMEOUTPUTFILE))).dylib $(SystemLibraries) -Wl,-dead_strip -Wl,-no_dead_strip_inits_and_terms + endif + +endif + +# +# Profile-directed optimizations. +# Note: Last time these were tested 3/5/08, it actually slowed down the server benchmark by 5%! +# +# First, uncomment these, build, and test. It will generate .gcda and .gcno files where the .o files are. +# PROFILE_LINKER_FLAG=-fprofile-arcs +# PROFILE_COMPILER_FLAG=-fprofile-arcs +# +# Then, comment the above flags out again and rebuild with this flag uncommented: +# PROFILE_COMPILER_FLAG=-fprofile-use +# + +############################################################################# +# The compiler command lne for each src code file to compile +############################################################################# + +OBJ_DIR = ./obj_$(NAME)_$(TARGET_PLATFORM)/$(CFG) +CPP_TO_OBJ = $(CPPFILES:.cpp=.o) +CXX_TO_OBJ = $(CPP_TO_OBJ:.cxx=.oxx) +CC_TO_OBJ = $(CXX_TO_OBJ:.cc=.o) +MM_TO_OBJ = $(CC_TO_OBJ:.mm=.o) +C_TO_OBJ = $(MM_TO_OBJ:.c=.o) +OBJS = $(addprefix $(OBJ_DIR)/, $(notdir $(C_TO_OBJ))) + +ifeq ($(MAKE_VERBOSE),1) + QUIET_PREFIX = + QUIET_ECHO_POSTFIX = +else + QUIET_PREFIX = @ + QUIET_ECHO_POSTFIX = > /dev/null +endif + +ifeq ($(CONFTYPE),lib) + LIB_File = $(OUTPUTFILE) +endif + +ifeq ($(CONFTYPE),dll) + SO_File = $(OUTPUTFILE) +endif + +ifeq ($(CONFTYPE),exe) + EXE_File = $(OUTPUTFILE) +endif + +# we generate dependencies as a side-effect of compilation now +GEN_DEP_FILE= + +PRE_COMPILE_FILE = + +POST_COMPILE_FILE = + +ifeq ($(BUILDING_MULTI_ARCH),1) + SINGLE_ARCH_CXXFLAGS=$(subst -arch x86_64,,$(CXXFLAGS)) + COMPILE_FILE = \ + $(QUIET_PREFIX) \ + echo "---- COMPILING $(lastword $(subst /, ,$<)) as MULTIARCH----";\ + mkdir -p $(OBJ_DIR) && \ + $(CXX) $(SINGLE_ARCH_CXXFLAGS) $(GENDEP_CXXFLAGS) -o $@ -c $< && \ + $(CXX) $(CXXFLAGS) -o $@ -c $< +else + COMPILE_FILE = \ + $(QUIET_PREFIX) \ + echo "---- COMPILING $(lastword $(subst /, ,$<)) ----";\ + mkdir -p $(OBJ_DIR) && \ + $(CXX) $(CXXFLAGS) $(GENDEP_CXXFLAGS) -o $@ -c $< +endif + +ifneq "$(origin VALVE_NO_AUTO_P4)" "undefined" + P4_EDIT_START = chmod -R +w + P4_EDIT_END = || true + P4_REVERT_START = true + P4_REVERT_END = +else + P4_EDIT_START := for f in + P4_EDIT_END := ; do if [ -n $$f ]; then if [ -d $$f ]; then find $$f -type f -print | p4 -x - edit; else p4 edit $$f; fi; fi; done $(QUIET_ECHO_POSTFIX) + P4_REVERT_START := for f in + P4_REVERT_END := ; do if [ -n $$f ]; then if [ -d $$f ]; then find $$f -type f -print | p4 -x - revert; else p4 revert $$f; fi; fi; done $(QUIET_ECHO_POSTFIX) +endif + +ifeq ($(CONFTYPE),dll) +all: $(OTHER_DEPENDENCIES) $(OBJS) $(GAMEOUTPUTFILE) + @echo $(GAMEOUTPUTFILE) $(QUIET_ECHO_POSTFIX) +else +all: $(OTHER_DEPENDENCIES) $(OBJS) $(OUTPUTFILE) + @echo $(OUTPUTFILE) $(QUIET_ECHO_POSTFIX) +endif + +.PHONY: clean cleantargets rebuild relink RemoveOutputFile SingleFile + + +rebuild : + $(MAKE) -f $(firstword $(MAKEFILE_LIST)) clean + $(MAKE) -f $(firstword $(MAKEFILE_LIST)) + + +# Use the relink target to force to relink the project. +relink: RemoveOutputFile all + +RemoveOutputFile: + rm -f $(OUTPUTFILE) + + +# This rule is so you can say "make SingleFile SingleFilename=/home/myname/valve_main/src/engine/language.cpp" and have it only build that file. +# It basically just translates the full filename to create a dependency on the appropriate .o file so it'll build that. +SingleFile : RemoveSingleFile $(OBJ_DIR)/$(basename $(notdir $(SingleFilename))).o + @echo "" + +RemoveSingleFile: + $(QUIET_PREFIX) rm -f $(OBJ_DIR)/$(basename $(notdir $(SingleFilename))).o + +clean: +ifneq "$(OBJ_DIR)" "" + $(QUIET_PREFIX) echo "removing $(OBJ_DIR)" + $(QUIET_PREFIX) rm -rf $(OBJ_DIR) +endif +ifneq "$(OUTPUTFILE)" "" + $(QUIET_PREFIX) if [ -e $(OUTPUTFILE) ]; then \ + echo "cleaning $(OUTPUTFILE)"; \ + $(P4_REVERT_START) $(OUTPUTFILE) $(OUTPUTFILE)$(SYM_EXT) $(P4_REVERT_END); \ + fi; +endif +ifneq "$(OTHER_DEPENDENCIES)" "" + $(QUIET_PREFIX) rm -f $(OTHER_DEPENDENCIES) +endif +ifneq "$(GAMEOUTPUTFILE)" "" + $(QUIET_PREFIX) echo "reverting $(GAMEOUTPUTFILE)" + $(QUIET_PREFIX) $(P4_REVERT_START) $(GAMEOUTPUTFILE) $(GAMEOUTPUTFILE)$(SYM_EXT) $(P4_REVERT_END) +endif + +# This just deletes the final targets so it'll do a relink next time we build. +cleantargets: + $(QUIET_PREFIX) rm -f $(OUTPUTFILE) $(GAMEOUTPUTFILE) + + +$(LIB_File): $(OTHER_DEPENDENCIES) $(OBJS) + $(QUIET_PREFIX) -$(P4_EDIT_START) $(LIB_File) $(P4_EDIT_END); + $(QUIET_PREFIX) $(AR) $(LIB_File) $(OBJS) $(LIBFILES); + +SO_GameOutputFile = $(GAMEOUTPUTFILE) + +$(SO_GameOutputFile): $(SO_File) + $(QUIET_PREFIX) \ + $(P4_EDIT_START) $(GAMEOUTPUTFILE) $(P4_EDIT_END) && \ + echo "----" $(QUIET_ECHO_POSTFIX);\ + echo "---- COPYING TO $@ ----";\ + echo "----" $(QUIET_ECHO_POSTFIX); + $(QUIET_PREFIX) -$(P4_EDIT_START) $(GAMEOUTPUTFILE) $(P4_EDIT_END); + $(QUIET_PREFIX) -mkdir -p `dirname $(GAMEOUTPUTFILE)` > /dev/null; + $(QUIET_PREFIX) cp -v $(OUTPUTFILE) $(GAMEOUTPUTFILE) $(QUIET_ECHO_POSTFIX); + $(QUIET_PREFIX) -$(P4_EDIT_START) $(GAMEOUTPUTFILE)$(SYM_EXT) $(P4_EDIT_END); + $(QUIET_PREFIX) $(GEN_SYM) $(GAMEOUTPUTFILE); + $(QUIET_PREFIX) -$(STRIP) $(GAMEOUTPUTFILE); + $(QUIET_PREFIX) $(VSIGN) -signvalve $(GAMEOUTPUTFILE); + $(QUIET_PREFIX) if [ "$(IMPORTLIBRARY)" != "" ]; then\ + echo "----" $(QUIET_ECHO_POSTFIX);\ + echo "---- COPYING TO IMPORT LIBRARY $(IMPORTLIBRARY) ----";\ + echo "----" $(QUIET_ECHO_POSTFIX);\ + $(P4_EDIT_START) $(IMPORTLIBRARY) $(P4_EDIT_END) && \ + mkdir -p `dirname $(IMPORTLIBRARY)` > /dev/null && \ + cp -v $(OUTPUTFILE) $(IMPORTLIBRARY); \ + fi; + + +$(SO_File): $(OTHER_DEPENDENCIES) $(OBJS) $(LIBFILENAMES) + $(QUIET_PREFIX) \ + echo "----" $(QUIET_ECHO_POSTFIX);\ + echo "---- LINKING $@ ----";\ + echo "----" $(QUIET_ECHO_POSTFIX);\ + \ + $(LINK) $(SHLIBLDFLAGS) $(PROFILE_LINKER_FLAG) -o $(OUTPUTFILE) $(LIB_START_SHLIB) $(OBJS) $(LIBFILES) $(SystemLibraries) $(LIB_END_SHLIB); + $(VSIGN) -signvalve $(OUTPUTFILE); + + +$(EXE_File) : $(OTHER_DEPENDENCIES) $(OBJS) $(LIBFILENAMES) + $(QUIET_PREFIX) \ + echo "----" $(QUIET_ECHO_POSTFIX);\ + echo "---- LINKING EXE $@ ----";\ + echo "----" $(QUIET_ECHO_POSTFIX);\ + \ + $(P4_EDIT_START) $(OUTPUTFILE) $(P4_EDIT_END);\ + $(LINK) $(LDFLAGS) $(PROFILE_LINKER_FLAG) -o $(OUTPUTFILE) $(LIB_START_EXE) $(OBJS) $(LIBFILES) $(SystemLibraries) $(LIB_END_EXE); + $(QUIET_PREFIX) -$(P4_EDIT_START) $(OUTPUTFILE)$(SYM_EXT) $(P4_EDIT_END); + $(QUIET_PREFIX) $(GEN_SYM) $(OUTPUTFILE); + $(QUIET_PREFIX) -$(STRIP) $(OUTPUTFILE); + $(QUIET_PREFIX) $(VSIGN) -signvalve $(OUTPUTFILE); diff --git a/devtools/release.xcconfig b/devtools/release.xcconfig new file mode 100644 index 0000000..a228993 --- /dev/null +++ b/devtools/release.xcconfig @@ -0,0 +1 @@ +#include "base.xcconfig" \ No newline at end of file diff --git a/interfaces/interfaces.cpp b/interfaces/interfaces.cpp new file mode 100644 index 0000000..9bb6e3f --- /dev/null +++ b/interfaces/interfaces.cpp @@ -0,0 +1,251 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A higher level link library for general use in the game and tools. + +#include "interfaces/interfaces.h" + +#include "tier0/platform.h" +#include "tier0/dbg.h" + +#include + +// Tier 1 libraries +ICvar *cvar{nullptr}; +ICvar *g_pCVar{nullptr}; +IProcessUtils *g_pProcessUtils{nullptr}; +IPhysics2 *g_pPhysics2{nullptr}; +IPhysics2ActorManager *g_pPhysics2ActorManager{nullptr}; +IPhysics2ResourceManager *g_pPhysics2ResourceManager{nullptr}; +IEventSystem *g_pEventSystem{nullptr}; +ILocalize *g_pLocalize{nullptr}; + +// for utlsortvector.h +#ifndef _WIN32 +void *g_pUtlSortVectorQSortContext{nullptr}; +#endif + +// Tier 2 libraries +IResourceSystem *g_pResourceSystem{nullptr}; +IRenderDeviceMgr *g_pRenderDeviceMgr{nullptr}; +IFileSystem *g_pFullFileSystem{nullptr}; +IAsyncFileSystem *g_pAsyncFileSystem{nullptr}; +IMaterialSystem *materials{nullptr}; +IMaterialSystem *g_pMaterialSystem{nullptr}; +IMaterialSystem2 *g_pMaterialSystem2{nullptr}; +IInputSystem *g_pInputSystem{nullptr}; +IInputStackSystem *g_pInputStackSystem{nullptr}; +INetworkSystem *g_pNetworkSystem{nullptr}; +ISoundSystem *g_pSoundSystem{nullptr}; +IMaterialSystemHardwareConfig *g_pMaterialSystemHardwareConfig{nullptr}; +IDebugTextureInfo *g_pMaterialSystemDebugTextureInfo{nullptr}; +IVBAllocTracker *g_VBAllocTracker{nullptr}; +IColorCorrectionSystem *colorcorrection{nullptr}; +IP4 *p4{nullptr}; //-V707 +IMdlLib *mdllib{nullptr}; +IQueuedLoader *g_pQueuedLoader{nullptr}; +IResourceAccessControl *g_pResourceAccessControl{nullptr}; +IPrecacheSystem *g_pPrecacheSystem{nullptr}; +ISceneSystem *g_pSceneSystem{nullptr}; + +#if defined(PLATFORM_X360) +IXboxInstaller *g_pXboxInstaller{nullptr}; +#endif + +IMatchFramework *g_pMatchFramework{nullptr}; +IGameUISystemMgr *g_pGameUISystemMgr{nullptr}; + +// Not exactly a global, but we're going to keep track of these here anyways +IRenderDevice *g_pRenderDevice{nullptr}; +IRenderHardwareConfig *g_pRenderHardwareConfig{nullptr}; + +// Tier3 libraries +IMeshSystem *g_pMeshSystem{nullptr}; +IStudioRender *g_pStudioRender{nullptr}; +IStudioRender *studiorender{nullptr}; +IMatSystemSurface *g_pMatSystemSurface{nullptr}; +vgui::IInput *g_pVGuiInput{nullptr}; +vgui::ISurface *g_pVGuiSurface{nullptr}; +vgui::IPanel *g_pVGuiPanel{nullptr}; +vgui::IVGui *g_pVGui{nullptr}; +vgui::IVGUILocalize *g_pVGuiLocalize{nullptr}; +vgui::ISchemeManager *g_pVGuiSchemeManager{nullptr}; +vgui::ISystem *g_pVGuiSystem{nullptr}; +IDataCache *g_pDataCache{nullptr}; +IMDLCache *g_pMDLCache{nullptr}; +IMDLCache *mdlcache{nullptr}; +IAvi *g_pAVI{nullptr}; +IBik *g_pBIK{nullptr}; +IDmeMakefileUtils *g_pDmeMakefileUtils{nullptr}; +IPhysicsCollision *g_pPhysicsCollision{nullptr}; +ISoundEmitterSystemBase *g_pSoundEmitterSystem{nullptr}; +IWorldRendererMgr *g_pWorldRendererMgr{nullptr}; +IVGuiRenderSurface *g_pVGuiRenderSurface{nullptr}; + +// Mapping of interface string to globals +struct InterfaceGlobal { + const char *interface_name; + void *global; +}; + +// At each level of connection, we're going to keep track of which interfaces +// we filled in. When we disconnect, we'll clear those interface pointers out. +struct ConnectionRegistration { + void *global_storage; + size_t connection_phase; +}; + +namespace { + +InterfaceGlobal interface_globals[] { + {CVAR_INTERFACE_VERSION, &cvar}, {CVAR_INTERFACE_VERSION, &g_pCVar}, + {EVENTSYSTEM_INTERFACE_VERSION, &g_pEventSystem}, + {PROCESS_UTILS_INTERFACE_VERSION, &g_pProcessUtils}, + {VPHYSICS2_INTERFACE_VERSION, &g_pPhysics2}, + {VPHYSICS2_ACTOR_MGR_INTERFACE_VERSION, &g_pPhysics2ActorManager}, + {VPHYSICS2_RESOURCE_MGR_INTERFACE_VERSION, &g_pPhysics2ResourceManager}, + {FILESYSTEM_INTERFACE_VERSION, &g_pFullFileSystem}, + {ASYNCFILESYSTEM_INTERFACE_VERSION, &g_pAsyncFileSystem}, + {RESOURCESYSTEM_INTERFACE_VERSION, &g_pResourceSystem}, + {MATERIAL_SYSTEM_INTERFACE_VERSION, &g_pMaterialSystem}, + {MATERIAL_SYSTEM_INTERFACE_VERSION, &materials}, + {MATERIAL_SYSTEM2_INTERFACE_VERSION, &g_pMaterialSystem2}, + {INPUTSYSTEM_INTERFACE_VERSION, &g_pInputSystem}, + {INPUTSTACKSYSTEM_INTERFACE_VERSION, &g_pInputStackSystem}, + {NETWORKSYSTEM_INTERFACE_VERSION, &g_pNetworkSystem}, + {RENDER_DEVICE_MGR_INTERFACE_VERSION, &g_pRenderDeviceMgr}, + {MATERIALSYSTEM_HARDWARECONFIG_INTERFACE_VERSION, + &g_pMaterialSystemHardwareConfig}, + {SOUNDSYSTEM_INTERFACE_VERSION, &g_pSoundSystem}, + {DEBUG_TEXTURE_INFO_VERSION, &g_pMaterialSystemDebugTextureInfo}, + {VB_ALLOC_TRACKER_INTERFACE_VERSION, &g_VBAllocTracker}, + {COLORCORRECTION_INTERFACE_VERSION, &colorcorrection}, + {P4_INTERFACE_VERSION, &p4}, {MDLLIB_INTERFACE_VERSION, &mdllib}, + {QUEUEDLOADER_INTERFACE_VERSION, &g_pQueuedLoader}, + {RESOURCE_ACCESS_CONTROL_INTERFACE_VERSION, &g_pResourceAccessControl}, + {PRECACHE_SYSTEM_INTERFACE_VERSION, &g_pPrecacheSystem}, + {STUDIO_RENDER_INTERFACE_VERSION, &g_pStudioRender}, + {STUDIO_RENDER_INTERFACE_VERSION, &studiorender}, + {VGUI_IVGUI_INTERFACE_VERSION, &g_pVGui}, + {VGUI_INPUT_INTERFACE_VERSION, &g_pVGuiInput}, + {VGUI_PANEL_INTERFACE_VERSION, &g_pVGuiPanel}, + {VGUI_SURFACE_INTERFACE_VERSION, &g_pVGuiSurface}, + {VGUI_SCHEME_INTERFACE_VERSION, &g_pVGuiSchemeManager}, + {VGUI_SYSTEM_INTERFACE_VERSION, &g_pVGuiSystem}, + {LOCALIZE_INTERFACE_VERSION, &g_pLocalize}, + {LOCALIZE_INTERFACE_VERSION, &g_pVGuiLocalize}, + {MAT_SYSTEM_SURFACE_INTERFACE_VERSION, &g_pMatSystemSurface}, + {DATACACHE_INTERFACE_VERSION, &g_pDataCache}, + {MDLCACHE_INTERFACE_VERSION, &g_pMDLCache}, + {MDLCACHE_INTERFACE_VERSION, &mdlcache}, {AVI_INTERFACE_VERSION, &g_pAVI}, + {BIK_INTERFACE_VERSION, &g_pBIK}, + {DMEMAKEFILE_UTILS_INTERFACE_VERSION, &g_pDmeMakefileUtils}, + {VPHYSICS_COLLISION_INTERFACE_VERSION, &g_pPhysicsCollision}, + {SOUNDEMITTERSYSTEM_INTERFACE_VERSION, &g_pSoundEmitterSystem}, + {MESHSYSTEM_INTERFACE_VERSION, &g_pMeshSystem}, + {RENDER_DEVICE_INTERFACE_VERSION, &g_pRenderDevice}, + {RENDER_HARDWARECONFIG_INTERFACE_VERSION, &g_pRenderHardwareConfig}, + {SCENESYSTEM_INTERFACE_VERSION, &g_pSceneSystem}, + {WORLD_RENDERER_MGR_INTERFACE_VERSION, &g_pWorldRendererMgr}, + {RENDER_SYSTEM_SURFACE_INTERFACE_VERSION, &g_pVGuiRenderSurface}, + +#if defined(_X360) + {XBOXINSTALLER_INTERFACE_VERSION, &g_pXboxInstaller}, +#endif + + {MATCHFRAMEWORK_INTERFACE_VERSION, &g_pMatchFramework}, + {GAMEUISYSTEMMGR_INTERFACE_VERSION, &g_pGameUISystemMgr}, +}; + +// The # of times this DLL has been connected +size_t connections_count{0}; + +size_t registrations_count{0}; + +ConnectionRegistration + connection_registrations[std::size(interface_globals) + 1]; + +void RegisterInterface(CreateInterfaceFn factory, const char *name, + void **global) { + if (!(*global)) { + *global = factory(name, nullptr); + + if (*global) { + Assert(registrations_count < std::size(connection_registrations)); + + ConnectionRegistration ®{ + connection_registrations[registrations_count++]}; + + reg.global_storage = global; + reg.connection_phase = connections_count; + } + } +} + +void ReconnectInterface(CreateInterfaceFn factory, const char *name, + void **global) { + *global = factory(name, nullptr); + + bool bFound = false; + + Assert(registrations_count < std::size(connection_registrations)); + + for (size_t i = 0; i < registrations_count; ++i) { + ConnectionRegistration ®{connection_registrations[i]}; + + if (reg.global_storage != global) continue; + + reg.global_storage = global; + + bFound = true; + } + + if (!bFound && *global) { + Assert(registrations_count < std::size(connection_registrations)); + + ConnectionRegistration ®{ + connection_registrations[registrations_count++]}; + + reg.global_storage = global; + reg.connection_phase = connections_count; + } +} + +} // namespace + +// Call this to connect to all tier 1 libraries. It's up to the caller to check +// the globals it cares about to see if ones are missing +void ConnectInterfaces(CreateInterfaceFn *factories, int count) { + // This is no longer questionable: ConnectInterfaces() is expected to be + // called multiple times for a file that exports multiple interfaces. + for (int i = 0; i < count; ++i) { + for (const auto &global : interface_globals) { + ReconnectInterface(factories[i], global.interface_name, + (void **)global.global); + } + } + + ++connections_count; +} + +void DisconnectInterfaces() { + Assert(connections_count > 0); + + if (--connections_count == std::numeric_limits::max()) return; + + for (size_t i = 0; i < registrations_count; ++i) { + if (connection_registrations[i].connection_phase != connections_count) + continue; + + // Disconnect! + *(void **)(connection_registrations[i].global_storage) = nullptr; + } +} + +// Reloads an interface +void ReconnectInterface(CreateInterfaceFn factory, const char *name) { + for (const auto &global : interface_globals) { + if (strcmp(global.interface_name, name) != 0) continue; + + ReconnectInterface(factory, global.interface_name, (void **)global.global); + } +} diff --git a/public/appframework/iappsystem.h b/public/appframework/iappsystem.h new file mode 100644 index 0000000..6504b0d --- /dev/null +++ b/public/appframework/iappsystem.h @@ -0,0 +1,92 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: An application framework. + +#ifndef VPC_APPFRAMEWORK_IAPPSYSTEM_H_ +#define VPC_APPFRAMEWORK_IAPPSYSTEM_H_ +#include "tier1/interface.h" +#include "interfaces/interfaces.h" + +// Specifies a module + interface name for initialization +struct AppSystemInfo_t { + const char *m_pModuleName; + const char *m_pInterfaceName; +}; + +// Client systems are singleton objects in the client codebase responsible for +// various tasks. +// +// The order in which the client systems appear in this list are the +// order in which they are initialized and updated. They are shut down in +// reverse order from which they are initialized. +enum InitReturnVal_t { + INIT_FAILED = 0, + INIT_OK, + + INIT_LAST_VAL, +}; + +enum AppSystemTier_t { + APP_SYSTEM_TIER0 = 0, + APP_SYSTEM_TIER1, + APP_SYSTEM_TIER2, + APP_SYSTEM_TIER3, + + APP_SYSTEM_TIER_OTHER, +}; + +abstract_class IAppSystem { + public: + // Here's where the app systems get to learn about each other + virtual bool Connect(CreateInterfaceFn factory) = 0; + virtual void Disconnect() = 0; + + // Here's where systems can access other interfaces implemented by this object + // Returns NULL if it doesn't implement the requested interface + virtual void *QueryInterface(const char *pInterfaceName) = 0; + + // Init, shutdown + virtual InitReturnVal_t Init() = 0; + virtual void Shutdown() = 0; + + // Returns all dependent libraries + virtual const AppSystemInfo_t *GetDependencies() = 0; + + // Returns the tier + virtual AppSystemTier_t GetTier() = 0; + + // Reconnect to a particular interface + virtual void Reconnect(CreateInterfaceFn factory, + const char *pInterfaceName) = 0; +}; + +// Helper empty implementation of an IAppSystem +template +class CBaseAppSystem : public IInterface { + public: + // Here's where the app systems get to learn about each other + virtual bool Connect(CreateInterfaceFn factory) { return true; } + virtual void Disconnect() {} + + // Here's where systems can access other interfaces implemented by this object + // Returns NULL if it doesn't implement the requested interface + virtual void *QueryInterface(const char *pInterfaceName) { return NULL; } + + // Init, shutdown + virtual InitReturnVal_t Init() { return INIT_OK; } + virtual void Shutdown() {} + + virtual const AppSystemInfo_t *GetDependencies() { return NULL; } + virtual AppSystemTier_t GetTier() { return APP_SYSTEM_TIER_OTHER; } + + virtual void Reconnect(CreateInterfaceFn factory, + const char *pInterfaceName) { + ReconnectInterface(factory, pInterfaceName); + } +}; + +// Helper implementation of an IAppSystem for tier0 +template +class CTier0AppSystem : public CBaseAppSystem {}; + +#endif // VPC_APPFRAMEWORK_IAPPSYSTEM_H_ diff --git a/public/color.h b/public/color.h new file mode 100644 index 0000000..db1d530 --- /dev/null +++ b/public/color.h @@ -0,0 +1,83 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_COLOR_H_ +#define VPC_COLOR_H_ + +#include "tier0/basetypes.h" + +// Purpose: Basic handler for an rgb set of colors +class alignas(int) Color { + public: + // constructors + constexpr Color() : _color{0, 0, 0, 0} {} + Color(int _r, int _g, int _b) { SetColor(_r, _g, _b, 0); } + Color(int _r, int _g, int _b, int _a) { SetColor(_r, _g, _b, _a); } + constexpr Color(const Color &rhs) + : _color{rhs._color[0], rhs._color[1], rhs._color[2], rhs._color[3]} {} + + // set the color + // r - red component (0-255) + // g - green component (0-255) + // b - blue component (0-255) + // a - alpha component, controls transparency (0 - transparent, 255 - opaque); + void SetColor(int _r, int _g, int _b, int _a = 0) { + _color[0] = (unsigned char)_r; + _color[1] = (unsigned char)_g; + _color[2] = (unsigned char)_b; + _color[3] = (unsigned char)_a; + } + + void GetColor(int &_r, int &_g, int &_b, int &_a) const { + _r = _color[0]; + _g = _color[1]; + _b = _color[2]; + _a = _color[3]; + } + + void SetRawColor(int color32) { *((int *)this) = color32; } + + int GetRawColor() const { return *((int *)this); } + + constexpr int r() const { return _color[0]; } + constexpr int g() const { return _color[1]; } + constexpr int b() const { return _color[2]; } + constexpr int a() const { return _color[3]; } + + unsigned char &operator[](int index) { return _color[index]; } + + const unsigned char &operator[](int index) const { return _color[index]; } + + constexpr bool operator==(const Color &rhs) const { + return _color[0] == rhs._color[0] && _color[1] == rhs._color[1] && + _color[2] == rhs._color[2] && _color[3] == rhs._color[3]; + } + + constexpr bool operator!=(const Color &rhs) const { + return !(operator==(rhs)); + } + + constexpr Color &operator=(const Color &rhs) { + _color[0] = rhs._color[0]; + _color[1] = rhs._color[1]; + _color[2] = rhs._color[2]; + _color[3] = rhs._color[3]; + return *this; + } + + Color &operator=(const color32 &rhs) { + _color[0] = rhs.r; + _color[1] = rhs.g; + _color[2] = rhs.b; + _color[3] = rhs.a; + return *this; + } + + color32 ToColor32() const { + return {_color[0], _color[1], _color[2], _color[3]}; + } + + private: + unsigned char _color[4]; +}; + +#endif // VPC_COLOR_H_ diff --git a/public/datamap.h b/public/datamap.h new file mode 100644 index 0000000..69e4c6c --- /dev/null +++ b/public/datamap.h @@ -0,0 +1,651 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_DATAMAP_H_ +#define VPC_DATAMAP_H_ + +#include "mathlib/vector.h" + +#include "tier1/utlvector.h" + +#include "tier0/memdbgon.h" + +// SINGLE_INHERITANCE restricts the size of CBaseEntity +// pointers-to-member-functions to 4 bytes +class SINGLE_INHERITANCE CBaseEntity; +struct inputdata_t; + +#define INVALID_TIME (FLT_MAX * -1.0) // Special value not rebased on save/load + +typedef enum _fieldtypes { + FIELD_VOID = 0, // No type or value + FIELD_FLOAT, // Any floating point value + FIELD_STRING, // A string ID (return from ALLOC_STRING) + FIELD_VECTOR, // Any vector, QAngle, or AngularImpulse + FIELD_QUATERNION, // A quaternion + FIELD_INTEGER, // Any integer or enum + FIELD_BOOLEAN, // boolean, implemented as an int, I may use this as a hint + // for compression + FIELD_SHORT, // 2 byte integer + FIELD_CHARACTER, // a byte + FIELD_COLOR32, // 8-bit per channel r,g,b,a (32bit color) + FIELD_EMBEDDED, // an embedded object with a datadesc, recursively traverse + // and embedded class/structure based on an additional + // typedescription + FIELD_CUSTOM, // special type that contains function pointers to it's + // read/write/parse functions + + FIELD_CLASSPTR, // CBaseEntity * + FIELD_EHANDLE, // Entity handle + FIELD_EDICT, // edict_t * + + FIELD_POSITION_VECTOR, // A world coordinate (these are fixed up across level + // transitions automagically) + FIELD_TIME, // a floating point time (these are fixed up automatically too!) + FIELD_TICK, // an integer tick count( fixed up similarly to time) + FIELD_MODELNAME, // Engine string that is a model name (needs precache) + FIELD_SOUNDNAME, // Engine string that is a sound name (needs precache) + + FIELD_INPUT, // a list of inputed data fields (all derived from + // CMultiInputVar) + FIELD_FUNCTION, // A class function pointer (Think, Use, etc) + + FIELD_VMATRIX, // a vmatrix (output coords are NOT worldspace) + + // NOTE: Use float arrays for local transformations that don't need to be + // fixed up. + FIELD_VMATRIX_WORLDSPACE, // A VMatrix that maps some local space to world + // space (translation is fixed up on level + // transitions) + FIELD_MATRIX3X4_WORLDSPACE, // matrix3x4_t that maps some local space to + // world space (translation is fixed up on level + // transitions) + + FIELD_INTERVAL, // a start and range floating point interval ( e.g., 3.2->3.6 + // == 3.2 and 0.4 ) + FIELD_MODELINDEX, // a model index + FIELD_MATERIALINDEX, // a material index (using the material precache string + // table) + + FIELD_VECTOR2D, // 2 floats + FIELD_INTEGER64, // 64bit integer + + FIELD_VECTOR4D, // 4 floats + + FIELD_TYPECOUNT, // MUST BE LAST +} fieldtype_t; + +//----------------------------------------------------------------------------- +// Field sizes... +//----------------------------------------------------------------------------- +template +class CDatamapFieldSizeDeducer { + public: + enum { SIZE = 0 }; + + static int FieldSize() { return 0; } +}; + +#define DECLARE_FIELD_SIZE(_fieldType, _fieldSize) \ + template <> \ + class CDatamapFieldSizeDeducer<_fieldType> { \ + public: \ + enum { SIZE = _fieldSize }; \ + static int FieldSize() { return _fieldSize; } \ + }; +#define FIELD_SIZE(_fieldType) CDatamapFieldSizeDeducer<_fieldType>::SIZE +#define FIELD_BITS(_fieldType) (FIELD_SIZE(_fieldType) * 8) + +DECLARE_FIELD_SIZE(FIELD_FLOAT, sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_STRING, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_VECTOR, 3 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_VECTOR2D, 2 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_VECTOR4D, 4 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_QUATERNION, 4 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_INTEGER, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_INTEGER64, sizeof(int64)) +DECLARE_FIELD_SIZE(FIELD_BOOLEAN, sizeof(char)) +DECLARE_FIELD_SIZE(FIELD_SHORT, sizeof(short)) +DECLARE_FIELD_SIZE(FIELD_CHARACTER, sizeof(char)) +DECLARE_FIELD_SIZE(FIELD_COLOR32, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_CLASSPTR, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_EHANDLE, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_EDICT, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_POSITION_VECTOR, 3 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_TIME, sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_TICK, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_MODELNAME, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_SOUNDNAME, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_INPUT, sizeof(int)) +#if defined(_WIN32) +DECLARE_FIELD_SIZE(FIELD_FUNCTION, sizeof(void *)) +#elif defined(POSIX) +// pointer to members under gnuc are 8bytes if you have a virtual func +DECLARE_FIELD_SIZE(FIELD_FUNCTION, 2 * sizeof(void *)) +#else +#error +#endif +DECLARE_FIELD_SIZE(FIELD_VMATRIX, 16 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_VMATRIX_WORLDSPACE, 16 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_MATRIX3X4_WORLDSPACE, 12 * sizeof(float)) +// NOTE: Must match interval.h definition +DECLARE_FIELD_SIZE(FIELD_INTERVAL, 2 * sizeof(float)) +DECLARE_FIELD_SIZE(FIELD_MODELINDEX, sizeof(int)) +DECLARE_FIELD_SIZE(FIELD_MATERIALINDEX, sizeof(int)) + +#define ARRAYSIZE2D(p) (sizeof(p) / sizeof(p[0][0])) +#define SIZE_OF_ARRAY(p) _ARRAYSIZE(p) + +#define _FIELD(name, fieldtype, count, flags, mapname, tolerance) \ + { \ + fieldtype, #name, offsetof(classNameTypedef, name), count, flags, mapname, \ + NULL, NULL, NULL, sizeof(((classNameTypedef *)0)->name), NULL, 0, \ + tolerance \ + } +#define DEFINE_FIELD_NULL \ + { FIELD_VOID, 0, 0, 0, 0, 0, 0, 0, 0 } +#define DEFINE_FIELD(name, fieldtype) \ + _FIELD(name, fieldtype, 1, FTYPEDESC_SAVE, NULL, 0) +#define DEFINE_FIELD_NOT_SAVED(name, fieldtype) \ + _FIELD(name, fieldtype, 1, 0, NULL, 0) + +#define DEFINE_KEYFIELD(name, fieldtype, mapname) \ + _FIELD(name, fieldtype, 1, FTYPEDESC_KEY | FTYPEDESC_SAVE, mapname, 0) +#define DEFINE_KEYFIELD_NOT_SAVED(name, fieldtype, mapname) \ + _FIELD(name, fieldtype, 1, FTYPEDESC_KEY, mapname, 0) +#define DEFINE_AUTO_ARRAY(name, fieldtype) \ + _FIELD(name, fieldtype, SIZE_OF_ARRAY(((classNameTypedef *)0)->name), \ + FTYPEDESC_SAVE, NULL, 0) +#define DEFINE_AUTO_ARRAY_KEYFIELD(name, fieldtype, mapname) \ + _FIELD(name, fieldtype, SIZE_OF_ARRAY(((classNameTypedef *)0)->name), \ + FTYPEDESC_SAVE, mapname, 0) +#define DEFINE_ARRAY(name, fieldtype, count) \ + _FIELD(name, fieldtype, count, FTYPEDESC_SAVE, NULL, 0) +#define DEFINE_ARRAY_NOT_SAVED(name, fieldtype, count) \ + _FIELD(name, fieldtype, count, 0, NULL, 0) +#define DEFINE_ENTITY_FIELD(name, fieldtype) \ + _FIELD(edict_t, name, fieldtype, 1, FTYPEDESC_KEY | FTYPEDESC_SAVE, #name, 0) +#define DEFINE_ENTITY_GLOBAL_FIELD(name, fieldtype) \ + _FIELD(edict_t, name, fieldtype, 1, \ + FTYPEDESC_KEY | FTYPEDESC_SAVE | FTYPEDESC_GLOBAL, #name, 0) +#define DEFINE_GLOBAL_FIELD(name, fieldtype) \ + _FIELD(name, fieldtype, 1, FTYPEDESC_GLOBAL | FTYPEDESC_SAVE, NULL, 0) +#define DEFINE_GLOBAL_KEYFIELD(name, fieldtype, mapname) \ + _FIELD(name, fieldtype, 1, \ + FTYPEDESC_GLOBAL | FTYPEDESC_KEY | FTYPEDESC_SAVE, mapname, 0) +#define DEFINE_CUSTOM_FIELD(name, datafuncs) \ + { \ + FIELD_CUSTOM, #name, offsetof(classNameTypedef, name), 1, FTYPEDESC_SAVE, \ + NULL, datafuncs, NULL \ + } +#define DEFINE_CUSTOM_KEYFIELD(name, datafuncs, mapname) \ + { \ + FIELD_CUSTOM, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_SAVE | FTYPEDESC_KEY, mapname, datafuncs, NULL \ + } +#define DEFINE_AUTO_ARRAY2D(name, fieldtype) \ + _FIELD(name, fieldtype, ARRAYSIZE2D(((classNameTypedef *)0)->name), \ + FTYPEDESC_SAVE, NULL, 0) +// Used by byteswap datadescs +#define DEFINE_BITFIELD(name, fieldtype, bitcount) \ + DEFINE_ARRAY(name, fieldtype, \ + (((bitcount) + FIELD_BITS(fieldtype) - 1) & \ + ~(FIELD_BITS(fieldtype) - 1)) / \ + FIELD_BITS(fieldtype)) +#define DEFINE_INDEX(name, fieldtype) \ + _FIELD(name, fieldtype, 1, FTYPEDESC_INDEX, NULL, 0) + +#define DEFINE_EMBEDDED(name) \ + { \ + FIELD_EMBEDDED, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_SAVE, NULL, NULL, NULL, \ + &(((classNameTypedef *)0)->name.m_DataMap), \ + sizeof(((classNameTypedef *)0)->name), NULL, 0, 0.0f \ + } + +#define DEFINE_EMBEDDED_OVERRIDE(name, overridetype) \ + { \ + FIELD_EMBEDDED, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_SAVE, NULL, NULL, NULL, &((overridetype *)0)->m_DataMap, \ + sizeof(((classNameTypedef *)0)->name), NULL, 0, 0.0f \ + } + +#define DEFINE_EMBEDDEDBYREF(name) \ + { \ + FIELD_EMBEDDED, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_SAVE | FTYPEDESC_PTR, NULL, NULL, NULL, \ + &(((classNameTypedef *)0)->name->m_DataMap), \ + sizeof(*(((classNameTypedef *)0)->name)), NULL, 0, 0.0f \ + } + +#define DEFINE_EMBEDDED_ARRAY(name, count) \ + { \ + FIELD_EMBEDDED, #name, offsetof(classNameTypedef, name), count, \ + FTYPEDESC_SAVE, NULL, NULL, NULL, \ + &(((classNameTypedef *)0)->name->m_DataMap), \ + sizeof(((classNameTypedef *)0)->name[0]), NULL, 0, 0.0f \ + } + +#define DEFINE_EMBEDDED_AUTO_ARRAY(name) \ + { \ + FIELD_EMBEDDED, #name, offsetof(classNameTypedef, name), \ + SIZE_OF_ARRAY(((classNameTypedef *)0)->name), FTYPEDESC_SAVE, NULL, \ + NULL, NULL, &(((classNameTypedef *)0)->name->m_DataMap), \ + sizeof(((classNameTypedef *)0)->name[0]), NULL, 0, 0.0f \ + } + +#ifndef NO_ENTITY_PREDICTION + +// FTYPEDESC_KEY tells the prediction copy system to report the full nameof the +// field when reporting errors +#define DEFINE_PRED_TYPEDESCRIPTION(name, fieldtype) \ + { \ + FIELD_EMBEDDED, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_SAVE | FTYPEDESC_KEY, NULL, NULL, NULL, \ + &fieldtype::m_PredMap \ + } + +#define DEFINE_PRED_TYPEDESCRIPTION_PTR(name, fieldtype) \ + { \ + FIELD_EMBEDDED, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_SAVE | FTYPEDESC_PTR | FTYPEDESC_KEY, NULL, NULL, NULL, \ + &fieldtype::m_PredMap \ + } + +#else + +#define DEFINE_PRED_TYPEDESCRIPTION(name, fieldtype) DEFINE_FIELD_NULL +#define DEFINE_PRED_TYPEDESCRIPTION_PTR(name, fieldtype) DEFINE_FIELD_NULL + +#endif + +// Extensions to datamap.h macros for predicted entities only +#define DEFINE_PRED_FIELD(name, fieldtype, flags) \ + _FIELD(name, fieldtype, 1, flags, NULL, 0.0f) +#define DEFINE_PRED_ARRAY(name, fieldtype, count, flags) \ + _FIELD(name, fieldtype, count, flags, NULL, 0.0f) +#define DEFINE_FIELD_NAME(localname, netname, fieldtype) \ + _FIELD(localname, fieldtype, 1, 0, #netname, 0.0f) +// Predictable macros, which include a tolerance for floating point values... +#define DEFINE_PRED_FIELD_TOL(name, fieldtype, flags, tolerance) \ + _FIELD(name, fieldtype, 1, flags, NULL, tolerance) +#define DEFINE_PRED_ARRAY_TOL(name, fieldtype, count, flags, tolerance) \ + _FIELD(name, fieldtype, count, flags, NULL, tolerance) +#define DEFINE_FIELD_NAME_TOL(localname, netname, fieldtolerance) \ + _FIELD(localname, fieldtype, 1, 0, #netname, tolerance) + +//#define DEFINE_DATA( name, fieldextname, flags ) _FIELD(name, fieldtype, 1, +// flags, extname ) + +// INPUTS +#define DEFINE_INPUT(name, fieldtype, inputname) \ + { \ + fieldtype, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_INPUT | FTYPEDESC_SAVE | FTYPEDESC_KEY, inputname, NULL, \ + NULL, NULL, sizeof(((classNameTypedef *)0)->name) \ + } +#define DEFINE_INPUTFUNC(fieldtype, inputname, inputfunc) \ + { \ + fieldtype, #inputfunc, NULL, 1, FTYPEDESC_INPUT, inputname, NULL, \ + static_cast(&classNameTypedef::inputfunc) \ + } + +// OUTPUTS +// the variable 'name' MUST BE derived from CBaseOutput +// we know the output type from the variable itself, so it doesn't need to be +// specified here +class ISaveRestoreOps; +extern ISaveRestoreOps *eventFuncs; + +#define DEFINE_OUTPUT(name, outputname) \ + { \ + FIELD_CUSTOM, #name, offsetof(classNameTypedef, name), 1, \ + FTYPEDESC_OUTPUT | FTYPEDESC_SAVE | FTYPEDESC_KEY, outputname, \ + eventFuncs \ + } + +// replaces EXPORT table for portability and non-DLL based systems (xbox) +#define DEFINE_FUNCTION_RAW(function, func_type) \ + { \ + FIELD_VOID, nameHolder.GenerateName(#function), NULL, 1, \ + FTYPEDESC_FUNCTIONTABLE, NULL, NULL, \ + (inputfunc_t)((func_type)(&classNameTypedef::function)) \ + } +#define DEFINE_FUNCTION(function) DEFINE_FUNCTION_RAW(function, inputfunc_t) + +// This field is masked for global entity save/restore +#define FTYPEDESC_GLOBAL 0x0001 +// This field is saved to disk +#define FTYPEDESC_SAVE 0x0002 +// This field can be requested and written to by string name at load +// time +#define FTYPEDESC_KEY 0x0004 +// This field can be written to by string name at run time, and a +// function called +#define FTYPEDESC_INPUT 0x0008 +// This field propagates its value to all targets whenever it changes +#define FTYPEDESC_OUTPUT 0x0010 +// This is a table entry for a member function pointer +#define FTYPEDESC_FUNCTIONTABLE 0x0020 +// This field is a pointer, not an embedded object +#define FTYPEDESC_PTR 0x0040 +// The field is an override for one in a base class (only used by +// prediction system for now) +#define FTYPEDESC_OVERRIDE 0x0080 + +// Flags used by other systems (e.g., prediction system) + +// This field is present in a network SendTable +#define FTYPEDESC_INSENDTABLE 0x0100 +// The field is local to the client or server only (not referenced by +// prediction code and not replicated by networking) +#define FTYPEDESC_PRIVATE 0x0200 +// The field is part of the prediction typedescription, but doesn't +// get compared when checking for errors +#define FTYPEDESC_NOERRORCHECK 0x0400 +// The field is a model index (used for debugging output) +#define FTYPEDESC_MODELINDEX 0x0800 + +// The field is an index into file data, used for byteswapping. +#define FTYPEDESC_INDEX 0x1000 + +// These flags apply to C_BasePlayer derived objects only + +// By default you can only view fields on the local player (yourself), +// but if this is set, then we allow you to see fields on other players +#define FTYPEDESC_VIEW_OTHER_PLAYER 0x2000 +// Only show this data if the player is on the same team as the local +// player +#define FTYPEDESC_VIEW_OWN_TEAM 0x4000 +// Never show this field to anyone, even the local player (unusual) +#define FTYPEDESC_VIEW_NEVER 0x8000 + +// This is a FIELD_FLOAT and should only be checked to be within 0.001 +// of the networked info +#define TD_MSECTOLERANCE 0.001f + +struct typedescription_t; + +class ISaveRestoreOps; + +// +// Function prototype for all input handlers. +// +using inputfunc_t = void (CBaseEntity::*)(inputdata_t &data); + +struct datamap_t; +struct typedescription_t; + +enum { + PC_NON_NETWORKED_ONLY = 0, + PC_NETWORKED_ONLY, + + PC_COPYTYPE_COUNT, + PC_EVERYTHING = PC_COPYTYPE_COUNT, +}; + +enum { + TD_OFFSET_NORMAL = 0, + TD_OFFSET_PACKED = 1, + + // Must be last + TD_OFFSET_COUNT, +}; + +struct typedescription_t { + fieldtype_t fieldType; + const char *fieldName; + int fieldOffset; // Local offset value + unsigned short fieldSize; + short flags; + // the name of the variable in the map/fgd data, or the name of the action + const char *externalName; + // pointer to the function set for save/restoring of custom data types + ISaveRestoreOps *pSaveRestoreOps; + // for associating function with string names + inputfunc_t inputFunc; + // For embedding additional datatables inside this one + datamap_t *td; + + // Stores the actual member variable size in bytes + int fieldSizeInBytes; + + // FTYPEDESC_OVERRIDE point to first baseclass instance if chains_validated + // has occurred + struct typedescription_t *override_field; + + // Used to track exclusion of baseclass fields + int override_count; + + // Tolerance for field errors for float fields + float fieldTolerance; + + // For raw fields (including children of embedded stuff) this is the flattened + // offset + int flatOffset[TD_OFFSET_COUNT]; + unsigned short flatGroup; +}; + +// See predictioncopy.h for implementation and notes +struct optimized_datamap_t; + +//----------------------------------------------------------------------------- +// Purpose: stores the list of objects in the hierarchy +// used to iterate through an object's data descriptions +//----------------------------------------------------------------------------- +struct datamap_t { + typedescription_t *dataDesc; + int dataNumFields; + char const *dataClassName; + datamap_t *baseMap; + + int m_nPackedSize; + optimized_datamap_t *m_pOptimizedDataMap; + +#if defined(_DEBUG) + bool bValidityChecked; +#endif // _DEBUG +}; + +//----------------------------------------------------------------------------- +// +// Macros used to implement datadescs +// +#define DECLARE_FRIEND_DATADESC_ACCESS() \ + template \ + friend void DataMapAccess(T *, datamap_t **p); \ + template \ + friend datamap_t *DataMapInit(T *); + +#define DECLARE_SIMPLE_DATADESC() \ + static datamap_t m_DataMap; \ + static datamap_t *GetBaseMap(); \ + template \ + friend void DataMapAccess(T *, datamap_t **p); \ + template \ + friend datamap_t *DataMapInit(T *); + +#if defined(POSIX) && !defined(_PS3) + +#define DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE() \ + static datamap_t m_DataMap; \ + static datamap_t *GetBaseMap(); \ + template \ + friend void ::DataMapAccess(T *, datamap_t **p); + +#else +#define DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE() \ + static datamap_t m_DataMap; \ + static datamap_t *GetBaseMap(); \ + template \ + friend void ::DataMapAccess(T *, datamap_t **p); \ + template \ + friend datamap_t * ::DataMapInit(T *); + +#endif + +#define DECLARE_DATADESC() \ + DECLARE_SIMPLE_DATADESC() \ + virtual datamap_t *GetDataDescMap(void); + +#define BEGIN_DATADESC(className) \ + datamap_t className::m_DataMap = {0, 0, #className, NULL}; \ + datamap_t *className::GetDataDescMap(void) { return &m_DataMap; } \ + datamap_t *className::GetBaseMap() { \ + datamap_t *pResult; \ + DataMapAccess((BaseClass *)NULL, &pResult); \ + return pResult; \ + } \ + BEGIN_DATADESC_GUTS(className) + +#define BEGIN_DATADESC_NO_BASE(className) \ + datamap_t className::m_DataMap = {0, 0, #className, NULL}; \ + datamap_t *className::GetDataDescMap(void) { return &m_DataMap; } \ + datamap_t *className::GetBaseMap() { return NULL; } \ + BEGIN_DATADESC_GUTS(className) + +#define BEGIN_SIMPLE_DATADESC(className) \ + datamap_t className::m_DataMap = {0, 0, #className, NULL}; \ + datamap_t *className::GetBaseMap() { return NULL; } \ + BEGIN_DATADESC_GUTS(className) + +#define BEGIN_SIMPLE_DATADESC_(className, BaseClass) \ + datamap_t className::m_DataMap = {0, 0, #className, NULL}; \ + datamap_t *className::GetBaseMap() { \ + datamap_t *pResult; \ + DataMapAccess((BaseClass *)NULL, &pResult); \ + return pResult; \ + } \ + BEGIN_DATADESC_GUTS(className) + +#define BEGIN_DATADESC_GUTS(className) \ + template \ + datamap_t *DataMapInit(T *); \ + template <> \ + datamap_t *DataMapInit(className *); \ + namespace className##_DataDescInit { \ + datamap_t *g_DataMapHolder = DataMapInit( \ + (className *) \ + NULL); /* This can/will be used for some clean up duties later */ \ + } \ + \ + template <> \ + datamap_t *DataMapInit(className *) { \ + typedef className classNameTypedef; \ + static CDatadescGeneratedNameHolder nameHolder(#className); \ + className::m_DataMap.baseMap = className::GetBaseMap(); \ + static typedescription_t dataDesc[] = { \ + {FIELD_VOID, 0, 0, 0, 0, 0, 0, 0, \ + 0}, /* so you can define "empty" tables */ + +#define BEGIN_DATADESC_GUTS_NAMESPACE(className, nameSpace) \ + template \ + datamap_t *nameSpace::DataMapInit(T *); \ + template <> \ + datamap_t *nameSpace::DataMapInit(className *); \ + namespace className##_DataDescInit { \ + datamap_t *g_DataMapHolder = nameSpace::DataMapInit( \ + (className *) \ + NULL); /* This can/will be used for some clean up duties later */ \ + } \ + \ + template <> \ + datamap_t *nameSpace::DataMapInit(className *) { \ + typedef className classNameTypedef; \ + static CDatadescGeneratedNameHolder nameHolder(#className); \ + className::m_DataMap.baseMap = className::GetBaseMap(); \ + static typedescription_t dataDesc[] = { \ + {FIELD_VOID, 0, 0, 0, 0, 0, 0, 0, \ + 0}, /* so you can define "empty" tables */ + +#define END_DATADESC() \ + } \ + ; \ + \ + if (sizeof(dataDesc) > sizeof(dataDesc[0])) { \ + classNameTypedef::m_DataMap.dataNumFields = SIZE_OF_ARRAY(dataDesc) - 1; \ + classNameTypedef::m_DataMap.dataDesc = &dataDesc[1]; \ + } else { \ + classNameTypedef::m_DataMap.dataNumFields = 1; \ + classNameTypedef::m_DataMap.dataDesc = dataDesc; \ + } \ + return &classNameTypedef::m_DataMap; \ + } + +// used for when there is no data description +#define IMPLEMENT_NULL_SIMPLE_DATADESC(derivedClass) \ + BEGIN_SIMPLE_DATADESC(derivedClass) \ + END_DATADESC() + +#define IMPLEMENT_NULL_SIMPLE_DATADESC_(derivedClass, baseClass) \ + BEGIN_SIMPLE_DATADESC_(derivedClass, baseClass) \ + END_DATADESC() + +#define IMPLEMENT_NULL_DATADESC(derivedClass) \ + BEGIN_DATADESC(derivedClass) \ + END_DATADESC \ + () + +// helps get the offset of a bitfield +#define BEGIN_BITFIELD(name) \ + union { \ + char name; \ + struct { +#define END_BITFIELD() \ + } \ + ; \ + } \ + ; + +//----------------------------------------------------------------------------- +// Forward compatability with potential separate byteswap datadescs + +#define DECLARE_BYTESWAP_DATADESC() DECLARE_SIMPLE_DATADESC() +#define BEGIN_BYTESWAP_DATADESC(name) BEGIN_SIMPLE_DATADESC(name) +#define BEGIN_BYTESWAP_DATADESC_(name, base) BEGIN_SIMPLE_DATADESC_(name, base) +#define END_BYTESWAP_DATADESC() \ + END_DATADESC \ + () + +//----------------------------------------------------------------------------- + +template +inline void DataMapAccess(T *ignored, datamap_t **p) { + *p = &T::m_DataMap; +} + +//----------------------------------------------------------------------------- + +class CDatadescGeneratedNameHolder { + public: + CDatadescGeneratedNameHolder(const char *pszBase) : m_pszBase(pszBase) { + m_nLenBase = strlen(m_pszBase); + } + + ~CDatadescGeneratedNameHolder() { + for (int i = 0; i < m_Names.Count(); i++) { + delete m_Names[i]; + } + } + + const char *GenerateName(const char *pszIdentifier) { + char *pBuf = new char[m_nLenBase + strlen(pszIdentifier) + 1]; + strcpy(pBuf, m_pszBase); + strcat(pBuf, pszIdentifier); + m_Names.AddToTail(pBuf); + return pBuf; + } + + private: + const char *m_pszBase; + size_t m_nLenBase; + CUtlVector m_Names; +}; + +// Compiler can require the global-namespace template friend to be declared +// before DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE() can be used +template +datamap_t *DataMapInit(T *); + +#include "tier0/memdbgoff.h" + +#endif // VPC_DATAMAP_H_ diff --git a/public/filesystem.h b/public/filesystem.h new file mode 100644 index 0000000..c49922a --- /dev/null +++ b/public/filesystem.h @@ -0,0 +1,1034 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_FILESYSTEM_H_ +#define VPC_FILESYSTEM_H_ + +#include + +#include "tier0/threadtools.h" +#include "tier0/memalloc.h" +#include "tier0/tslist.h" +#include "tier1/interface.h" +#include "tier1/utlsymbol.h" +#include "tier1/utlstring.h" +#include "tier1/functors.h" +#include "tier1/checksum_crc.h" +#include "tier1/utlqueue.h" +#include "appframework/iappsystem.h" +#include "tier2/tier2.h" + +#ifdef _PS3 +#include +#include + +struct HddCacheFileStatus; +extern char gSrcGameDataPath[]; +class CFileGroupSystem; +#endif + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- + +class CUtlBuffer; +class KeyValues; +class IFileList; + +typedef void *FileHandle_t; +typedef int FileFindHandle_t; +typedef void (*FileSystemLoggingFunc_t)(const char *fileName, + const char *accessType); +typedef int WaitForResourcesHandle_t; + +#ifdef _X360 +typedef void *HANDLE; +#endif + +//----------------------------------------------------------------------------- +// Enums used by the interface +//----------------------------------------------------------------------------- + +#define FILESYSTEM_MAX_SEARCH_PATHS 128 + +enum FileSystemSeek_t { + FILESYSTEM_SEEK_HEAD = SEEK_SET, + FILESYSTEM_SEEK_CURRENT = SEEK_CUR, + FILESYSTEM_SEEK_TAIL = SEEK_END, +}; + +enum { FILESYSTEM_INVALID_FIND_HANDLE = -1 }; + +enum FileWarningLevel_t { + // A problem! + FILESYSTEM_WARNING = -1, + + // Don't print anything + FILESYSTEM_WARNING_QUIET = 0, + + // On shutdown, report names of files left unclosed + FILESYSTEM_WARNING_REPORTUNCLOSED, + + // Report number of times a file was opened, closed + FILESYSTEM_WARNING_REPORTUSAGE, + + // Report all open/close events to console ( !slow! ) + FILESYSTEM_WARNING_REPORTALLACCESSES, + + // Report all open/close/read events to the console ( !slower! ) + FILESYSTEM_WARNING_REPORTALLACCESSES_READ, + + // Report all open/close/read/write events to the console ( !slower! ) + FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, + + // Report all open/close/read/write events and all async I/O file events to + // the console ( !slower(est)! ) + FILESYSTEM_WARNING_REPORTALLACCESSES_ASYNC, + +}; + +// search path filtering +enum PathTypeFilter_t { + FILTER_NONE = 0, // no filtering, all search path types match + FILTER_CULLPACK = 1, // pack based search paths are culled (maps and zips) + FILTER_CULLNONPACK = 2, // non-pack based search paths are culled + FILTER_CULLLOCALIZED = 3, // Ignore localized paths, assumes CULLNONPACK + FILTER_CULLLOCALIZED_ANY = 4, // Ignore any localized paths +}; + +// search path querying (bit flags) +enum { + PATH_IS_NORMAL = 0x00, // normal path, not pack based + PATH_IS_PACKFILE = 0x01, // path is a pack file + PATH_IS_MAPPACKFILE = 0x02, // path is a map pack file + PATH_IS_DVDDEV = 0x04, // path is the dvddev cache +}; +typedef uint32 PathTypeQuery_t; + +#define IS_PACKFILE(n) ((n) & (PATH_IS_PACKFILE | PATH_IS_MAPPACKFILE)) +#define IS_DVDDEV(n) ((n) & PATH_IS_DVDDEV) + +enum DVDMode_t { + DVDMODE_OFF = 0, // not using dvd + DVDMODE_STRICT = 1, // dvd device only + DVDMODE_DEV = 2, // dev mode, mutiple devices ok + DVDMODE_DEV_VISTA = 3, // dev mode from a vista host, mutiple devices ok +}; + +#ifdef _PS3 + +enum FsState_t { + FS_STATE_INIT = 0, + FS_STATE_LEVEL_LOAD = 1, + FS_STATE_LEVEL_RUN = 2, + FS_STATE_LEVEL_RESTORE = 3, + FS_STATE_LEVEL_LOAD_END = 4, + FS_STATE_EXITING = 5 +}; + +enum Ps3FileType_t { + PS3_FILETYPE_WAV, + PS3_FILETYPE_ANI, + PS3_FILETYPE_BSP, + PS3_FILETYPE_VMT, + PS3_FILETYPE_QPRE, + PS3_FILETYPE_OTHER, + PS3_FILETYPE_DIR, + PS3_FILETYPE_UNKNOWN +}; + +#endif + +// In non-retail builds, enable the file blocking access tracking stuff... +#if defined(TRACK_BLOCKING_IO) +enum FileBlockingWarning_t { + // Report how long synchronous i/o took to complete + FILESYSTEM_BLOCKING_SYNCHRONOUS = 0, + // Report how long async i/o took to complete if AsyncFileFinished caused it + // to load via "blocking" i/o + FILESYSTEM_BLOCKING_ASYNCHRONOUS_BLOCK, + // Report how long async i/o took to complete + FILESYSTEM_BLOCKING_ASYNCHRONOUS, + // Report how long the async "callback" took + FILESYSTEM_BLOCKING_CALLBACKTIMING, + + FILESYSTEM_BLOCKING_NUMBINS, +}; + +#pragma pack(1) +class FileBlockingItem { + public: + enum { + FB_ACCESS_OPEN = 1, + FB_ACCESS_CLOSE = 2, + FB_ACCESS_READ = 3, + FB_ACCESS_WRITE = 4, + FB_ACCESS_APPEND = 5, + FB_ACCESS_SIZE = 6 + }; + + FileBlockingItem() + : m_ItemType((FileBlockingWarning_t)0), + m_flElapsed(0.0f), + m_nAccessType(0) { + SetFileName(NULL); + } + + FileBlockingItem(int type, char const *filename, float elapsed, + int accessType) + : m_ItemType((FileBlockingWarning_t)type), + m_flElapsed(elapsed), + m_nAccessType(accessType) { + SetFileName(filename); + } + + void SetFileName(char const *filename) { + if (!filename) { + m_szFilename[0] = 0; + return; + } + + int len = V_strlen(filename); + if (len >= sizeof(m_szFilename)) { + V_strncpy(m_szFilename, &filename[len - sizeof(m_szFilename) + 1], + sizeof(m_szFilename)); + } else { + V_strncpy(m_szFilename, filename, sizeof(m_szFilename)); + } + } + + char const *GetFileName() const { return m_szFilename; } + + FileBlockingWarning_t m_ItemType; + float m_flElapsed; + byte m_nAccessType; + + private: + char m_szFilename[32]; +}; +#pragma pack() + +class IBlockingFileItemList { + public: + // You can't call any of the below calls without locking first + virtual void LockMutex() = 0; + virtual void UnlockMutex() = 0; + + virtual int First() const = 0; + virtual int Next(int i) const = 0; + virtual int InvalidIndex() const = 0; + + virtual const FileBlockingItem &Get(int index) const = 0; + + virtual void Reset() = 0; +}; + +#endif // TRACK_BLOCKING_IO + +enum FilesystemMountRetval_t { + FILESYSTEM_MOUNT_OK = 0, + FILESYSTEM_MOUNT_FAILED, +}; + +enum SearchPathAdd_t { + PATH_ADD_TO_HEAD, // First path searched + PATH_ADD_TO_TAIL, // Last path searched + PATH_ADD_TO_TAIL_ATINDEX, // First path searched +}; + +enum FilesystemOpenExFlags_t { + FSOPEN_UNBUFFERED = (1 << 0), + // This makes it calculate a CRC for the file (if the file came + // from disk) regardless of the IFileList passed to + // RegisterFileWhitelist. + FSOPEN_FORCE_TRACK_CRC = (1 << 1), + // 360 only, hint to FS that file is not allowed to be in pack file + FSOPEN_NEVERINPACK = (1 << 2), +}; + +#define FILESYSTEM_INVALID_HANDLE (FileHandle_t)0 + +//----------------------------------------------------------------------------- +// Structures used by the interface +//----------------------------------------------------------------------------- + +struct FileSystemStatistics { + CInterlockedUInt nReads, nWrites, nBytesRead, nBytesWritten, nSeeks; +}; + +//----------------------------------------------------------------------------- +// File system allocation functions. Client must free on failure +//----------------------------------------------------------------------------- +typedef void *(*FSAllocFunc_t)(const char *pszFilename, unsigned nBytes); + +//----------------------------------------------------------------------------- +// Used to display dirty disk error functions +//----------------------------------------------------------------------------- +typedef void (*FSDirtyDiskReportFunc_t)(); + +//----------------------------------------------------------------------------- +// Asynchronous support types +//----------------------------------------------------------------------------- +DECLARE_POINTER_HANDLE(FSAsyncControl_t); +DECLARE_POINTER_HANDLE(FSAsyncFile_t); +const FSAsyncFile_t FS_INVALID_ASYNC_FILE = (FSAsyncFile_t)(0x0000ffff); + +//--------------------------------------------------------- +// Async file status +//--------------------------------------------------------- +enum FSAsyncStatus_t { + FSASYNC_ERR_ALIGNMENT = -6, // read parameters invalid for unbuffered IO + FSASYNC_ERR_FAILURE = -5, // hard subsystem failure + FSASYNC_ERR_READING = -4, // read error on file + FSASYNC_ERR_NOMEMORY = -3, // out of memory for file read + FSASYNC_ERR_UNKNOWNID = -2, // caller's provided id is not recognized + // filename could not be opened (bad path, not exist, etc) + FSASYNC_ERR_FILEOPEN = -1, + FSASYNC_OK = 0, // operation is successful + FSASYNC_STATUS_PENDING, // file is properly queued, waiting for service + FSASYNC_STATUS_INPROGRESS, // file is being accessed + FSASYNC_STATUS_ABORTED, // file was aborted by caller + FSASYNC_STATUS_UNSERVICED, // file is not yet queued +}; + +//--------------------------------------------------------- +// Async request flags +//--------------------------------------------------------- +enum FSAsyncFlags_t { + // do the allocation for dataPtr, but don't free + FSASYNC_FLAGS_ALLOCNOFREE = (1 << 0), + // free the memory for the dataPtr post callback + FSASYNC_FLAGS_FREEDATAPTR = (1 << 1), + // Actually perform the operation synchronously. Used to simplify client code + // paths + FSASYNC_FLAGS_SYNC = (1 << 2), + // allocate an extra byte and null terminate the buffer read in + FSASYNC_FLAGS_NULLTERMINATE = (1 << 3), +}; + +//--------------------------------------------------------- +// Return value for CheckFileCRC. +//--------------------------------------------------------- +enum EFileCRCStatus { + k_eFileCRCStatus_CantOpenFile, // We don't have this file. + k_eFileCRCStatus_GotCRC +}; + +// Used in CacheFileCRCs. +enum ECacheCRCType { + k_eCacheCRCType_SingleFile, + k_eCacheCRCType_Directory, + k_eCacheCRCType_Directory_Recursive +}; + +//--------------------------------------------------------- +// Optional completion callback for each async file serviced (or failed) +// call is not reentrant, async i/o guaranteed suspended until return +// Note: If you change the signature of the callback, you will have to account +// for it in FileSystemV12 (toml [4/18/2005] ) +//--------------------------------------------------------- +struct FileAsyncRequest_t; +typedef void (*FSAsyncCallbackFunc_t)(const FileAsyncRequest_t &request, + int nBytesRead, FSAsyncStatus_t err); + +//----------------------------------------------------------------------------- +// Used to add results from async directory scans +//----------------------------------------------------------------------------- +typedef void (*FSAsyncScanAddFunc_t)(void *pContext, char *pFoundPath, + char *pFoundFile); +typedef void (*FSAsyncScanCompleteFunc_t)(void *pContext, FSAsyncStatus_t err); + +//--------------------------------------------------------- +// Description of an async request +//--------------------------------------------------------- +struct FileAsyncRequest_t { + FileAsyncRequest_t() { + memset(this, 0, sizeof(*this)); + hSpecificAsyncFile = FS_INVALID_ASYNC_FILE; + } + const char *pszFilename; // file system name + void *pData; // optional, system will alloc/free if NULL + int nOffset; // optional initial seek_set, 0=beginning + int nBytes; // optional read clamp, -1=exist test, 0=full read + FSAsyncCallbackFunc_t pfnCallback; // optional completion callback + void *pContext; // caller's unique file identifier + int priority; // inter list priority, 0=lowest + unsigned flags; // behavior modifier + const char *pszPathID; // path ID (NOTE: this field is here to remain binary + // compatible with release HL2 filesystem interface) + // Optional hint obtained using AsyncBeginRead() + FSAsyncFile_t hSpecificAsyncFile; + // custom allocator. can be null. not compatible with + // FSASYNC_FLAGS_FREEDATAPTR + FSAllocFunc_t pfnAlloc; +}; + +class CUnverifiedCRCFile { + public: + char m_PathID[MAX_PATH]; + char m_Filename[MAX_PATH]; + CRC32_t m_CRC; +}; + +// Spew flags for SetWhitelistSpewFlags (set with the fs_whitelist_spew_flags +// cvar). Update the comment for the fs_whitelist_spew_flags cvar if you change +// these. + +// list files as they are added to the CRC tracker +#define WHITELIST_SPEW_WHILE_LOADING 0x0001 +// show files the filesystem is telling the engine to reload +#define WHITELIST_SPEW_RELOAD_FILES 0x0002 +// show files the filesystem is NOT telling the engine to reload +#define WHITELIST_SPEW_DONT_RELOAD_FILES 0x0004 + +// DLC license mask flags is 32 publisher defined bits +// MSW 16 bits in 8.8: Type.SubVersion +// LSW 16 bits: Flags + +// return id component +#define DLC_LICENSE_ID(x) ((((unsigned int)(x)) >> 24) & 0x000000FF) +// returns minor version component (not generally used, i.e. we dont rev dlc's +// yet) +#define DLC_LICENSE_MINORVERSION(x) ((((unsigned int)(x)) >> 16) & 0x000000FF) +// returns license flags +#define DLC_LICENSE_FLAGS(x) ((((unsigned int)(x)) & 0x0000FFFF)) + +#define DLCFLAGS_PRESENCE_ONLY 0x0001 // causes no search path loadout + +//----------------------------------------------------------------------------- +// Base file system interface +//----------------------------------------------------------------------------- + +// This is the minimal interface that can be implemented to provide access to +// a named set of files. +#define BASEFILESYSTEM_INTERFACE_VERSION "VBaseFileSystem011" + +abstract_class IBaseFileSystem { + public: + virtual int Read(void *pOutput, int size, FileHandle_t file) = 0; + virtual int Write(void const *pInput, int size, FileHandle_t file) = 0; + + // if pathID is NULL, all paths will be searched for the file + virtual FileHandle_t Open(const char *pFileName, const char *pOptions, + const char *pathID = 0) = 0; + virtual void Close(FileHandle_t file) = 0; + + virtual void Seek(FileHandle_t file, int pos, FileSystemSeek_t seekType) = 0; + virtual unsigned int Tell(FileHandle_t file) = 0; + virtual unsigned int Size(FileHandle_t file) = 0; + virtual unsigned int Size(const char *pFileName, const char *pPathID = 0) = 0; + + virtual void Flush(FileHandle_t file) = 0; + virtual bool Precache(const char *pFileName, const char *pPathID = 0) = 0; + + virtual bool FileExists(const char *pFileName, const char *pPathID = 0) = 0; + virtual bool IsFileWritable(char const *pFileName, + const char *pPathID = 0) = 0; + virtual bool SetFileWritable(char const *pFileName, bool writable, + const char *pPathID = 0) = 0; + + virtual long GetFileTime(const char *pFileName, const char *pPathID = 0) = 0; + + //-------------------------------------------------------- + // Reads/writes files to utlbuffers. Use this for optimal read performance + // when doing open/read/close + //-------------------------------------------------------- + virtual bool ReadFile(const char *pFileName, const char *pPath, + CUtlBuffer &buf, int nMaxBytes = 0, + int nStartingByte = 0, + FSAllocFunc_t pfnAlloc = NULL) = 0; + virtual bool WriteFile(const char *pFileName, const char *pPath, + CUtlBuffer &buf) = 0; + virtual bool UnzipFile(const char *pFileName, const char *pPath, + const char *pDestination) = 0; +}; + +abstract_class IIoStats { + public: + virtual void OnFileSeek(int nTimeInMs) = 0; + virtual void OnFileRead(int nTimeInMs, int nBytesRead) = 0; + virtual void OnFileOpen(const char *pFileName) = 0; + + virtual int GetNumberOfFileSeeks() = 0; + virtual int GetTimeInFileSeek() = 0; + + virtual int GetNumberOfFileReads() = 0; + virtual int GetTimeInFileReads() = 0; + virtual int GetFileReadTotalSize() = 0; + + virtual int GetNumberOfFileOpens() = 0; + + virtual void Reset() = 0; + + protected: + virtual ~IIoStats() { + // Do nothing... + } +}; + +//----------------------------------------------------------------------------- +// Main file system interface +//----------------------------------------------------------------------------- +abstract_class IFileSystem : public IAppSystem, public IBaseFileSystem { + public: + //-------------------------------------------------------- + // Steam operations + //-------------------------------------------------------- + + virtual bool IsSteam() const = 0; + + // Supplying an extra app id will mount this app in addition + // to the one specified in the environment variable "steamappid" + // + // If nExtraAppId is < -1, then it will mount that app ID only. + // (Was needed by the dedicated server b/c the "SteamAppId" env var only gets + // passed to steam.dll at load time, so the dedicated couldn't pass it in that + // way). + virtual FilesystemMountRetval_t MountSteamContent(int nExtraAppId = -1) = 0; + + //-------------------------------------------------------- + // Search path manipulation + //-------------------------------------------------------- + + // Add paths in priority order (mod dir, game dir, ....) + // If one or more .pak files are in the specified directory, then they are + // added after the file system path + // If the path is the relative path to a .bsp file, then any previous .bsp + // file + // override is cleared and the current .bsp is searched for an embedded PAK + // file and this file becomes the highest priority search path ( i.e., it's + // looked at first + // even before the mod's file system path ). + virtual void AddSearchPath(const char *pPath, const char *pathID, + SearchPathAdd_t addType = PATH_ADD_TO_TAIL) = 0; + virtual bool RemoveSearchPath(const char *pPath, const char *pathID = 0) = 0; + + // Remove all search paths (including write path?) + virtual void RemoveAllSearchPaths(void) = 0; + + // Remove search paths associated with a given pathID + virtual void RemoveSearchPaths(const char *szPathID) = 0; + + // This is for optimization. If you mark a path ID as "by request only", then + // files inside it will only be accessed if the path ID is specifically + // requested. Otherwise, it will be ignored. If there are currently no search + // paths with the specified path ID, then it will still remember it in case + // you add search paths with this path ID. + virtual void MarkPathIDByRequestOnly(const char *pPathID, + bool bRequestOnly) = 0; + + // converts a partial path into a full path + virtual const char *RelativePathToFullPath( + const char *pFileName, const char *pPathID, char *pLocalPath, + int localPathBufferSize, PathTypeFilter_t pathFilter = FILTER_NONE, + PathTypeQuery_t *pPathType = NULL) = 0; +#if IsGameConsole() + // Given a relative path, gets the PACK file that contained this file and its + // offset and size. Can be used to prefetch a file to a HDD for caching + // reason. + virtual bool GetPackFileInfoFromRelativePath( + const char *pFileName, const char *pPathID, char *pPackPath, + int nPackPathBufferSize, int64 &nPosition, int64 &nLength) = 0; +#endif + // Returns the search path, each path is separated by ;s. Returns the length + // of the string returned + virtual int GetSearchPath(const char *pathID, bool bGetPackFiles, char *pPath, + int nMaxLen) = 0; + + // interface for custom pack files > 4Gb + virtual bool AddPackFile(const char *fullpath, const char *pathID) = 0; + + //-------------------------------------------------------- + // File manipulation operations + //-------------------------------------------------------- + + // Deletes a file (on the WritePath) + virtual void RemoveFile(char const *pRelativePath, + const char *pathID = 0) = 0; + + // Renames a file (on the WritePath) + virtual bool RenameFile(char const *pOldPath, char const *pNewPath, + const char *pathID = 0) = 0; + + // create a local directory structure + virtual void CreateDirHierarchy(const char *path, const char *pathID = 0) = 0; + + // File I/O and info + virtual bool IsDirectory(const char *pFileName, const char *pathID = 0) = 0; + + virtual void FileTimeToString(char *pStrip, int maxCharsIncludingTerminator, + long fileTime) = 0; + + //-------------------------------------------------------- + // Open file operations + //-------------------------------------------------------- + + virtual void SetBufferSize(FileHandle_t file, unsigned nBytes) = 0; + + virtual bool IsOk(FileHandle_t file) = 0; + + virtual bool EndOfFile(FileHandle_t file) = 0; + + virtual char *ReadLine(char *pOutput, int maxChars, FileHandle_t file) = 0; + virtual int FPrintf(FileHandle_t file, const char *pFormat, ...) + FMTFUNCTION(3, 4) = 0; + + //-------------------------------------------------------- + // Dynamic library operations + //-------------------------------------------------------- + + // load/unload modules + virtual CSysModule *LoadModule(const char *pFileName, const char *pPathID = 0, + bool bValidatedDllOnly = true) = 0; + virtual void UnloadModule(CSysModule * pModule) = 0; + + //-------------------------------------------------------- + // File searching operations + //-------------------------------------------------------- + + // FindFirst/FindNext. Also see FindFirstEx. + virtual const char *FindFirst(const char *pWildCard, + FileFindHandle_t *pHandle) = 0; + virtual const char *FindNext(FileFindHandle_t handle) = 0; + virtual bool FindIsDirectory(FileFindHandle_t handle) = 0; + virtual void FindClose(FileFindHandle_t handle) = 0; + + // Same as FindFirst, but you can filter by path ID, which can make it faster. + virtual const char *FindFirstEx(const char *pWildCard, const char *pPathID, + FileFindHandle_t *pHandle) = 0; + + // Searches for a file in all paths and results absolute path names for the + // file, works in pack files (zip and vpk) too Lets you search for something + // like sound/sound.cache and get a list of every sound cache + virtual void FindFileAbsoluteList( + CUtlVector & outAbsolutePathNames, const char *pWildCard, + const char *pPathID) = 0; + + //-------------------------------------------------------- + // File name and directory operations + //-------------------------------------------------------- + + // FIXME: This method is obsolete! Use RelativePathToFullPath instead! + // converts a partial path into a full path + virtual const char *GetLocalPath(const char *pFileName, char *pLocalPath, + int localPathBufferSize) = 0; + + // Returns true on success ( based on current list of search paths, otherwise + // false if + // it can't be resolved ) + virtual bool FullPathToRelativePath(const char *pFullpath, char *pRelative, + int maxlen) = 0; + + // Gets the current working directory + virtual bool GetCurrentDirectory(char *pDirectory, int maxlen) = 0; + + //-------------------------------------------------------- + // Filename dictionary operations + //-------------------------------------------------------- + + virtual FileNameHandle_t FindOrAddFileName(char const *pFileName) = 0; + virtual bool String(const FileNameHandle_t &handle, char *buf, + int buflen) = 0; + + //-------------------------------------------------------- + // Asynchronous file operations + //-------------------------------------------------------- + + //------------------------------------ + // Global operations + //------------------------------------ + FSAsyncStatus_t AsyncRead(const FileAsyncRequest_t &request, + FSAsyncControl_t *phControl = NULL) { + return AsyncReadMultiple(&request, 1, phControl); + } + virtual FSAsyncStatus_t AsyncReadMultiple( + const FileAsyncRequest_t *pRequests, int nRequests, + FSAsyncControl_t *phControls = NULL) = 0; + virtual FSAsyncStatus_t AsyncAppend(const char *pFileName, const void *pSrc, + int nSrcBytes, bool bFreeMemory, + FSAsyncControl_t *pControl = NULL) = 0; + virtual FSAsyncStatus_t AsyncAppendFile( + const char *pAppendToFileName, const char *pAppendFromFileName, + FSAsyncControl_t *pControl = NULL) = 0; + virtual void AsyncFinishAll(int iToPriority = 0) = 0; + virtual void AsyncFinishAllWrites() = 0; + virtual FSAsyncStatus_t AsyncFlush() = 0; + virtual bool AsyncSuspend() = 0; + virtual bool AsyncResume() = 0; + + //------------------------------------ + // Functions to hold a file open if planning on doing mutiple reads. Use is + // optional, and is taken only as a hint + //------------------------------------ + virtual FSAsyncStatus_t AsyncBeginRead(const char *pszFile, + FSAsyncFile_t *phFile) = 0; + virtual FSAsyncStatus_t AsyncEndRead(FSAsyncFile_t hFile) = 0; + + //------------------------------------ + // Request management + //------------------------------------ + virtual FSAsyncStatus_t AsyncFinish(FSAsyncControl_t hControl, + bool wait = true) = 0; + virtual FSAsyncStatus_t AsyncGetResult(FSAsyncControl_t hControl, + void **ppData, int *pSize) = 0; + virtual FSAsyncStatus_t AsyncAbort(FSAsyncControl_t hControl) = 0; + virtual FSAsyncStatus_t AsyncStatus(FSAsyncControl_t hControl) = 0; + // set a new priority for a file already in the queue + virtual FSAsyncStatus_t AsyncSetPriority(FSAsyncControl_t hControl, + int newPriority) = 0; + virtual void AsyncAddRef(FSAsyncControl_t hControl) = 0; + virtual void AsyncRelease(FSAsyncControl_t hControl) = 0; + + //-------------------------------------------------------- + // Remote resource management + //-------------------------------------------------------- + + // starts waiting for resources to be available + // returns FILESYSTEM_INVALID_HANDLE if there is nothing to wait on + virtual WaitForResourcesHandle_t WaitForResources( + const char *resourcelist) = 0; + // get progress on waiting for resources; progress is a float [0, 1], complete + // is true on the waiting being done returns false if no progress is available + // any calls after complete is true or on an invalid handle will return false, + // 0.0f, true + virtual bool GetWaitForResourcesProgress(WaitForResourcesHandle_t handle, + float *progress /* out */, + bool *complete /* out */) = 0; + // cancels a progress call + virtual void CancelWaitForResources(WaitForResourcesHandle_t handle) = 0; + + // hints that a set of files will be loaded in near future + // HintResourceNeed() is not to be confused with resource precaching. + virtual int HintResourceNeed(const char *hintlist, int forgetEverything) = 0; + // returns true if a file is on disk + virtual bool IsFileImmediatelyAvailable(const char *pFileName) = 0; + + // copies file out of pak/bsp/steam cache onto disk (to be accessible by + // third-party code) + virtual void GetLocalCopy(const char *pFileName) = 0; + + //-------------------------------------------------------- + // Debugging operations + //-------------------------------------------------------- + + // Dump to printf/OutputDebugString the list of files that have not been + // closed + virtual void PrintOpenedFiles(void) = 0; + virtual void PrintSearchPaths(void) = 0; + + // output + virtual void SetWarningFunc(void (*pfnWarning)(const char *fmt, ...)) = 0; + virtual void SetWarningLevel(FileWarningLevel_t level) = 0; + virtual void AddLoggingFunc( + void (*pfnLogFunc)(const char *fileName, const char *accessType)) = 0; + virtual void RemoveLoggingFunc(FileSystemLoggingFunc_t logFunc) = 0; + + // Returns the file system statistics retreived by the implementation. Returns + // NULL if not supported. + virtual const FileSystemStatistics *GetFilesystemStatistics() = 0; + +#if defined(_PS3) + // EA cruft not used: virtual Ps3FileType_t GetPs3FileType(const char* path) + // = 0; + virtual void LogFileAccess(const char *pFullFileName) = 0; + + // Prefetches a full file in the HDD cache. + virtual bool PrefetchFile(const char *pFileName, int nPriority, + bool bPersist) = 0; + // Prefetches a file portion in the HDD cache. + virtual bool PrefetchFile(const char *pFileName, int nPriority, bool bPersist, + int64 nOffset, int64 nSize) = 0; + // Flushes the HDD cache. + virtual void FlushCache() = 0; + // Suspends all prefetches (like when the game is doing a file intensive + // operation not controlled by the HDD cache, like Bink movies). + virtual void SuspendPrefetches(const char *pWhy) = 0; + // Resumes prefetches. This function has to to be called as many time as + // SuspendPrefetches() to effectively resumes prefetches. + virtual void ResumePrefetches(const char *pWhy) = 0; + + // Gets called when we are starting / ending a save (it allows the file system + // to reduce its HDD usage and use BluRay instead). + virtual void OnSaveStateChanged(bool bSaving) = 0; + + // Returns the prefetching state. If true, everything has been prefetched on + // the HDD. + virtual bool IsPrefetchingDone() = 0; + +#endif //_PS3 + //-------------------------------------------------------- + // Start of new functions after Lost Coast release (7/05) + //-------------------------------------------------------- + + virtual FileHandle_t OpenEx(const char *pFileName, const char *pOptions, + unsigned flags = 0, const char *pathID = 0, + char **ppszResolvedFilename = NULL) = 0; + + // Extended version of read provides more context to allow for more optimal + // reading + virtual int ReadEx(void *pOutput, int sizeDest, int size, + FileHandle_t file) = 0; + virtual int ReadFileEx(const char *pFileName, const char *pPath, void **ppBuf, + bool bNullTerminate = false, + bool bOptimalAlloc = false, int nMaxBytes = 0, + int nStartingByte = 0, + FSAllocFunc_t pfnAlloc = NULL) = 0; + + virtual FileNameHandle_t FindFileName(char const *pFileName) = 0; + +#if defined(TRACK_BLOCKING_IO) + virtual void EnableBlockingFileAccessTracking(bool state) = 0; + virtual bool IsBlockingFileAccessEnabled() const = 0; + + virtual IBlockingFileItemList *RetrieveBlockingFileAccessInfo() = 0; +#endif + + virtual void SetupPreloadData() = 0; + virtual void DiscardPreloadData() = 0; + + // Fixme, we could do these via a string embedded into the compiled data, + // etc... + enum KeyValuesPreloadType_t { + TYPE_VMT, + TYPE_SOUNDEMITTER, + TYPE_SOUNDSCAPE, + TYPE_SOUNDOPERATORS, + NUM_PRELOAD_TYPES + }; + + // If the "PreloadedData" hasn't been purged, then this'll try and instance + // the KeyValues using the fast path of compiled keyvalues loaded during + // startup. Otherwise, it'll just fall through to the regular KeyValues + // loading routines + virtual KeyValues *LoadKeyValues(KeyValuesPreloadType_t type, + char const *filename, + char const *pPathID = 0) = 0; + virtual bool LoadKeyValues(KeyValues & head, KeyValuesPreloadType_t type, + char const *filename, char const *pPathID = 0) = 0; + + virtual FSAsyncStatus_t AsyncWrite( + const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, + bool bAppend = false, FSAsyncControl_t *pControl = NULL) = 0; + virtual FSAsyncStatus_t AsyncWriteFile(const char *pFileName, + const CUtlBuffer *pSrc, int nSrcBytes, + bool bFreeMemory, bool bAppend = false, + FSAsyncControl_t *pControl = NULL) = 0; + // Async read functions with memory blame + FSAsyncStatus_t AsyncReadCreditAlloc(const FileAsyncRequest_t &request, + const char *pszFile, int line, + FSAsyncControl_t *phControl = NULL) { + return AsyncReadMultipleCreditAlloc(&request, 1, pszFile, line, phControl); + } + virtual FSAsyncStatus_t AsyncReadMultipleCreditAlloc( + const FileAsyncRequest_t *pRequests, int nRequests, const char *pszFile, + int line, FSAsyncControl_t *phControls = NULL) = 0; + + virtual FSAsyncStatus_t AsyncDirectoryScan( + const char *pSearchSpec, bool recurseFolders, void *pContext, + FSAsyncScanAddFunc_t pfnAdd, FSAsyncScanCompleteFunc_t pfnDone, + FSAsyncControl_t *pControl = NULL) = 0; + + virtual bool GetFileTypeForFullPath(char const *pFullPath, wchar_t *buf, + size_t bufSizeInBytes) = 0; + + //-------------------------------------------------------- + //-------------------------------------------------------- + virtual bool ReadToBuffer(FileHandle_t hFile, CUtlBuffer & buf, + int nMaxBytes = 0, + FSAllocFunc_t pfnAlloc = NULL) = 0; + + //-------------------------------------------------------- + // Optimal IO operations + //-------------------------------------------------------- + virtual bool GetOptimalIOConstraints( + FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, + unsigned *pBufferAlign) = 0; + inline unsigned GetOptimalReadSize(FileHandle_t hFile, unsigned nLogicalSize); + virtual void *AllocOptimalReadBuffer(FileHandle_t hFile, unsigned nSize = 0, + unsigned nOffset = 0) = 0; + virtual void FreeOptimalReadBuffer(void *) = 0; + + //-------------------------------------------------------- + // + //-------------------------------------------------------- + virtual void BeginMapAccess() = 0; + virtual void EndMapAccess() = 0; + + // Returns true on success, otherwise false if it can't be resolved + virtual bool FullPathToRelativePathEx(const char *pFullpath, + const char *pPathId, char *pRelative, + int maxlen) = 0; + + virtual int GetPathIndex(const FileNameHandle_t &handle) = 0; + virtual long GetPathTime(const char *pPath, const char *pPathID) = 0; + + virtual DVDMode_t GetDVDMode() = 0; + + //-------------------------------------------------------- + // Whitelisting for pure servers. + //-------------------------------------------------------- + + // This should be called ONCE at startup. Multiplayer games (gameinfo.txt does + // not contain singleplayer_only) want to enable this so sv_pure works. + virtual void EnableWhitelistFileTracking(bool bEnable) = 0; + + // This is called when the client connects to a server using a + // pure_server_whitelist.txt file. + // + // Files listed in pWantCRCList will have CRCs calculated for them IF they + // come off disk (and those CRCs will come out of GetUnverifiedCRCFiles). + // + // Files listed in pAllowFromDiskList will be allowed to load from disk. All + // other files will be forced to come from Steam. + // + // The filesystem hangs onto the whitelists you pass in here, and it will + // Release() them when it closes down or when you call this function again. + // + // NOTE: The whitelists you pass in here will be accessed from multiple + // threads, so make sure the + // IsFileInList function is thread safe. + // + // If pFilesToReload is non-null, the filesystem will hand back a list of + // files that should be reloaded because they are now "dirty". For example, if + // you were on a non-pure server and you loaded a certain model, and then you + // connected to a pure server that said that model had to come from Steam, + // then pFilesToReload would specify that model and the engine should reload + // it so it can come from Steam. + // + // Be sure to call Release() on pFilesToReload. + virtual void RegisterFileWhitelist(IFileList * pWantCRCList, + IFileList * pAllowFromDiskList, + IFileList * *pFilesToReload) = 0; + + // Called when the client logs onto a server. Any files that came off disk + // should be marked as unverified because this server may have a different set + // of files it wants to guarantee. + virtual void MarkAllCRCsUnverified() = 0; + + // As the server loads whitelists when it transitions maps, it calls this to + // calculate CRCs for any files marked with check_crc. Then it calls + // CheckCachedFileCRC later when it gets client requests to verify CRCs. + virtual void CacheFileCRCs(const char *pPathname, ECacheCRCType eType, + IFileList *pFilter) = 0; + virtual EFileCRCStatus CheckCachedFileCRC( + const char *pPathID, const char *pRelativeFilename, CRC32_t *pCRC) = 0; + + // Fills in the list of files that have been loaded off disk and have not been + // verified. Returns the number of files filled in (between 0 and nMaxFiles). + // + // This also removes any files it's returning from the unverified CRC list, so + // they won't be returned from here again. The client sends batches of these + // to the server to verify. + virtual int GetUnverifiedCRCFiles(CUnverifiedCRCFile * pFiles, + int nMaxFiles) = 0; + + // Control debug message output. + // Pass a combination of WHITELIST_SPEW_ flags. + virtual int GetWhitelistSpewFlags() = 0; + virtual void SetWhitelistSpewFlags(int flags) = 0; + + // Installs a callback used to display a dirty disk dialog + virtual void InstallDirtyDiskReportFunc(FSDirtyDiskReportFunc_t func) = 0; + + virtual bool IsLaunchedFromXboxHDD() = 0; + virtual bool IsInstalledToXboxHDDCache() = 0; + virtual bool IsDVDHosted() = 0; + virtual bool IsInstallAllowed() = 0; + + virtual int GetSearchPathID(char *pPath, int nMaxLen) = 0; + virtual bool FixupSearchPathsAfterInstall() = 0; + + virtual FSDirtyDiskReportFunc_t GetDirtyDiskReportFunc() = 0; + + virtual void AddVPKFile(char const *pszName, + SearchPathAdd_t addType = PATH_ADD_TO_TAIL) = 0; + virtual void RemoveVPKFile(char const *pszName) = 0; + virtual void GetVPKFileNames(CUtlVector & destVector) = 0; + virtual void RemoveAllMapSearchPaths() = 0; + virtual void SyncDvdDevCache() = 0; + + virtual bool GetStringFromKVPool(CRC32_t poolKey, unsigned int key, + char *pOutBuff, int buflen) = 0; + + virtual bool DiscoverDLC(int iController) = 0; + virtual int IsAnyDLCPresent(bool *pbDLCSearchPathMounted = NULL) = 0; + virtual bool GetAnyDLCInfo(int iDLC, unsigned int *pLicenseMask, + wchar_t *pTitleBuff, int nOutTitleSize) = 0; + virtual int IsAnyCorruptDLC() = 0; + virtual bool GetAnyCorruptDLCInfo(int iCorruptDLC, wchar_t *pTitleBuff, + int nOutTitleSize) = 0; + virtual bool AddDLCSearchPaths() = 0; + virtual bool IsSpecificDLCPresent(unsigned int nDLCPackage) = 0; + + // call this to look for CPU-hogs during loading processes. When you set this, + // a breakpoint will be issued whenever the indicated # of seconds go by + // without an i/o request. Passing 0.0 will turn off the functionality. + virtual void SetIODelayAlarm(float flThreshhold) = 0; + + virtual bool AddXLSPUpdateSearchPath(const void *pData, int nSize) = 0; + + virtual IIoStats *GetIoStats() = 0; +}; + + //----------------------------------------------------------------------------- + +#if defined(_X360) && !defined(_CERT) +extern char g_szXboxProfileLastFileOpened[MAX_PATH]; +#define SetLastProfileFileRead(s) \ + V_strncpy(g_szXboxProfileLastFileOpened, \ + sizeof(g_szXboxProfileLastFileOpened), pFileName) +#define GetLastProfileFileRead() (&g_szXboxProfileLastFileOpened[0]) +#else +#define SetLastProfileFileRead(s) ((void)0) +#define GetLastProfileFileRead() NULL +#endif + +#if defined(_X360) && defined(_BASETSD_H_) +class CXboxDiskCacheSetter { + public: + CXboxDiskCacheSetter(SIZE_T newSize) { + m_oldSize = XGetFileCacheSize(); + XSetFileCacheSize(newSize); + } + + ~CXboxDiskCacheSetter() { XSetFileCacheSize(m_oldSize); } + + private: + SIZE_T m_oldSize; +}; +#define DISK_INTENSIVE() CXboxDiskCacheSetter cacheSetter(1024 * 1024) +#else +#define DISK_INTENSIVE() ((void)0) +#endif + +//----------------------------------------------------------------------------- + +inline unsigned IFileSystem::GetOptimalReadSize(FileHandle_t hFile, + unsigned nLogicalSize) { + unsigned align; + if (GetOptimalIOConstraints(hFile, &align, NULL, NULL)) + return AlignValue(nLogicalSize, align); + else + return nLogicalSize; +} + +//----------------------------------------------------------------------------- + +// We include this here so it'll catch compile errors in VMPI early. +#include "filesystem_passthru.h" + +//----------------------------------------------------------------------------- +// Async memory tracking +//----------------------------------------------------------------------------- + +#if (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +#define AsyncRead(a, b) AsyncReadCreditAlloc(a, __FILE__, __LINE__, b) +#define AsyncReadMutiple(a, b, c) \ + AsyncReadMultipleCreditAlloc(a, b, __FILE__, __LINE__, c) +#endif + +//----------------------------------------------------------------------------- +// Globals Exposed +//----------------------------------------------------------------------------- +DECLARE_TIER2_INTERFACE(IFileSystem, g_pFullFileSystem); + +#endif // VPC_FILESYSTEM_H_ diff --git a/public/filesystem_passthru.h b/public/filesystem_passthru.h new file mode 100644 index 0000000..dbd359f --- /dev/null +++ b/public/filesystem_passthru.h @@ -0,0 +1,643 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_FILESYSTEM_PASSTHRU_H_ +#define VPC_FILESYSTEM_PASSTHRU_H_ + +#include "filesystem.h" +#include +#include + +#ifdef AsyncRead +#undef AsyncRead +#undef AsyncReadMutiple +#endif + +// +// These classes pass all filesystem interface calls through to another +// filesystem interface. They can be used anytime you want to override a couple +// things in a filesystem. VMPI uses this to override the base filesystem calls +// while allowing the rest of the filesystem functionality to work on the +// master. +// + +template +class CInternalFileSystemPassThru : public Base { + public: + CInternalFileSystemPassThru() { m_pBaseFileSystemPassThru = NULL; } + virtual void InitPassThru(IBaseFileSystem *pBaseFileSystemPassThru) { + m_pBaseFileSystemPassThru = pBaseFileSystemPassThru; + } + virtual int Read(void *pOutput, int size, FileHandle_t file) { + return m_pBaseFileSystemPassThru->Read(pOutput, size, file); + } + virtual int Write(void const *pInput, int size, FileHandle_t file) { + return m_pBaseFileSystemPassThru->Write(pInput, size, file); + } + virtual FileHandle_t Open(const char *pFileName, const char *pOptions, + const char *pathID) { + return m_pBaseFileSystemPassThru->Open(pFileName, pOptions, pathID); + } + virtual void Close(FileHandle_t file) { + m_pBaseFileSystemPassThru->Close(file); + } + virtual void Seek(FileHandle_t file, int pos, FileSystemSeek_t seekType) { + m_pBaseFileSystemPassThru->Seek(file, pos, seekType); + } + virtual unsigned int Tell(FileHandle_t file) { + return m_pBaseFileSystemPassThru->Tell(file); + } + virtual unsigned int Size(FileHandle_t file) { + return m_pBaseFileSystemPassThru->Size(file); + } + virtual unsigned int Size(const char *pFileName, const char *pPathID) { + return m_pBaseFileSystemPassThru->Size(pFileName, pPathID); + } + virtual void Flush(FileHandle_t file) { + m_pBaseFileSystemPassThru->Flush(file); + } + virtual bool Precache(const char *pFileName, const char *pPathID) { + return m_pBaseFileSystemPassThru->Precache(pFileName, pPathID); + } + virtual bool FileExists(const char *pFileName, const char *pPathID) { + return m_pBaseFileSystemPassThru->FileExists(pFileName, pPathID); + } + virtual bool IsFileWritable(char const *pFileName, const char *pPathID) { + return m_pBaseFileSystemPassThru->IsFileWritable(pFileName, pPathID); + } + virtual bool SetFileWritable(char const *pFileName, bool writable, + const char *pPathID) { + return m_pBaseFileSystemPassThru->SetFileWritable(pFileName, writable, + pPathID); + } + virtual long GetFileTime(const char *pFileName, const char *pPathID) { + return m_pBaseFileSystemPassThru->GetFileTime(pFileName, pPathID); + } + virtual bool ReadFile(const char *pFileName, const char *pPath, + CUtlBuffer &buf, int nMaxBytes = 0, + int nStartingByte = 0, FSAllocFunc_t pfnAlloc = NULL) { + return m_pBaseFileSystemPassThru->ReadFile(pFileName, pPath, buf, nMaxBytes, + nStartingByte, pfnAlloc); + } + virtual bool WriteFile(const char *pFileName, const char *pPath, + CUtlBuffer &buf) { + return m_pBaseFileSystemPassThru->WriteFile(pFileName, pPath, buf); + } + virtual bool UnzipFile(const char *pFileName, const char *pPath, + const char *pDestination) { + return m_pBaseFileSystemPassThru->UnzipFile(pFileName, pPath, pDestination); + } + + protected: + IBaseFileSystem *m_pBaseFileSystemPassThru; +}; + +class CBaseFileSystemPassThru + : public CInternalFileSystemPassThru { + public: +}; + +class CFileSystemPassThru : public CInternalFileSystemPassThru { + public: + typedef CInternalFileSystemPassThru BaseClass; + + CFileSystemPassThru() { m_pFileSystemPassThru = NULL; } + virtual void InitPassThru(IFileSystem *pFileSystemPassThru, bool bBaseOnly) { + if (!bBaseOnly) m_pFileSystemPassThru = pFileSystemPassThru; + + BaseClass::InitPassThru(pFileSystemPassThru); + } + + // IAppSystem stuff. + // Here's where the app systems get to learn about each other + virtual bool Connect(CreateInterfaceFn factory) { + return m_pFileSystemPassThru->Connect(factory); + } + virtual void Disconnect() { m_pFileSystemPassThru->Disconnect(); } + virtual void *QueryInterface(const char *pInterfaceName) { + return m_pFileSystemPassThru->QueryInterface(pInterfaceName); + } + virtual InitReturnVal_t Init() { return m_pFileSystemPassThru->Init(); } + virtual void Shutdown() { m_pFileSystemPassThru->Shutdown(); } + virtual const AppSystemInfo_t *GetDependencies() { + return m_pFileSystemPassThru->GetDependencies(); + } + virtual AppSystemTier_t GetTier() { return m_pFileSystemPassThru->GetTier(); } + virtual void Reconnect(CreateInterfaceFn factory, + const char *pInterfaceName) { + m_pFileSystemPassThru->Reconnect(factory, pInterfaceName); + } + + virtual void RemoveAllSearchPaths(void) { + m_pFileSystemPassThru->RemoveAllSearchPaths(); + } + virtual void AddSearchPath(const char *pPath, const char *pathID, + SearchPathAdd_t addType) { + m_pFileSystemPassThru->AddSearchPath(pPath, pathID, addType); + } + virtual bool RemoveSearchPath(const char *pPath, const char *pathID) { + return m_pFileSystemPassThru->RemoveSearchPath(pPath, pathID); + } + virtual void RemoveFile(char const *pRelativePath, const char *pathID) { + m_pFileSystemPassThru->RemoveFile(pRelativePath, pathID); + } + virtual bool RenameFile(char const *pOldPath, char const *pNewPath, + const char *pathID) { + return m_pFileSystemPassThru->RenameFile(pOldPath, pNewPath, pathID); + } + virtual void CreateDirHierarchy(const char *path, const char *pathID) { + m_pFileSystemPassThru->CreateDirHierarchy(path, pathID); + } + virtual bool IsDirectory(const char *pFileName, const char *pathID) { + return m_pFileSystemPassThru->IsDirectory(pFileName, pathID); + } + virtual void FileTimeToString(char *pStrip, int maxCharsIncludingTerminator, + long fileTime) { + m_pFileSystemPassThru->FileTimeToString(pStrip, maxCharsIncludingTerminator, + fileTime); + } + virtual void SetBufferSize(FileHandle_t file, unsigned nBytes) { + m_pFileSystemPassThru->SetBufferSize(file, nBytes); + } + virtual bool IsOk(FileHandle_t file) { + return m_pFileSystemPassThru->IsOk(file); + } + virtual bool EndOfFile(FileHandle_t file) { + return m_pFileSystemPassThru->EndOfFile(file); + } + virtual char *ReadLine(char *pOutput, int maxChars, FileHandle_t file) { + return m_pFileSystemPassThru->ReadLine(pOutput, maxChars, file); + } + virtual int FPrintf(FileHandle_t file, const char *pFormat, ...) { + char str[8192]; + va_list marker; + va_start(marker, pFormat); + _vsnprintf(str, sizeof(str), pFormat, marker); + va_end(marker); + return m_pFileSystemPassThru->FPrintf(file, "%s", str); + } + virtual CSysModule *LoadModule(const char *pFileName, const char *pPathID, + bool bValidatedDllOnly) { + return m_pFileSystemPassThru->LoadModule(pFileName, pPathID, + bValidatedDllOnly); + } + virtual void UnloadModule(CSysModule *pModule) { + m_pFileSystemPassThru->UnloadModule(pModule); + } + virtual const char *FindFirst(const char *pWildCard, + FileFindHandle_t *pHandle) { + return m_pFileSystemPassThru->FindFirst(pWildCard, pHandle); + } + virtual const char *FindNext(FileFindHandle_t handle) { + return m_pFileSystemPassThru->FindNext(handle); + } + virtual bool FindIsDirectory(FileFindHandle_t handle) { + return m_pFileSystemPassThru->FindIsDirectory(handle); + } + virtual void FindClose(FileFindHandle_t handle) { + m_pFileSystemPassThru->FindClose(handle); + } + virtual void FindFileAbsoluteList( + CUtlVector &outAbsolutePathNames, const char *pWildCard, + const char *pPathID) { + m_pFileSystemPassThru->FindFileAbsoluteList(outAbsolutePathNames, pWildCard, + pPathID); + } + virtual const char *GetLocalPath(const char *pFileName, char *pLocalPath, + int localPathBufferSize) { + return m_pFileSystemPassThru->GetLocalPath(pFileName, pLocalPath, + localPathBufferSize); + } + virtual bool FullPathToRelativePath(const char *pFullpath, char *pRelative, + int maxlen) { + return m_pFileSystemPassThru->FullPathToRelativePath(pFullpath, pRelative, + maxlen); + } + virtual bool GetCurrentDirectory(char *pDirectory, int maxlen) { + return m_pFileSystemPassThru->GetCurrentDirectory(pDirectory, maxlen); + } + virtual void PrintOpenedFiles(void) { + m_pFileSystemPassThru->PrintOpenedFiles(); + } + virtual void PrintSearchPaths(void) { + m_pFileSystemPassThru->PrintSearchPaths(); + } + virtual void SetWarningFunc(void (*pfnWarning)(const char *fmt, ...)) { + m_pFileSystemPassThru->SetWarningFunc(pfnWarning); + } + virtual void SetWarningLevel(FileWarningLevel_t level) { + m_pFileSystemPassThru->SetWarningLevel(level); + } + virtual void AddLoggingFunc(void (*pfnLogFunc)(const char *fileName, + const char *accessType)) { + m_pFileSystemPassThru->AddLoggingFunc(pfnLogFunc); + } + virtual void RemoveLoggingFunc(FileSystemLoggingFunc_t logFunc) { + m_pFileSystemPassThru->RemoveLoggingFunc(logFunc); + } + virtual FSAsyncStatus_t AsyncReadMultiple(const FileAsyncRequest_t *pRequests, + int nRequests, + FSAsyncControl_t *pControls) { + return m_pFileSystemPassThru->AsyncReadMultiple(pRequests, nRequests, + pControls); + } + virtual FSAsyncStatus_t AsyncReadMultipleCreditAlloc( + const FileAsyncRequest_t *pRequests, int nRequests, const char *pszFile, + int line, FSAsyncControl_t *pControls) { + return m_pFileSystemPassThru->AsyncReadMultipleCreditAlloc( + pRequests, nRequests, pszFile, line, pControls); + } + virtual FSAsyncStatus_t AsyncDirectoryScan( + const char *pSearchSpec, bool recurseFolders, void *pContext, + FSAsyncScanAddFunc_t pfnAdd, FSAsyncScanCompleteFunc_t pfnDone, + FSAsyncControl_t *pControl = NULL) { + return m_pFileSystemPassThru->AsyncDirectoryScan( + pSearchSpec, recurseFolders, pContext, pfnAdd, pfnDone, pControl); + } + virtual FSAsyncStatus_t AsyncFinish(FSAsyncControl_t hControl, bool wait) { + return m_pFileSystemPassThru->AsyncFinish(hControl, wait); + } + virtual FSAsyncStatus_t AsyncGetResult(FSAsyncControl_t hControl, + void **ppData, int *pSize) { + return m_pFileSystemPassThru->AsyncGetResult(hControl, ppData, pSize); + } + virtual FSAsyncStatus_t AsyncAbort(FSAsyncControl_t hControl) { + return m_pFileSystemPassThru->AsyncAbort(hControl); + } + virtual FSAsyncStatus_t AsyncStatus(FSAsyncControl_t hControl) { + return m_pFileSystemPassThru->AsyncStatus(hControl); + } + virtual FSAsyncStatus_t AsyncFlush() { + return m_pFileSystemPassThru->AsyncFlush(); + } + virtual void AsyncAddRef(FSAsyncControl_t hControl) { + m_pFileSystemPassThru->AsyncAddRef(hControl); + } + virtual void AsyncRelease(FSAsyncControl_t hControl) { + m_pFileSystemPassThru->AsyncRelease(hControl); + } + virtual FSAsyncStatus_t AsyncBeginRead(const char *pszFile, + FSAsyncFile_t *phFile) { + return m_pFileSystemPassThru->AsyncBeginRead(pszFile, phFile); + } + virtual FSAsyncStatus_t AsyncEndRead(FSAsyncFile_t hFile) { + return m_pFileSystemPassThru->AsyncEndRead(hFile); + } + virtual const FileSystemStatistics *GetFilesystemStatistics() { + return m_pFileSystemPassThru->GetFilesystemStatistics(); + } + +#if defined(_PS3) + // These should never be called on PS3! + virtual Ps3FileType_t GetPs3FileType(const char *path) { + return PS3_FILETYPE_UNKNOWN; + } + virtual void LogFileAccess(const char *pFullFileName) {} + + virtual bool PrefetchFile(const char *pFileName, int nPriority, + bool bPersist) { + return m_pFileSystemPassThru->PrefetchFile(pFileName, nPriority, bPersist); + } + virtual bool PrefetchFile(const char *pFileName, int nPriority, bool bPersist, + int64 nOffset, int64 nSize) { + return m_pFileSystemPassThru->PrefetchFile(pFileName, nPriority, bPersist, + nOffset, nSize); + } + virtual void FlushCache() { m_pFileSystemPassThru->FlushCache(); } + virtual void SuspendPrefetches(const char *pWhy) { + m_pFileSystemPassThru->SuspendPrefetches(pWhy); + } + virtual void ResumePrefetches(const char *pWhy) { + m_pFileSystemPassThru->ResumePrefetches(pWhy); + } + virtual void OnSaveStateChanged(bool bSaving) { + m_pFileSystemPassThru->OnSaveStateChanged(bSaving); + } + virtual bool IsPrefetchingDone() { + return m_pFileSystemPassThru->IsPrefetchingDone(); + } +#endif //_PS3 + + virtual WaitForResourcesHandle_t WaitForResources(const char *resourcelist) { + return m_pFileSystemPassThru->WaitForResources(resourcelist); + } + virtual bool GetWaitForResourcesProgress(WaitForResourcesHandle_t handle, + float *progress, bool *complete) { + return m_pFileSystemPassThru->GetWaitForResourcesProgress(handle, progress, + complete); + } + virtual void CancelWaitForResources(WaitForResourcesHandle_t handle) { + m_pFileSystemPassThru->CancelWaitForResources(handle); + } + virtual int HintResourceNeed(const char *hintlist, int forgetEverything) { + return m_pFileSystemPassThru->HintResourceNeed(hintlist, forgetEverything); + } + virtual bool IsFileImmediatelyAvailable(const char *pFileName) { + return m_pFileSystemPassThru->IsFileImmediatelyAvailable(pFileName); + } + virtual void GetLocalCopy(const char *pFileName) { + m_pFileSystemPassThru->GetLocalCopy(pFileName); + } + virtual FileNameHandle_t FindOrAddFileName(char const *pFileName) { + return m_pFileSystemPassThru->FindOrAddFileName(pFileName); + } + virtual FileNameHandle_t FindFileName(char const *pFileName) { + return m_pFileSystemPassThru->FindFileName(pFileName); + } + virtual bool String(const FileNameHandle_t &handle, char *buf, int buflen) { + return m_pFileSystemPassThru->String(handle, buf, buflen); + } + virtual bool IsOk2(FileHandle_t file) { return IsOk(file); } + virtual void RemoveSearchPaths(const char *szPathID) { + m_pFileSystemPassThru->RemoveSearchPaths(szPathID); + } + virtual bool IsSteam() const { return m_pFileSystemPassThru->IsSteam(); } + virtual FilesystemMountRetval_t MountSteamContent(int nExtraAppId = -1) { + return m_pFileSystemPassThru->MountSteamContent(nExtraAppId); + } + + virtual const char *FindFirstEx(const char *pWildCard, const char *pPathID, + FileFindHandle_t *pHandle) { + return m_pFileSystemPassThru->FindFirstEx(pWildCard, pPathID, pHandle); + } + virtual void MarkPathIDByRequestOnly(const char *pPathID, bool bRequestOnly) { + m_pFileSystemPassThru->MarkPathIDByRequestOnly(pPathID, bRequestOnly); + } + virtual bool AddPackFile(const char *fullpath, const char *pathID) { + return m_pFileSystemPassThru->AddPackFile(fullpath, pathID); + } + virtual FSAsyncStatus_t AsyncAppend(const char *pFileName, const void *pSrc, + int nSrcBytes, bool bFreeMemory, + FSAsyncControl_t *pControl) { + return m_pFileSystemPassThru->AsyncAppend(pFileName, pSrc, nSrcBytes, + bFreeMemory, pControl); + } + virtual FSAsyncStatus_t AsyncWrite(const char *pFileName, const void *pSrc, + int nSrcBytes, bool bFreeMemory, + bool bAppend, FSAsyncControl_t *pControl) { + return m_pFileSystemPassThru->AsyncWrite(pFileName, pSrc, nSrcBytes, + bFreeMemory, bAppend, pControl); + } + virtual FSAsyncStatus_t AsyncWriteFile(const char *pFileName, + const CUtlBuffer *pSrc, int nSrcBytes, + bool bFreeMemory, bool bAppend, + FSAsyncControl_t *pControl) { + return m_pFileSystemPassThru->AsyncWriteFile( + pFileName, pSrc, nSrcBytes, bFreeMemory, bAppend, pControl); + } + virtual FSAsyncStatus_t AsyncAppendFile(const char *pDestFileName, + const char *pSrcFileName, + FSAsyncControl_t *pControl) { + return m_pFileSystemPassThru->AsyncAppendFile(pDestFileName, pSrcFileName, + pControl); + } + virtual void AsyncFinishAll(int iToPriority) { + m_pFileSystemPassThru->AsyncFinishAll(iToPriority); + } + virtual void AsyncFinishAllWrites() { + m_pFileSystemPassThru->AsyncFinishAllWrites(); + } + virtual FSAsyncStatus_t AsyncSetPriority(FSAsyncControl_t hControl, + int newPriority) { + return m_pFileSystemPassThru->AsyncSetPriority(hControl, newPriority); + } + virtual bool AsyncSuspend() { return m_pFileSystemPassThru->AsyncSuspend(); } + virtual bool AsyncResume() { return m_pFileSystemPassThru->AsyncResume(); } + virtual const char *RelativePathToFullPath( + const char *pFileName, const char *pPathID, char *pLocalPath, + int localPathBufferSize, PathTypeFilter_t pathFilter = FILTER_NONE, + PathTypeQuery_t *pPathType = NULL) { + return m_pFileSystemPassThru->RelativePathToFullPath( + pFileName, pPathID, pLocalPath, localPathBufferSize, pathFilter, + pPathType); + } +#if IsGameConsole() + // Given a relative path, gets the PACK file that contained this file and its + // offset and size. Can be used to prefetch a file to a HDD for caching + // reason. + virtual bool GetPackFileInfoFromRelativePath( + const char *pFileName, const char *pPathID, char *pPackPath, + int nPackPathBufferSize, int64 &nPosition, int64 &nLength) { + return m_pFileSystemPassThru->GetPackFileInfoFromRelativePath( + pFileName, pPathID, pPackPath, nPackPathBufferSize, nPosition, nLength); + } +#endif + + virtual int GetSearchPath(const char *pathID, bool bGetPackFiles, char *pPath, + int nMaxLen) { + return m_pFileSystemPassThru->GetSearchPath(pathID, bGetPackFiles, pPath, + nMaxLen); + } + + virtual FileHandle_t OpenEx(const char *pFileName, const char *pOptions, + unsigned flags = 0, const char *pathID = 0, + char **ppszResolvedFilename = NULL) { + return m_pFileSystemPassThru->OpenEx(pFileName, pOptions, flags, pathID, + ppszResolvedFilename); + } + virtual int ReadEx(void *pOutput, int destSize, int size, FileHandle_t file) { + return m_pFileSystemPassThru->ReadEx(pOutput, destSize, size, file); + } + virtual int ReadFileEx(const char *pFileName, const char *pPath, void **ppBuf, + bool bNullTerminate, bool bOptimalAlloc, + int nMaxBytes = 0, int nStartingByte = 0, + FSAllocFunc_t pfnAlloc = NULL) { + return m_pFileSystemPassThru->ReadFileEx( + pFileName, pPath, ppBuf, bNullTerminate, bOptimalAlloc, nMaxBytes, + nStartingByte, pfnAlloc); + } + +#if defined(TRACK_BLOCKING_IO) + virtual void EnableBlockingFileAccessTracking(bool state) { + m_pFileSystemPassThru->EnableBlockingFileAccessTracking(state); + } + virtual bool IsBlockingFileAccessEnabled() const { + return m_pFileSystemPassThru->IsBlockingFileAccessEnabled(); + } + + virtual IBlockingFileItemList *RetrieveBlockingFileAccessInfo() { + return m_pFileSystemPassThru->RetrieveBlockingFileAccessInfo(); + } +#endif + virtual void SetupPreloadData() {} + virtual void DiscardPreloadData() {} + + // If the "PreloadedData" hasn't been purged, then this'll try and instance + // the KeyValues using the fast path of compiled keyvalues loaded during + // startup. Otherwise, it'll just fall through to the regular KeyValues + // loading routines + virtual KeyValues *LoadKeyValues(KeyValuesPreloadType_t type, + char const *filename, + char const *pPathID = 0) { + return m_pFileSystemPassThru->LoadKeyValues(type, filename, pPathID); + } + virtual bool LoadKeyValues(KeyValues &head, KeyValuesPreloadType_t type, + char const *filename, char const *pPathID = 0) { + return m_pFileSystemPassThru->LoadKeyValues(head, type, filename, pPathID); + } + + virtual bool GetFileTypeForFullPath(char const *pFullPath, wchar_t *buf, + size_t bufSizeInBytes) { + return m_pFileSystemPassThru->GetFileTypeForFullPath(pFullPath, buf, + bufSizeInBytes); + } + + virtual bool GetOptimalIOConstraints(FileHandle_t hFile, + unsigned *pOffsetAlign, + unsigned *pSizeAlign, + unsigned *pBufferAlign) { + return m_pFileSystemPassThru->GetOptimalIOConstraints( + hFile, pOffsetAlign, pSizeAlign, pBufferAlign); + } + virtual void *AllocOptimalReadBuffer(FileHandle_t hFile, unsigned nSize, + unsigned nOffset) { + return m_pFileSystemPassThru->AllocOptimalReadBuffer(hFile, nSize, nOffset); + } + virtual void FreeOptimalReadBuffer(void *p) { + m_pFileSystemPassThru->FreeOptimalReadBuffer(p); + } + + virtual void BeginMapAccess() { m_pFileSystemPassThru->BeginMapAccess(); } + virtual void EndMapAccess() { m_pFileSystemPassThru->EndMapAccess(); } + + virtual bool ReadToBuffer(FileHandle_t hFile, CUtlBuffer &buf, + int nMaxBytes = 0, FSAllocFunc_t pfnAlloc = NULL) { + return m_pFileSystemPassThru->ReadToBuffer(hFile, buf, nMaxBytes, pfnAlloc); + } + virtual bool FullPathToRelativePathEx(const char *pFullPath, + const char *pPathId, char *pRelative, + int nMaxLen) { + return m_pFileSystemPassThru->FullPathToRelativePathEx(pFullPath, pPathId, + pRelative, nMaxLen); + } + virtual int GetPathIndex(const FileNameHandle_t &handle) { + return m_pFileSystemPassThru->GetPathIndex(handle); + } + virtual long GetPathTime(const char *pPath, const char *pPathID) { + return m_pFileSystemPassThru->GetPathTime(pPath, pPathID); + } + + virtual DVDMode_t GetDVDMode() { return m_pFileSystemPassThru->GetDVDMode(); } + + virtual void EnableWhitelistFileTracking(bool bEnable) { + m_pFileSystemPassThru->EnableWhitelistFileTracking(bEnable); + } + virtual void RegisterFileWhitelist(IFileList *pForceMatchList, + IFileList *pAllowFromDiskList, + IFileList **pFilesToReload) { + m_pFileSystemPassThru->RegisterFileWhitelist( + pForceMatchList, pAllowFromDiskList, pFilesToReload); + } + virtual void MarkAllCRCsUnverified() { + m_pFileSystemPassThru->MarkAllCRCsUnverified(); + } + virtual void CacheFileCRCs(const char *pPathname, ECacheCRCType eType, + IFileList *pFilter) { + return m_pFileSystemPassThru->CacheFileCRCs(pPathname, eType, pFilter); + } + virtual EFileCRCStatus CheckCachedFileCRC(const char *pPathID, + const char *pRelativeFilename, + CRC32_t *pCRC) { + return m_pFileSystemPassThru->CheckCachedFileCRC(pPathID, pRelativeFilename, + pCRC); + } + virtual int GetUnverifiedCRCFiles(CUnverifiedCRCFile *pFiles, int nMaxFiles) { + return m_pFileSystemPassThru->GetUnverifiedCRCFiles(pFiles, nMaxFiles); + } + virtual int GetWhitelistSpewFlags() { + return m_pFileSystemPassThru->GetWhitelistSpewFlags(); + } + virtual void SetWhitelistSpewFlags(int spewFlags) { + m_pFileSystemPassThru->SetWhitelistSpewFlags(spewFlags); + } + virtual void InstallDirtyDiskReportFunc(FSDirtyDiskReportFunc_t func) { + m_pFileSystemPassThru->InstallDirtyDiskReportFunc(func); + } + + virtual bool IsLaunchedFromXboxHDD() { + return m_pFileSystemPassThru->IsLaunchedFromXboxHDD(); + } + virtual bool IsInstalledToXboxHDDCache() { + return m_pFileSystemPassThru->IsInstalledToXboxHDDCache(); + } + virtual bool IsDVDHosted() { return m_pFileSystemPassThru->IsDVDHosted(); } + virtual bool IsInstallAllowed() { + return m_pFileSystemPassThru->IsInstallAllowed(); + } + virtual int GetSearchPathID(char *pPath, int nMaxLen) { + return m_pFileSystemPassThru->GetSearchPathID(pPath, nMaxLen); + } + virtual bool FixupSearchPathsAfterInstall() { + return m_pFileSystemPassThru->FixupSearchPathsAfterInstall(); + } + + virtual FSDirtyDiskReportFunc_t GetDirtyDiskReportFunc() { + return m_pFileSystemPassThru->GetDirtyDiskReportFunc(); + } + + virtual void AddVPKFile(char const *pPkName, + SearchPathAdd_t addType = PATH_ADD_TO_TAIL) { + m_pFileSystemPassThru->AddVPKFile(pPkName, addType); + } + virtual void RemoveVPKFile(char const *pPkName) { + m_pFileSystemPassThru->RemoveVPKFile(pPkName); + } + virtual void GetVPKFileNames(CUtlVector &destVector) { + m_pFileSystemPassThru->GetVPKFileNames(destVector); + } + + virtual void RemoveAllMapSearchPaths(void) { + m_pFileSystemPassThru->RemoveAllMapSearchPaths(); + } + + virtual void SyncDvdDevCache(void) { + m_pFileSystemPassThru->SyncDvdDevCache(); + } + + virtual bool GetStringFromKVPool(CRC32_t poolKey, unsigned int key, + char *pOutBuff, int buflen) { + return m_pFileSystemPassThru->GetStringFromKVPool(poolKey, key, pOutBuff, + buflen); + } + + virtual bool DiscoverDLC(int iController) { + return m_pFileSystemPassThru->DiscoverDLC(iController); + } + virtual int IsAnyDLCPresent(bool *pbDLCSearchPathMounted = NULL) { + return m_pFileSystemPassThru->IsAnyDLCPresent(pbDLCSearchPathMounted); + } + virtual bool GetAnyDLCInfo(int iDLC, unsigned int *pLicenseMask, + wchar_t *pTitleBuff, int nOutTitleSize) { + return m_pFileSystemPassThru->GetAnyDLCInfo(iDLC, pLicenseMask, pTitleBuff, + nOutTitleSize); + } + virtual int IsAnyCorruptDLC() { + return m_pFileSystemPassThru->IsAnyCorruptDLC(); + } + virtual bool GetAnyCorruptDLCInfo(int iCorruptDLC, wchar_t *pTitleBuff, + int nOutTitleSize) { + return m_pFileSystemPassThru->GetAnyCorruptDLCInfo(iCorruptDLC, pTitleBuff, + nOutTitleSize); + } + virtual bool AddDLCSearchPaths() { + return m_pFileSystemPassThru->AddDLCSearchPaths(); + } + virtual bool IsSpecificDLCPresent(unsigned int nDLCPackage) { + return m_pFileSystemPassThru->IsSpecificDLCPresent(nDLCPackage); + } + virtual void SetIODelayAlarm(float flThreshhold) { + m_pFileSystemPassThru->SetIODelayAlarm(flThreshhold); + } + virtual bool AddXLSPUpdateSearchPath(const void *pData, int nSize) { + return m_pFileSystemPassThru->AddXLSPUpdateSearchPath(pData, nSize); + } + virtual IIoStats *GetIoStats() { return m_pFileSystemPassThru->GetIoStats(); } + + protected: + IFileSystem *m_pFileSystemPassThru; +}; + +// This is so people who change the filesystem interface are forced to add the +// passthru wrapper into CFileSystemPassThru, so they don't break VMPI. +inline void GiveMeACompileError() { CFileSystemPassThru asdf; } + +#endif // VPC_FILESYSTEM_PASSTHRU_H_ diff --git a/public/icvar.h b/public/icvar.h new file mode 100644 index 0000000..a9e5294 --- /dev/null +++ b/public/icvar.h @@ -0,0 +1,189 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_ICVAR_H_ +#define VPC_ICVAR_H_ + +#include "appframework/iappsystem.h" +#include "tier1/iconvar.h" +#include "tier1/utlvector.h" + +class ConCommandBase; +class ConCommand; +class ConVar; +class Color; + +//----------------------------------------------------------------------------- +// ConVars/ComCommands are marked as having a particular DLL identifier +//----------------------------------------------------------------------------- +typedef int CVarDLLIdentifier_t; + +//----------------------------------------------------------------------------- +// Used to display console messages +//----------------------------------------------------------------------------- +abstract_class IConsoleDisplayFunc { + public: + virtual void ColorPrint(const Color &clr, const char *pMessage) = 0; + virtual void Print(const char *pMessage) = 0; + virtual void DPrint(const char *pMessage) = 0; + + virtual void GetConsoleText(char *pchText, size_t bufSize) const = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Applications can implement this to modify behavior in ICvar +//----------------------------------------------------------------------------- +#define CVAR_QUERY_INTERFACE_VERSION "VCvarQuery001" +abstract_class ICvarQuery : public IAppSystem { + public: + // Can these two convars be aliased? + virtual bool AreConVarsLinkable(const ConVar *child, + const ConVar *parent) = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: DLL interface to ConVars/ConCommands +//----------------------------------------------------------------------------- +abstract_class ICvar : public IAppSystem { + public: + // Allocate a unique DLL identifier + virtual CVarDLLIdentifier_t AllocateDLLIdentifier() = 0; + + // Register, unregister commands + virtual void RegisterConCommand(ConCommandBase * pCommandBase) = 0; + virtual void UnregisterConCommand(ConCommandBase * pCommandBase) = 0; + virtual void UnregisterConCommands(CVarDLLIdentifier_t id) = 0; + + // If there is a + on the command line, this returns the + // value. Otherwise, it returns NULL. + virtual const char *GetCommandLineValue(const char *pVariableName) = 0; + + // Try to find the cvar pointer by name + virtual ConCommandBase *FindCommandBase(const char *name) = 0; + virtual const ConCommandBase *FindCommandBase(const char *name) const = 0; + virtual ConVar *FindVar(const char *var_name) = 0; + virtual const ConVar *FindVar(const char *var_name) const = 0; + virtual ConCommand *FindCommand(const char *name) = 0; + virtual const ConCommand *FindCommand(const char *name) const = 0; + + // Install a global change callback (to be called when any convar changes) + virtual void InstallGlobalChangeCallback(FnChangeCallback_t callback) = 0; + virtual void RemoveGlobalChangeCallback(FnChangeCallback_t callback) = 0; + virtual void CallGlobalChangeCallbacks(ConVar * var, const char *pOldString, + float flOldValue) = 0; + + // Install a console printer + virtual void InstallConsoleDisplayFunc(IConsoleDisplayFunc * + pDisplayFunc) = 0; + virtual void RemoveConsoleDisplayFunc(IConsoleDisplayFunc * pDisplayFunc) = 0; + virtual void ConsoleColorPrintf(const Color &clr, + PRINTF_FORMAT_STRING const char *pFormat, ...) + const = 0; + virtual void ConsolePrintf(PRINTF_FORMAT_STRING const char *pFormat, ...) + const = 0; + virtual void ConsoleDPrintf(PRINTF_FORMAT_STRING const char *pFormat, ...) + const = 0; + + // Reverts cvars which contain a specific flag + virtual void RevertFlaggedConVars(int nFlag) = 0; + + // Method allowing the engine ICvarQuery interface to take over + // A little hacky, owing to the fact the engine is loaded + // well after ICVar, so we can't use the standard connect pattern + virtual void InstallCVarQuery(ICvarQuery * pQuery) = 0; + +#if defined(USE_VXCONSOLE) + virtual void PublishToVXConsole() = 0; +#endif + + virtual void SetMaxSplitScreenSlots(int nSlots) = 0; + virtual int GetMaxSplitScreenSlots() const = 0; + + virtual void AddSplitScreenConVars() = 0; + virtual void RemoveSplitScreenConVars(CVarDLLIdentifier_t id) = 0; + + virtual intp GetConsoleDisplayFuncCount() const = 0; + virtual void GetConsoleText(int nDisplayFuncIndex, char *pchText, + size_t bufSize) const = 0; + + // Utilities for convars accessed by the material system thread + virtual bool IsMaterialThreadSetAllowed() const = 0; + virtual void QueueMaterialThreadSetValue(ConVar * pConVar, + const char *pValue) = 0; + virtual void QueueMaterialThreadSetValue(ConVar * pConVar, int nValue) = 0; + virtual void QueueMaterialThreadSetValue(ConVar * pConVar, float flValue) = 0; + virtual bool HasQueuedMaterialThreadConVarSets() const = 0; + virtual int ProcessQueuedMaterialThreadConVarSets() = 0; + + protected: + class ICVarIteratorInternal; + + public: + /// Iteration over all cvars. + /// (THIS IS A SLOW OPERATION AND YOU SHOULD AVOID IT.) + /// usage: + /// { ICVar::Iterator iter(g_pCVar); + /// for ( iter.SetFirst() ; iter.IsValid() ; iter.Next() ) + /// { + /// ConCommandBase *cmd = iter.Get(); + /// } + /// } + /// The Iterator class actually wraps the internal factory methods + /// so you don't need to worry about new/delete -- scope takes care + // of it. + /// We need an iterator like this because we can't simply return a + /// pointer to the internal data type that contains the cvars -- + /// it's a custom, protected class with unusual semantics and is + /// prone to change. + class Iterator { + public: + inline Iterator(ICvar *icvar); + inline ~Iterator(void); + inline void SetFirst(void); + inline void Next(void); + inline bool IsValid(void); + inline ConCommandBase *Get(void); + + private: + ICVarIteratorInternal *m_pIter; + }; + + protected: + // internals for ICVarIterator + class ICVarIteratorInternal { + public: + virtual void SetFirst(void) = 0; + virtual void Next(void) = 0; + virtual bool IsValid(void) = 0; + virtual ConCommandBase *Get(void) = 0; + + virtual ~ICVarIteratorInternal() {} + }; + + virtual ICVarIteratorInternal *FactoryInternalIterator(void) = 0; + friend class Iterator; +}; + +inline ICvar::Iterator::Iterator(ICvar *icvar) { + m_pIter = icvar->FactoryInternalIterator(); +} + +inline ICvar::Iterator::~Iterator(void) { delete m_pIter; } + +inline void ICvar::Iterator::SetFirst(void) { m_pIter->SetFirst(); } + +inline void ICvar::Iterator::Next(void) { m_pIter->Next(); } + +inline bool ICvar::Iterator::IsValid(void) { return m_pIter->IsValid(); } + +inline ConCommandBase *ICvar::Iterator::Get(void) { return m_pIter->Get(); } + +//----------------------------------------------------------------------------- +// These global names are defined by tier1.h, duplicated here so you +// don't have to include tier1.h +//----------------------------------------------------------------------------- + +// These are marked DLL_EXPORT for Linux. +DECLARE_TIER1_INTERFACE(ICvar, cvar); +DECLARE_TIER1_INTERFACE(ICvar, g_pCVar); + +#endif // VPC_ICVAR_H_ diff --git a/public/ilaunchabledll.h b/public/ilaunchabledll.h new file mode 100644 index 0000000..9a9f8cc --- /dev/null +++ b/public/ilaunchabledll.h @@ -0,0 +1,16 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_ILAUNCHABLEDLL_H_ +#define VPC_ILAUNCHABLEDLL_H_ + +#define LAUNCHABLE_DLL_INTERFACE_VERSION "launchable_dll_1" + +// vmpi_service can use this to debug worker apps in-process, +// and some of the launchers (like texturecompile) use this. +class ILaunchableDLL { + public: + // All vrad.exe does is load the VRAD DLL and run this. + virtual int main(int argc, char **argv) = 0; +}; + +#endif // VPC_ILAUNCHABLEDLL_H_ diff --git a/public/interfaces/interfaces.h b/public/interfaces/interfaces.h new file mode 100644 index 0000000..16103d9 --- /dev/null +++ b/public/interfaces/interfaces.h @@ -0,0 +1,264 @@ +// Copyright ?2005-2005, Valve Corporation, All rights reserved. +// +// Purpose: A higher level link library for general use in the game and tools. + +#ifndef VPC_INTERFACES_INTERFACES_H_ +#define VPC_INTERFACES_INTERFACES_H_ + +// Interface creation function +using CreateInterfaceFn = void *(*)(const char *pName, int *pReturnCode); + +// Macros to declare interfaces appropriate for various tiers +#if 1 || defined(TIER1_LIBRARY) || defined(TIER2_LIBRARY) || \ + defined(TIER3_LIBRARY) || defined(TIER4_LIBRARY) || defined(APPLICATION) +#define DECLARE_TIER1_INTERFACE(_Interface, _Global) extern _Interface *_Global; +#else +#define DECLARE_TIER1_INTERFACE(_Interface, _Global) +#endif + +#if 1 || defined(TIER2_LIBRARY) || defined(TIER3_LIBRARY) || \ + defined(TIER4_LIBRARY) || defined(APPLICATION) +#define DECLARE_TIER2_INTERFACE(_Interface, _Global) extern _Interface *_Global; +#else +#define DECLARE_TIER2_INTERFACE(_Interface, _Global) +#endif + +#if 1 || defined(TIER3_LIBRARY) || defined(TIER4_LIBRARY) || \ + defined(APPLICATION) +#define DECLARE_TIER3_INTERFACE(_Interface, _Global) extern _Interface *_Global; +#else +#define DECLARE_TIER3_INTERFACE(_Interface, _Global) +#endif + +// Forward declarations +class ICvar; +class IProcessUtils; +class ILocalize; +class IPhysics2; +class IPhysics2ActorManager; +class IPhysics2ResourceManager; +class IEventSystem; + +class IAsyncFileSystem; +class IColorCorrectionSystem; +class IDebugTextureInfo; +class IFileSystem; +class IRenderHardwareConfig; +class IInputSystem; +class IInputStackSystem; +class IMaterialSystem; +class IMaterialSystem2; +class IMaterialSystemHardwareConfig; +class IMdlLib; +class INetworkSystem; +class IP4; +class IQueuedLoader; +class IResourceAccessControl; +class IPrecacheSystem; +class IRenderDevice; +class IRenderDeviceMgr; +class IResourceSystem; +class IVBAllocTracker; +class IXboxInstaller; +class IMatchFramework; +class ISoundSystem; +class IStudioRender; +class IMatSystemSurface; +class IGameUISystemMgr; +class IDataCache; +class IMDLCache; +class IAvi; +class IBik; +class IDmeMakefileUtils; +class IPhysicsCollision; +class ISoundEmitterSystemBase; +class IMeshSystem; +class IWorldRendererMgr; +class ISceneSystem; +class IVGuiRenderSurface; + +namespace vgui { +class ISurface; +class IVGui; +class IInput; +class IPanel; +class IVGUILocalize; +class ISchemeManager; +class ISystem; +} // namespace vgui + +// Fills out global DLL exported interface pointers +#define CVAR_INTERFACE_VERSION "VEngineCvar007" +DECLARE_TIER1_INTERFACE(ICvar, cvar); +DECLARE_TIER1_INTERFACE(ICvar, g_pCVar) + +#define PROCESS_UTILS_INTERFACE_VERSION "VProcessUtils002" +DECLARE_TIER1_INTERFACE(IProcessUtils, g_pProcessUtils); + +#define VPHYSICS2_INTERFACE_VERSION "Physics2 Interface v0.3" +DECLARE_TIER1_INTERFACE(IPhysics2, g_pPhysics2); + +#define VPHYSICS2_ACTOR_MGR_INTERFACE_VERSION "Physics2 Interface ActorMgr v0.1" +DECLARE_TIER1_INTERFACE(IPhysics2ActorManager, g_pPhysics2ActorManager); + +#define VPHYSICS2_RESOURCE_MGR_INTERFACE_VERSION \ + "Physics2 Interface ResourceMgr v0.1" +DECLARE_TIER1_INTERFACE(IPhysics2ResourceManager, g_pPhysics2ResourceManager); + +#define EVENTSYSTEM_INTERFACE_VERSION "EventSystem001" +DECLARE_TIER1_INTERFACE(IEventSystem, g_pEventSystem); + +#define LOCALIZE_INTERFACE_VERSION "Localize_001" +DECLARE_TIER2_INTERFACE(ILocalize, g_pLocalize); +DECLARE_TIER3_INTERFACE(vgui::IVGUILocalize, g_pVGuiLocalize); + +#define RENDER_DEVICE_MGR_INTERFACE_VERSION "RenderDeviceMgr001" +DECLARE_TIER2_INTERFACE(IRenderDeviceMgr, g_pRenderDeviceMgr); + +#define FILESYSTEM_INTERFACE_VERSION "VFileSystem017" +DECLARE_TIER2_INTERFACE(IFileSystem, g_pFullFileSystem); + +#define ASYNCFILESYSTEM_INTERFACE_VERSION "VNewAsyncFileSystem001" +DECLARE_TIER2_INTERFACE(IAsyncFileSystem, g_pAsyncFileSystem); + +#define RESOURCESYSTEM_INTERFACE_VERSION "ResourceSystem004" +DECLARE_TIER2_INTERFACE(IResourceSystem, g_pResourceSystem); + +#define MATERIAL_SYSTEM_INTERFACE_VERSION "VMaterialSystem080" +DECLARE_TIER2_INTERFACE(IMaterialSystem, materials); +DECLARE_TIER2_INTERFACE(IMaterialSystem, g_pMaterialSystem); + +#define MATERIAL_SYSTEM2_INTERFACE_VERSION "VMaterialSystem2_001" +DECLARE_TIER2_INTERFACE(IMaterialSystem2, g_pMaterialSystem2); + +#define INPUTSYSTEM_INTERFACE_VERSION "InputSystemVersion001" +DECLARE_TIER2_INTERFACE(IInputSystem, g_pInputSystem); + +#define INPUTSTACKSYSTEM_INTERFACE_VERSION "InputStackSystemVersion001" +DECLARE_TIER2_INTERFACE(IInputStackSystem, g_pInputStackSystem); + +#define NETWORKSYSTEM_INTERFACE_VERSION "NetworkSystemVersion001" +DECLARE_TIER2_INTERFACE(INetworkSystem, g_pNetworkSystem); + +#define MATERIALSYSTEM_HARDWARECONFIG_INTERFACE_VERSION \ + "MaterialSystemHardwareConfig013" +DECLARE_TIER2_INTERFACE(IMaterialSystemHardwareConfig, + g_pMaterialSystemHardwareConfig); + +#define DEBUG_TEXTURE_INFO_VERSION "DebugTextureInfo001" +DECLARE_TIER2_INTERFACE(IDebugTextureInfo, g_pMaterialSystemDebugTextureInfo); + +#define VB_ALLOC_TRACKER_INTERFACE_VERSION "VBAllocTracker001" +DECLARE_TIER2_INTERFACE(IVBAllocTracker, g_VBAllocTracker); + +#define COLORCORRECTION_INTERFACE_VERSION "COLORCORRECTION_VERSION_1" +DECLARE_TIER2_INTERFACE(IColorCorrectionSystem, colorcorrection); + +#define P4_INTERFACE_VERSION "VP4002" +DECLARE_TIER2_INTERFACE(IP4, p4); //-V707 + +#define MDLLIB_INTERFACE_VERSION "VMDLLIB001" +DECLARE_TIER2_INTERFACE(IMdlLib, mdllib); + +#define QUEUEDLOADER_INTERFACE_VERSION "QueuedLoaderVersion001" +DECLARE_TIER2_INTERFACE(IQueuedLoader, g_pQueuedLoader); + +#define RESOURCE_ACCESS_CONTROL_INTERFACE_VERSION "VResourceAccessControl001" +DECLARE_TIER2_INTERFACE(IResourceAccessControl, g_pResourceAccessControl); + +#define PRECACHE_SYSTEM_INTERFACE_VERSION "VPrecacheSystem001" +DECLARE_TIER2_INTERFACE(IPrecacheSystem, g_pPrecacheSystem); + +#if defined(_X360) +#define XBOXINSTALLER_INTERFACE_VERSION "XboxInstallerVersion001" +DECLARE_TIER2_INTERFACE(IXboxInstaller, g_pXboxInstaller); +#endif + +#define MATCHFRAMEWORK_INTERFACE_VERSION "MATCHFRAMEWORK_001" +DECLARE_TIER2_INTERFACE(IMatchFramework, g_pMatchFramework); + +#define GAMEUISYSTEMMGR_INTERFACE_VERSION "GameUISystemMgr001" +DECLARE_TIER3_INTERFACE(IGameUISystemMgr, g_pGameUISystemMgr); + +// Not exactly a global, but we're going to keep track of these here anyways +// NOTE: Appframework deals with connecting these bad boys. See +// materialsystem2app.cpp +#define RENDER_DEVICE_INTERFACE_VERSION "RenderDevice001" +DECLARE_TIER2_INTERFACE(IRenderDevice, g_pRenderDevice); + +#define RENDER_HARDWARECONFIG_INTERFACE_VERSION "RenderHardwareConfig001" +DECLARE_TIER2_INTERFACE(IRenderHardwareConfig, g_pRenderHardwareConfig); + +#define SOUNDSYSTEM_INTERFACE_VERSION "SoundSystem001" +DECLARE_TIER2_INTERFACE(ISoundSystem, g_pSoundSystem); + +#define MESHSYSTEM_INTERFACE_VERSION "MeshSystem001" +DECLARE_TIER3_INTERFACE(IMeshSystem, g_pMeshSystem); + +#define STUDIO_RENDER_INTERFACE_VERSION "VStudioRender026" +DECLARE_TIER3_INTERFACE(IStudioRender, g_pStudioRender); +DECLARE_TIER3_INTERFACE(IStudioRender, studiorender); + +#define MAT_SYSTEM_SURFACE_INTERFACE_VERSION "MatSystemSurface006" +DECLARE_TIER3_INTERFACE(IMatSystemSurface, g_pMatSystemSurface); + +#define RENDER_SYSTEM_SURFACE_INTERFACE_VERSION "RenderSystemSurface001" +DECLARE_TIER3_INTERFACE(IVGuiRenderSurface, g_pVGuiRenderSurface); + +#define SCENESYSTEM_INTERFACE_VERSION "SceneSystem_001" +DECLARE_TIER3_INTERFACE(ISceneSystem, g_pSceneSystem); + +#define VGUI_SURFACE_INTERFACE_VERSION "VGUI_Surface031" +DECLARE_TIER3_INTERFACE(vgui::ISurface, g_pVGuiSurface); + +#define SCHEME_SURFACE_INTERFACE_VERSION "SchemeSurface001" + +#define VGUI_INPUT_INTERFACE_VERSION "VGUI_Input005" +DECLARE_TIER3_INTERFACE(vgui::IInput, g_pVGuiInput); + +#define VGUI_IVGUI_INTERFACE_VERSION "VGUI_ivgui008" +DECLARE_TIER3_INTERFACE(vgui::IVGui, g_pVGui); + +#define VGUI_PANEL_INTERFACE_VERSION "VGUI_Panel009" +DECLARE_TIER3_INTERFACE(vgui::IPanel, g_pVGuiPanel); + +#define VGUI_SCHEME_INTERFACE_VERSION "VGUI_Scheme010" +DECLARE_TIER3_INTERFACE(vgui::ISchemeManager, g_pVGuiSchemeManager); + +#define VGUI_SYSTEM_INTERFACE_VERSION "VGUI_System010" +DECLARE_TIER3_INTERFACE(vgui::ISystem, g_pVGuiSystem); + +#define DATACACHE_INTERFACE_VERSION "VDataCache003" +// FIXME: Should IDataCache be in tier2? +DECLARE_TIER3_INTERFACE(IDataCache, g_pDataCache); + +#define MDLCACHE_INTERFACE_VERSION "MDLCache004" +DECLARE_TIER3_INTERFACE(IMDLCache, g_pMDLCache); +DECLARE_TIER3_INTERFACE(IMDLCache, mdlcache); + +#define AVI_INTERFACE_VERSION "VAvi001" +DECLARE_TIER3_INTERFACE(IAvi, g_pAVI); + +#define BIK_INTERFACE_VERSION "VBik001" +DECLARE_TIER3_INTERFACE(IBik, g_pBIK); + +#define DMEMAKEFILE_UTILS_INTERFACE_VERSION "VDmeMakeFileUtils001" +DECLARE_TIER3_INTERFACE(IDmeMakefileUtils, g_pDmeMakefileUtils); + +#define VPHYSICS_COLLISION_INTERFACE_VERSION "VPhysicsCollision007" +DECLARE_TIER3_INTERFACE(IPhysicsCollision, g_pPhysicsCollision); + +#define SOUNDEMITTERSYSTEM_INTERFACE_VERSION "VSoundEmitter003" +DECLARE_TIER3_INTERFACE(ISoundEmitterSystemBase, g_pSoundEmitterSystem); + +#define WORLD_RENDERER_MGR_INTERFACE_VERSION "WorldRendererMgr001" +DECLARE_TIER3_INTERFACE(IWorldRendererMgr, g_pWorldRendererMgr); + +// Fills out global DLL exported interface pointers +void ConnectInterfaces(CreateInterfaceFn *pFactoryList, int nFactoryCount); +void DisconnectInterfaces(); + +// Reconnects an interface +void ReconnectInterface(CreateInterfaceFn factory, const char *pInterfaceName); + +#endif // VPC_INTERFACES_INTERFACES_H_ diff --git a/public/mathlib/fltx4.h b/public/mathlib/fltx4.h new file mode 100644 index 0000000..1679ae9 --- /dev/null +++ b/public/mathlib/fltx4.h @@ -0,0 +1,94 @@ + +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Defines the type fltx4 + avoids cyclic includion. + +#ifndef VPC_MATHLIB_FLTX4_H_ +#define VPC_MATHLIB_FLTX4_H_ + +#define USE_STDC_FOR_SIMD 0 + +#if (!defined(PLATFORM_PPC) && (USE_STDC_FOR_SIMD == 0)) +#define _SSE1 1 +#endif + +// I thought about defining a class/union for the SIMD packed floats instead of +// using fltx4, but decided against it because (a) the nature of SIMD code which +// includes comparisons is to blur the relationship between packed floats and +// packed integer types and (b) not sure that the compiler would handle +// generating good code for the intrinsics. + +#if USE_STDC_FOR_SIMD + +#error "hello" +union fltx4 { + float m128_f32[4]; + uint32 m128_u32[4]; +}; + +typedef fltx4 i32x4; +typedef fltx4 u32x4; + +#ifdef _PS3 +typedef fltx4 u32x4; +typedef fltx4 i32x4; +#endif +typedef fltx4 bi32x4; + +#elif (defined(_PS3)) + +union fltx4_union { + // This union allows float/int access (which generally shouldn't be done in + // inner loops) + + vec_float4 vmxf; + vec_int4 vmxi; + vec_uint4 vmxui; + __vector bool vmxbi; + + struct { + float x; + float y; + float z; + float w; + }; + + float m128_f32[4]; + uint32 m128_u32[4]; + int32 m128_i32[4]; +}; + +typedef vec_float4 fltx4; +typedef vec_uint4 u32x4; +typedef vec_int4 i32x4; +typedef __vector bool bi32x4; + +#define DIFFERENT_NATIVE_VECTOR_TYPES // true if the compiler has different + // types for float4, uint4, int4, etc + +#elif (defined(_X360)) + +union fltx4_union { + // This union allows float/int access (which generally shouldn't be done in + // inner loops) + __vector4 vmx; + float m128_f32[4]; + uint32 m128_u32[4]; +}; + +typedef __vector4 fltx4; +typedef __vector4 i32x4; // a VMX register; just a way of making it explicit + // that we're doing integer ops. +typedef __vector4 u32x4; // a VMX register; just a way of making it explicit + // that we're doing unsigned integer ops. +typedef fltx4 bi32x4; +#else + +using fltx4 = __m128; +using i32x4 = __m128; +using u32x4 = __m128; +using bi32x4 = fltx4; + +#endif + +#endif // VPC_MATHLIB_FLTX4_H_ diff --git a/public/mathlib/math_pfns.h b/public/mathlib/math_pfns.h new file mode 100644 index 0000000..694d01e --- /dev/null +++ b/public/mathlib/math_pfns.h @@ -0,0 +1,238 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_MATHLIB_MATH_PFNS_H_ +#define VPC_MATHLIB_MATH_PFNS_H_ + +#include + +#if defined(_X360) +#include +#elif defined(_PS3) + +#ifndef SPU +#include +#endif + +// Note that similar defines exist in ssemath.h +// Maybe we should consolidate in one place for all platforms. + +#define _VEC_0x7ff \ + (vec_int4) { 0x7ff, 0x7ff, 0x7ff, 0x7ff } +#define _VEC_0x3ff \ + (vec_int4) { 0x3ff, 0x3ff, 0x3ff, 0x3ff } +#define _VEC_22L \ + (vector unsigned int) { 22, 22, 22, 22 } +#define _VEC_11L \ + (vector unsigned int) { 11, 11, 11, 11 } +#define _VEC_0L \ + (vector unsigned int) { 0, 0, 0, 0 } +#define _VEC_255F \ + (vector float) { 255.0f, 255.0f, 255.0f, 255.0f } +#define _VEC_NEGONEF \ + (vector float) { -1.0f, -1.0f, -1.0f, -1.0f } +#define _VEC_ONEF \ + (vector float) { 1.0f, 1.0f, 1.0f, 1.0f } +#define _VEC_ZEROF \ + (vector float) { 0.0f, 0.0f, 0.0f, 0.0f } +#define _VEC_ZEROxyzONEwF \ + (vector float) { 0.0f, 0.0f, 0.0f, 1.0f } +#define _VEC_HALFF \ + (vector float) { 0.5f, 0.5f, 0.5f, 0.5f } +#define _VEC_HALFxyzZEROwF \ + (vector float) { 0.5f, 0.5f, 0.5f, 0.0f } +#define _VEC_PERMUTE_XYZ0W1 \ + (vector unsigned char) { \ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, \ + 0x1c, 0x1d, 0x1e, 0x1f \ + } + +#define _VEC_IEEEHACK \ + (vector float) { \ + (float)(1 << 23), (float)(1 << 23), (float)(1 << 23), (float)(1 << 23) \ + } +#define _VEC_PERMUTE_FASTFTOC \ + (vector unsigned char) { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03, 0x07, 0x0b, 0x0f \ + } + +// AngleQuaternion +#define _VEC_PERMUTE_AQsxsxcxcx \ + (vector unsigned char) { \ + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, \ + 0x10, 0x11, 0x12, 0x13 \ + } +#define _VEC_PERMUTE_AQczszszcz \ + (vector unsigned char) { \ + 0x18, 0x19, 0x1a, 0x1b, 0x08, 0x09, 0x0a, 0x0b, 0x08, 0x09, 0x0a, 0x0b, \ + 0x18, 0x19, 0x1a, 0x1b \ + } +#define _VEC_PERMUTE_AQcxcxsxsx \ + (vector unsigned char) { \ + 0x10, 0x11, 0x12, 0x13, 0x10, 0x11, 0x12, 0x13, 0x00, 0x01, 0x02, 0x03, \ + 0x00, 0x01, 0x02, 0x03 \ + } +#define _VEC_PERMUTE_AQszczczsz \ + (vector unsigned char) { \ + 0x08, 0x09, 0x0a, 0x0b, 0x18, 0x19, 0x1a, 0x1b, 0x18, 0x19, 0x1a, 0x1b, \ + 0x08, 0x09, 0x0a, 0x0b \ + } +#define _VEC_PERMUTE_ANGLEQUAT \ + (vector unsigned char) { \ + 0x10, 0x11, 0x12, 0x13, 0x04, 0x05, 0x06, 0x07, 0x18, 0x19, 0x1a, 0x1b, \ + 0x0c, 0x0d, 0x0e, 0x0f \ + } + +#define _VEC_EPSILONF \ + (__vector float) { FLT_EPSILON, FLT_EPSILON, FLT_EPSILON, FLT_EPSILON } + +#endif + +#if !(defined(PLATFORM_PPC) || defined(SPU)) +// If we are not PPC based or SPU based, then assumes it is SSE2. We should make +// this code cleaner. +#include + +// These globals are initialized by mathlib and redirected based on available +// fpu features + +// The following are not declared as macros because they are often used in +// limiting situations, and sometimes the compiler simply refuses to inline them +// for some reason +FORCEINLINE float FastSqrt(float x) { + __m128 root{_mm_sqrt_ss(_mm_load_ss(&x))}; + return *(reinterpret_cast(&root)); +} + +FORCEINLINE float FastRSqrtFast(float x) { + // use intrinsics + __m128 rroot{_mm_rsqrt_ss(_mm_load_ss(&x))}; + return *(reinterpret_cast(&rroot)); +} + +// Single iteration NewtonRaphson reciprocal square root: +// 0.5 * rsqrtps * (3 - x * rsqrtps(x) * rsqrtps(x)) +// Very low error, and fine to use in place of 1.f / sqrtf(x). +FORCEINLINE float FastRSqrt(float x) { + float rroot{FastRSqrtFast(x)}; + return (0.5f * rroot) * (3.f - (x * rroot) * rroot); +} + +void FastSinCos(float x, float* s, float* c); // any x +float FastCos(float x); + +inline float FastRecip(float x) { return 1.0f / x; } + +// Simple SSE rsqrt. Usually accurate to around 6 (relative) decimal places +// or so, so ok for closed transforms. (ie, computing lighting normals) +inline float FastSqrtEst(float x) { return FastRSqrtFast(x) * x; } + +#else // !defined( PLATFORM_PPC ) && !defined(_SPU) + +#ifndef SPU +// We may not need this for SPU, so let's not bother for now + +FORCEINLINE float _VMX_Sqrt(float x) { return __fsqrts(x); } + +FORCEINLINE double _VMX_RSqrt(double x) { + double rroot = __frsqrte(x); + + // Single iteration NewtonRaphson on reciprocal square root estimate + return (0.5f * rroot) * (3.0f - (x * rroot) * rroot); +} + +FORCEINLINE double _VMX_RSqrtFast(double x) { return __frsqrte(x); } + +#ifdef _X360 +FORCEINLINE void _VMX_SinCos(float a, float *pS, float *pC) { + XMScalarSinCos(pS, pC, a); +} + +FORCEINLINE float _VMX_Cos(float a) { return XMScalarCos(a); } +#endif + +// the 360 has fixed hw and calls directly +#define FastSqrt(x) _VMX_Sqrt(x) +#define FastRSqrt(x) _VMX_RSqrt(x) +#define FastRSqrtFast(x) _VMX_RSqrtFast(x) +#define FastSinCos(x, s, c) _VMX_SinCos(x, s, c) +#define FastCos(x) _VMX_Cos(x) + +inline double FastRecip(double x) { return __fres(x); } + +inline double FastSqrtEst(double x) { return __frsqrte(x) * x; } + +#endif // !defined( PLATFORM_PPC ) && !defined(_SPU) + +// if x is infinite, return FLT_MAX +inline float FastClampInfinity(float x) { +#ifdef PLATFORM_PPC + return fsel(std::numeric_limits::infinity() - x, x, FLT_MAX); +#else + return (x > FLT_MAX ? FLT_MAX : x); +#endif +} + +#if defined(_PS3) && !defined(SPU) + +// extern float cosvf(float); /* single precision cosine */ +// extern float sinvf(float); /* single precision sine */ +// TODO: need a faster single precision equivalent +#define cosvf cosf +#define sinvf sinf + +inline int _rotl(int x, int c) { return __rlwimi(x, x, c, 0, 31); } + +inline int64 _rotl64(int64 x, int c) { return __rldicl(x, c, 0); } + +// Vector Unions + +// Floats +union vector_float_union { + vector float vf; + float f[4]; +}; + +// Ints +union vector_int4_union { + vector int vi; + int i[4]; +}; + +union vector_uint4_union { + vector unsigned int vui; + unsigned int ui[4]; +}; + +// Shorts +union vector_short8_union { + vector signed short vs; + signed short s[8]; +}; + +union vector_ushort8_union { + vector unsigned short vus; + unsigned short us[8]; +}; + +// Chars +union vector_char16_union { + vector signed char vc; + signed char c[16]; +}; + +union vector_uchar16_union { + vector unsigned char vuc; + unsigned char uc[16]; +}; + +FORCEINLINE void _VMX_SinCos(float a, float *pS, float *pC) { + *pS = sinvf(a); + *pC = cosvf(a); +} + +FORCEINLINE float _VMX_Cos(float a) { return cosvf(a); } + +#endif // _PS3 +#endif // #ifndef SPU + +#endif // VPC_MATHLIB_MATH_PFNS_H_ diff --git a/public/mathlib/mathlib.h b/public/mathlib/mathlib.h new file mode 100644 index 0000000..e2bdf2d --- /dev/null +++ b/public/mathlib/mathlib.h @@ -0,0 +1,2296 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_MATHLIB_MATH_LIB_H_ +#define VPC_MATHLIB_MATH_LIB_H_ + +#include +#include "tier0/basetypes.h" +#include "mathlib/vector.h" +#include "mathlib/vector2d.h" +#include "tier0/dbg.h" + +#include "mathlib/math_pfns.h" +#include "mathlib/fltx4.h" + +#ifndef ALIGN8_POST +#define ALIGN8_POST +#endif + +#if defined(_PS3) +#include +#include +#include +#endif + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +// FIXME: does the asm code even exist anymore? +// FIXME: this should move to a different file +struct cplane_t { + Vector normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; + +#ifdef VECTOR_NO_SLOW_OPERATIONS + cplane_t() {} + + private: + // No copy constructors allowed if we're in optimal mode + cplane_t(const cplane_t &vOther); +#endif +}; + +// structure offset for asm code +#define CPLANE_NORMAL_X 0 +#define CPLANE_NORMAL_Y 4 +#define CPLANE_NORMAL_Z 8 +#define CPLANE_DIST 12 +#define CPLANE_TYPE 16 +#define CPLANE_SIGNBITS 17 +#define CPLANE_PAD0 18 +#define CPLANE_PAD1 19 + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +// Frustum plane indices. +// WARNING: there is code that depends on these values +enum { + FRUSTUM_RIGHT = 0, + FRUSTUM_LEFT = 1, + FRUSTUM_TOP = 2, + FRUSTUM_BOTTOM = 3, + FRUSTUM_NEARZ = 4, + FRUSTUM_FARZ = 5, + FRUSTUM_NUMPLANES = 6 +}; + +extern byte SignbitsForPlane(cplane_t *out); +class Frustum_t; + +// Computes Y fov from an X fov and a screen aspect ratio + X from Y +float CalcFovY(float flFovX, float flScreenAspect); +float CalcFovX(float flFovY, float flScreenAspect); + +// Generate a frustum based on perspective view parameters +// NOTE: FOV is specified in degrees, as the *full* view angle (not half-angle) +class VPlane; +void GeneratePerspectiveFrustum(const Vector &origin, const QAngle &angles, + float flZNear, float flZFar, float flFovX, + float flAspectRatio, Frustum_t &frustum); +void GeneratePerspectiveFrustum(const Vector &origin, const Vector &forward, + const Vector &right, const Vector &up, + float flZNear, float flZFar, float flFovX, + float flFovY, VPlane *pPlanesOut); +// Cull the world-space bounding box to the specified frustum. +// bool R_CullBox( const Vector& mins, const Vector& maxs, const Frustum_t +// &frustum ); bool R_CullBoxSkipNear( const Vector& mins, const Vector& maxs, +// const Frustum_t &frustum ); +void GenerateOrthoFrustum(const Vector &origin, const Vector &forward, + const Vector &right, const Vector &up, float flLeft, + float flRight, float flBottom, float flTop, + float flZNear, float flZFar, VPlane *pPlanesOut); + +class matrix3x4a_t; + +struct matrix3x4_t { + matrix3x4_t() = default; + matrix3x4_t(float m00, float m01, float m02, float m03, float m10, float m11, + float m12, float m13, float m20, float m21, float m22, + float m23) { + m_flMatVal[0][0] = m00; + m_flMatVal[0][1] = m01; + m_flMatVal[0][2] = m02; + m_flMatVal[0][3] = m03; + m_flMatVal[1][0] = m10; + m_flMatVal[1][1] = m11; + m_flMatVal[1][2] = m12; + m_flMatVal[1][3] = m13; + m_flMatVal[2][0] = m20; + m_flMatVal[2][1] = m21; + m_flMatVal[2][2] = m22; + m_flMatVal[2][3] = m23; + } + + //----------------------------------------------------------------------------- + // Creates a matrix where the X axis = forward + // the Y axis = left, and the Z axis = up + //----------------------------------------------------------------------------- + void Init(const Vector &xAxis, const Vector &yAxis, const Vector &zAxis, + const Vector &vecOrigin) { + m_flMatVal[0][0] = xAxis.x; + m_flMatVal[0][1] = yAxis.x; + m_flMatVal[0][2] = zAxis.x; + m_flMatVal[0][3] = vecOrigin.x; + m_flMatVal[1][0] = xAxis.y; + m_flMatVal[1][1] = yAxis.y; + m_flMatVal[1][2] = zAxis.y; + m_flMatVal[1][3] = vecOrigin.y; + m_flMatVal[2][0] = xAxis.z; + m_flMatVal[2][1] = yAxis.z; + m_flMatVal[2][2] = zAxis.z; + m_flMatVal[2][3] = vecOrigin.z; + } + + //----------------------------------------------------------------------------- + // Creates a matrix where the X axis = forward + // the Y axis = left, and the Z axis = up + //----------------------------------------------------------------------------- + matrix3x4_t(const Vector &xAxis, const Vector &yAxis, const Vector &zAxis, + const Vector &vecOrigin) { + Init(xAxis, yAxis, zAxis, vecOrigin); + } + + inline void SetOrigin(Vector const &p) { + m_flMatVal[0][3] = p.x; + m_flMatVal[1][3] = p.y; + m_flMatVal[2][3] = p.z; + } + + inline void Invalidate(void) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 4; j++) { + m_flMatVal[i][j] = VEC_T_NAN; + } + } + } + + float *operator[](int i) { + Assert((i >= 0) && (i < 3)); + return m_flMatVal[i]; + } + const float *operator[](int i) const { + Assert((i >= 0) && (i < 3)); + return m_flMatVal[i]; + } + float *Base() { return &m_flMatVal[0][0]; } + const float *Base() const { return &m_flMatVal[0][0]; } + + float m_flMatVal[3][4]; +}; + +class ALIGN16 matrix3x4a_t : public matrix3x4_t { + public: + /* + matrix3x4a_t() { if (((size_t)Base()) % 16 != 0) { Error( "matrix3x4a_t + missaligned" ); } } + */ + matrix3x4a_t &operator=(const matrix3x4_t &src) { + memcpy(Base(), src.Base(), sizeof(float) * 3 * 4); + return *this; + }; +} ALIGN16_POST; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#define M_PI_F ((float)(M_PI)) // Shouldn't collide with anything. + +// NJS: Inlined to prevent floats from being autopromoted to doubles, as with +// the old system. +#ifndef RAD2DEG +#define RAD2DEG(x) ((float)(x) * (float)(180.f / M_PI_F)) +#endif + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(M_PI_F / 180.f)) +#endif + +// Used to represent sides of things like planes. +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS -2 // necessary for polylib.c + +// Use different side values (1, 2, 4) instead of (0, 1, 2) so we can '|' and +// '&' them, and quickly determine overall clipping without having to maintain +// counters and read / write memory. +enum Sides { + OR_SIDE_FRONT = 1, + OR_SIDE_BACK = 2, + OR_SIDE_ON = 4, +}; + +#define ON_VIS_EPSILON \ + 0.01 // necessary for vvis (flow.c) -- again look into moving later! +#define EQUAL_EPSILON \ + 0.001 // necessary for vbsp (faces.c) -- should look into moving it there? + +extern bool s_bMathlibInitialized; + +extern const Vector vec3_origin; +extern const QAngle vec3_angle; +extern const Quaternion quat_identity; +extern const Vector vec3_invalid; +extern const int nanmask; + +#define IS_NAN(x) (((*(int *)&x) & nanmask) == nanmask) + +FORCEINLINE vec_t DotProduct(const vec_t *v1, const vec_t *v2) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} +FORCEINLINE void VectorSubtract(const vec_t *a, const vec_t *b, vec_t *c) { + c[0] = a[0] - b[0]; + c[1] = a[1] - b[1]; + c[2] = a[2] - b[2]; +} +FORCEINLINE void VectorAdd(const vec_t *a, const vec_t *b, vec_t *c) { + c[0] = a[0] + b[0]; + c[1] = a[1] + b[1]; + c[2] = a[2] + b[2]; +} +FORCEINLINE void VectorCopy(const vec_t *a, vec_t *b) { + b[0] = a[0]; + b[1] = a[1]; + b[2] = a[2]; +} +FORCEINLINE void VectorClear(vec_t *a) { a[0] = a[1] = a[2] = 0; } + +FORCEINLINE float VectorMaximum(const vec_t *v) { + return MAX(v[0], MAX(v[1], v[2])); +} + +FORCEINLINE float VectorMaximum(const Vector &v) { + return MAX(v.x, MAX(v.y, v.z)); +} + +FORCEINLINE void VectorScale(const float *in, vec_t scale, float *out) { + out[0] = in[0] * scale; + out[1] = in[1] * scale; + out[2] = in[2] * scale; +} + +// Cannot be forceinline as they have overloads: +inline void VectorFill(vec_t *a, float b) { a[0] = a[1] = a[2] = b; } + +inline void VectorNegate(vec_t *a) { + a[0] = -a[0]; + a[1] = -a[1]; + a[2] = -a[2]; +} + +//#define VectorMaximum(a) ( max( (a)[0], max( (a)[1], (a)[2] ) ) ) +#define Vector2Clear(x) \ + { (x)[0] = (x)[1] = 0; } +#define Vector2Negate(x) \ + { \ + (x)[0] = -((x)[0]); \ + (x)[1] = -((x)[1]); \ + } +#define Vector2Copy(a, b) \ + { \ + (b)[0] = (a)[0]; \ + (b)[1] = (a)[1]; \ + } +#define Vector2Subtract(a, b, c) \ + { \ + (c)[0] = (a)[0] - (b)[0]; \ + (c)[1] = (a)[1] - (b)[1]; \ + } +#define Vector2Add(a, b, c) \ + { \ + (c)[0] = (a)[0] + (b)[0]; \ + (c)[1] = (a)[1] + (b)[1]; \ + } +#define Vector2Scale(a, b, c) \ + { \ + (c)[0] = (b) * (a)[0]; \ + (c)[1] = (b) * (a)[1]; \ + } + +// NJS: Some functions in VBSP still need to use these for dealing with mixing +// vec4's and shorts with vec_t's. remove when no longer needed. +#define VECTOR_COPY(A, B) \ + do { \ + (B)[0] = (A)[0]; \ + (B)[1] = (A)[1]; \ + (B)[2] = (A)[2]; \ + } while (0) +#define DOT_PRODUCT(A, B) ((A)[0] * (B)[0] + (A)[1] * (B)[1] + (A)[2] * (B)[2]) + +FORCEINLINE void VectorMAInline(const float *start, float scale, + const float *direction, float *dest) { + dest[0] = start[0] + direction[0] * scale; + dest[1] = start[1] + direction[1] * scale; + dest[2] = start[2] + direction[2] * scale; +} + +FORCEINLINE void VectorMAInline(const Vector &start, float scale, + const Vector &direction, Vector &dest) { + dest.x = start.x + direction.x * scale; + dest.y = start.y + direction.y * scale; + dest.z = start.z + direction.z * scale; +} + +FORCEINLINE void VectorMA(const Vector &start, float scale, + const Vector &direction, Vector &dest) { + VectorMAInline(start, scale, direction, dest); +} + +FORCEINLINE void VectorMA(const float *start, float scale, + const float *direction, float *dest) { + VectorMAInline(start, scale, direction, dest); +} + +int VectorCompare(const float *v1, const float *v2); + +inline float VectorLength(const float *v) { + return FastSqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + FLT_EPSILON); +} + +void CrossProduct(const float *v1, const float *v2, float *cross); + +qboolean VectorsEqual(const float *v1, const float *v2); + +inline vec_t RoundInt(vec_t in) { return floor(in + 0.5f); } + +size_t Q_log2(unsigned int val); + +// Math routines done in optimized assembly math package routines +void inline SinCos(float radians, float *RESTRICT sine, + float *RESTRICT cosine) { +#if defined(_X360) + XMScalarSinCos(sine, cosine, radians); +#elif defined(_PS3) +#if (__GNUC__ == 4) && (__GNUC_MINOR__ == 1) && (__GNUC_PATCHLEVEL__ == 1) + vector_float_union s; + vector_float_union c; + + vec_float4 rad = vec_splats(radians); + vec_float4 sin; + vec_float4 cos; + + sincosf4(rad, &sin, &cos); + + vec_st(sin, 0, s.f); + vec_st(cos, 0, c.f); + + *sine = s.f[0]; + *cosine = c.f[0]; +#else //__GNUC__ == 4 && __GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ == 1 + vector_float_union r; + vector_float_union s; + vector_float_union c; + + vec_float4 rad; + vec_float4 sin; + vec_float4 cos; + + r.f[0] = radians; + rad = vec_ld(0, r.f); + + sincosf4(rad, &sin, &cos); + + vec_st(sin, 0, s.f); + vec_st(cos, 0, c.f); + + *sine = s.f[0]; + *cosine = c.f[0]; +#endif //__GNUC__ == 4 && __GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ == 1 +#elif defined(COMPILER_MSVC32) + _asm + { + fld DWORD PTR [radians] + fsincos + + mov edx, DWORD PTR [cosine] + mov eax, DWORD PTR [sine] + + fstp DWORD PTR [edx] + fstp DWORD PTR [eax] + } +#elif defined(GNUC) + register double __cosr, __sinr; + __asm __volatile__("fsincos" : "=t"(__cosr), "=u"(__sinr) : "0"(radians)); + + *sine = __sinr; + *cosine = __cosr; +#else + *sine = sinf(radians); + *cosine = cosf(radians); +#endif +} + +#define SIN_TABLE_SIZE 256 +#define FTOIBIAS 12582912.f +extern float SinCosTable[SIN_TABLE_SIZE]; + +inline float TableCos(float theta) { + union { + int i; + float f; + } ftmp; + + // ideally, the following should compile down to: theta * constant + constant, + // changing any of these constants from defines sometimes fubars this. + ftmp.f = theta * (float)(SIN_TABLE_SIZE / (2.0f * M_PI)) + + (FTOIBIAS + (SIN_TABLE_SIZE / 4)); + return SinCosTable[ftmp.i & (SIN_TABLE_SIZE - 1)]; +} + +inline float TableSin(float theta) { + union { + int i; + float f; + } ftmp; + + // ideally, the following should compile down to: theta * constant + constant + ftmp.f = theta * (float)(SIN_TABLE_SIZE / (2.0f * M_PI)) + FTOIBIAS; + return SinCosTable[ftmp.i & (SIN_TABLE_SIZE - 1)]; +} + +template +FORCEINLINE T Square(T const &a) { + return a * a; +} + +FORCEINLINE bool IsPowerOfTwo(uint x) { return (x & (x - 1)) == 0; } + +// return the smallest power of two >= x. +// returns 0 if x == 0 or x > 0x80000000 (ie numbers that would be negative if x +// was signed) NOTE: the old code took an int, and if you pass in an int of +// 0x80000000 casted to a uint, +// you'll get 0x80000000, which is correct for uints, instead of 0, which +// was correct for ints +FORCEINLINE uint SmallestPowerOfTwoGreaterOrEqual(uint x) { + x -= 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; +} + +// return the largest power of two <= x. Will return 0 if passed 0 +FORCEINLINE uint LargestPowerOfTwoLessThanOrEqual(uint x) { + if (x >= 0x80000000) return 0x80000000; + + return SmallestPowerOfTwoGreaterOrEqual(x + 1) >> 1; +} + +// Math routines for optimizing division +void FloorDivMod(double numer, double denom, int *quotient, int *rem); +int GreatestCommonDivisor(int i1, int i2); + +// Test for FPU denormal mode +bool IsDenormal(const float &val); + +// MOVEMENT INFO +enum { + PITCH = 0, // up / down + YAW, // left / right + ROLL // fall over +}; + +void MatrixAngles(const matrix3x4_t &matrix, float *angles); // !!!! +void MatrixVectors(const matrix3x4_t &matrix, Vector *pForward, Vector *pRight, + Vector *pUp); +void VectorTransform(const float *in1, const matrix3x4_t &in2, float *out); +void VectorITransform(const float *in1, const matrix3x4_t &in2, float *out); +void VectorRotate(const float *in1, const matrix3x4_t &in2, float *out); +void VectorRotate(const Vector &in1, const QAngle &in2, Vector &out); +void VectorRotate(const Vector &in1, const Quaternion &in2, Vector &out); +void VectorIRotate(const float *in1, const matrix3x4_t &in2, float *out); + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +QAngle TransformAnglesToLocalSpace(const QAngle &angles, + const matrix3x4_t &parentMatrix); +QAngle TransformAnglesToWorldSpace(const QAngle &angles, + const matrix3x4_t &parentMatrix); + +#endif + +void MatrixInitialize(matrix3x4_t &mat, const Vector &vecOrigin, + const Vector &vecXAxis, const Vector &vecYAxis, + const Vector &vecZAxis); +void MatrixCopy(const matrix3x4_t &in, matrix3x4_t &out); +void MatrixInvert(const matrix3x4_t &in, matrix3x4_t &out); + +// Matrix equality test +bool MatricesAreEqual(const matrix3x4_t &src1, const matrix3x4_t &src2, + float flTolerance = 1e-5); + +void MatrixGetColumn(const matrix3x4_t &in, int column, Vector &out); +void MatrixSetColumn(const Vector &in, int column, matrix3x4_t &out); + +// void DecomposeRotation( const matrix3x4_t &mat, float *out ); +void ConcatRotations(const matrix3x4_t &in1, const matrix3x4_t &in2, + matrix3x4_t &out); +void ConcatTransforms(const matrix3x4_t &in1, const matrix3x4_t &in2, + matrix3x4_t &out); +// faster version assumes m0, m1, out are 16-byte aligned addresses +void ConcatTransforms_Aligned(const matrix3x4a_t &m0, const matrix3x4a_t &m1, + matrix3x4a_t &out); + +// For identical interface w/ VMatrix +inline void MatrixMultiply(const matrix3x4_t &in1, const matrix3x4_t &in2, + matrix3x4_t &out) { + ConcatTransforms(in1, in2, out); +} + +void QuaternionExp(const Quaternion &p, Quaternion &q); +void QuaternionLn(const Quaternion &p, Quaternion &q); +void QuaternionAverageExponential(Quaternion &q, int nCount, + const Quaternion *pQuaternions, + const float *pflWeights = NULL); +void QuaternionLookAt(const Vector &vecForward, const Vector &referenceUp, + Quaternion &q); +void QuaternionSlerp(const Quaternion &p, const Quaternion &q, float t, + Quaternion &qt); +void QuaternionSlerpNoAlign(const Quaternion &p, const Quaternion &q, float t, + Quaternion &qt); +void QuaternionBlend(const Quaternion &p, const Quaternion &q, float t, + Quaternion &qt); +void QuaternionBlendNoAlign(const Quaternion &p, const Quaternion &q, float t, + Quaternion &qt); +void QuaternionIdentityBlend(const Quaternion &p, float t, Quaternion &qt); +float QuaternionAngleDiff(const Quaternion &p, const Quaternion &q); +void QuaternionScale(const Quaternion &p, float t, Quaternion &q); +void QuaternionAlign(const Quaternion &p, const Quaternion &q, Quaternion &qt); +float QuaternionDotProduct(const Quaternion &p, const Quaternion &q); +void QuaternionConjugate(const Quaternion &p, Quaternion &q); +void QuaternionInvert(const Quaternion &p, Quaternion &q); +float QuaternionNormalize(Quaternion &q); +void QuaternionAdd(const Quaternion &p, const Quaternion &q, Quaternion &qt); +void QuaternionMult(const Quaternion &p, const Quaternion &q, Quaternion &qt); +void QuaternionMatrix(const Quaternion &q, matrix3x4_t &matrix); +void QuaternionMatrix(const Quaternion &q, const Vector &pos, + matrix3x4_t &matrix); +void QuaternionAngles(const Quaternion &q, QAngle &angles); +void AngleQuaternion(const QAngle &angles, Quaternion &qt); +void QuaternionAngles(const Quaternion &q, RadianEuler &angles); +void AngleQuaternion(RadianEuler const &angles, Quaternion &qt); +void QuaternionAxisAngle(const Quaternion &q, Vector &axis, float &angle); +void AxisAngleQuaternion(const Vector &axis, float angle, Quaternion &q); +void BasisToQuaternion(const Vector &vecForward, const Vector &vecRight, + const Vector &vecUp, Quaternion &q); +void MatrixQuaternion(const matrix3x4_t &mat, Quaternion &q); + +// A couple methods to find the dot product of a vector with a matrix row or +// column... +inline float MatrixRowDotProduct(const matrix3x4_t &in1, int row, + const Vector &in2) { + Assert((row >= 0) && (row < 3)); + return DotProduct(in1[row], in2.Base()); +} + +inline float MatrixColumnDotProduct(const matrix3x4_t &in1, int col, + const Vector &in2) { + Assert((col >= 0) && (col < 4)); + return in1[0][col] * in2[0] + in1[1][col] * in2[1] + in1[2][col] * in2[2]; +} + +int __cdecl BoxOnPlaneSide(const float *emins, const float *emaxs, + const cplane_t *plane); + +inline float anglemod(float a) { + a = (360.f / 65536) * ((int)(a * (65536.f / 360.0f)) & 65535); + return a; +} + +//// CLAMP +#if defined(__cplusplus) && defined(PLATFORM_PPC) + +#ifdef _X360 +#define __fsels __fsel +#endif + +template <> +inline double clamp(double const &val, double const &minVal, + double const &maxVal) { + float diffmin = val - minVal; + float diffmax = maxVal - val; + float r; + r = __fsel(diffmin, val, minVal); + r = __fsel(diffmax, r, maxVal); + return r; +} + +template <> +inline double clamp(double const &val, float const &minVal, + float const &maxVal) { + // these typecasts are actually free since all FPU regs are 64 bit on PPC + // anyway + return clamp(val, (double)minVal, (double)maxVal); +} +template <> +inline double clamp(double const &val, float const &minVal, + double const &maxVal) { + return clamp(val, (double)minVal, (double)maxVal); +} +template <> +inline double clamp(double const &val, double const &minVal, + float const &maxVal) { + return clamp(val, (double)minVal, (double)maxVal); +} + +template <> +inline float clamp(float const &val, float const &minVal, float const &maxVal) { + float diffmin = val - minVal; + float diffmax = maxVal - val; + float r; + r = __fsels(diffmin, val, minVal); + r = __fsels(diffmax, r, maxVal); + return r; +} + +template <> +inline float clamp(float const &val, double const &minVal, + double const &maxVal) { + float diffmin = val - minVal; + float diffmax = maxVal - val; + float r; + r = __fsels(diffmin, val, minVal); + r = __fsels(diffmax, r, maxVal); + return r; +} +template <> +inline float clamp(float const &val, double const &minVal, + float const &maxVal) { + return clamp(val, (float)minVal, maxVal); +} +template <> +inline float clamp(float const &val, float const &minVal, + double const &maxVal) { + return clamp(val, minVal, (float)maxVal); +} + +#endif + +// Remap a value in the range [A,B] to [C,D]. +inline float RemapVal(float val, float A, float B, float C, float D) { + if (A == B) return fsel(val - B, D, C); + return C + (D - C) * (val - A) / (B - A); +} + +inline float RemapValClamped(float val, float A, float B, float C, float D) { + if (A == B) return fsel(val - B, D, C); + float cVal = (val - A) / (B - A); + cVal = clamp(cVal, 0.0f, 1.0f); + + return C + (D - C) * cVal; +} + +// Returns A + (B-A)*flPercent. +// float Lerp( float flPercent, float A, float B ); +template +FORCEINLINE T Lerp(float flPercent, T const &A, T const &B) { + return A + (B - A) * flPercent; +} + +FORCEINLINE float Sqr(float f) { return f * f; } + +// 5-argument floating point linear interpolation. +// FLerp(f1,f2,i1,i2,x)= +// f1 at x=i1 +// f2 at x=i2 +// smooth lerp between f1 and f2 at x>i1 and xi2 +// +// If you know a function f(x)'s value (f1) at position i1, and its value (f2) +// at position i2, the function can be linearly interpolated with +// FLerp(f1,f2,i1,i2,x) +// i2=i1 will cause a divide by zero. +static inline float FLerp(float f1, float f2, float i1, float i2, float x) { + return f1 + (f2 - f1) * (x - i1) / (i2 - i1); +} + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +// YWB: Specialization for interpolating euler angles via quaternions... +template <> +FORCEINLINE QAngle Lerp(float flPercent, const QAngle &q1, + const QAngle &q2) { + // Avoid precision errors + if (q1 == q2) return q1; + + Quaternion src, dest; + + // Convert to quaternions + AngleQuaternion(q1, src); + AngleQuaternion(q2, dest); + + Quaternion result; + + // Slerp + QuaternionSlerp(src, dest, flPercent, result); + + // Convert to euler + QAngle output; + QuaternionAngles(result, output); + return output; +} + +#else + +#pragma error + +// NOTE NOTE: I haven't tested this!! It may not work! Check out +// interpolatedvar.cpp in the client dll to try it +template <> +FORCEINLINE QAngleByValue Lerp(float flPercent, + const QAngleByValue &q1, + const QAngleByValue &q2) { + // Avoid precision errors + if (q1 == q2) return q1; + + Quaternion src, dest; + + // Convert to quaternions + AngleQuaternion(q1, src); + AngleQuaternion(q2, dest); + + Quaternion result; + + // Slerp + QuaternionSlerp(src, dest, flPercent, result); + + // Convert to euler + QAngleByValue output; + QuaternionAngles(result, output); + return output; +} + +#endif // VECTOR_NO_SLOW_OPERATIONS + +// Swap two of anything. +template +FORCEINLINE void V_swap(T &x, T &y) { + T temp = x; + x = y; + y = temp; +} + +template +FORCEINLINE T AVG(T a, T b) { + return (a + b) / 2; +} + +// number of elements in an array of static size +#define NELEMS(x) ((sizeof(x)) / sizeof(x[0])) + +// XYZ macro, for printf type functions - ex printf("%f %f %f",XYZ(myvector)); +#define XYZ(v) (v).x, (v).y, (v).z + +inline float Sign(float x) { + return fsel(x, 1.0f, -1.0f); // x >= 0 ? 1.0f : -1.0f + // return (x <0.0f) ? -1.0f : 1.0f; +} + +// +// Clamps the input integer to the given array bounds. +// Equivalent to the following, but without using any branches: +// +// if( n < 0 ) return 0; +// else if ( n > maxindex ) return maxindex; +// else return n; +// +// This is not always a clear performance win, but when you have situations +// where a clamped value is thrashing against a boundary this is a big win. (ie, +// valid, invalid, valid, invalid, ...) +// +// Note: This code has been run against all possible integers. +// +inline int ClampArrayBounds(int n, unsigned maxindex) { + // mask is 0 if less than 4096, 0xFFFFFFFF if greater than + unsigned int inrangemask = 0xFFFFFFFF + (((unsigned)n) > maxindex); + unsigned int lessthan0mask = 0xFFFFFFFF + (n >= 0); + + // If the result was valid, set the result, (otherwise sets zero) + int result = (inrangemask & n); + + // if the result was out of range or zero. + result |= ((~inrangemask) & (~lessthan0mask)) & maxindex; + + return result; +} + +// Turn a number "inside out". +// See Recording Animation in Binary Order for Progressive Temporal Refinement +// by Paul Heckbert from "Graphics Gems". +// +// If you want to iterate something from 0 to n, you can use this to iterate +// non-sequentially, in such a way that you will start with widely separated +// values and then refine the gaps between them, as you would for progressive +// refinement. This works with non-power of two ranges. +int InsideOut(int nTotal, int nCounter); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3) ? (((p)->dist <= (emins)[(p)->type]) \ + ? 1 \ + : (((p)->dist >= (emaxs)[(p)->type]) ? 2 : 3)) \ + : BoxOnPlaneSide((emins), (emaxs), (p))) + +// FIXME: Vector versions.... the float versions will go away hopefully soon! + +void AngleVectors(const QAngle &angles, Vector *forward); +void AngleVectors(const QAngle &angles, Vector *forward, Vector *right, + Vector *up); +void AngleVectorsTranspose(const QAngle &angles, Vector *forward, Vector *right, + Vector *up); +void AngleMatrix(const QAngle &angles, matrix3x4_t &mat); +void AngleMatrix(const QAngle &angles, const Vector &position, + matrix3x4_t &mat); +void AngleMatrix(const RadianEuler &angles, matrix3x4_t &mat); +void AngleMatrix(RadianEuler const &angles, const Vector &position, + matrix3x4_t &mat); +void AngleIMatrix(const QAngle &angles, matrix3x4_t &mat); +void AngleIMatrix(const QAngle &angles, const Vector &position, + matrix3x4_t &mat); +void AngleIMatrix(const RadianEuler &angles, matrix3x4_t &mat); +void VectorAngles(const Vector &forward, QAngle &angles); +void VectorAngles(const Vector &forward, const Vector &pseudoup, + QAngle &angles); +void VectorMatrix(const Vector &forward, matrix3x4_t &mat); +void VectorVectors(const Vector &forward, Vector &right, Vector &up); +void SetIdentityMatrix(matrix3x4_t &mat); +void SetScaleMatrix(float x, float y, float z, matrix3x4_t &dst); +void MatrixBuildRotationAboutAxis(const Vector &vAxisOfRot, float angleDegrees, + matrix3x4_t &dst); + +inline bool MatrixIsIdentity(const matrix3x4_t &m) { + return m.m_flMatVal[0][0] == 1.0f && m.m_flMatVal[0][1] == 0.0f && + m.m_flMatVal[0][2] == 0.0f && m.m_flMatVal[0][3] == 0.0f && + m.m_flMatVal[1][0] == 0.0f && m.m_flMatVal[1][1] == 1.0f && + m.m_flMatVal[1][2] == 0.0f && m.m_flMatVal[1][3] == 0.0f && + m.m_flMatVal[2][0] == 0.0f && m.m_flMatVal[2][1] == 0.0f && + m.m_flMatVal[2][2] == 1.0f && m.m_flMatVal[2][3] == 0.0f; +} + +inline void SetScaleMatrix(float flScale, matrix3x4_t &dst) { + SetScaleMatrix(flScale, flScale, flScale, dst); +} + +inline void SetScaleMatrix(const Vector &scale, matrix3x4_t &dst) { + SetScaleMatrix(scale.x, scale.y, scale.z, dst); +} + +// Computes the inverse transpose +void MatrixTranspose(matrix3x4_t &mat); +void MatrixTranspose(const matrix3x4_t &src, matrix3x4_t &dst); +void MatrixInverseTranspose(const matrix3x4_t &src, matrix3x4_t &dst); + +inline void PositionMatrix(const Vector &position, matrix3x4_t &mat) { + MatrixSetColumn(position, 3, mat); +} + +inline void MatrixPosition(const matrix3x4_t &matrix, Vector &position) { + position[0] = matrix[0][3]; + position[1] = matrix[1][3]; + position[2] = matrix[2][3]; +} + +inline void VectorRotate(const Vector &in1, const matrix3x4_t &in2, + Vector &out) { + VectorRotate(&in1.x, in2, &out.x); +} + +inline void VectorIRotate(const Vector &in1, const matrix3x4_t &in2, + Vector &out) { + VectorIRotate(&in1.x, in2, &out.x); +} + +inline void MatrixAngles(const matrix3x4_t &matrix, QAngle &angles) { + MatrixAngles(matrix, &angles.x); +} + +inline void MatrixAngles(const matrix3x4_t &matrix, QAngle &angles, + Vector &position) { + MatrixAngles(matrix, angles); + MatrixPosition(matrix, position); +} + +inline void MatrixAngles(const matrix3x4_t &matrix, RadianEuler &angles) { + MatrixAngles(matrix, &angles.x); + + angles.Init(DEG2RAD(angles.z), DEG2RAD(angles.x), DEG2RAD(angles.y)); +} + +void MatrixAngles(const matrix3x4_t &mat, RadianEuler &angles, + Vector &position); + +void MatrixAngles(const matrix3x4_t &mat, Quaternion &q, Vector &position); + +inline int VectorCompare(const Vector &v1, const Vector &v2) { + return v1 == v2; +} + +inline void VectorTransform(const Vector &in1, const matrix3x4_t &in2, + Vector &out) { + VectorTransform(&in1.x, in2, &out.x); +} + +inline void VectorITransform(const Vector &in1, const matrix3x4_t &in2, + Vector &out) { + VectorITransform(&in1.x, in2, &out.x); +} + +inline int BoxOnPlaneSide(const Vector &emins, const Vector &emaxs, + const cplane_t *plane) { + return BoxOnPlaneSide(&emins.x, &emaxs.x, plane); +} + +inline void VectorFill(Vector &a, float b) { a[0] = a[1] = a[2] = b; } + +inline void VectorNegate(Vector &a) { + a[0] = -a[0]; + a[1] = -a[1]; + a[2] = -a[2]; +} + +inline vec_t VectorAvg(Vector &a) { return (a[0] + a[1] + a[2]) / 3; } + +// Box/plane test (slow version) +inline int FASTCALL BoxOnPlaneSide2(const Vector &emins, const Vector &emaxs, + const cplane_t *p, float tolerance = 0.f) { + Vector corners[2]; + + if (p->normal[0] < 0) { + corners[0][0] = emins[0]; + corners[1][0] = emaxs[0]; + } else { + corners[1][0] = emins[0]; + corners[0][0] = emaxs[0]; + } + + if (p->normal[1] < 0) { + corners[0][1] = emins[1]; + corners[1][1] = emaxs[1]; + } else { + corners[1][1] = emins[1]; + corners[0][1] = emaxs[1]; + } + + if (p->normal[2] < 0) { + corners[0][2] = emins[2]; + corners[1][2] = emaxs[2]; + } else { + corners[1][2] = emins[2]; + corners[0][2] = emaxs[2]; + } + + int sides = 0; + + float dist1 = DotProduct(p->normal, corners[0]) - p->dist; + if (dist1 >= tolerance) sides = 1; + + float dist2 = DotProduct(p->normal, corners[1]) - p->dist; + if (dist2 < -tolerance) sides |= 2; + + return sides; +} + +// Helpers for bounding box construction + +void ClearBounds(Vector &mins, Vector &maxs); +void AddPointToBounds(const Vector &v, Vector &mins, Vector &maxs); + +// Ensures that the min and max bounds values are valid. +// (ClearBounds() sets min > max, which is clearly invalid.) +bool AreBoundsValid(const Vector &vMin, const Vector &vMax); + +// Returns true if the provided point is in the AABB defined by vMin +// at the lower corner and vMax at the upper corner. +bool IsPointInBounds(const Vector &vPoint, const Vector &vMin, + const Vector &vMax); + +// +// COLORSPACE/GAMMA CONVERSION STUFF +// +void BuildGammaTable(float gamma, float texGamma, float brightness, + int overbright); + +// convert texture to linear 0..1 value +inline float TexLightToLinear(int c, int exponent) { + extern float power2_n[256]; + Assert(exponent >= -128 && exponent <= 127); + return (float)c * power2_n[exponent + 128]; +} + +// convert texture to linear 0..1 value +int LinearToTexture(float f); +// converts 0..1 linear value to screen gamma (0..255) +int LinearToScreenGamma(float f); +float TextureToLinear(int c); + +// compressed color format +struct ColorRGBExp32 { + byte r, g, b; + signed char exponent; +}; + +void ColorRGBExp32ToVector(const ColorRGBExp32 &in, Vector &out); +void VectorToColorRGBExp32(const Vector &v, ColorRGBExp32 &c); + +// solve for "x" where "a x^2 + b x + c = 0", return true if solution exists +bool SolveQuadratic(float a, float b, float c, float &root1, float &root2); + +// solves for "a, b, c" where "a x^2 + b x + c = y", return true if solution +// exists +bool SolveInverseQuadratic(float x1, float y1, float x2, float y2, float x3, + float y3, float &a, float &b, float &c); + +// solves for a,b,c specified as above, except that it always creates a +// monotonically increasing or decreasing curve if the data is monotonically +// increasing or decreasing. In order to enforce the monoticity condition, it is +// possible that the resulting quadratic will only approximate the data instead +// of interpolating it. This code is not especially fast. +bool SolveInverseQuadraticMonotonic(float x1, float y1, float x2, float y2, + float x3, float y3, float &a, float &b, + float &c); + +// solves for "a, b, c" where "1/(a x^2 + b x + c ) = y", return true if +// solution exists +bool SolveInverseReciprocalQuadratic(float x1, float y1, float x2, float y2, + float x3, float y3, float &a, float &b, + float &c); + +// rotate a vector around the Z axis (YAW) +void VectorYawRotate(const Vector &in, float flYaw, Vector &out); + +// Bias takes an X value between 0 and 1 and returns another value between 0 and +// 1 The curve is biased towards 0 or 1 based on biasAmt, which is between 0 +// and 1. Lower values of biasAmt bias the curve towards 0 and higher values +// bias it towards 1. +// +// For example, with biasAmt = 0.2, the curve looks like this: +// +// 1 +// | * +// | * +// | * +// | ** +// | ** +// | **** +// |********* +// |___________________ +// 0 1 +// +// +// With biasAmt = 0.8, the curve looks like this: +// +// 1 +// | ************** +// | ** +// | * +// | * +// |* +// |* +// |* +// |___________________ +// 0 1 +// +// With a biasAmt of 0.5, Bias returns X. +float Bias(float x, float biasAmt); + +// Gain is similar to Bias, but biasAmt biases towards or away from 0.5. +// Lower bias values bias towards 0.5 and higher bias values bias away from it. +// +// For example, with biasAmt = 0.2, the curve looks like this: +// +// 1 +// | * +// | * +// | ** +// | *************** +// | ** +// | * +// |* +// |___________________ +// 0 1 +// +// +// With biasAmt = 0.8, the curve looks like this: +// +// 1 +// | ***** +// | *** +// | * +// | * +// | * +// | *** +// |***** +// |___________________ +// 0 1 +float Gain(float x, float biasAmt); + +// SmoothCurve maps a 0-1 value into another 0-1 value based on a cosine wave +// where the derivatives of the function at 0 and 1 (and 0.5) are 0. This is +// useful for any fadein/fadeout effect where it should start and end smoothly. +// +// The curve looks like this: +// +// 1 +// | ** +// | * * +// | * * +// | * * +// | * * +// | ** ** +// |*** *** +// |___________________ +// 0 1 +// +float SmoothCurve(float x); + +// This works like SmoothCurve, with two changes: +// +// 1. Instead of the curve peaking at 0.5, it will peak at flPeakPos. +// (So if you specify flPeakPos=0.2, then the peak will slide to the left). +// +// 2. flPeakSharpness is a 0-1 value controlling the sharpness of the peak. +// Low values blunt the peak and high values sharpen the peak. +float SmoothCurve_Tweak(float x, float flPeakPos = 0.5, + float flPeakSharpness = 0.5); + +// float ExponentialDecay( float halflife, float dt ); +// float ExponentialDecay( float decayTo, float decayTime, float dt ); + +// halflife is time for value to reach 50% +inline float ExponentialDecay(float halflife, float dt) { + // log(0.5) == -0.69314718055994530941723212145818 + return expf(-0.69314718f / halflife * dt); +} + +// decayTo is factor the value should decay to in decayTime +inline float ExponentialDecay(float decayTo, float decayTime, float dt) { + return expf(logf(decayTo) / decayTime * dt); +} + +// Get the integrated distanced traveled +// decayTo is factor the value should decay to in decayTime +// dt is the time relative to the last velocity update +inline float ExponentialDecayIntegral(float decayTo, float decayTime, + float dt) { + return (powf(decayTo, dt / decayTime) * decayTime - decayTime) / + logf(decayTo); +} + +// hermite basis function for smooth interpolation +// Similar to Gain() above, but very cheap to call +// value should be between 0 & 1 inclusive +inline float SimpleSpline(float value) { + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return (3 * valueSquared - 2 * valueSquared * value); +} + +// remaps a value in [startInterval, startInterval+rangeInterval] from linear to +// spline using SimpleSpline +inline float SimpleSplineRemapVal(float val, float A, float B, float C, + float D) { + if (A == B) return val >= B ? D : C; + float cVal = (val - A) / (B - A); + return C + (D - C) * SimpleSpline(cVal); +} + +// remaps a value in [startInterval, startInterval+rangeInterval] from linear to +// spline using SimpleSpline +inline float SimpleSplineRemapValClamped(float val, float A, float B, float C, + float D) { + if (A == B) return val >= B ? D : C; + float cVal = (val - A) / (B - A); + cVal = clamp(cVal, 0.0f, 1.0f); + return C + (D - C) * SimpleSpline(cVal); +} + +FORCEINLINE int RoundFloatToInt(float f) { +#if defined(_X360) +#ifdef Assert + Assert(IsFPUControlWordSet()); +#endif + union { + double flResult; + int pResult[2]; + }; + flResult = __fctiw(f); + return pResult[1]; +#elif defined(_PS3) + return __fctiw(f); +#else // !X360 + int nResult; +#if defined(COMPILER_MSVC32) + __asm + { + fld f + fistp nResult + } +#elif GNUC + __asm __volatile__("fistpl %0;" : "=m"(nResult) : "t"(f) : "st"); +#else + nResult = static_cast(f); +#endif + return nResult; +#endif +} + +FORCEINLINE unsigned char RoundFloatToByte(float f) { +#if defined(_X360) +#ifdef Assert + Assert(IsFPUControlWordSet()); +#endif + union { + double flResult; + int pIntResult[2]; + unsigned char pResult[8]; + }; + flResult = __fctiw(f); +#ifdef Assert + Assert(pIntResult[1] >= 0 && pIntResult[1] <= 255); +#endif + return pResult[7]; + +#elif defined(_PS3) + return __fctiw(f); +#else // !X360 + + int nResult; + +#if defined(COMPILER_MSVC32) + __asm + { + fld f + fistp nResult + } +#elif GNUC + __asm __volatile__("fistpl %0;" : "=m"(nResult) : "t"(f) : "st"); +#else + nResult = static_cast(f) & 0xff; +#endif + +#ifdef Assert + Assert(nResult >= 0 && nResult <= 255); +#endif + return static_cast(clamp(nResult, 0, 255)); +#endif +} + +FORCEINLINE unsigned long RoundFloatToUnsignedLong(float f) { +#if defined(_X360) +#ifdef Assert + Assert(IsFPUControlWordSet()); +#endif + union { + double flResult; + int pIntResult[2]; + unsigned long pResult[2]; + }; + flResult = __fctiw(f); + Assert(pIntResult[1] >= 0); + return pResult[1]; +#elif defined(_PS3) + return __fctiw(f); +#else // !X360 + +#if defined(COMPILER_MSVC32) + alignas(unsigned long) unsigned char nResult[8]; + __asm + { + fld f + fistp qword ptr nResult + } + return *((unsigned long *)nResult); +#elif defined(COMPILER_GCC) + unsigned char nResult[8]; + __asm __volatile__("fistpl %0;" : "=m"(nResult) : "t"(f) : "st"); + return *((unsigned long *)nResult); +#else + return static_cast(f); +#endif + +#endif +} + +FORCEINLINE bool IsIntegralValue(float flValue, float flTolerance = 0.001f) { + return fabs(RoundFloatToInt(flValue) - flValue) < flTolerance; +} + +// Fast, accurate ftol: +FORCEINLINE int Float2Int(float a) { +#if defined(_X360) + union { + double flResult; + int pResult[2]; + }; + flResult = __fctiwz(a); + return pResult[1]; +#elif defined(_PS3) + return __fctiwz(a); +#else // !X360 + + int RetVal; + +#if defined(COMPILER_MSVC32) + int CtrlwdHolder; + int CtrlwdSetter; + __asm + { + fld a // push 'a' onto the FP stack + fnstcw CtrlwdHolder // store FPU control word + movzx eax, CtrlwdHolder // move and zero extend word into eax + and eax, 0xFFFFF3FF // set all bits except rounding bits to 1 + or eax, 0x00000C00 // set rounding mode bits to round towards zero + mov CtrlwdSetter, eax // Prepare to set the rounding mode -- prepare to enter plaid! + fldcw CtrlwdSetter // Entering plaid! + fistp RetVal // Store and converted (to int) result + fldcw CtrlwdHolder // Restore control word + } +#else + RetVal = static_cast(a); +#endif + + return RetVal; +#endif +} + +// Over 15x faster than: (int)floor(value) +inline int Floor2Int(float a) { + int RetVal; + +#if defined(PLATFORM_PPC) + RetVal = (int)floor(a); +#elif defined(COMPILER_MSVC32) + int CtrlwdHolder; + int CtrlwdSetter; + __asm + { + fld a // push 'a' onto the FP stack + fnstcw CtrlwdHolder // store FPU control word + movzx eax, CtrlwdHolder // move and zero extend word into eax + and eax, 0xFFFFF3FF // set all bits except rounding bits to 1 + or eax, 0x00000400 // set rounding mode bits to round down + mov CtrlwdSetter, eax // Prepare to set the rounding mode -- prepare to enter plaid! + fldcw CtrlwdSetter // Entering plaid! + fistp RetVal // Store floored and converted (to int) result + fldcw CtrlwdHolder // Restore control word + } +#else + RetVal = static_cast(floor(a)); +#endif + + return RetVal; +} + +// Fast color conversion from float to unsigned char +FORCEINLINE unsigned char FastFToC(float c) { + volatile float dc; + + // ieee trick + dc = c * 255.0f + (float)(1 << 23); + + // return the lsb +#if defined(_X360) || defined(_PS3) + return ((unsigned char *)&dc)[3]; +#else + return *(unsigned char *)&dc; +#endif +} + +// Purpose: Bound input float to .001 (millisecond) boundary +inline float ClampToMsec(float in) { + int msec = Floor2Int(in * 1000.0f + 0.5f); + return msec / 1000.0f; +} + +// Over 15x faster than: (int)ceil(value) +inline int Ceil2Int(float a) { + int RetVal; + +#if defined(PLATFORM_PPC) + RetVal = (int)ceil(a); +#elif defined(COMPILER_MSVC32) + int CtrlwdHolder; + int CtrlwdSetter; + __asm + { + fld a // push 'a' onto the FP stack + fnstcw CtrlwdHolder // store FPU control word + movzx eax, CtrlwdHolder // move and zero extend word into eax + and eax, 0xFFFFF3FF // set all bits except rounding bits to 1 + or eax, 0x00000800 // set rounding mode bits to round down + mov CtrlwdSetter, eax // Prepare to set the rounding mode -- prepare to enter plaid! + fldcw CtrlwdSetter // Entering plaid! + fistp RetVal // Store floored and converted (to int) result + fldcw CtrlwdHolder // Restore control word + } +#else + RetVal = static_cast(ceil(a)); +#endif + + return RetVal; +} + +// Regular signed area of triangle +#define TriArea2D(A, B, C) \ + (0.5f * ((B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x))) + +// This version doesn't premultiply by 0.5f, so it's the area of the rectangle +// instead +#define TriArea2DTimesTwo(A, B, C) \ + (((B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x))) + +// Get the barycentric coordinates of "pt" in triangle [A,B,C]. +inline void GetBarycentricCoords2D(Vector2D const &A, Vector2D const &B, + Vector2D const &C, Vector2D const &pt, + float bcCoords[3]) { + // Note, because to top and bottom are both x2, the issue washes out in the + // composite + float invTriArea = 1.0f / TriArea2DTimesTwo(A, B, C); + + // NOTE: We assume here that the lightmap coordinate vertices go + // counterclockwise. If not, TriArea2D() is negated so this works out right. + bcCoords[0] = TriArea2DTimesTwo(B, C, pt) * invTriArea; + bcCoords[1] = TriArea2DTimesTwo(C, A, pt) * invTriArea; + bcCoords[2] = TriArea2DTimesTwo(A, B, pt) * invTriArea; +} + +// Return true of the sphere might touch the box (the sphere is actually treated +// like a box itself, so this may return true if the sphere's bounding box +// touches a corner of the box but the sphere itself doesn't). +inline bool QuickBoxSphereTest(const Vector &vOrigin, float flRadius, + const Vector &bbMin, const Vector &bbMax) { + return vOrigin.x - flRadius < bbMax.x && vOrigin.x + flRadius > bbMin.x && + vOrigin.y - flRadius < bbMax.y && vOrigin.y + flRadius > bbMin.y && + vOrigin.z - flRadius < bbMax.z && vOrigin.z + flRadius > bbMin.z; +} + +// Return true of the boxes intersect (but not if they just touch). +inline bool QuickBoxIntersectTest(const Vector &vBox1Min, + const Vector &vBox1Max, + const Vector &vBox2Min, + const Vector &vBox2Max) { + return vBox1Min.x < vBox2Max.x && vBox1Max.x > vBox2Min.x && + vBox1Min.y < vBox2Max.y && vBox1Max.y > vBox2Min.y && + vBox1Min.z < vBox2Max.z && vBox1Max.z > vBox2Min.z; +} + +extern float GammaToLinearFullRange(float gamma); +extern float LinearToGammaFullRange(float linear); +extern float GammaToLinear(float gamma); +extern float LinearToGamma(float linear); + +extern float SrgbGammaToLinear(float flSrgbGammaValue); +extern float SrgbLinearToGamma(float flLinearValue); +extern float X360GammaToLinear(float fl360GammaValue); +extern float X360LinearToGamma(float flLinearValue); +extern float SrgbGammaTo360Gamma(float flSrgbGammaValue); + +// linear (0..4) to screen corrected vertex space (0..1?) +FORCEINLINE float LinearToVertexLight(float f) { + extern float lineartovertex[4096]; + + // Gotta clamp before the multiply; could overflow... + // assume 0..4 range + int i = RoundFloatToInt(f * 1024.f); + + // Presumably the comman case will be not to clamp, so check that first: + if ((unsigned)i > 4095) { + if (i < 0) + i = 0; // Compare to zero instead of 4095 to save 4 bytes in the + // instruction stream + else + i = 4095; + } + + return lineartovertex[i]; +} + +FORCEINLINE unsigned char LinearToLightmap(float f) { + extern unsigned char lineartolightmap[4096]; + + // Gotta clamp before the multiply; could overflow... + int i = RoundFloatToInt(f * 1024.f); // assume 0..4 range + + // Presumably the comman case will be not to clamp, so check that first: + if ((unsigned)i > 4095) { + if (i < 0) + i = 0; // Compare to zero instead of 4095 to save 4 bytes in the + // instruction stream + else + i = 4095; + } + + return lineartolightmap[i]; +} + +FORCEINLINE void ColorClamp(Vector &color) { + float maxc = MAX(color.x, MAX(color.y, color.z)); + if (maxc > 1.0f) { + float ooMax = 1.0f / maxc; + color.x *= ooMax; + color.y *= ooMax; + color.z *= ooMax; + } + + if (color[0] < 0.f) color[0] = 0.f; + if (color[1] < 0.f) color[1] = 0.f; + if (color[2] < 0.f) color[2] = 0.f; +} + +inline void ColorClampTruncate(Vector &color) { + if (color[0] > 1.0f) + color[0] = 1.0f; + else if (color[0] < 0.0f) + color[0] = 0.0f; + if (color[1] > 1.0f) + color[1] = 1.0f; + else if (color[1] < 0.0f) + color[1] = 0.0f; + if (color[2] > 1.0f) + color[2] = 1.0f; + else if (color[2] < 0.0f) + color[2] = 0.0f; +} + +// Interpolate a Catmull-Rom spline. +// t is a [0,1] value and interpolates a curve between p2 and p3. +void Catmull_Rom_Spline(const Vector &p1, const Vector &p2, const Vector &p3, + const Vector &p4, float t, Vector &output); + +// Interpolate a Catmull-Rom spline. +// Returns the tangent of the point at t of the spline +void Catmull_Rom_Spline_Tangent(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +// area under the curve [0..t] +void Catmull_Rom_Spline_Integral(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +// area under the curve [0..1] +void Catmull_Rom_Spline_Integral(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, + Vector &output); + +// Interpolate a Catmull-Rom spline. +// Normalize p2->p1 and p3->p4 to be the same length as p2->p3 +void Catmull_Rom_Spline_Normalize(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +// area under the curve [0..t] +// Normalize p2->p1 and p3->p4 to be the same length as p2->p3 +void Catmull_Rom_Spline_Integral_Normalize(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, + float t, Vector &output); + +// Interpolate a Catmull-Rom spline. +// Normalize p2.x->p1.x and p3.x->p4.x to be the same length as p2.x->p3.x +void Catmull_Rom_Spline_NormalizeX(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +// area under the curve [0..t] +void Catmull_Rom_Spline_NormalizeX(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +// Interpolate a Hermite spline. +// t is a [0,1] value and interpolates a curve between p1 and p2 with the deltas +// d1 and d2. +void Hermite_Spline(const Vector &p1, const Vector &p2, const Vector &d1, + const Vector &d2, float t, Vector &output); + +float Hermite_Spline(float p1, float p2, float d1, float d2, float t); + +// t is a [0,1] value and interpolates a curve between p1 and p2 with the slopes +// p0->p1 and p1->p2 +void Hermite_Spline(const Vector &p0, const Vector &p1, const Vector &p2, + float t, Vector &output); + +float Hermite_Spline(float p0, float p1, float p2, float t); + +void Hermite_SplineBasis(float t, float basis[]); + +void Hermite_Spline(const Quaternion &q0, const Quaternion &q1, + const Quaternion &q2, float t, Quaternion &output); + +// See https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline +// +// Tension: -1 = Round -> 1 = Tight +// Bias: -1 = Pre-shoot (bias left) -> 1 = Post-shoot (bias right) +// Continuity: -1 = Box corners -> 1 = Inverted corners +// +// If T=B=C=0 it's the same matrix as Catmull-Rom. +// If T=1 & B=C=0 it's the same as Cubic. +// If T=B=0 & C=-1 it's just linear interpolation +// +// See +// http://news.povray.org/povray.binaries.tutorials/attachment/%3CXns91B880592482seed7@povray.org%3E/Splines.bas.txt +// for example code and descriptions of various spline types... +// +void Kochanek_Bartels_Spline(float tension, float bias, float continuity, + const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +void Kochanek_Bartels_Spline_NormalizeX(float tension, float bias, + float continuity, const Vector &p1, + const Vector &p2, const Vector &p3, + const Vector &p4, float t, + Vector &output); + +// See link at Kochanek_Bartels_Spline for info on the basis matrix used +void Cubic_Spline(const Vector &p1, const Vector &p2, const Vector &p3, + const Vector &p4, float t, Vector &output); + +void Cubic_Spline_NormalizeX(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +// See link at Kochanek_Bartels_Spline for info on the basis matrix used +void BSpline(const Vector &p1, const Vector &p2, const Vector &p3, + const Vector &p4, float t, Vector &output); + +void BSpline_NormalizeX(const Vector &p1, const Vector &p2, const Vector &p3, + const Vector &p4, float t, Vector &output); + +// See link at Kochanek_Bartels_Spline for info on the basis matrix used +void Parabolic_Spline(const Vector &p1, const Vector &p2, const Vector &p3, + const Vector &p4, float t, Vector &output); + +void Parabolic_Spline_NormalizeX(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, float t, + Vector &output); + +// Evaluate the cubic Bernstein basis for the input parametric coordinate. +// Output is the coefficient for that basis polynomial. +float CubicBasis0(float t); +float CubicBasis1(float t); +float CubicBasis2(float t); +float CubicBasis3(float t); + +// quintic interpolating polynomial from Perlin. +// 0->0, 1->1, smooth-in between with smooth tangents +FORCEINLINE float QuinticInterpolatingPolynomial(float t) { + // 6t^5-15t^4+10t^3 + return t * t * t * (t * (t * 6.0F - 15.0F) + 10.0F); +} + +// given a table of sorted tabulated positions, return the two indices and +// blendfactor to linear interpolate. Does a search. Can be used to find the +// blend value to interpolate between keyframes. +void GetInterpolationData(float const *pKnotPositions, float const *pKnotValues, + int nNumValuesinList, int nInterpolationRange, + float flPositionToInterpolateAt, bool bWrap, + float *pValueA, float *pValueB, + float *pInterpolationValue); +float RangeCompressor(float flValue, float flMin, float flMax, float flBase); + +// Get the minimum distance from vOrigin to the bounding box defined by +// [mins,maxs] using voronoi regions. 0 is returned if the origin is inside the +// box. +float CalcSqrDistanceToAABB(const Vector &mins, const Vector &maxs, + const Vector &point); +void CalcClosestPointOnAABB(const Vector &mins, const Vector &maxs, + const Vector &point, Vector &closestOut); +void CalcSqrDistAndClosestPointOnAABB(const Vector &mins, const Vector &maxs, + const Vector &point, Vector &closestOut, + float &distSqrOut); + +inline float CalcDistanceToAABB(const Vector &mins, const Vector &maxs, + const Vector &point) { + float flDistSqr = CalcSqrDistanceToAABB(mins, maxs, point); + return sqrt(flDistSqr); +} + +// Get the closest point from P to the (infinite) line through vLineA and vLineB +// and calculate the shortest distance from P to the line. If you pass in a +// value for t, it will tell you the t for (A + (B-A)t) to get the closest +// point. If the closest point lies on the segment between A and B, then 0 <= t +// <= 1. +void CalcClosestPointOnLine(const Vector &P, const Vector &vLineA, + const Vector &vLineB, Vector &vClosest, + float *t = 0); +float CalcDistanceToLine(const Vector &P, const Vector &vLineA, + const Vector &vLineB, float *t = 0); +float CalcDistanceSqrToLine(const Vector &P, const Vector &vLineA, + const Vector &vLineB, float *t = 0); + +// The same three functions as above, except now the line is closed between A +// and B. +void CalcClosestPointOnLineSegment(const Vector &P, const Vector &vLineA, + const Vector &vLineB, Vector &vClosest, + float *t = 0); +float CalcDistanceToLineSegment(const Vector &P, const Vector &vLineA, + const Vector &vLineB, float *t = 0); +float CalcDistanceSqrToLineSegment(const Vector &P, const Vector &vLineA, + const Vector &vLineB, float *t = 0); + +// A function to compute the closes line segment connnection two lines (or false +// if the lines are parallel, etc.) +bool CalcLineToLineIntersectionSegment(const Vector &p1, const Vector &p2, + const Vector &p3, const Vector &p4, + Vector *s1, Vector *s2, float *t1, + float *t2); + +// The above functions in 2D +void CalcClosestPointOnLine2D(Vector2D const &P, Vector2D const &vLineA, + Vector2D const &vLineB, Vector2D &vClosest, + float *t = 0); +float CalcDistanceToLine2D(Vector2D const &P, Vector2D const &vLineA, + Vector2D const &vLineB, float *t = 0); +float CalcDistanceSqrToLine2D(Vector2D const &P, Vector2D const &vLineA, + Vector2D const &vLineB, float *t = 0); +void CalcClosestPointOnLineSegment2D(Vector2D const &P, Vector2D const &vLineA, + Vector2D const &vLineB, Vector2D &vClosest, + float *t = 0); +float CalcDistanceToLineSegment2D(Vector2D const &P, Vector2D const &vLineA, + Vector2D const &vLineB, float *t = 0); +float CalcDistanceSqrToLineSegment2D(Vector2D const &P, Vector2D const &vLineA, + Vector2D const &vLineB, float *t = 0); + +// Init the mathlib +void MathLib_Init(float gamma = 2.2f, float texGamma = 2.2f, + float brightness = 0.0f, int overbright = 2.0f, + bool bAllow3DNow = true, bool bAllowSSE = true, + bool bAllowSSE2 = true, bool bAllowMMX = true); +bool MathLib_MMXEnabled(void); +bool MathLib_SSEEnabled(void); +bool MathLib_SSE2Enabled(void); + +inline float Approach(float target, float value, float speed); +float ApproachAngle(float target, float value, float speed); +float AngleDiff(float destAngle, float srcAngle); +float AngleDistance(float next, float cur); +float AngleNormalize(float angle); + +// ensure that 0 <= angle <= 360 +float AngleNormalizePositive(float angle); + +bool AnglesAreEqual(float a, float b, float tolerance = 0.0f); + +void RotationDeltaAxisAngle(const QAngle &srcAngles, const QAngle &destAngles, + Vector &deltaAxis, float &deltaAngle); +void RotationDelta(const QAngle &srcAngles, const QAngle &destAngles, + QAngle *out); + +// Clips a line segment such that only the portion in the positive half-space +// of the plane remains. If the segment is entirely clipped, the vectors +// are set to vec3_invalid (all components are FLT_MAX). +// +// flBias is added to the dot product with the normal. A positive bias +// results in a more inclusive positive half-space, while a negative bias +// results in a more exclusive positive half-space. +void ClipLineSegmentToPlane(const Vector &vNormal, const Vector &vPlanePoint, + Vector *p1, Vector *p2, float flBias = 0.0f); + +void ComputeTrianglePlane(const Vector &v1, const Vector &v2, const Vector &v3, + Vector &normal, float &intercept); +int PolyFromPlane(Vector *pOutVerts, const Vector &normal, float dist, + float fHalfScale = 9000.0f); +void PolyFromPlane_SIMD(fltx4 *pOutVerts, const fltx4 &plane, + float fHalfScale = 9000.0f); +int ClipPolyToPlane(Vector *inVerts, int vertCount, Vector *outVerts, + const Vector &normal, float dist, + float fOnPlaneEpsilon = 0.1f); +int ClipPolyToPlane_SIMD(fltx4 *pInVerts, int vertCount, fltx4 *pOutVerts, + const fltx4 &plane, float fOnPlaneEpsilon = 0.1f); +int ClipPolyToPlane_Precise(double *inVerts, int vertCount, double *outVerts, + const double *normal, double dist, + double fOnPlaneEpsilon = 0.1); +float TetrahedronVolume(const Vector &p0, const Vector &p1, const Vector &p2, + const Vector &p3); +float TriangleArea(const Vector &p0, const Vector &p1, const Vector &p2); + +// Computes a reasonable tangent space for a triangle +void CalcTriangleTangentSpace(const Vector &p0, const Vector &p1, + const Vector &p2, const Vector2D &t0, + const Vector2D &t1, const Vector2D &t2, + Vector &sVect, Vector &tVect); + +// Transforms a AABB into another space; which will inherently grow the box. +void TransformAABB(const matrix3x4_t &in1, const Vector &vecMinsIn, + const Vector &vecMaxsIn, Vector &vecMinsOut, + Vector &vecMaxsOut); + +// Uses the inverse transform of in1 +void ITransformAABB(const matrix3x4_t &in1, const Vector &vecMinsIn, + const Vector &vecMaxsIn, Vector &vecMinsOut, + Vector &vecMaxsOut); + +// Rotates a AABB into another space; which will inherently grow the box. +// (same as TransformAABB, but doesn't take the translation into account) +void RotateAABB(const matrix3x4_t &in1, const Vector &vecMinsIn, + const Vector &vecMaxsIn, Vector &vecMinsOut, + Vector &vecMaxsOut); + +// Uses the inverse transform of in1 +void IRotateAABB(const matrix3x4_t &in1, const Vector &vecMinsIn, + const Vector &vecMaxsIn, Vector &vecMinsOut, + Vector &vecMaxsOut); + +// Transform a plane +inline void MatrixTransformPlane(const matrix3x4_t &src, + const cplane_t &inPlane, cplane_t &outPlane) { + // What we want to do is the following: + // 1) transform the normal into the new space. + // 2) Determine a point on the old plane given by plane dist * plane normal + // 3) Transform that point into the new space + // 4) Plane dist = DotProduct( new normal, new point ) + + // An optimized version, which works if the plane is orthogonal. + // 1) Transform the normal into the new space + // 2) Realize that transforming the old plane point into the new space + // is given by [ d * n'x + Tx, d * n'y + Ty, d * n'z + Tz ] + // where d = old plane dist, n' = transformed normal, Tn = translational + // component of transform 3) Compute the new plane dist using the dot product + // of the normal result of #2 + + // For a correct result, this should be an inverse-transpose matrix + // but that only matters if there are nonuniform scale or skew factors in this + // matrix. + VectorRotate(inPlane.normal, src, outPlane.normal); + outPlane.dist = inPlane.dist * DotProduct(outPlane.normal, outPlane.normal); + outPlane.dist += outPlane.normal.x * src[0][3] + + outPlane.normal.y * src[1][3] + + outPlane.normal.z * src[2][3]; +} + +inline void MatrixITransformPlane(const matrix3x4_t &src, + const cplane_t &inPlane, cplane_t &outPlane) { + // The trick here is that Tn = translational component of transform, + // but for an inverse transform, Tn = - R^-1 * T + Vector vecTranslation; + MatrixGetColumn(src, 3, vecTranslation); + + Vector vecInvTranslation; + VectorIRotate(vecTranslation, src, vecInvTranslation); + + VectorIRotate(inPlane.normal, src, outPlane.normal); + outPlane.dist = inPlane.dist * DotProduct(outPlane.normal, outPlane.normal); + outPlane.dist -= outPlane.normal.x * vecInvTranslation[0] + + outPlane.normal.y * vecInvTranslation[1] + + outPlane.normal.z * vecInvTranslation[2]; +} + +int CeilPow2(int in); +int FloorPow2(int in); + +FORCEINLINE float *UnpackNormal_HEND3N(const unsigned int *pPackedNormal, + float *pNormal) { + int temp[3]; + temp[0] = ((*pPackedNormal >> 0L) & 0x7ff); + if (temp[0] & 0x400) { + temp[0] = 2048 - temp[0]; + } + temp[1] = ((*pPackedNormal >> 11L) & 0x7ff); + if (temp[1] & 0x400) { + temp[1] = 2048 - temp[1]; + } + temp[2] = ((*pPackedNormal >> 22L) & 0x3ff); + if (temp[2] & 0x200) { + temp[2] = 1024 - temp[2]; + } + pNormal[0] = (float)temp[0] * 1.0f / 1023.0f; + pNormal[1] = (float)temp[1] * 1.0f / 1023.0f; + pNormal[2] = (float)temp[2] * 1.0f / 511.0f; + return pNormal; +} + +FORCEINLINE unsigned int *PackNormal_HEND3N(const float *pNormal, + unsigned int *pPackedNormal) { + int temp[3]; + + temp[0] = Float2Int(pNormal[0] * 1023.0f); + temp[1] = Float2Int(pNormal[1] * 1023.0f); + temp[2] = Float2Int(pNormal[2] * 511.0f); + + // the normal is out of bounds, determine the source and fix + // clamping would be even more of a slowdown here + Assert(temp[0] >= -1023 && temp[0] <= 1023); + Assert(temp[1] >= -1023 && temp[1] <= 1023); + Assert(temp[2] >= -511 && temp[2] <= 511); + + *pPackedNormal = ((temp[2] & 0x3ff) << 22L) | ((temp[1] & 0x7ff) << 11L) | + ((temp[0] & 0x7ff) << 0L); + return pPackedNormal; +} + +FORCEINLINE unsigned int *PackNormal_HEND3N(float nx, float ny, float nz, + unsigned int *pPackedNormal) { + int temp[3]; + + temp[0] = Float2Int(nx * 1023.0f); + temp[1] = Float2Int(ny * 1023.0f); + temp[2] = Float2Int(nz * 511.0f); + + // the normal is out of bounds, determine the source and fix + // clamping would be even more of a slowdown here + Assert(temp[0] >= -1023 && temp[0] <= 1023); + Assert(temp[1] >= -1023 && temp[1] <= 1023); + Assert(temp[2] >= -511 && temp[2] <= 511); + + *pPackedNormal = ((temp[2] & 0x3ff) << 22L) | ((temp[1] & 0x7ff) << 11L) | + ((temp[0] & 0x7ff) << 0L); + return pPackedNormal; +} + +FORCEINLINE float *UnpackNormal_SHORT2(const unsigned int *pPackedNormal, + float *pNormal, + bool bIsTangent = FALSE) { + // Unpacks from Jason's 2-short format (fills in a 4th binormal-sign (+1/-1) + // value, if this is a tangent vector) + + // FIXME: short math is slow on 360 - use ints here instead (bit-twiddle to + // deal w/ the sign bits) + short iX = (*pPackedNormal & 0x0000FFFF); + short iY = (*pPackedNormal & 0xFFFF0000) >> 16; + + float zSign = +1; + if (iX < 0) { + zSign = -1; + iX = -iX; + } + float tSign = +1; + if (iY < 0) { + tSign = -1; + iY = -iY; + } + + pNormal[0] = (iX - 16384.0f) / 16384.0f; + pNormal[1] = (iY - 16384.0f) / 16384.0f; + float mag = (pNormal[0] * pNormal[0] + pNormal[1] * pNormal[1]); + if (mag > 1.0f) { + mag = 1.0f; + } + pNormal[2] = zSign * sqrtf(1.0f - mag); + if (bIsTangent) { + pNormal[3] = tSign; + } + + return pNormal; +} + +FORCEINLINE unsigned int *PackNormal_SHORT2(float nx, float ny, float nz, + unsigned int *pPackedNormal, + float binormalSign = +1.0f) { + // Pack a vector (ASSUMED TO BE NORMALIZED) into Jason's 4-byte (SHORT2) + // format. This simply reconstructs Z from X & Y. It uses the sign bits of the + // X & Y coords to reconstruct the sign of Z and, if this is a tangent vector, + // the sign of the binormal (this is needed because tangent/binormal vectors + // are supposed to follow UV gradients, but shaders reconstruct the binormal + // from the tangent and normal assuming that they form a right-handed basis). + + nx += 1; // [-1,+1] -> [0,2] + ny += 1; + nx *= 16384.0f; // [ 0, 2] -> [0,32768] + ny *= 16384.0f; + + // '0' and '32768' values are invalid encodings + nx = MAX(nx, 1.0f); // Make sure there are no zero values + ny = MAX(ny, 1.0f); + nx = MIN(nx, 32767.0f); // Make sure there are no 32768 values + ny = MIN(ny, 32767.0f); + + if (nz < 0.0f) nx = -nx; // Set the sign bit for z + + ny *= binormalSign; // Set the sign bit for the binormal (use when encoding a + // tangent vector) + + // FIXME: short math is slow on 360 - use ints here instead (bit-twiddle to + // deal w/ the sign bits), also use Float2Int() + short sX = (short)nx; // signed short [1,32767] + short sY = (short)ny; + + // NOTE: The mask is necessary (if sX is negative and cast to an int...) + *pPackedNormal = (sX & 0x0000FFFF) | (sY << 16); + + return pPackedNormal; +} + +FORCEINLINE unsigned int *PackNormal_SHORT2(const float *pNormal, + unsigned int *pPackedNormal, + float binormalSign = +1.0f) { + return PackNormal_SHORT2(pNormal[0], pNormal[1], pNormal[2], pPackedNormal, + binormalSign); +} + +// Unpacks a UBYTE4 normal (for a tangent, the result's fourth component +// receives the binormal 'sign') +FORCEINLINE float *UnpackNormal_UBYTE4(const unsigned int *pPackedNormal, + float *pNormal, + bool bIsTangent = FALSE) { + unsigned char cX, cY; + if (bIsTangent) { + cX = static_cast(*pPackedNormal >> 16); // Unpack Z + cY = static_cast(*pPackedNormal >> 24); // Unpack W + } else { + cX = static_cast(*pPackedNormal >> 0); // Unpack X + cY = static_cast(*pPackedNormal >> 8); // Unpack Y + } + + float x = cX - 128.0f; + float y = cY - 128.0f; + float z; + + float zSignBit = + x < 0 ? 1.0f : 0.0f; // z and t negative bits (like slt asm instruction) + float tSignBit = y < 0 ? 1.0f : 0.0f; + float zSign = -(2 * zSignBit - 1); // z and t signs + float tSign = -(2 * tSignBit - 1); + + x = x * zSign - zSignBit; // 0..127 + y = y * tSign - tSignBit; + x = x - 64; // -64..63 + y = y - 64; + + float xSignBit = + x < 0 ? 1.0f : 0.0f; // x and y negative bits (like slt asm instruction) + float ySignBit = y < 0 ? 1.0f : 0.0f; + float xSign = -(2 * xSignBit - 1); // x and y signs + float ySign = -(2 * ySignBit - 1); + + x = (x * xSign - xSignBit) / 63.0f; // 0..1 range + y = (y * ySign - ySignBit) / 63.0f; + z = 1.0f - x - y; + + float oolen = 1.0f / sqrt(x * x + y * y + z * z); // Normalize and + x *= oolen * xSign; // Recover signs + y *= oolen * ySign; + z *= oolen * zSign; + + pNormal[0] = x; + pNormal[1] = y; + pNormal[2] = z; + if (bIsTangent) { + pNormal[3] = tSign; + } + + return pNormal; +} + +////////////////////////////////////////////////////////////////////////////// +// See: +// http://www.oroboro.com/rafael/docserv.php/index/programming/article/unitv2 +// +// UBYTE4 encoding, using per-octant projection onto x+y+z=1 +// Assume input vector is already unit length +// +// binormalSign specifies 'sign' of binormal, stored in t sign bit of tangent +// (lets the shader know whether norm/tan/bin form a right-handed basis) +// +// bIsTangent is used to specify which WORD of the output to store the data +// The expected usage is to call once with the normal and once with +// the tangent and binormal sign flag, bitwise OR'ing the returned DWORDs +FORCEINLINE unsigned int *PackNormal_UBYTE4(float nx, float ny, float nz, + unsigned int *pPackedNormal, + bool bIsTangent = false, + float binormalSign = +1.0f) { + float xSign = nx < 0.0f ? -1.0f : 1.0f; // -1 or 1 sign + float ySign = ny < 0.0f ? -1.0f : 1.0f; + float zSign = nz < 0.0f ? -1.0f : 1.0f; + float tSign = binormalSign; + Assert((binormalSign == +1.0f) || (binormalSign == -1.0f)); + + float xSignBit = 0.5f * (1 - xSign); // [-1,+1] -> [1,0] + float ySignBit = + 0.5f * (1 - ySign); // 1 is negative bit (like slt instruction) + float zSignBit = 0.5f * (1 - zSign); + float tSignBit = 0.5f * (1 - binormalSign); + + float absX = xSign * nx; // 0..1 range (abs) + float absY = ySign * ny; + float absZ = zSign * nz; + + float xbits = absX / (absX + absY + absZ); // Project onto x+y+z=1 plane + float ybits = absY / (absX + absY + absZ); + + xbits *= 63; // 0..63 + ybits *= 63; + + xbits = xbits * xSign - xSignBit; // -64..63 range + ybits = ybits * ySign - ySignBit; + xbits += 64.0f; // 0..127 range + ybits += 64.0f; + + xbits = xbits * zSign - zSignBit; // Negate based on z and t + ybits = ybits * tSign - tSignBit; // -128..127 range + + xbits += 128.0f; // 0..255 range + ybits += 128.0f; + + unsigned char cX = (unsigned char)xbits; + unsigned char cY = (unsigned char)ybits; + + if (!bIsTangent) + *pPackedNormal = (cX << 0) | (cY << 8); // xy for normal + else + *pPackedNormal = (cX << 16) | (cY << 24); // zw for tangent + + return pPackedNormal; +} + +FORCEINLINE unsigned int *PackNormal_UBYTE4(const float *pNormal, + unsigned int *pPackedNormal, + bool bIsTangent = false, + float binormalSign = +1.0f) { + return PackNormal_UBYTE4(pNormal[0], pNormal[1], pNormal[2], pPackedNormal, + bIsTangent, binormalSign); +} + +FORCEINLINE void RGB2YUV(int &nR, int &nG, int &nB, float &fY, float &fU, + float &fV, bool bApplySaturationCurve) { + // YUV conversion: + // |Y| | 0.299f 0.587f 0.114f | |R| + // |U| = | -0.14713f -0.28886f 0.436f | x |G| + // |V| | 0.615f -0.51499f -0.10001f | |B| + // + // The coefficients in the first row sum to one, whereas the 2nd and 3rd rows + // each sum to zero (UV (0,0) means greyscale). Ranges are Y [0,1], U + // [-0.436,+0.436] and V [-0.615,+0.615]. We scale and offset to [0,1] and + // allow the caller to round as they please. + + fY = (0.29900f * nR + 0.58700f * nG + 0.11400f * nB) / 255; + fU = (-0.14713f * nR + -0.28886f * nG + 0.43600f * nB) * (0.5f / 0.436f) / + 255 + + 0.5f; + fV = (0.61500f * nR + -0.51499f * nG + -0.10001f * nB) * (0.5f / 0.615f) / + 255 + + 0.5f; + + if (bApplySaturationCurve) { + // Apply a curve to saturation, and snap-to-grey for low saturations + const float SNAP_TO_GREY = + 0; // 0.0125f; Disabled, saturation curve seems sufficient + float dX, dY, sat, scale; + dX = 2 * (fU - 0.5f); + dY = 2 * (fV - 0.5f); + sat = sqrtf(dX * dX + dY * dY); + sat = clamp((sat * (1 + SNAP_TO_GREY) - SNAP_TO_GREY), 0.0F, 1.0F); + scale = (sat == 0) ? 0 : MIN((sqrtf(sat) / sat), 4.0f); + fU = 0.5f + scale * (fU - 0.5f); + fV = 0.5f + scale * (fV - 0.5f); + } +} + +#ifdef _X360 +// Used for direct CPU access to VB data on 360 (used by shaderapi, studiorender +// and engine) +struct VBCPU_AccessInfo_t { + // Points to the GPU data pointer in the CVertexBuffer struct (VB data can be + // relocated during level transitions) + const byte **ppBaseAddress; + // pBaseAddress should be computed from ppBaseAddress immediately before use + const byte *pBaseAddress; + int nStride; + int nPositionOffset; + int nTexCoord0_Offset; + int nNormalOffset; + int nBoneIndexOffset; + int nBoneWeightOffset; + int nCompressionType; + // TODO: if needed, add colour and tangents +}; +#endif + +// Convert RGB to HSV +void RGBtoHSV(const Vector &rgb, Vector &hsv); + +// Convert HSV to RGB +void HSVtoRGB(const Vector &hsv, Vector &rgb); + +// Fast version of pow and log +#ifndef _PS3 // these actually aren't fast (or correct) on the PS3 +float FastLog2(float i); // log2( i ) +float FastPow2(float i); // 2^i +float FastPow(float a, float b); // a^b +float FastPow10(float i); // 10^i +#else +inline float FastLog2(float i) { return logbf(i); } // log2( i ) +inline float FastPow2(float i) { return exp2f(i); } // 2^i +inline float FastPow(float a, float b) { return powf(a, b); } // a^b +#define LOGBASE2OF10 3.3219280948873623478703194294893901758648313930 +inline float FastPow10(float i) { + return exp2f(i * LOGBASE2OF10); +} // 10^i, transform to base two, so log2(10^y) = y log2(10) . log2(10) + // = 3.3219280948873623478703194294893901758648313930 +#endif + +// For testing float equality + +inline bool CloseEnough(float a, float b, float epsilon = EQUAL_EPSILON) { + return fabs(a - b) <= epsilon; +} + +inline bool CloseEnough(const Vector &a, const Vector &b, + float epsilon = EQUAL_EPSILON) { + return fabs(a.x - b.x) <= epsilon && fabs(a.y - b.y) <= epsilon && + fabs(a.z - b.z) <= epsilon; +} + +// Fast compare +// maxUlps is the maximum error in terms of Units in the Last Place. This +// specifies how big an error we are willing to accept in terms of the value +// of the least significant digit of the floating point number’s +// representation. maxUlps can also be interpreted in terms of how many +// representable floats we are willing to accept between A and B. +// This function will allow maxUlps-1 floats between A and B. +bool AlmostEqual(float a, float b, int maxUlps = 10); + +inline bool AlmostEqual(const Vector &a, const Vector &b, int maxUlps = 10) { + return AlmostEqual(a.x, b.x, maxUlps) && AlmostEqual(a.y, b.y, maxUlps) && + AlmostEqual(a.z, b.z, maxUlps); +} + +inline float Approach(float target, float value, float speed) { + float delta = target - value; + +#if defined(_X360) || defined(_PS3) // use conditional move for speed on 360 + + return fsel(delta - speed, // delta >= speed ? + value + speed, // if delta == speed, then value + speed == value + // + delta == target + fsel((-speed) - delta, // delta <= -speed + value - speed, + target)); // delta < speed && delta > -speed + +#else + + if (delta > speed) + value += speed; + else if (delta < -speed) + value -= speed; + else + value = target; + + return value; + +#endif +} + +// on PPC we can do this truncate without converting to int +#if defined(_X360) || defined(_PS3) +inline double TruncateFloatToIntAsFloat(double flVal) { +#if defined(_X360) + double flIntFormat = __fctiwz(flVal); + return __fcfid(flIntFormat); +#elif defined(_PS3) + double flIntFormat = __builtin_fctiwz(flVal); + return __builtin_fcfid(flIntFormat); +#endif +} +#endif + +inline double SubtractIntegerPart(double flVal) { +#if defined(_X360) || defined(_PS3) + return flVal - TruncateFloatToIntAsFloat(flVal); +#else + return flVal - int(flVal); +#endif +} +#endif // VPC_MATHLIB_MATH_LIB_H_ diff --git a/public/mathlib/vector.h b/public/mathlib/vector.h new file mode 100644 index 0000000..c17971a --- /dev/null +++ b/public/mathlib/vector.h @@ -0,0 +1,2329 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_MATHLIB_VECTOR_H_ +#define VPC_MATHLIB_VECTOR_H_ + +#include +#include + +// For vec_t, put this somewhere else? +#include "tier0/basetypes.h" + +#if defined(_PS3) +//#include +#include +#include "platform.h" +#include "mathlib/math_pfns.h" +#endif + +#ifndef PLATFORM_PPC // we want our linux with xmm support +// For MMX intrinsics +#include +#endif + +#ifndef ALIGN16_POST +#define ALIGN16_POST +#endif + +#include "tier0/dbg.h" +#include "tier0/platform.h" +#include "tier0/threadtools.h" +#include "mathlib/vector2d.h" +#include "mathlib/math_pfns.h" +#include "tier0/memalloc.h" +#include "vstdlib/random.h" +// Uncomment this to add extra Asserts to check for NANs, uninitialized vecs, +// etc. +//#define VECTOR_PARANOIA 1 + +// Uncomment this to make sure we don't do anything slow with our vectors +//#define VECTOR_NO_SLOW_OPERATIONS 1 + +// Used to make certain code easier to read. +#define X_INDEX 0 +#define Y_INDEX 1 +#define Z_INDEX 2 + +#ifdef VECTOR_PARANOIA +#define CHECK_VALID(_v) Assert((_v).IsValid()) +#else +#ifdef GNUC +#define CHECK_VALID(_v) +#else +#define CHECK_VALID(_v) 0 +#endif +#endif + +#define VecToString(v) \ + (static_cast(CFmtStr( \ + "(%f, %f, %f)", (v).x, (v).y, \ + (v).z))) // ** Note: this generates a temporary, don't hold reference! + +class VectorByValue; + +//========================================================= +// 3D Vector +//========================================================= +class Vector { + public: + // Members + vec_t x, y, z; + + // Construction/destruction: + Vector(void); + Vector(vec_t X, vec_t Y, vec_t Z); + + // Initialization + void Init(vec_t ix = 0.0f, vec_t iy = 0.0f, vec_t iz = 0.0f); + // TODO (Ilya): Should there be an init that takes a single float for + // consistency? + + // Got any nasty NAN's? + bool IsValid() const; + void Invalidate(); + + // array access... + vec_t operator[](int i) const; + vec_t& operator[](int i); + + // Base address... + vec_t* Base(); + vec_t const* Base() const; + + // Cast to Vector2D... + Vector2D& AsVector2D(); + const Vector2D& AsVector2D() const; + + // Initialization methods + void Random(vec_t minVal, vec_t maxVal); + inline void Zero(); ///< zero out a vector + + // equality + bool operator==(const Vector& v) const; + bool operator!=(const Vector& v) const; + + // arithmetic operations + FORCEINLINE Vector& operator+=(const Vector& v); + FORCEINLINE Vector& operator-=(const Vector& v); + FORCEINLINE Vector& operator*=(const Vector& v); + FORCEINLINE Vector& operator*=(float s); + FORCEINLINE Vector& operator/=(const Vector& v); + FORCEINLINE Vector& operator/=(float s); + FORCEINLINE Vector& operator+=(float fl); ///< broadcast add + FORCEINLINE Vector& operator-=(float fl); ///< broadcast sub + + // negate the vector components + void Negate(); + + // Get the vector's magnitude. + inline vec_t Length() const; + + // Get the vector's magnitude squared. + FORCEINLINE vec_t LengthSqr(void) const { + CHECK_VALID(*this); + return (x * x + y * y + z * z); + } + + // Get one over the vector's length + // via fast hardware approximation + inline vec_t LengthRecipFast(void) const { + return FastRSqrtFast(LengthSqr()); + } + + // return true if this vector is (0,0,0) within tolerance + bool IsZero(float tolerance = 0.01f) const { + return (x > -tolerance && x < tolerance && y > -tolerance && + y < tolerance && z > -tolerance && z < tolerance); + } + + // return true if this vector is exactly (0,0,0) -- only fast if vector is + // coming from memory, not registers + inline bool IsZeroFast() const RESTRICT { + static_assert(sizeof(vec_t) == sizeof(int)); + return (*(const int*)(&x) == 0 && *(const int*)(&y) == 0 && + *(const int*)(&z) == 0); + } + + vec_t NormalizeInPlace(); + Vector Normalized() const; + bool IsLengthGreaterThan(float val) const; + bool IsLengthLessThan(float val) const; + + // check if a vector is within the box defined by two other vectors + FORCEINLINE bool WithinAABox(Vector const& boxmin, Vector const& boxmax); + + // Get the distance from this vector to the other one. + vec_t DistTo(const Vector& vOther) const; + + // Get the distance from this vector to the other one squared. + // NJS: note, VC wasn't inlining it correctly in several deeply nested inlines + // due to being an 'out of line' inline. may be able to tidy this up after + // switching to VC7 + FORCEINLINE vec_t DistToSqr(const Vector& vOther) const { + Vector delta; + + delta.x = x - vOther.x; + delta.y = y - vOther.y; + delta.z = z - vOther.z; + + return delta.LengthSqr(); + } + + // Copy + void CopyToArray(float* rgfl) const; + + // Multiply, add, and assign to this (ie: *this = a + b * scalar). This + // is about 12% faster than the actual vector equation (because it's done + // per-component rather than per-vector). + void MulAdd(const Vector& a, const Vector& b, float scalar); + + // Dot product. + vec_t Dot(const Vector& vOther) const; + + // assignment + Vector& operator=(const Vector& vOther); + + // returns 0, 1, 2 corresponding to the component with the largest absolute + // value + inline int LargestComponent() const; + + // 2d + vec_t Length2D(void) const; + vec_t Length2DSqr(void) const; + + /// get the component of this vector parallel to some other given vector + inline Vector ProjectOnto(const Vector& onto); + + operator VectorByValue&() { return *((VectorByValue*)(this)); } + operator const VectorByValue&() const { + return *((const VectorByValue*)(this)); + } + +#ifndef VECTOR_NO_SLOW_OPERATIONS + // copy constructors + // Vector(const Vector &vOther); + + // arithmetic operations + Vector operator-(void) const; + + Vector operator+(const Vector& v) const; + Vector operator-(const Vector& v) const; + Vector operator*(const Vector& v) const; + Vector operator/(const Vector& v) const; + Vector operator*(float fl) const; + Vector operator/(float fl) const; + + // Cross product between two vectors. + Vector Cross(const Vector& vOther) const; + + // Returns a vector with the min or max in X, Y, and Z. + Vector Min(const Vector& vOther) const; + Vector Max(const Vector& vOther) const; + +#else + + private: + // No copy constructors allowed if we're in optimal mode + Vector(const Vector& vOther); +#endif +}; + +#define USE_M64S defined(PLATFORM_WINDOWS_PC) + +//========================================================= +// 4D Short Vector (aligned on 8-byte boundary) +//========================================================= +class ALIGN8 ShortVector { + public: + short x, y, z, w; + + // Initialization + void Init(short ix = 0, short iy = 0, short iz = 0, short iw = 0); + +#if USE_M64S + __m64& AsM64() { return *(__m64*)&x; } + const __m64& AsM64() const { return *(const __m64*)&x; } +#endif + + // Setter + void Set(const ShortVector& vOther); + void Set(const short ix, const short iy, const short iz, const short iw); + + // array access... + short operator[](int i) const; + short& operator[](int i); + + // Base address... + short* Base(); + short const* Base() const; + + // equality + bool operator==(const ShortVector& v) const; + bool operator!=(const ShortVector& v) const; + + // Arithmetic operations + FORCEINLINE ShortVector& operator+=(const ShortVector& v); + FORCEINLINE ShortVector& operator-=(const ShortVector& v); + FORCEINLINE ShortVector& operator*=(const ShortVector& v); + FORCEINLINE ShortVector& operator*=(float s); + FORCEINLINE ShortVector& operator/=(const ShortVector& v); + FORCEINLINE ShortVector& operator/=(float s); + FORCEINLINE ShortVector operator*(float fl) const; + + private: + // No copy constructors allowed if we're in optimal mode + // ShortVector(ShortVector const& vOther); + + // No assignment operators either... + // ShortVector& operator=( ShortVector const& src ); + +} ALIGN8_POST; + +//========================================================= +// 4D Integer Vector +//========================================================= +class IntVector4D { + public: + int x, y, z, w; + + // Initialization + void Init(int ix = 0, int iy = 0, int iz = 0, int iw = 0); + +#if USE_M64S + __m64& AsM64() { return *(__m64*)&x; } + const __m64& AsM64() const { return *(const __m64*)&x; } +#endif + + // Setter + void Set(const IntVector4D& vOther); + void Set(const int ix, const int iy, const int iz, const int iw); + + // array access... + int operator[](int i) const; + int& operator[](int i); + + // Base address... + int* Base(); + int const* Base() const; + + // equality + bool operator==(const IntVector4D& v) const; + bool operator!=(const IntVector4D& v) const; + + // Arithmetic operations + FORCEINLINE IntVector4D& operator+=(const IntVector4D& v); + FORCEINLINE IntVector4D& operator-=(const IntVector4D& v); + FORCEINLINE IntVector4D& operator*=(const IntVector4D& v); + FORCEINLINE IntVector4D& operator*=(float s); + FORCEINLINE IntVector4D& operator/=(const IntVector4D& v); + FORCEINLINE IntVector4D& operator/=(float s); + FORCEINLINE IntVector4D operator*(float fl) const; + + private: + // No copy constructors allowed if we're in optimal mode + // IntVector4D(IntVector4D const& vOther); + + // No assignment operators either... + // IntVector4D& operator=( IntVector4D const& src ); +}; + +//----------------------------------------------------------------------------- +// Allows us to specifically pass the vector by value when we need to +//----------------------------------------------------------------------------- +class VectorByValue : public Vector { + public: + // Construction/destruction: + VectorByValue(void) : Vector() {} + VectorByValue(vec_t X, vec_t Y, vec_t Z) : Vector(X, Y, Z) {} + VectorByValue(const VectorByValue& vOther) { *this = vOther; } +}; + +//----------------------------------------------------------------------------- +// Utility to simplify table construction. No constructor means can use +// traditional C-style initialization +//----------------------------------------------------------------------------- +class TableVector { + public: + vec_t x, y, z; + + operator Vector&() { return *((Vector*)(this)); } + operator const Vector&() const { return *((const Vector*)(this)); } + + // array access... + inline vec_t& operator[](int i) { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; + } + + inline vec_t operator[](int i) const { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; + } +}; + +//----------------------------------------------------------------------------- +// Here's where we add all those lovely SSE optimized routines +//----------------------------------------------------------------------------- + +class ALIGN16 VectorAligned : public Vector { + public: + inline VectorAligned() = default; + inline VectorAligned(vec_t X, vec_t Y, vec_t Z) { Init(X, Y, Z); } + +#ifdef VECTOR_NO_SLOW_OPERATIONS + + private: + // No copy constructors allowed if we're in optimal mode + VectorAligned(const VectorAligned& vOther); + VectorAligned(const Vector& vOther); + +#else + public: + explicit VectorAligned(const Vector& vOther) { + Init(vOther.x, vOther.y, vOther.z); + } + + VectorAligned& operator=(const Vector& vOther) { + Init(vOther.x, vOther.y, vOther.z); + return *this; + } + + VectorAligned& operator=(const VectorAligned& vOther) { + // we know we're aligned, so use simd + // we can't use the convenient abstract interface coz it gets declared later +#ifdef _X360 + XMStoreVector4A(Base(), XMLoadVector4A(vOther.Base())); +#elif _WIN32 + _mm_store_ps(Base(), _mm_load_ps(vOther.Base())); +#else + Init(vOther.x, vOther.y, vOther.z); +#endif + return *this; + } + +#endif + float w; // this space is used anyway + +#if !defined(NO_MALLOC_OVERRIDE) + void* operator new[](size_t nSize) { + return MemAlloc_AllocAligned(nSize, 16); + } + + void* operator new[](size_t nSize, const char* pFileName, int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + + void* operator new[](size_t nSize, int /*nBlockUse*/, const char* pFileName, + int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + + void operator delete[](void* p) { MemAlloc_FreeAligned(p); } + + void operator delete[](void* p, const char* pFileName, int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } + + void operator delete[](void* p, int /*nBlockUse*/, const char* pFileName, + int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } + + // please don't allocate a single quaternion... + void* operator new(size_t nSize) { return MemAlloc_AllocAligned(nSize, 16); } + void* operator new(size_t nSize, const char* pFileName, int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + void* operator new(size_t nSize, int /*nBlockUse*/, const char* pFileName, + int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + void operator delete(void* p) { MemAlloc_FreeAligned(p); } + + void operator delete(void* p, const char* pFileName, int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } + + void operator delete(void* p, int /*nBlockUse*/, const char* pFileName, + int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } +#endif +} ALIGN16_POST; + +//----------------------------------------------------------------------------- +// Vector related operations +//----------------------------------------------------------------------------- + +// Vector clear +FORCEINLINE void VectorClear(Vector& a); + +// Copy +FORCEINLINE void VectorCopy(const Vector& src, Vector& dst); + +// Vector arithmetic +FORCEINLINE void VectorAdd(const Vector& a, const Vector& b, Vector& result); +FORCEINLINE void VectorSubtract(const Vector& a, const Vector& b, + Vector& result); +FORCEINLINE void VectorMultiply(const Vector& a, vec_t b, Vector& result); +FORCEINLINE void VectorMultiply(const Vector& a, const Vector& b, + Vector& result); +FORCEINLINE void VectorDivide(const Vector& a, vec_t b, Vector& result); +FORCEINLINE void VectorDivide(const Vector& a, const Vector& b, Vector& result); +inline void VectorScale(const Vector& in, vec_t scale, Vector& result); +void VectorMA(const Vector& start, float scale, const Vector& direction, + Vector& dest); + +// Vector equality with tolerance +bool VectorsAreEqual(const Vector& src1, const Vector& src2, + float tolerance = 0.0f); + +#define VectorExpand(v) (v).x, (v).y, (v).z + +// Normalization +// FIXME: Can't use quite yet +// vec_t VectorNormalize( Vector& v ); + +// Length +inline vec_t VectorLength(const Vector& v); + +// Dot Product +FORCEINLINE vec_t DotProduct(const Vector& a, const Vector& b); + +// Cross product +void CrossProduct(const Vector& a, const Vector& b, Vector& result); + +// Store the min or max of each of x, y, and z into the result. +void VectorMin(const Vector& a, const Vector& b, Vector& result); +void VectorMax(const Vector& a, const Vector& b, Vector& result); + +// Linearly interpolate between two vectors +void VectorLerp(const Vector& src1, const Vector& src2, vec_t t, Vector& dest); +Vector VectorLerp(const Vector& src1, const Vector& src2, vec_t t); + +FORCEINLINE Vector ReplicateToVector(float x) { return Vector(x, x, x); } + +FORCEINLINE bool PointWithinViewAngle(Vector const& vecSrcPosition, + Vector const& vecTargetPosition, + Vector const& vecLookDirection, + float flCosHalfFOV) { + Vector vecDelta = vecTargetPosition - vecSrcPosition; + float cosDiff = DotProduct(vecLookDirection, vecDelta); + + if (flCosHalfFOV <= 0) // >180 + { + // signs are different, answer is implicit + if (cosDiff > 0) return true; + + // a/sqrt(b) > c == a^2 < b * c ^2 + // IFF left and right sides are <= 0 + float flLen2 = vecDelta.LengthSqr(); + return (cosDiff * cosDiff <= flLen2 * flCosHalfFOV * flCosHalfFOV); + } else // flCosHalfFOV > 0 + { + // signs are different, answer is implicit + if (cosDiff < 0) return false; + + // a/sqrt(b) > c == a^2 > b * c ^2 + // IFF left and right sides are >= 0 + float flLen2 = vecDelta.LengthSqr(); + return (cosDiff * cosDiff >= flLen2 * flCosHalfFOV * flCosHalfFOV); + } +} + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +// Cross product +Vector CrossProduct(const Vector& a, const Vector& b); + +// Random vector creation +Vector RandomVector(vec_t minVal, vec_t maxVal); + +#endif + +float RandomVectorInUnitSphere(Vector* pVector); +float RandomVectorInUnitCircle(Vector2D* pVector); + +//----------------------------------------------------------------------------- +// +// Inlined Vector methods +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// constructors +//----------------------------------------------------------------------------- +inline Vector::Vector() { +#ifdef _DEBUG +#ifdef VECTOR_PARANOIA + // Initialize to NAN to catch errors + x = y = z = VEC_T_NAN; +#endif +#endif +} + +inline Vector::Vector(vec_t X, vec_t Y, vec_t Z) { + x = X; + y = Y; + z = Z; + CHECK_VALID(*this); +} + +// inline Vector::Vector(const float *pFloat) +//{ +// Assert( pFloat ); +// x = pFloat[0]; y = pFloat[1]; z = pFloat[2]; +// CHECK_VALID(*this); +//} + +#if 0 +//----------------------------------------------------------------------------- +// copy constructor +//----------------------------------------------------------------------------- + +inline Vector::Vector(const Vector &vOther) +{ + CHECK_VALID(vOther); + x = vOther.x; y = vOther.y; z = vOther.z; +} +#endif + +//----------------------------------------------------------------------------- +// initialization +//----------------------------------------------------------------------------- + +inline void Vector::Init(vec_t ix, vec_t iy, vec_t iz) { + x = ix; + y = iy; + z = iz; + CHECK_VALID(*this); +} + +inline void Vector::Random(vec_t minVal, vec_t maxVal) { + x = RandomFloat(minVal, maxVal); + y = RandomFloat(minVal, maxVal); + z = RandomFloat(minVal, maxVal); + CHECK_VALID(*this); +} + +// This should really be a single opcode on the PowerPC (move r0 onto the vec +// reg) +inline void Vector::Zero() { x = y = z = 0.0f; } + +inline void VectorClear(Vector& a) { a.x = a.y = a.z = 0.0f; } + +//----------------------------------------------------------------------------- +// assignment +//----------------------------------------------------------------------------- + +inline Vector& Vector::operator=(const Vector& vOther) { + CHECK_VALID(vOther); + x = vOther.x; + y = vOther.y; + z = vOther.z; + return *this; +} + +//----------------------------------------------------------------------------- +// Array access +//----------------------------------------------------------------------------- +inline vec_t& Vector::operator[](int i) { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; +} + +inline vec_t Vector::operator[](int i) const { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; +} + +//----------------------------------------------------------------------------- +// Base address... +//----------------------------------------------------------------------------- +inline vec_t* Vector::Base() { return (vec_t*)this; } + +inline vec_t const* Vector::Base() const { return (vec_t const*)this; } + +//----------------------------------------------------------------------------- +// Cast to Vector2D... +//----------------------------------------------------------------------------- + +inline Vector2D& Vector::AsVector2D() { return *(Vector2D*)this; } + +inline const Vector2D& Vector::AsVector2D() const { + return *(const Vector2D*)this; +} + +//----------------------------------------------------------------------------- +// IsValid? +//----------------------------------------------------------------------------- + +inline bool Vector::IsValid() const { + return IsFinite(x) && IsFinite(y) && IsFinite(z); +} + +//----------------------------------------------------------------------------- +// Invalidate +//----------------------------------------------------------------------------- + +inline void Vector::Invalidate() { + //#ifdef _DEBUG + //#ifdef VECTOR_PARANOIA + x = y = z = VEC_T_NAN; + //#endif + //#endif +} + +//----------------------------------------------------------------------------- +// comparison +//----------------------------------------------------------------------------- + +inline bool Vector::operator==(const Vector& src) const { + CHECK_VALID(src); + CHECK_VALID(*this); + return (src.x == x) && (src.y == y) && (src.z == z); +} + +inline bool Vector::operator!=(const Vector& src) const { + CHECK_VALID(src); + CHECK_VALID(*this); + return (src.x != x) || (src.y != y) || (src.z != z); +} + +//----------------------------------------------------------------------------- +// Copy +//----------------------------------------------------------------------------- + +FORCEINLINE void VectorCopy(const Vector& src, Vector& dst) { + CHECK_VALID(src); + dst.x = src.x; + dst.y = src.y; + dst.z = src.z; +} + +inline void Vector::CopyToArray(float* rgfl) const { + Assert(rgfl); + CHECK_VALID(*this); + rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; +} + +//----------------------------------------------------------------------------- +// standard math operations +//----------------------------------------------------------------------------- +// #pragma message("TODO: these should be SSE") + +inline void Vector::Negate() { + CHECK_VALID(*this); + x = -x; + y = -y; + z = -z; +} + +FORCEINLINE Vector& Vector::operator+=(const Vector& v) { + CHECK_VALID(*this); + CHECK_VALID(v); + x += v.x; + y += v.y; + z += v.z; + return *this; +} + +FORCEINLINE Vector& Vector::operator-=(const Vector& v) { + CHECK_VALID(*this); + CHECK_VALID(v); + x -= v.x; + y -= v.y; + z -= v.z; + return *this; +} + +FORCEINLINE Vector& Vector::operator*=(float fl) { + x *= fl; + y *= fl; + z *= fl; + CHECK_VALID(*this); + return *this; +} + +FORCEINLINE Vector& Vector::operator*=(const Vector& v) { + CHECK_VALID(v); + x *= v.x; + y *= v.y; + z *= v.z; + CHECK_VALID(*this); + return *this; +} + +// this ought to be an opcode. +FORCEINLINE Vector& Vector::operator+=(float fl) { + x += fl; + y += fl; + z += fl; + CHECK_VALID(*this); + return *this; +} + +FORCEINLINE Vector& Vector::operator-=(float fl) { + x -= fl; + y -= fl; + z -= fl; + CHECK_VALID(*this); + return *this; +} + +FORCEINLINE Vector& Vector::operator/=(float fl) { + Assert(fl != 0.0f); + float oofl = 1.0f / fl; + x *= oofl; + y *= oofl; + z *= oofl; + CHECK_VALID(*this); + return *this; +} + +FORCEINLINE Vector& Vector::operator/=(const Vector& v) { + CHECK_VALID(v); + Assert(v.x != 0.0f && v.y != 0.0f && v.z != 0.0f); + x /= v.x; + y /= v.y; + z /= v.z; + CHECK_VALID(*this); + return *this; +} + +// get the component of this vector parallel to some other given vector +inline Vector Vector::ProjectOnto(const Vector& onto) { + return onto * (this->Dot(onto) / (onto.LengthSqr())); +} + +//----------------------------------------------------------------------------- +// +// Inlined Short Vector methods +// +//----------------------------------------------------------------------------- + +inline void ShortVector::Init(short ix, short iy, short iz, short iw) { + x = ix; + y = iy; + z = iz; + w = iw; +} + +FORCEINLINE void ShortVector::Set(const ShortVector& vOther) { + x = vOther.x; + y = vOther.y; + z = vOther.z; + w = vOther.w; +} + +FORCEINLINE void ShortVector::Set(const short ix, const short iy, + const short iz, const short iw) { + x = ix; + y = iy; + z = iz; + w = iw; +} + +//----------------------------------------------------------------------------- +// Array access +//----------------------------------------------------------------------------- +inline short ShortVector::operator[](int i) const { + Assert((i >= 0) && (i < 4)); + return ((short*)this)[i]; +} + +inline short& ShortVector::operator[](int i) { + Assert((i >= 0) && (i < 4)); + return ((short*)this)[i]; +} + +//----------------------------------------------------------------------------- +// Base address... +//----------------------------------------------------------------------------- +inline short* ShortVector::Base() { return (short*)this; } + +inline short const* ShortVector::Base() const { return (short const*)this; } + +//----------------------------------------------------------------------------- +// comparison +//----------------------------------------------------------------------------- + +inline bool ShortVector::operator==(const ShortVector& src) const { + return (src.x == x) && (src.y == y) && (src.z == z) && (src.w == w); +} + +inline bool ShortVector::operator!=(const ShortVector& src) const { + return (src.x != x) || (src.y != y) || (src.z != z) || (src.w != w); +} + +//----------------------------------------------------------------------------- +// standard math operations +//----------------------------------------------------------------------------- + +FORCEINLINE ShortVector& ShortVector::operator+=(const ShortVector& v) { + x += v.x; + y += v.y; + z += v.z; + w += v.w; + return *this; +} + +FORCEINLINE ShortVector& ShortVector::operator-=(const ShortVector& v) { + x -= v.x; + y -= v.y; + z -= v.z; + w -= v.w; + return *this; +} + +FORCEINLINE ShortVector& ShortVector::operator*=(float fl) { + x = (short)(x * fl); + y = (short)(y * fl); + z = (short)(z * fl); + w = (short)(w * fl); + return *this; +} + +FORCEINLINE ShortVector& ShortVector::operator*=(const ShortVector& v) { + x = (short)(x * v.x); + y = (short)(y * v.y); + z = (short)(z * v.z); + w = (short)(w * v.w); + return *this; +} + +FORCEINLINE ShortVector& ShortVector::operator/=(float fl) { + Assert(fl != 0.0f); + float oofl = 1.0f / fl; + x = (short)(x * oofl); + y = (short)(y * oofl); + z = (short)(z * oofl); + w = (short)(w * oofl); + return *this; +} + +FORCEINLINE ShortVector& ShortVector::operator/=(const ShortVector& v) { + Assert(v.x != 0 && v.y != 0 && v.z != 0 && v.w != 0); + x = (short)(x / v.x); + y = (short)(y / v.y); + z = (short)(z / v.z); + w = (short)(w / v.w); + return *this; +} + +FORCEINLINE void ShortVectorMultiply(const ShortVector& src, float fl, + ShortVector& res) { + Assert(IsFinite(fl)); + res.x = (short)(src.x * fl); + res.y = (short)(src.y * fl); + res.z = (short)(src.z * fl); + res.w = (short)(src.w * fl); +} + +FORCEINLINE ShortVector ShortVector::operator*(float fl) const { + ShortVector res; + ShortVectorMultiply(*this, fl, res); + return res; +} + +//----------------------------------------------------------------------------- +// +// Inlined Integer Vector methods +// +//----------------------------------------------------------------------------- + +inline void IntVector4D::Init(int ix, int iy, int iz, int iw) { + x = ix; + y = iy; + z = iz; + w = iw; +} + +FORCEINLINE void IntVector4D::Set(const IntVector4D& vOther) { + x = vOther.x; + y = vOther.y; + z = vOther.z; + w = vOther.w; +} + +FORCEINLINE void IntVector4D::Set(const int ix, const int iy, const int iz, + const int iw) { + x = ix; + y = iy; + z = iz; + w = iw; +} + +//----------------------------------------------------------------------------- +// Array access +//----------------------------------------------------------------------------- +inline int IntVector4D::operator[](int i) const { + Assert((i >= 0) && (i < 4)); + return ((int*)this)[i]; +} + +inline int& IntVector4D::operator[](int i) { + Assert((i >= 0) && (i < 4)); + return ((int*)this)[i]; +} + +//----------------------------------------------------------------------------- +// Base address... +//----------------------------------------------------------------------------- +inline int* IntVector4D::Base() { return (int*)this; } + +inline int const* IntVector4D::Base() const { return (int const*)this; } + +//----------------------------------------------------------------------------- +// comparison +//----------------------------------------------------------------------------- + +inline bool IntVector4D::operator==(const IntVector4D& src) const { + return (src.x == x) && (src.y == y) && (src.z == z) && (src.w == w); +} + +inline bool IntVector4D::operator!=(const IntVector4D& src) const { + return (src.x != x) || (src.y != y) || (src.z != z) || (src.w != w); +} + +//----------------------------------------------------------------------------- +// standard math operations +//----------------------------------------------------------------------------- + +FORCEINLINE IntVector4D& IntVector4D::operator+=(const IntVector4D& v) { + x += v.x; + y += v.y; + z += v.z; + w += v.w; + return *this; +} + +FORCEINLINE IntVector4D& IntVector4D::operator-=(const IntVector4D& v) { + x -= v.x; + y -= v.y; + z -= v.z; + w -= v.w; + return *this; +} + +FORCEINLINE IntVector4D& IntVector4D::operator*=(float fl) { + x = (int)(x * fl); + y = (int)(y * fl); + z = (int)(z * fl); + w = (int)(w * fl); + return *this; +} + +FORCEINLINE IntVector4D& IntVector4D::operator*=(const IntVector4D& v) { + x = (int)(x * v.x); + y = (int)(y * v.y); + z = (int)(z * v.z); + w = (int)(w * v.w); + return *this; +} + +FORCEINLINE IntVector4D& IntVector4D::operator/=(float fl) { + Assert(fl != 0.0f); + float oofl = 1.0f / fl; + x = (int)(x * oofl); + y = (int)(y * oofl); + z = (int)(z * oofl); + w = (int)(w * oofl); + return *this; +} + +FORCEINLINE IntVector4D& IntVector4D::operator/=(const IntVector4D& v) { + Assert(v.x != 0 && v.y != 0 && v.z != 0 && v.w != 0); + x = (int)(x / v.x); + y = (int)(y / v.y); + z = (int)(z / v.z); + w = (int)(w / v.w); + return *this; +} + +FORCEINLINE void IntVector4DMultiply(const IntVector4D& src, float fl, + IntVector4D& res) { + Assert(IsFinite(fl)); + res.x = (int)(src.x * fl); + res.y = (int)(src.y * fl); + res.z = (int)(src.z * fl); + res.w = (int)(src.w * fl); +} + +FORCEINLINE IntVector4D IntVector4D::operator*(float fl) const { + IntVector4D res; + IntVector4DMultiply(*this, fl, res); + return res; +} + +// ======================= + +FORCEINLINE void VectorAdd(const Vector& a, const Vector& b, Vector& c) { + CHECK_VALID(a); + CHECK_VALID(b); + c.x = a.x + b.x; + c.y = a.y + b.y; + c.z = a.z + b.z; +} + +FORCEINLINE void VectorSubtract(const Vector& a, const Vector& b, Vector& c) { + CHECK_VALID(a); + CHECK_VALID(b); + c.x = a.x - b.x; + c.y = a.y - b.y; + c.z = a.z - b.z; +} + +FORCEINLINE void VectorMultiply(const Vector& a, vec_t b, Vector& c) { + CHECK_VALID(a); + Assert(IsFinite(b)); + c.x = a.x * b; + c.y = a.y * b; + c.z = a.z * b; +} + +FORCEINLINE void VectorMultiply(const Vector& a, const Vector& b, Vector& c) { + CHECK_VALID(a); + CHECK_VALID(b); + c.x = a.x * b.x; + c.y = a.y * b.y; + c.z = a.z * b.z; +} + +// for backwards compatability +inline void VectorScale(const Vector& in, vec_t scale, Vector& result) { + VectorMultiply(in, scale, result); +} + +FORCEINLINE void VectorDivide(const Vector& a, vec_t b, Vector& c) { + CHECK_VALID(a); + Assert(b != 0.0f); + vec_t oob = 1.0f / b; + c.x = a.x * oob; + c.y = a.y * oob; + c.z = a.z * oob; +} + +FORCEINLINE void VectorDivide(const Vector& a, const Vector& b, Vector& c) { + CHECK_VALID(a); + CHECK_VALID(b); + Assert((b.x != 0.0f) && (b.y != 0.0f) && (b.z != 0.0f)); + c.x = a.x / b.x; + c.y = a.y / b.y; + c.z = a.z / b.z; +} + +// FIXME: Remove +// For backwards compatability +inline void Vector::MulAdd(const Vector& a, const Vector& b, float scalar) { + CHECK_VALID(a); + CHECK_VALID(b); + x = a.x + b.x * scalar; + y = a.y + b.y * scalar; + z = a.z + b.z * scalar; +} + +inline void VectorLerp(const Vector& src1, const Vector& src2, vec_t t, + Vector& dest) { + CHECK_VALID(src1); + CHECK_VALID(src2); + dest.x = src1.x + (src2.x - src1.x) * t; + dest.y = src1.y + (src2.y - src1.y) * t; + dest.z = src1.z + (src2.z - src1.z) * t; +} + +inline Vector VectorLerp(const Vector& src1, const Vector& src2, vec_t t) { + Vector result; + VectorLerp(src1, src2, t, result); + return result; +} + +//----------------------------------------------------------------------------- +// Temporary storage for vector results so const Vector& results can be returned +//----------------------------------------------------------------------------- +inline Vector& AllocTempVector() { + static Vector s_vecTemp[128]; + static CInterlockedInt s_nIndex; + + int nIndex; + for (;;) { + int nOldIndex = s_nIndex; + nIndex = ((nOldIndex + 0x10001) & 0x7F); + + if (s_nIndex.AssignIf(nOldIndex, nIndex)) { + break; + } + ThreadPause(); + } + return s_vecTemp[nIndex & 0x7F]; +} + +//----------------------------------------------------------------------------- +// dot, cross +//----------------------------------------------------------------------------- +FORCEINLINE vec_t DotProduct(const Vector& a, const Vector& b) { + CHECK_VALID(a); + CHECK_VALID(b); + return (a.x * b.x + a.y * b.y + a.z * b.z); +} + +// for backwards compatability +inline vec_t Vector::Dot(const Vector& vOther) const { + CHECK_VALID(vOther); + return DotProduct(*this, vOther); +} + +inline int Vector::LargestComponent() const { + float flAbsx = fabs(x); + float flAbsy = fabs(y); + float flAbsz = fabs(z); + if (flAbsx > flAbsy) { + if (flAbsx > flAbsz) return X_INDEX; + return Z_INDEX; + } + if (flAbsy > flAbsz) return Y_INDEX; + return Z_INDEX; +} + +inline void CrossProduct(const Vector& a, const Vector& b, Vector& result) { + CHECK_VALID(a); + CHECK_VALID(b); + Assert(&a != &result); + Assert(&b != &result); + result.x = a.y * b.z - a.z * b.y; + result.y = a.z * b.x - a.x * b.z; + result.z = a.x * b.y - a.y * b.x; +} + +inline vec_t DotProductAbs(const Vector& v0, const Vector& v1) { + CHECK_VALID(v0); + CHECK_VALID(v1); + return FloatMakePositive(v0.x * v1.x) + FloatMakePositive(v0.y * v1.y) + + FloatMakePositive(v0.z * v1.z); +} + +inline vec_t DotProductAbs(const Vector& v0, const float* v1) { + return FloatMakePositive(v0.x * v1[0]) + FloatMakePositive(v0.y * v1[1]) + + FloatMakePositive(v0.z * v1[2]); +} + +//----------------------------------------------------------------------------- +// length +//----------------------------------------------------------------------------- + +inline vec_t VectorLength(const Vector& v) { + CHECK_VALID(v); + return (vec_t)FastSqrt(v.x * v.x + v.y * v.y + v.z * v.z); +} + +inline vec_t Vector::Length(void) const { + CHECK_VALID(*this); + return VectorLength(*this); +} + +//----------------------------------------------------------------------------- +// Normalization +//----------------------------------------------------------------------------- + +/* +// FIXME: Can't use until we're un-macroed in mathlib.h +inline vec_t VectorNormalize( Vector& v ) +{ + Assert( v.IsValid() ); + vec_t l = v.Length(); + if (l != 0.0f) + { + v /= l; + } + else + { + // FIXME: + // Just copying the existing implemenation; shouldn't res.z == +0? v.x = v.y = 0.0f; v.z = 1.0f; + } + return l; +} +*/ + +// check a point against a box +bool Vector::WithinAABox(Vector const& boxmin, Vector const& boxmax) { + return ((x >= boxmin.x) && (x <= boxmax.x) && (y >= boxmin.y) && + (y <= boxmax.y) && (z >= boxmin.z) && (z <= boxmax.z)); +} + +//----------------------------------------------------------------------------- +// Get the distance from this vector to the other one +//----------------------------------------------------------------------------- +inline vec_t Vector::DistTo(const Vector& vOther) const { + Vector delta; + VectorSubtract(*this, vOther, delta); + return delta.Length(); +} + +//----------------------------------------------------------------------------- +// Vector equality with tolerance +//----------------------------------------------------------------------------- +inline bool VectorsAreEqual(const Vector& src1, const Vector& src2, + float tolerance) { + if (FloatMakePositive(src1.x - src2.x) > tolerance) return false; + if (FloatMakePositive(src1.y - src2.y) > tolerance) return false; + return (FloatMakePositive(src1.z - src2.z) <= tolerance); +} + +//----------------------------------------------------------------------------- +// Computes the closest point to vecTarget no farther than flMaxDist from +// vecStart +//----------------------------------------------------------------------------- +inline void ComputeClosestPoint(const Vector& vecStart, float flMaxDist, + const Vector& vecTarget, Vector* pResult) { + Vector vecDelta; + VectorSubtract(vecTarget, vecStart, vecDelta); + float flDistSqr = vecDelta.LengthSqr(); + if (flDistSqr <= flMaxDist * flMaxDist) { + *pResult = vecTarget; + } else { + vecDelta /= FastSqrt(flDistSqr); + VectorMA(vecStart, flMaxDist, vecDelta, *pResult); + } +} + +//----------------------------------------------------------------------------- +// Takes the absolute value of a vector +//----------------------------------------------------------------------------- +inline void VectorAbs(const Vector& src, Vector& dst) { + dst.x = FloatMakePositive(src.x); + dst.y = FloatMakePositive(src.y); + dst.z = FloatMakePositive(src.z); +} + +//----------------------------------------------------------------------------- +// +// Slow methods +// +//----------------------------------------------------------------------------- + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +//----------------------------------------------------------------------------- +// Returns a vector with the min or max in X, Y, and Z. +//----------------------------------------------------------------------------- +inline Vector Vector::Min(const Vector& vOther) const { + return Vector(x < vOther.x ? x : vOther.x, y < vOther.y ? y : vOther.y, + z < vOther.z ? z : vOther.z); +} + +inline Vector Vector::Max(const Vector& vOther) const { + return Vector(x > vOther.x ? x : vOther.x, y > vOther.y ? y : vOther.y, + z > vOther.z ? z : vOther.z); +} + +//----------------------------------------------------------------------------- +// arithmetic operations +//----------------------------------------------------------------------------- + +inline Vector Vector::operator-(void) const { return Vector(-x, -y, -z); } + +inline Vector Vector::operator+(const Vector& v) const { + Vector res; + VectorAdd(*this, v, res); + return res; +} + +inline Vector Vector::operator-(const Vector& v) const { + Vector res; + VectorSubtract(*this, v, res); + return res; +} + +inline Vector Vector::operator*(float fl) const { + Vector res; + VectorMultiply(*this, fl, res); + return res; +} + +inline Vector Vector::operator*(const Vector& v) const { + Vector res; + VectorMultiply(*this, v, res); + return res; +} + +inline Vector Vector::operator/(float fl) const { + Vector res; + VectorDivide(*this, fl, res); + return res; +} + +inline Vector Vector::operator/(const Vector& v) const { + Vector res; + VectorDivide(*this, v, res); + return res; +} + +inline Vector operator*(float fl, const Vector& v) { return v * fl; } + +//----------------------------------------------------------------------------- +// cross product +//----------------------------------------------------------------------------- + +inline Vector Vector::Cross(const Vector& vOther) const { + Vector res; + CrossProduct(*this, vOther, res); + return res; +} + +//----------------------------------------------------------------------------- +// 2D +//----------------------------------------------------------------------------- + +inline vec_t Vector::Length2D(void) const { + return (vec_t)FastSqrt(x * x + y * y); +} + +inline vec_t Vector::Length2DSqr(void) const { return (x * x + y * y); } + +inline Vector CrossProduct(const Vector& a, const Vector& b) { + return Vector(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x); +} + +inline void VectorMin(const Vector& a, const Vector& b, Vector& result) { + result.x = fpmin(a.x, b.x); + result.y = fpmin(a.y, b.y); + result.z = fpmin(a.z, b.z); +} + +inline void VectorMax(const Vector& a, const Vector& b, Vector& result) { + result.x = fpmax(a.x, b.x); + result.y = fpmax(a.y, b.y); + result.z = fpmax(a.z, b.z); +} + +// and when you want to return the vector rather than cause a LHS with it... +inline Vector VectorMin(const Vector& a, const Vector& b) { + return Vector(fpmin(a.x, b.x), fpmin(a.y, b.y), fpmin(a.z, b.z)); +} + +inline Vector VectorMax(const Vector& a, const Vector& b) { + return Vector(fpmax(a.x, b.x), fpmax(a.y, b.y), fpmax(a.z, b.z)); +} + +inline float ComputeVolume(const Vector& vecMins, const Vector& vecMaxs) { + Vector vecDelta; + VectorSubtract(vecMaxs, vecMins, vecDelta); + return DotProduct(vecDelta, vecDelta); +} + +// Get a random vector. +inline Vector RandomVector(float minVal, float maxVal) { + Vector random; + random.Random(minVal, maxVal); + return random; +} + +#endif // slow + +//----------------------------------------------------------------------------- +// Helper debugging stuff.... +//----------------------------------------------------------------------------- + +inline bool operator==(float const*, const Vector&) { + // AIIIEEEE!!!! + Assert(0); + return false; +} + +inline bool operator==(const Vector&, float const*) { + // AIIIEEEE!!!! + Assert(0); + return false; +} + +inline bool operator!=(float const*, const Vector&) { + // AIIIEEEE!!!! + Assert(0); + return false; +} + +inline bool operator!=(const Vector&, float const*) { + // AIIIEEEE!!!! + Assert(0); + return false; +} + +// return a vector perpendicular to another, with smooth variation. The +// difference between this and something like VectorVectors is that there are +// now discontinuities. _unlike_ VectorVectors, you won't get an "u +void VectorPerpendicularToVector(Vector const& in, Vector* pvecOut); + +//----------------------------------------------------------------------------- +// AngularImpulse +//----------------------------------------------------------------------------- +// AngularImpulse are exponetial maps (an axis scaled by a "twist" angle in +// degrees) +typedef Vector AngularImpulse; + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +inline AngularImpulse RandomAngularImpulse(float minVal, float maxVal) { + AngularImpulse angImp; + angImp.Random(minVal, maxVal); + return angImp; +} + +#endif + +//----------------------------------------------------------------------------- +// Quaternion +//----------------------------------------------------------------------------- + +class RadianEuler; + +class Quaternion // same data-layout as engine's vec4_t, +{ // which is a vec_t[4] + public: + inline Quaternion() { + // Initialize to NAN to catch errors +#ifdef _DEBUG +#ifdef VECTOR_PARANOIA + x = y = z = w = VEC_T_NAN; +#endif +#endif + } + inline Quaternion(vec_t ix, vec_t iy, vec_t iz, vec_t iw) + : x(ix), y(iy), z(iz), w(iw) {} + inline Quaternion(RadianEuler const& angle); // evil auto type promotion!!! + + inline void Init(vec_t ix = 0.0f, vec_t iy = 0.0f, vec_t iz = 0.0f, + vec_t iw = 0.0f) { + x = ix; + y = iy; + z = iz; + w = iw; + } + + bool IsValid() const; + void Invalidate(); + + bool operator==(const Quaternion& src) const; + bool operator!=(const Quaternion& src) const; + + inline Quaternion Conjugate() const { return Quaternion(-x, -y, -z, w); } + + vec_t* Base() { return (vec_t*)this; } + const vec_t* Base() const { return (vec_t*)this; } + + // convenience for debugging + inline void Print() const; + + // array access... + vec_t operator[](int i) const; + vec_t& operator[](int i); + + vec_t x, y, z, w; +}; + +//----------------------------------------------------------------------------- +// Array access +//----------------------------------------------------------------------------- +inline vec_t& Quaternion::operator[](int i) { + Assert((i >= 0) && (i < 4)); + return ((vec_t*)this)[i]; +} + +inline vec_t Quaternion::operator[](int i) const { + Assert((i >= 0) && (i < 4)); + return ((vec_t*)this)[i]; +} + +//----------------------------------------------------------------------------- +// Equality test +//----------------------------------------------------------------------------- +inline bool Quaternion::operator==(const Quaternion& src) const { + return (x == src.x) && (y == src.y) && (z == src.z) && (w == src.w); +} + +inline bool Quaternion::operator!=(const Quaternion& src) const { + return !operator==(src); +} + +//----------------------------------------------------------------------------- +// Debugging only +//----------------------------------------------------------------------------- +void Quaternion::Print() const { +#ifndef _CERT + Msg("q{ %.3fi + %.3fj + %.3fk + %.3f }", x, y, z, w); +#endif +} + +//----------------------------------------------------------------------------- +// Quaternion equality with tolerance +//----------------------------------------------------------------------------- +inline bool QuaternionsAreEqual(const Quaternion& src1, const Quaternion& src2, + float tolerance) { + if (FloatMakePositive(src1.x - src2.x) > tolerance) return false; + if (FloatMakePositive(src1.y - src2.y) > tolerance) return false; + if (FloatMakePositive(src1.z - src2.z) > tolerance) return false; + return (FloatMakePositive(src1.w - src2.w) <= tolerance); +} + +//----------------------------------------------------------------------------- +// Here's where we add all those lovely SSE optimized routines +//----------------------------------------------------------------------------- +class ALIGN16 QuaternionAligned : public Quaternion { + public: + inline QuaternionAligned() = default; + inline QuaternionAligned(vec_t X, vec_t Y, vec_t Z, vec_t W) { + Init(X, Y, Z, W); + } + + operator Quaternion*() { return this; } + operator const Quaternion*() { return this; } + +#ifdef VECTOR_NO_SLOW_OPERATIONS + + private: + // No copy constructors allowed if we're in optimal mode + QuaternionAligned(const QuaternionAligned& vOther); + QuaternionAligned(const Quaternion& vOther); + +#else + public: + explicit QuaternionAligned(const Quaternion& vOther) { + Init(vOther.x, vOther.y, vOther.z, vOther.w); + } + + QuaternionAligned& operator=(const Quaternion& vOther) { + Init(vOther.x, vOther.y, vOther.z, vOther.w); + return *this; + } + + QuaternionAligned& operator=(const QuaternionAligned& vOther) { + // we know we're aligned, so use simd + // we can't use the convenient abstract interface coz it gets declared later +#ifdef _X360 + XMStoreVector4A(Base(), XMLoadVector4A(vOther.Base())); +#elif _WIN32 + _mm_store_ps(Base(), _mm_load_ps(vOther.Base())); +#else + Init(vOther.x, vOther.y, vOther.z, vOther.w); +#endif + return *this; + } + +#endif + +#if !defined(NO_MALLOC_OVERRIDE) + void* operator new[](size_t nSize) { + return MemAlloc_AllocAligned(nSize, 16); + } + + void* operator new[](size_t nSize, const char* pFileName, int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + + void* operator new[](size_t nSize, int /*nBlockUse*/, const char* pFileName, + int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + + void operator delete[](void* p) { MemAlloc_FreeAligned(p); } + + void operator delete[](void* p, const char* pFileName, int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } + + void operator delete[](void* p, int /*nBlockUse*/, const char* pFileName, + int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } + + // please don't allocate a single quaternion... + void* operator new(size_t nSize) { return MemAlloc_AllocAligned(nSize, 16); } + void* operator new(size_t nSize, const char* pFileName, int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + void* operator new(size_t nSize, int /*nBlockUse*/, const char* pFileName, + int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, 16, pFileName, nLine); + } + void operator delete(void* p) { MemAlloc_FreeAligned(p); } + + void operator delete(void* p, const char* pFileName, int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } + + void operator delete(void* p, int /*nBlockUse*/, const char* pFileName, + int nLine) { + MemAlloc_FreeAligned(p, pFileName, nLine); + } +#endif +} ALIGN16_POST; + +//----------------------------------------------------------------------------- +// Radian Euler angle aligned to axis (NOT ROLL/PITCH/YAW) +//----------------------------------------------------------------------------- +class QAngle; +class RadianEuler { + public: + inline RadianEuler() = default; + inline RadianEuler(vec_t X, vec_t Y, vec_t Z) { + x = X; + y = Y; + z = Z; + } + inline RadianEuler(Quaternion const& q); // evil auto type promotion!!! + inline RadianEuler(QAngle const& angles); // evil auto type promotion!!! + + // Initialization + inline void Init(vec_t ix = 0.0f, vec_t iy = 0.0f, vec_t iz = 0.0f) { + x = ix; + y = iy; + z = iz; + } + + // conversion to qangle + QAngle ToQAngle(void) const; + bool IsValid() const; + void Invalidate(); + + inline vec_t* Base() { return &x; } + inline const vec_t* Base() const { return &x; } + + // array access... + vec_t operator[](int i) const; + vec_t& operator[](int i); + + vec_t x, y, z; +}; + +extern void AngleQuaternion(RadianEuler const& angles, Quaternion& qt); +extern void QuaternionAngles(Quaternion const& q, RadianEuler& angles); +inline Quaternion::Quaternion(RadianEuler const& angle) { + AngleQuaternion(angle, *this); +} + +inline bool Quaternion::IsValid() const { + return IsFinite(x) && IsFinite(y) && IsFinite(z) && IsFinite(w); +} + +inline void Quaternion::Invalidate() { + //#ifdef _DEBUG + //#ifdef VECTOR_PARANOIA + x = y = z = w = VEC_T_NAN; + //#endif + //#endif +} + +inline RadianEuler::RadianEuler(Quaternion const& q) { + QuaternionAngles(q, *this); +} + +inline void VectorCopy(RadianEuler const& src, RadianEuler& dst) { + CHECK_VALID(src); + dst.x = src.x; + dst.y = src.y; + dst.z = src.z; +} + +inline void VectorScale(RadianEuler const& src, float b, RadianEuler& dst) { + CHECK_VALID(src); + Assert(IsFinite(b)); + dst.x = src.x * b; + dst.y = src.y * b; + dst.z = src.z * b; +} + +inline bool RadianEuler::IsValid() const { + return IsFinite(x) && IsFinite(y) && IsFinite(z); +} + +inline void RadianEuler::Invalidate() { + //#ifdef _DEBUG + //#ifdef VECTOR_PARANOIA + x = y = z = VEC_T_NAN; + //#endif + //#endif +} + +//----------------------------------------------------------------------------- +// Array access +//----------------------------------------------------------------------------- +inline vec_t& RadianEuler::operator[](int i) { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; +} + +inline vec_t RadianEuler::operator[](int i) const { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; +} + +//----------------------------------------------------------------------------- +// Degree Euler QAngle pitch, yaw, roll +//----------------------------------------------------------------------------- +class QAngleByValue; + +class QAngle { + public: + // Members + vec_t x, y, z; + + // Construction/destruction + QAngle(void); + QAngle(vec_t X, vec_t Y, vec_t Z); +#ifndef _PS3 +// QAngle(RadianEuler const &angles); // evil auto type promotion!!! +#endif + + // Allow pass-by-value + operator QAngleByValue&() { return *((QAngleByValue*)(this)); } + operator const QAngleByValue&() const { + return *((const QAngleByValue*)(this)); + } + + // Initialization + void Init(vec_t ix = 0.0f, vec_t iy = 0.0f, vec_t iz = 0.0f); + void Random(vec_t minVal, vec_t maxVal); + + // Got any nasty NAN's? + bool IsValid() const; + void Invalidate(); + + // array access... + vec_t operator[](int i) const; + vec_t& operator[](int i); + + // Base address... + vec_t* Base(); + vec_t const* Base() const; + + // equality + bool operator==(const QAngle& v) const; + bool operator!=(const QAngle& v) const; + + // arithmetic operations + QAngle& operator+=(const QAngle& v); + QAngle& operator-=(const QAngle& v); + QAngle& operator*=(float s); + QAngle& operator/=(float s); + + // Get the vector's magnitude. + vec_t Length() const; + vec_t LengthSqr() const; + + // negate the QAngle components + // void Negate(); + + // No assignment operators either... + QAngle& operator=(const QAngle& src); + +#ifndef VECTOR_NO_SLOW_OPERATIONS + // copy constructors + + // arithmetic operations + QAngle operator-(void) const; + + QAngle operator+(const QAngle& v) const; + QAngle operator-(const QAngle& v) const; + QAngle operator*(float fl) const; + QAngle operator/(float fl) const; +#else + + private: + // No copy constructors allowed if we're in optimal mode + QAngle(const QAngle& vOther); + +#endif +}; + +//----------------------------------------------------------------------------- +// Allows us to specifically pass the vector by value when we need to +//----------------------------------------------------------------------------- +class QAngleByValue : public QAngle { + public: + // Construction/destruction: + QAngleByValue(void) : QAngle() {} + QAngleByValue(vec_t X, vec_t Y, vec_t Z) : QAngle(X, Y, Z) {} + QAngleByValue(const QAngleByValue& vOther) { *this = vOther; } +}; + +inline void VectorAdd(const QAngle& a, const QAngle& b, QAngle& result) { + CHECK_VALID(a); + CHECK_VALID(b); + result.x = a.x + b.x; + result.y = a.y + b.y; + result.z = a.z + b.z; +} + +inline void VectorMA(const QAngle& start, float scale, const QAngle& direction, + QAngle& dest) { + CHECK_VALID(start); + CHECK_VALID(direction); + dest.x = start.x + scale * direction.x; + dest.y = start.y + scale * direction.y; + dest.z = start.z + scale * direction.z; +} + +//----------------------------------------------------------------------------- +// constructors +//----------------------------------------------------------------------------- +inline QAngle::QAngle() { +#ifdef _DEBUG +#ifdef VECTOR_PARANOIA + // Initialize to NAN to catch errors + x = y = z = VEC_T_NAN; +#endif +#endif +} + +inline QAngle::QAngle(vec_t X, vec_t Y, vec_t Z) { + x = X; + y = Y; + z = Z; + CHECK_VALID(*this); +} + +//----------------------------------------------------------------------------- +// initialization +//----------------------------------------------------------------------------- +inline void QAngle::Init(vec_t ix, vec_t iy, vec_t iz) { + x = ix; + y = iy; + z = iz; + CHECK_VALID(*this); +} + +inline void QAngle::Random(vec_t minVal, vec_t maxVal) { + x = RandomFloat(minVal, maxVal); + y = RandomFloat(minVal, maxVal); + z = RandomFloat(minVal, maxVal); + CHECK_VALID(*this); +} + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +inline QAngle RandomAngle(float minVal, float maxVal) { + Vector random; + random.Random(minVal, maxVal); + QAngle ret(random.x, random.y, random.z); + return ret; +} + +#endif + +inline RadianEuler::RadianEuler(QAngle const& angles) { + Init(angles.z * 3.14159265358979323846f / 180.f, + angles.x * 3.14159265358979323846f / 180.f, + angles.y * 3.14159265358979323846f / 180.f); +} + +inline QAngle RadianEuler::ToQAngle(void) const { + return QAngle(y * 180.f / 3.14159265358979323846f, + z * 180.f / 3.14159265358979323846f, + x * 180.f / 3.14159265358979323846f); +} + +//----------------------------------------------------------------------------- +// assignment +//----------------------------------------------------------------------------- +inline QAngle& QAngle::operator=(const QAngle& vOther) { + CHECK_VALID(vOther); + x = vOther.x; + y = vOther.y; + z = vOther.z; + return *this; +} + +//----------------------------------------------------------------------------- +// Array access +//----------------------------------------------------------------------------- +inline vec_t& QAngle::operator[](int i) { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; +} + +inline vec_t QAngle::operator[](int i) const { + Assert((i >= 0) && (i < 3)); + return ((vec_t*)this)[i]; +} + +//----------------------------------------------------------------------------- +// Base address... +//----------------------------------------------------------------------------- +inline vec_t* QAngle::Base() { return (vec_t*)this; } + +inline vec_t const* QAngle::Base() const { return (vec_t const*)this; } + +//----------------------------------------------------------------------------- +// IsValid? +//----------------------------------------------------------------------------- +inline bool QAngle::IsValid() const { + return IsFinite(x) && IsFinite(y) && IsFinite(z); +} + +//----------------------------------------------------------------------------- +// Invalidate +//----------------------------------------------------------------------------- + +inline void QAngle::Invalidate() { + //#ifdef _DEBUG + //#ifdef VECTOR_PARANOIA + x = y = z = VEC_T_NAN; + //#endif + //#endif +} + +//----------------------------------------------------------------------------- +// comparison +//----------------------------------------------------------------------------- +inline bool QAngle::operator==(const QAngle& src) const { + CHECK_VALID(src); + CHECK_VALID(*this); + return (src.x == x) && (src.y == y) && (src.z == z); +} + +inline bool QAngle::operator!=(const QAngle& src) const { + CHECK_VALID(src); + CHECK_VALID(*this); + return (src.x != x) || (src.y != y) || (src.z != z); +} + +//----------------------------------------------------------------------------- +// Copy +//----------------------------------------------------------------------------- +inline void VectorCopy(const QAngle& src, QAngle& dst) { + CHECK_VALID(src); + dst.x = src.x; + dst.y = src.y; + dst.z = src.z; +} + +//----------------------------------------------------------------------------- +// standard math operations +//----------------------------------------------------------------------------- +inline QAngle& QAngle::operator+=(const QAngle& v) { + CHECK_VALID(*this); + CHECK_VALID(v); + x += v.x; + y += v.y; + z += v.z; + return *this; +} + +inline QAngle& QAngle::operator-=(const QAngle& v) { + CHECK_VALID(*this); + CHECK_VALID(v); + x -= v.x; + y -= v.y; + z -= v.z; + return *this; +} + +inline QAngle& QAngle::operator*=(float fl) { + x *= fl; + y *= fl; + z *= fl; + CHECK_VALID(*this); + return *this; +} + +inline QAngle& QAngle::operator/=(float fl) { + Assert(fl != 0.0f); + float oofl = 1.0f / fl; + x *= oofl; + y *= oofl; + z *= oofl; + CHECK_VALID(*this); + return *this; +} + +//----------------------------------------------------------------------------- +// length +//----------------------------------------------------------------------------- +inline vec_t QAngle::Length() const { + CHECK_VALID(*this); + return (vec_t)FastSqrt(LengthSqr()); +} + +inline vec_t QAngle::LengthSqr() const { + CHECK_VALID(*this); + return x * x + y * y + z * z; +} + +//----------------------------------------------------------------------------- +// Vector equality with tolerance +//----------------------------------------------------------------------------- +inline bool QAnglesAreEqual(const QAngle& src1, const QAngle& src2, + float tolerance = 0.0f) { + if (FloatMakePositive(src1.x - src2.x) > tolerance) return false; + if (FloatMakePositive(src1.y - src2.y) > tolerance) return false; + return (FloatMakePositive(src1.z - src2.z) <= tolerance); +} + +//----------------------------------------------------------------------------- +// arithmetic operations (SLOW!!) +//----------------------------------------------------------------------------- +#ifndef VECTOR_NO_SLOW_OPERATIONS + +inline QAngle QAngle::operator-(void) const { + QAngle ret(-x, -y, -z); + return ret; +} + +inline QAngle QAngle::operator+(const QAngle& v) const { + QAngle res; + res.x = x + v.x; + res.y = y + v.y; + res.z = z + v.z; + return res; +} + +inline QAngle QAngle::operator-(const QAngle& v) const { + QAngle res; + res.x = x - v.x; + res.y = y - v.y; + res.z = z - v.z; + return res; +} + +inline QAngle QAngle::operator*(float fl) const { + QAngle res; + res.x = x * fl; + res.y = y * fl; + res.z = z * fl; + return res; +} + +inline QAngle QAngle::operator/(float fl) const { + QAngle res; + res.x = x / fl; + res.y = y / fl; + res.z = z / fl; + return res; +} + +inline QAngle operator*(float fl, const QAngle& v) { + QAngle ret(v * fl); + return ret; +} + +#endif // VECTOR_NO_SLOW_OPERATIONS + +//----------------------------------------------------------------------------- +// NOTE: These are not completely correct. The representations are not +// equivalent unless the QAngle represents a rotational impulse along a +// coordinate axis (x,y,z) +inline void QAngleToAngularImpulse(const QAngle& angles, + AngularImpulse& impulse) { + impulse.x = angles.z; + impulse.y = angles.x; + impulse.z = angles.y; +} + +inline void AngularImpulseToQAngle(const AngularImpulse& impulse, + QAngle& angles) { + angles.x = impulse.y; + angles.y = impulse.z; + angles.z = impulse.x; +} + +#if !defined(_X360) && !defined(_PS3) + +FORCEINLINE vec_t InvRSquared(const float* v) { + return 1.0F / MAX(1.0F, v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +} + +FORCEINLINE vec_t InvRSquared(const Vector& v) { return InvRSquared(v.Base()); } + +#else + +// call directly +FORCEINLINE float _VMX_InvRSquared(const Vector& v) { +#if !defined(_PS3) + XMVECTOR xmV = XMVector3ReciprocalLength(XMLoadVector3(v.Base())); + xmV = XMVector3Dot(xmV, xmV); + return xmV.x; +#else //!_PS3 + vector_float_union vRet; + vec_float4 v0, v1, vIn, vOut; + vector unsigned char permMask; + v0 = vec_ld(0, v.Base()); + permMask = vec_lvsl(0, v.Base()); + v1 = vec_ld(11, v.Base()); + vIn = vec_perm(v0, v1, permMask); + vOut = vec_madd(vIn, vIn, _VEC_ZEROF); + vec_float4 vTmp = vec_sld(vIn, vIn, 4); + vec_float4 vTmp2 = vec_sld(vIn, vIn, 8); + vOut = vec_madd(vTmp, vTmp, vOut); + vOut = vec_madd(vTmp2, vTmp2, vOut); + vOut = vec_re(vec_add(vOut, _VEC_EPSILONF)); + vec_st(vOut, 0, &vRet.vf); + float ret = vRet.f[0]; + return ret; +#endif //!_PS3 +} + +#define InvRSquared(x) _VMX_InvRSquared(x) + +#endif // _X360 + +#if !defined(_X360) && !defined(_PS3) + +// FIXME: Change this back to a #define once we get rid of the vec_t version +float VectorNormalize(Vector& v); + +// FIXME: Obsolete version of VectorNormalize, once we remove all the friggin +// float*s +FORCEINLINE float VectorNormalize(float* v) { + return VectorNormalize(*(reinterpret_cast(v))); +} + +#else +#if !defined(_PS3) +// modified version of Microsoft's XMVector3Length +// microsoft's version will return INF for very small vectors +// e.g. Vector vTest(7.98555446e-20,-6.85012984e-21,0); VectorNormalize( +// vTest ); so we clamp to epsilon instead of checking for zero +XMFINLINE XMVECTOR XMVector3Length_Fixed(FXMVECTOR V) { + // Returns a QNaN on infinite vectors. + static CONST XMVECTOR g_fl4SmallVectorEpsilon = {1e-24f, 1e-24f, 1e-24f, + 1e-24f}; + + XMVECTOR D; + XMVECTOR Rsq; + XMVECTOR Rcp; + XMVECTOR Zero; + XMVECTOR RT; + XMVECTOR Result; + XMVECTOR Length; + XMVECTOR H; + + H = __vspltisw(1); + D = __vmsum3fp(V, V); + H = __vcfsx(H, 1); + Rsq = __vrsqrtefp(D); + RT = __vmulfp(D, H); + Rcp = __vmulfp(Rsq, Rsq); + H = __vnmsubfp(RT, Rcp, H); + Rsq = __vmaddfp(Rsq, H, Rsq); + Zero = __vspltisw(0); + Result = __vcmpgefp(g_fl4SmallVectorEpsilon, D); + Length = __vmulfp(D, Rsq); + Result = __vsel(Length, Zero, Result); + + return Result; +} +#endif + +// call directly +FORCEINLINE float _VMX_VectorNormalize(Vector& vec) { +#if !defined _PS3 + float mag = XMVector3Length_Fixed(XMLoadVector3(vec.Base())).x; + float den = 1.f / (mag + FLT_EPSILON); + vec.x *= den; + vec.y *= den; + vec.z *= den; + return mag; +#else // !_PS3 + vec_float4 vIn; + vec_float4 v0, v1; + vector unsigned char permMask; + v0 = vec_ld(0, vec.Base()); + permMask = vec_lvsl(0, vec.Base()); + v1 = vec_ld(11, vec.Base()); + vIn = vec_perm(v0, v1, permMask); + float mag = vmathV3Length((VmathVector3*)&vIn); + float den = 1.f / (mag + FLT_EPSILON); + vec.x *= den; + vec.y *= den; + vec.z *= den; + return mag; +#endif // !_PS3 +} +// FIXME: Change this back to a #define once we get rid of the vec_t version +FORCEINLINE float VectorNormalize(Vector& v) { return _VMX_VectorNormalize(v); } +// FIXME: Obsolete version of VectorNormalize, once we remove all the friggin +// float*s +FORCEINLINE float VectorNormalize(float* pV) { + return _VMX_VectorNormalize(*(reinterpret_cast(pV))); +} + +#endif // _X360 + +#if !defined(_X360) && !defined(_PS3) +FORCEINLINE void VectorNormalizeFast(Vector& vec) { + float ool = + FastRSqrt(FLT_EPSILON + vec.x * vec.x + vec.y * vec.y + vec.z * vec.z); + + vec.x *= ool; + vec.y *= ool; + vec.z *= ool; +} +#else + +// call directly +FORCEINLINE void VectorNormalizeFast(Vector& vec) { +#if !defined(_PS3) + XMVECTOR xmV = XMVector3LengthEst(XMLoadVector3(vec.Base())); + float den = 1.f / (xmV.x + FLT_EPSILON); + vec.x *= den; + vec.y *= den; + vec.z *= den; +#else // !_PS3 + vector_float_union vVec; + + vec_float4 vIn, vOut, vOOLen, vDot; + + // load + vec_float4 v0, v1; + vector unsigned char permMask; + v0 = vec_ld(0, vec.Base()); + permMask = vec_lvsl(0, vec.Base()); + v1 = vec_ld(11, vec.Base()); + vIn = vec_perm(v0, v1, permMask); + + // vec.vec + vOut = vec_madd(vIn, vIn, _VEC_ZEROF); + vec_float4 vTmp = vec_sld(vIn, vIn, 4); + vec_float4 vTmp2 = vec_sld(vIn, vIn, 8); + vOut = vec_madd(vTmp, vTmp, vOut); + vOut = vec_madd(vTmp2, vTmp2, vOut); + + // splat dot to all + vDot = vec_splat(vOut, 0); + + vOOLen = vec_rsqrte(vec_add(vDot, _VEC_EPSILONF)); + + // vec * 1.0/sqrt(vec.vec) + vOut = vec_madd(vIn, vOOLen, _VEC_ZEROF); + + // store + vec_st(vOut, 0, &vVec.vf); + + // store vec + vec.x = vVec.f[0]; + vec.y = vVec.f[1]; + vec.z = vVec.f[2]; + +#endif // !_PS3 +} + +#endif // _X360 + +inline vec_t Vector::NormalizeInPlace() { return VectorNormalize(*this); } + +inline Vector Vector::Normalized() const { + Vector norm = *this; + VectorNormalize(norm); + return norm; +} + +inline bool Vector::IsLengthGreaterThan(float val) const { + return LengthSqr() > val * val; +} + +inline bool Vector::IsLengthLessThan(float val) const { + return LengthSqr() < val * val; +} + +#endif // VPC_MATHLIB_VECTOR_H_ diff --git a/public/mathlib/vector2d.h b/public/mathlib/vector2d.h new file mode 100644 index 0000000..5abb334 --- /dev/null +++ b/public/mathlib/vector2d.h @@ -0,0 +1,579 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_MATHLIB_VECTOR2D_H_ +#define VPC_MATHLIB_VECTOR2D_H_ + +#include +#include + +// For vec_t, put this somewhere else? +#include "tier0/basetypes.h" + +// For RandomFloat() +#include "vstdlib/random.h" + +#include "tier0/dbg.h" +#include "mathlib/math_pfns.h" + +// Vector2D +class Vector2D { + public: + // Members + vec_t x, y; + + // Construction/destruction + Vector2D(void); + Vector2D(vec_t X, vec_t Y); + Vector2D(const float* pFloat); + + // Initialization + void Init(vec_t ix = 0.0f, vec_t iy = 0.0f); + + // Got any nasty NAN's? + bool IsValid() const; + + // array access... + vec_t operator[](int i) const; + vec_t& operator[](int i); + + // Base address... + vec_t* Base(); + vec_t const* Base() const; + + // Initialization methods + void Random(float minVal, float maxVal); + + // equality + bool operator==(const Vector2D& v) const; + bool operator!=(const Vector2D& v) const; + + // arithmetic operations + Vector2D& operator+=(const Vector2D& v); + Vector2D& operator-=(const Vector2D& v); + Vector2D& operator*=(const Vector2D& v); + Vector2D& operator*=(float s); + Vector2D& operator/=(const Vector2D& v); + Vector2D& operator/=(float s); + + // negate the Vector2D components + void Negate(); + + // Get the Vector2D's magnitude. + vec_t Length() const; + + // Get the Vector2D's magnitude squared. + vec_t LengthSqr(void) const; + + // return true if this vector is (0,0) within tolerance + bool IsZero(float tolerance = 0.01f) const { + return (x > -tolerance && x < tolerance && y > -tolerance && y < tolerance); + } + + // Normalize in place and return the old length. + vec_t NormalizeInPlace(); + + // Compare length. + bool IsLengthGreaterThan(float val) const; + bool IsLengthLessThan(float val) const; + + // Get the distance from this Vector2D to the other one. + vec_t DistTo(const Vector2D& vOther) const; + + // Get the distance from this Vector2D to the other one squared. + vec_t DistToSqr(const Vector2D& vOther) const; + + // Copy + void CopyToArray(float* rgfl) const; + + // Multiply, add, and assign to this (ie: *this = a + b * scalar). This + // is about 12% faster than the actual Vector2D equation (because it's done + // per-component rather than per-Vector2D). + void MulAdd(const Vector2D& a, const Vector2D& b, float scalar); + + // Dot product. + vec_t Dot(const Vector2D& vOther) const; + + // assignment + Vector2D& operator=(const Vector2D& vOther); + +#ifndef VECTOR_NO_SLOW_OPERATIONS + // copy constructors + Vector2D(const Vector2D& vOther); + + // arithmetic operations + Vector2D operator-(void) const; + + Vector2D operator+(const Vector2D& v) const; + Vector2D operator-(const Vector2D& v) const; + Vector2D operator*(const Vector2D& v) const; + Vector2D operator/(const Vector2D& v) const; + Vector2D operator*(float fl) const; + Vector2D operator/(float fl) const; + + // Cross product between two vectors. + Vector2D Cross(const Vector2D& vOther) const; + + // Returns a Vector2D with the min or max in X, Y, and Z. + Vector2D Min(const Vector2D& vOther) const; + Vector2D Max(const Vector2D& vOther) const; + +#else + + private: + // No copy constructors allowed if we're in optimal mode + Vector2D(const Vector2D& vOther); +#endif +}; + +//----------------------------------------------------------------------------- + +const inline Vector2D vec2_origin(0, 0); +const inline Vector2D vec2_invalid(FLT_MAX, FLT_MAX); + +//----------------------------------------------------------------------------- +// Vector2D related operations +//----------------------------------------------------------------------------- + +// Vector2D clear +void Vector2DClear(Vector2D& a); + +// Copy +void Vector2DCopy(const Vector2D& src, Vector2D& dst); + +// Vector2D arithmetic +void Vector2DAdd(const Vector2D& a, const Vector2D& b, Vector2D& result); +void Vector2DSubtract(const Vector2D& a, const Vector2D& b, Vector2D& result); +void Vector2DMultiply(const Vector2D& a, vec_t b, Vector2D& result); +void Vector2DMultiply(const Vector2D& a, const Vector2D& b, Vector2D& result); +void Vector2DDivide(const Vector2D& a, vec_t b, Vector2D& result); +void Vector2DDivide(const Vector2D& a, const Vector2D& b, Vector2D& result); +void Vector2DMA(const Vector2D& start, float s, const Vector2D& dir, + Vector2D& result); + +// Store the min or max of each of x, y, and z into the result. +void Vector2DMin(const Vector2D& a, const Vector2D& b, Vector2D& result); +void Vector2DMax(const Vector2D& a, const Vector2D& b, Vector2D& result); + +#define Vector2DExpand(v) (v).x, (v).y + +// Normalization +vec_t Vector2DNormalize(Vector2D& v); + +// Length +vec_t Vector2DLength(const Vector2D& v); + +// Dot Product +vec_t DotProduct2D(const Vector2D& a, const Vector2D& b); + +// Linearly interpolate between two vectors +void Vector2DLerp(const Vector2D& src1, const Vector2D& src2, vec_t t, + Vector2D& dest); + +//----------------------------------------------------------------------------- +// +// Inlined Vector2D methods +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// constructors +//----------------------------------------------------------------------------- + +inline Vector2D::Vector2D(void) { +#ifdef _DEBUG + // Initialize to NAN to catch errors + x = y = VEC_T_NAN; +#endif +} + +inline Vector2D::Vector2D(vec_t X, vec_t Y) { + x = X; + y = Y; + Assert(IsValid()); +} + +inline Vector2D::Vector2D(const float* pFloat) { + Assert(pFloat); + x = pFloat[0]; + y = pFloat[1]; + Assert(IsValid()); +} + +//----------------------------------------------------------------------------- +// copy constructor +//----------------------------------------------------------------------------- + +inline Vector2D::Vector2D(const Vector2D& vOther) { + Assert(vOther.IsValid()); + x = vOther.x; + y = vOther.y; +} + +//----------------------------------------------------------------------------- +// initialization +//----------------------------------------------------------------------------- + +inline void Vector2D::Init(vec_t ix, vec_t iy) { + x = ix; + y = iy; + Assert(IsValid()); +} + +inline void Vector2D::Random(float minVal, float maxVal) { + x = RandomFloat(minVal, maxVal); + y = RandomFloat(minVal, maxVal); +} + +inline void Vector2DClear(Vector2D& a) { a.x = a.y = 0.0f; } + +//----------------------------------------------------------------------------- +// assignment +//----------------------------------------------------------------------------- + +inline Vector2D& Vector2D::operator=(const Vector2D& vOther) { + Assert(vOther.IsValid()); + x = vOther.x; + y = vOther.y; + return *this; +} + +//----------------------------------------------------------------------------- +// Array access +//----------------------------------------------------------------------------- + +inline vec_t& Vector2D::operator[](int i) { + Assert((i >= 0) && (i < 2)); + return ((vec_t*)this)[i]; +} + +inline vec_t Vector2D::operator[](int i) const { + Assert((i >= 0) && (i < 2)); + return ((vec_t*)this)[i]; +} + +//----------------------------------------------------------------------------- +// Base address... +//----------------------------------------------------------------------------- + +inline vec_t* Vector2D::Base() { return (vec_t*)this; } + +inline vec_t const* Vector2D::Base() const { return (vec_t const*)this; } + +//----------------------------------------------------------------------------- +// IsValid? +//----------------------------------------------------------------------------- + +inline bool Vector2D::IsValid() const { return IsFinite(x) && IsFinite(y); } + +//----------------------------------------------------------------------------- +// comparison +//----------------------------------------------------------------------------- + +inline bool Vector2D::operator==(const Vector2D& src) const { + Assert(src.IsValid() && IsValid()); + return (src.x == x) && (src.y == y); +} + +inline bool Vector2D::operator!=(const Vector2D& src) const { + Assert(src.IsValid() && IsValid()); + return (src.x != x) || (src.y != y); +} + +//----------------------------------------------------------------------------- +// Copy +//----------------------------------------------------------------------------- + +inline void Vector2DCopy(const Vector2D& src, Vector2D& dst) { + Assert(src.IsValid()); + dst.x = src.x; + dst.y = src.y; +} + +inline void Vector2D::CopyToArray(float* rgfl) const { + Assert(IsValid()); + Assert(rgfl); + rgfl[0] = x; + rgfl[1] = y; +} + +//----------------------------------------------------------------------------- +// standard math operations +//----------------------------------------------------------------------------- + +inline void Vector2D::Negate() { + Assert(IsValid()); + x = -x; + y = -y; +} + +inline Vector2D& Vector2D::operator+=(const Vector2D& v) { + Assert(IsValid() && v.IsValid()); + x += v.x; + y += v.y; + return *this; +} + +inline Vector2D& Vector2D::operator-=(const Vector2D& v) { + Assert(IsValid() && v.IsValid()); + x -= v.x; + y -= v.y; + return *this; +} + +inline Vector2D& Vector2D::operator*=(float fl) { + x *= fl; + y *= fl; + Assert(IsValid()); + return *this; +} + +inline Vector2D& Vector2D::operator*=(const Vector2D& v) { + x *= v.x; + y *= v.y; + Assert(IsValid()); + return *this; +} + +inline Vector2D& Vector2D::operator/=(float fl) { + Assert(fl != 0.0f); + float oofl = 1.0f / fl; + x *= oofl; + y *= oofl; + Assert(IsValid()); + return *this; +} + +inline Vector2D& Vector2D::operator/=(const Vector2D& v) { + Assert(v.x != 0.0f && v.y != 0.0f); + x /= v.x; + y /= v.y; + Assert(IsValid()); + return *this; +} + +inline void Vector2DAdd(const Vector2D& a, const Vector2D& b, Vector2D& c) { + Assert(a.IsValid() && b.IsValid()); + c.x = a.x + b.x; + c.y = a.y + b.y; +} + +inline void Vector2DSubtract(const Vector2D& a, const Vector2D& b, + Vector2D& c) { + Assert(a.IsValid() && b.IsValid()); + c.x = a.x - b.x; + c.y = a.y - b.y; +} + +inline void Vector2DMultiply(const Vector2D& a, vec_t b, Vector2D& c) { + Assert(a.IsValid() && IsFinite(b)); + c.x = a.x * b; + c.y = a.y * b; +} + +inline void Vector2DMultiply(const Vector2D& a, const Vector2D& b, + Vector2D& c) { + Assert(a.IsValid() && b.IsValid()); + c.x = a.x * b.x; + c.y = a.y * b.y; +} + +inline void Vector2DDivide(const Vector2D& a, vec_t b, Vector2D& c) { + Assert(a.IsValid()); + Assert(b != 0.0f); + vec_t oob = 1.0f / b; + c.x = a.x * oob; + c.y = a.y * oob; +} + +inline void Vector2DDivide(const Vector2D& a, const Vector2D& b, Vector2D& c) { + Assert(a.IsValid()); + Assert((b.x != 0.0f) && (b.y != 0.0f)); + c.x = a.x / b.x; + c.y = a.y / b.y; +} + +inline void Vector2DMA(const Vector2D& start, float s, const Vector2D& dir, + Vector2D& result) { + Assert(start.IsValid() && IsFinite(s) && dir.IsValid()); + result.x = start.x + s * dir.x; + result.y = start.y + s * dir.y; +} + +// FIXME: Remove +// For backwards compatability +inline void Vector2D::MulAdd(const Vector2D& a, const Vector2D& b, + float scalar) { + x = a.x + b.x * scalar; + y = a.y + b.y * scalar; +} + +inline void Vector2DLerp(const Vector2D& src1, const Vector2D& src2, vec_t t, + Vector2D& dest) { + dest[0] = src1[0] + (src2[0] - src1[0]) * t; + dest[1] = src1[1] + (src2[1] - src1[1]) * t; +} + +//----------------------------------------------------------------------------- +// dot, cross +//----------------------------------------------------------------------------- +inline vec_t DotProduct2D(const Vector2D& a, const Vector2D& b) { + Assert(a.IsValid() && b.IsValid()); + return (a.x * b.x + a.y * b.y); +} + +// for backwards compatability +inline vec_t Vector2D::Dot(const Vector2D& vOther) const { + return DotProduct2D(*this, vOther); +} + +//----------------------------------------------------------------------------- +// length +//----------------------------------------------------------------------------- +inline vec_t Vector2DLength(const Vector2D& v) { + Assert(v.IsValid()); + return (vec_t)FastSqrt(v.x * v.x + v.y * v.y); +} + +inline vec_t Vector2D::LengthSqr(void) const { + Assert(IsValid()); + return (x * x + y * y); +} + +inline vec_t Vector2D::NormalizeInPlace() { return Vector2DNormalize(*this); } + +inline bool Vector2D::IsLengthGreaterThan(float val) const { + return LengthSqr() > val * val; +} + +inline bool Vector2D::IsLengthLessThan(float val) const { + return LengthSqr() < val * val; +} + +inline vec_t Vector2D::Length(void) const { return Vector2DLength(*this); } + +inline void Vector2DMin(const Vector2D& a, const Vector2D& b, + Vector2D& result) { + result.x = (a.x < b.x) ? a.x : b.x; + result.y = (a.y < b.y) ? a.y : b.y; +} + +inline void Vector2DMax(const Vector2D& a, const Vector2D& b, + Vector2D& result) { + result.x = (a.x > b.x) ? a.x : b.x; + result.y = (a.y > b.y) ? a.y : b.y; +} + +//----------------------------------------------------------------------------- +// Normalization +//----------------------------------------------------------------------------- +inline vec_t Vector2DNormalize(Vector2D& v) { + Assert(v.IsValid()); + vec_t l = v.Length(); + if (l != 0.0f) { + v /= l; + } else { + v.x = v.y = 0.0f; + } + return l; +} + +//----------------------------------------------------------------------------- +// Get the distance from this Vector2D to the other one +//----------------------------------------------------------------------------- +inline vec_t Vector2D::DistTo(const Vector2D& vOther) const { + Vector2D delta; + Vector2DSubtract(*this, vOther, delta); + return delta.Length(); +} + +inline vec_t Vector2D::DistToSqr(const Vector2D& vOther) const { + Vector2D delta; + Vector2DSubtract(*this, vOther, delta); + return delta.LengthSqr(); +} + +//----------------------------------------------------------------------------- +// Computes the closest point to vecTarget no farther than flMaxDist from +// vecStart +//----------------------------------------------------------------------------- +inline void ComputeClosestPoint2D(const Vector2D& vecStart, float flMaxDist, + const Vector2D& vecTarget, + Vector2D* pResult) { + Vector2D vecDelta; + Vector2DSubtract(vecTarget, vecStart, vecDelta); + float flDistSqr = vecDelta.LengthSqr(); + if (flDistSqr <= flMaxDist * flMaxDist) { + *pResult = vecTarget; + } else { + vecDelta /= FastSqrt(flDistSqr); + Vector2DMA(vecStart, flMaxDist, vecDelta, *pResult); + } +} + +//----------------------------------------------------------------------------- +// +// Slow methods +// +//----------------------------------------------------------------------------- + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +//----------------------------------------------------------------------------- +// Returns a Vector2D with the min or max in X, Y, and Z. +//----------------------------------------------------------------------------- + +inline Vector2D Vector2D::Min(const Vector2D& vOther) const { + return Vector2D(x < vOther.x ? x : vOther.x, y < vOther.y ? y : vOther.y); +} + +inline Vector2D Vector2D::Max(const Vector2D& vOther) const { + return Vector2D(x > vOther.x ? x : vOther.x, y > vOther.y ? y : vOther.y); +} + +//----------------------------------------------------------------------------- +// arithmetic operations +//----------------------------------------------------------------------------- + +inline Vector2D Vector2D::operator-(void) const { return Vector2D(-x, -y); } + +inline Vector2D Vector2D::operator+(const Vector2D& v) const { + Vector2D res; + Vector2DAdd(*this, v, res); + return res; +} + +inline Vector2D Vector2D::operator-(const Vector2D& v) const { + Vector2D res; + Vector2DSubtract(*this, v, res); + return res; +} + +inline Vector2D Vector2D::operator*(float fl) const { + Vector2D res; + Vector2DMultiply(*this, fl, res); + return res; +} + +inline Vector2D Vector2D::operator*(const Vector2D& v) const { + Vector2D res; + Vector2DMultiply(*this, v, res); + return res; +} + +inline Vector2D Vector2D::operator/(float fl) const { + Vector2D res; + Vector2DDivide(*this, fl, res); + return res; +} + +inline Vector2D Vector2D::operator/(const Vector2D& v) const { + Vector2D res; + Vector2DDivide(*this, v, res); + return res; +} + +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +#endif // slow + +#endif // VPC_MATHLIB_VECTOR2D_H_ diff --git a/public/p4lib/ip4.h b/public/p4lib/ip4.h new file mode 100644 index 0000000..78f16ec --- /dev/null +++ b/public/p4lib/ip4.h @@ -0,0 +1,184 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_P4LIB_IP4_H_ +#define VPC_P4LIB_IP4_H_ + +#include "tier1/utlsymbol.h" +#include "tier1/utlvector.h" +#include "tier1/utlstring.h" +#include "appframework/iappsystem.h" + +// Current perforce file state +enum P4FileState_t { + P4FILE_UNOPENED = 0, + P4FILE_OPENED_FOR_ADD, + P4FILE_OPENED_FOR_EDIT, + P4FILE_OPENED_FOR_DELETE, + P4FILE_OPENED_FOR_INTEGRATE, +}; + +// Purpose: definition of a file +struct P4File_t { + CUtlSymbol m_sName; // file name + CUtlSymbol m_sPath; // residing folder + CUtlSymbol m_sDepotFile; // the name in the depot + CUtlSymbol m_sClientFile; // the name on the client in Perforce syntax + CUtlSymbol m_sLocalFile; // the name on the client in local syntax + int m_iHeadRevision; // head revision number + int m_iHaveRevision; // the revision the clientspec has synced locally + bool m_bOpenedByOther; // opened by another user + bool m_bDir; // directory + bool m_bDeleted; // deleted + P4FileState_t m_eOpenState; // current change state + int m_iChangelist; // changelist current opened in +}; + +// Purpose: a single revision of a file +struct P4Revision_t { + int m_iChange; // changelist number + int m_nYear, m_nMonth, m_nDay; + int m_nHour, m_nMinute, m_nSecond; + + CUtlSymbol m_sUser; // submitting user + CUtlSymbol m_sClient; // submitting client + CUtlString m_Description; +}; + +// Purpose: a single clientspec +struct P4Client_t { + CUtlSymbol m_sName; + CUtlSymbol m_sUser; + CUtlSymbol m_sHost; // machine name this client is on + CUtlSymbol m_sLocalRoot; // local path +}; + +// Purpose: Interface to accessing P4 commands + +// descriptions should be limited to this size! +#define P4_MAX_INPUT_BUFFER_SIZE 16384 + +abstract_class IP4 : public IAppSystem { + public: + // name of the current clientspec + virtual P4Client_t &GetActiveClient() = 0; + + // changes the current client + virtual void SetActiveClient(const char *clientname) = 0; + + // Refreshes the current client from p4 settings + virtual void RefreshActiveClient() = 0; + + // translate filespecs into the desired syntax + virtual void GetDepotFilePath(char *depotFilePath, const char *filespec, + int size) = 0; + virtual void GetClientFilePath(char *clientFilePath, const char *filespec, + int size) = 0; + virtual void GetLocalFilePath(char *localFilePath, const char *filespec, + int size) = 0; + + // retreives the list of files in a path + virtual CUtlVector &GetFileList(const char *path) = 0; + + // returns the list of files opened for edit/integrate/delete + virtual void GetOpenedFileList(CUtlVector & fileList, + bool bDefaultChangeOnly) = 0; + virtual void GetOpenedFileList(const char *pRootDirectory, + CUtlVector &fileList) = 0; + virtual void GetOpenedFileListInPath(const char *pPathID, + CUtlVector &fileList) = 0; + + // retrieves revision history for a file or directory + virtual CUtlVector &GetRevisionList(const char *path, + bool bIsDir) = 0; + + // returns a list of clientspecs + virtual CUtlVector &GetClientList() = 0; + + // changes the clientspec to remove the specified path (cloaking) + virtual void RemovePathFromActiveClientspec(const char *path) = 0; + + // file manipulation + virtual bool OpenFileForAdd(const char *pFullPath) = 0; + virtual bool OpenFileForEdit(const char *pFullPath) = 0; + virtual bool OpenFileForDelete(const char *pFullPath) = 0; + virtual bool SyncFile( + const char *pFullPath, + int nRevision = + -1) = 0; // default revision is to sync to the head revision + + // submit/revert + virtual bool SubmitFile(const char *pFullPath, const char *pDescription) = 0; + virtual bool RevertFile(const char *pFullPath) = 0; + + // file checkin/checkout for multiple files + virtual bool OpenFilesForAdd(int nCount, const char **ppFullPathList) = 0; + virtual bool OpenFilesForEdit(int nCount, const char **ppFullPathList) = 0; + virtual bool OpenFilesForDelete(int nCount, const char **ppFullPathList) = 0; + + // submit/revert for multiple files + virtual bool SubmitFiles(int nCount, const char **ppFullPathList, + const char *pDescription) = 0; + virtual bool RevertFiles(int nCount, const char **ppFullPathList) = 0; + + // Is this file in perforce? + virtual bool IsFileInPerforce(const char *pFullPath) = 0; + + // Get the perforce file state + virtual P4FileState_t GetFileState(const char *pFullPath) = 0; + + // depot root + virtual const char *GetDepotRoot() = 0; + virtual int GetDepotRootLength() = 0; + + // local root + virtual const char *GetLocalRoot() = 0; + virtual int GetLocalRootLength() = 0; + + // Gets a string for a symbol + virtual const char *String(CUtlSymbol s) const = 0; + + // Returns which clientspec a file lies under. This will + // search for p4config files in root directories of the file + // It returns false if it didn't find a p4config file. + virtual bool GetClientSpecForFile(const char *pFullPath, char *pClientSpec, + int nMaxLen) = 0; + virtual bool GetClientSpecForDirectory(const char *pFullPathDir, + char *pClientSpec, int nMaxLen) = 0; + + // Returns which clientspec a filesystem path ID lies under. This will + // search for p4config files in all root directories of the all paths in + // the search path. NOTE: All directories in a path need to use the same + // clientspec or this function will return false. It returns false if it + // didn't find a p4config file. + virtual bool GetClientSpecForPath(const char *pPathId, char *pClientSpec, + int nMaxLen) = 0; + + // Opens a file in p4 win + virtual void OpenFileInP4Win(const char *pFullPath) = 0; + + // have we connected? if not, nothing works + virtual bool IsConnectedToServer(bool bRetry = true) = 0; + + // Returns file information for a single file + virtual bool GetFileInfo(const char *pFullPath, P4File_t *pFileInfo) = 0; + + // retrieves the list of files in a path, using a known client spec + virtual CUtlVector &GetFileListUsingClientSpec( + const char *pPath, const char *pClientSpec) = 0; + + // retrieves the last error from the last op (which is likely to span multiple + // lines) this is only valid after OpenFile[s]For{Add,Edit,Delete} or + // {Submit,Revert}File[s] + virtual const char *GetLastError() = 0; + + // sets the name of the changelist to open files under, NULL for "Default" + // changelist + virtual void SetOpenFileChangeList(const char *pChangeListName) = 0; + + virtual void GetFileListInChangelist(unsigned int changeListNumber, + CUtlVector &fileList) = 0; +}; + +DECLARE_TIER2_INTERFACE(IP4, p4); //-V707 + +#endif // VPC_P4LIB_IP4_H_ diff --git a/public/tier0/annotations.h b/public/tier0/annotations.h new file mode 100644 index 0000000..377aa44 --- /dev/null +++ b/public/tier0/annotations.h @@ -0,0 +1,84 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_ANALYSIS_ANNOTATIONS_H_ +#define VPC_TIER0_ANALYSIS_ANNOTATIONS_H_ + +#if _MSC_VER >= 1600 // VS 2010 and above. +// Upgrading important helpful warnings to errors + +// warning C4789: destination of memory copy is too small +#pragma warning(error : 4789) + +// Suppress some code analysis warnings +// Include the annotation header file. +#include + +// For temporarily suppressing warnings -- the warnings are suppressed for the +// next source line. +#define ANALYZE_SUPPRESS(wnum) __pragma(warning(suppress : wnum)) +#define ANALYZE_SUPPRESS2(wnum1, wnum2) __pragma(warning(supress : wnum1 wnum2)) +#define ANALYZE_SUPPRESS3(wnum1, wnum2, wnum3) \ + __pragma(warning(suppress : wnum1 wnum2 wnum3)) +#define ANALYZE_SUPPRESS4(wnum1, wnum2, wnum3, wnum4) \ + __pragma(warning(suppress : wnum1 wnum2 wnum3 wnum4)) + +// Tag all printf style format strings with this +#define PRINTF_FORMAT_STRING _Printf_format_string_ +#define SCANF_FORMAT_STRING _Scanf_format_string_impl_ +// Various macros for specifying the capacity of the buffer pointed +// to by a function parameter. Variations include in/out/inout, +// CAP (elements) versus BYTECAP (bytes), and null termination or +// not (_Z). +#define IN_Z _In_z_ +#define IN_CAP(x) _In_count_(x) +#define IN_BYTECAP(x) _In_bytecount_(x) +#define OUT_Z_CAP(x) _Out_z_cap_(x) +#define OUT_CAP(x) _Out_cap_(x) +#define OUT_BYTECAP(x) _Out_bytecap_(x) +#define OUT_Z_BYTECAP(x) _Out_z_bytecap_(x) +#define INOUT_BYTECAP(x) _Inout_bytecap_(x) +#define INOUT_Z_CAP(x) _Inout_z_cap_(x) +#define INOUT_Z_BYTECAP(x) _Inout_z_bytecap_(x) +// These macros are use for annotating array reference parameters, typically +// used in functions such as V_strcpy_safe. Because they are array references +// the capacity is already known. +#if _MSC_VER >= 1700 +#define IN_Z_ARRAY _Pre_z_ +#define OUT_Z_ARRAY _Post_z_ +#define INOUT_Z_ARRAY _Prepost_z_ +#else +#define IN_Z_ARRAY _Deref_pre_z_ +#define OUT_Z_ARRAY _Deref_post_z_ +#define INOUT_Z_ARRAY _Deref_prepost_z_ +#endif // _MSC_VER >= 1700 +// Use the macros above to annotate string functions that fill buffers as shown +// here, in order to give VS's /analyze more opportunities to find bugs. void +// V_wcsncpy( OUT_Z_BYTECAP(maxLenInBytes) wchar_t *pDest, wchar_t const *pSrc, +// int maxLenInBytes ); int V_snwprintf( OUT_Z_CAP(maxLenInCharacters) wchar_t +// *pDest, int maxLenInCharacters, PRINTF_FORMAT_STRING const wchar_t *pFormat, +// ... ); + +#endif // _MSC_VER >= 1600 // VS 2010 and above. + +#ifndef ANALYZE_SUPPRESS +#define ANALYZE_SUPPRESS(wnum) +#define ANALYZE_SUPPRESS2(wnum1, wnum2) +#define ANALYZE_SUPPRESS3(wnum1, wnum2, wnum3) +#define ANALYZE_SUPPRESS4(wnum1, wnum2, wnum3, wnum4) +#define PRINTF_FORMAT_STRING +#define SCANF_FORMAT_STRING +#define IN_Z +#define IN_CAP(x) +#define IN_BYTECAP(x) +#define OUT_Z_CAP(x) +#define OUT_CAP(x) +#define OUT_BYTECAP(x) +#define OUT_Z_BYTECAP(x) +#define INOUT_BYTECAP(x) +#define INOUT_Z_CAP(x) +#define INOUT_Z_BYTECAP(x) +#define OUT_Z_ARRAY +#define INOUT_Z_ARRAY +#endif + +#endif // VPC_TIER0_ANALYSIS_ANNOTATIONS_H_ diff --git a/public/tier0/basetypes.h b/public/tier0/basetypes.h new file mode 100644 index 0000000..ddccf99 --- /dev/null +++ b/public/tier0/basetypes.h @@ -0,0 +1,597 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_BASETYPES_H_ +#define VPC_TIER0_BASETYPES_H_ + +// This is a trick to get the DLL extension off the -D option on the command +// line. +#define DLLExtTokenPaste(x) #x +#define DLLExtTokenPaste2(x) DLLExtTokenPaste(x) +#define DLL_EXT_STRING DLLExtTokenPaste2(_DLL_EXT) + +////////////////////////////////////////////////////////////////////////// + +#ifndef schema +#define schema \ + namespace ValveSchemaMarker {} +#endif + +#ifdef COMPILING_SCHEMA +#define UNSCHEMATIZED_METHOD(x) +#else +#define UNSCHEMATIZED_METHOD(x) x +#endif + +////////////////////////////////////////////////////////////////////////// + +#include "tier0/platform.h" +#include "commonmacros.h" +#include "wchartypes.h" + +#ifdef _PS3 +#include +#elif defined(PLATFORM_POSIX) +#include +#endif + +#include "tier0/valve_off.h" + +// There's a different version of this file in the xbox codeline +// so the PC version built in the xbox branch includes things like +// tickrate changes. +#include "xbox_codeline_defines.h" + +#if defined(_PS3) +#include +#include +#define PATH_MAX CELL_FS_MAX_FS_PATH_LENGTH +#define _MAX_PATH PATH_MAX +#endif +// stdio.h +#ifndef NULL +#define NULL 0 +#endif + +#ifdef PLATFORM_POSIX +#include + +template +T abs(const T &a) { + if (a < 0) + return -a; + else + return a; +} +#endif + +#define ExecuteNTimes(nTimes, x) \ + { \ + static int __executeCount = 0; \ + if (__executeCount < nTimes) { \ + ++__executeCount; \ + x; \ + } \ + } + +#define ExecuteOnce(x) ExecuteNTimes(1, x) + +// Pad a number so it lies on an N byte boundary. +// So PAD_NUMBER(0,4) is 0 and PAD_NUMBER(1,4) is 4 +#define PAD_NUMBER(number, boundary) \ + (((number) + ((boundary)-1)) / (boundary)) * (boundary) + +// In case this ever changes +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// #define COMPILETIME_MAX and COMPILETIME_MIN for max/min in constant +// expressions +#define COMPILETIME_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define COMPILETIME_MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifdef __cplusplus + +template +inline T clamp(T const &val, Y const &minVal, X const &maxVal) { + if (val < minVal) return minVal; + + if (val > maxVal) return maxVal; + + return val; +} + +#else + +#define clamp(val, min, max) \ + (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) + +#endif + +#ifndef FALSE +#define FALSE 0 +#define TRUE (!FALSE) +#endif + +//----------------------------------------------------------------------------- +// fsel +//----------------------------------------------------------------------------- +#if !defined(_PS3) && !defined(_X360) + +#define fsel(c, x, y) ((c) >= 0 ? (x) : (y)) + +// integer conditional move +// if a >= 0, return x, else y +#define isel(a, x, y) (((a) >= 0) ? (x) : (y)) + +// if x = y, return a, else b +#define ieqsel(x, y, a, b) (((x) == (y)) ? (a) : (b)) + +// if the nth bit of a is set (counting with 0 = LSB), +// return x, else y +// this is fast if nbit is a compile-time immediate +#define ibitsel(a, nbit, x, y) ((((a) & (1 << (nbit))) != 0) ? (x) : (y)) + +FORCEINLINE double fpmin(double a, double b) { return a > b ? b : a; } +FORCEINLINE float fpmin(float a, float b) { return a > b ? b : a; } + +FORCEINLINE double fpmax(double a, double b) { return a >= b ? a : b; } +FORCEINLINE float fpmax(float a, float b) { return a >= b ? a : b; } + +// clamp x to lie inside [a,b]. Assumes b>a +FORCEINLINE float fclamp(float x, float a, float b) { + return fpmin(fpmax(x, a), b); +} +// clamp x to lie inside [a,b]. Assumes b>a +FORCEINLINE double fclamp(double x, double a, double b) { + return fpmin(fpmax(x, a), b); +} + +// At some point, we will need a unified API. +#define imin(x, y) ((x) < (y) ? (x) : (y)) +#define imax(x, y) ((x) > (y) ? (x) : (y)) +#define iclamp clamp + +#else + +// __fsel(double fComparand, double fValGE, double fLT) == fComparand >= 0 ? +// fValGE : fLT this is much faster than if ( aFloat > 0 ) { x = .. } the XDK +// defines two intrinsics, one for floats and one for doubles -- it's the same +// opcode, but the __fself version tells the compiler not to do a wasteful +// unnecessary rounding op after each sel. #define fsel __fsel +#ifdef _X360 +FORCEINLINE double fsel(double fComparand, double fValGE, double fLT) { + return __fsel(fComparand, fValGE, fLT); +} +FORCEINLINE float fsel(float fComparand, float fValGE, float fLT) { + return __fself(fComparand, fValGE, fLT); +} +#else +FORCEINLINE double fsel(double fComparand, double fValGE, double fLT) { + return __fsel(fComparand, fValGE, fLT); +} +FORCEINLINE float fsel(float fComparand, float fValGE, float fLT) { + return __fsels(fComparand, fValGE, fLT); +} +#endif + +#if !defined(_X360) +FORCEINLINE float fpmin(float a, float b) { return fsel(a - b, b, a); } +FORCEINLINE double fpmin(double a, double b) { return fsel(a - b, b, a); } + +FORCEINLINE float fpmax(float a, float b) { return fsel(a - b, a, b); } +FORCEINLINE double fpmax(double a, double b) { return fsel(a - b, a, b); } + +// any mixed calls should promote to double +FORCEINLINE double fpmax(float a, double b) { return fpmax((double)a, b); } +// any mixed calls should promote to double +FORCEINLINE double fpmax(double a, float b) { + return fpmax((double)a, (double)b); +} +#endif + +// clamp x to lie inside [a,b]. Assumes b>a +FORCEINLINE float fclamp(float x, float a, float b) { + return fpmin(fpmax(x, a), b); +} +// clamp x to lie inside [a,b]. Assumes b>a +FORCEINLINE double fclamp(double x, double a, double b) { + return fpmin(fpmax(x, a), b); +} + +// if a >= 0, return x, else y +FORCEINLINE int isel(int a, int x, int y) { + int mask = a >> 31; // arithmetic shift right, splat out the sign bit + return x + ((y - x) & mask); +}; + +// if a >= 0, return x, else y +FORCEINLINE unsigned isel(int a, unsigned x, unsigned y) { + int mask = a >> 31; // arithmetic shift right, splat out the sign bit + return x + ((y - x) & mask); +}; + +// ( x == y ) ? a : b +FORCEINLINE unsigned ieqsel(unsigned x, unsigned y, unsigned a, unsigned b) { + unsigned mask = (x == y) ? 0 : -1; + return a + ((b - a) & mask); +}; + +// ( x == y ) ? a : b +FORCEINLINE int ieqsel(int x, int y, int a, int b) { + int mask = (x == y) ? 0 : -1; + return a + ((b - a) & mask); +}; + +FORCEINLINE int imin(int x, int y) { + int nMaxSign = x - y; // Positive if x greater than y + int nMaxMask = + nMaxSign >> 31; // 0 if x greater than y, 0xffffffff if x smaller than y + int nMaxSaturated = y + (nMaxSign & nMaxMask); + return nMaxSaturated; +} + +FORCEINLINE int imax(int x, int y) { + int nMinSign = y - x; // Positive if x smaller than y + int nMinMask = + nMinSign >> 31; // 0 if x smaller than y, 0xffffffff if x greater than y + int nMinSaturated = y - (nMinSign & nMinMask); + return nMinSaturated; +} + +FORCEINLINE int iclamp(int x, int min, int max) { + int nResult = imin(x, max); + return imax(nResult, min); +} + +// if the nth bit of a is set (counting with 0 = LSB), +// return x, else y +// this is fast if nbit is a compile-time immediate +#define ibitsel(a, nbit, x, y) \ + ((x) + (((y) - (x)) & (((a) & (1 << (nbit))) ? 0 : -1))) + +#endif + +#if CROSS_PLATFORM_VERSION < 1 + +#ifndef DONT_DEFINE_BOOL // Needed for Cocoa stuff to compile. +typedef int BOOL; +#endif + +typedef int qboolean; +typedef uint8 BYTE; +typedef uint8 byte; +typedef uint16 word; +#endif + +#if defined(_WIN32) || defined(_PS3) +typedef wchar_t ucs2; // under windows & PS3 wchar_t is ucs2 +#else +typedef unsigned short ucs2; +#endif + +enum ThreeState_t { + TRS_FALSE, + TRS_TRUE, + TRS_NONE, +}; + +typedef float vec_t; +#ifdef _WIN32 +typedef __int32 + vec_t_as_gpr; // a general purpose register type equal in size to a vec_t + // (in case we have to avoid the fpu for some reason) +#endif + +template +inline T AlignValue(T val, uintp alignment) { + return (T)(((uintp)val + alignment - 1) & ~(alignment - 1)); +} + +// FIXME: this should move +#ifndef __cplusplus +#define true TRUE +#define false FALSE +#endif + +//----------------------------------------------------------------------------- +// look for NANs, infinities, and underflows. +// This assumes the ANSI/IEEE 754-1985 standard +//----------------------------------------------------------------------------- + +#ifdef __cplusplus + +inline uint32 FloatBits(const vec_t &f) { + union Convertor_t { + vec_t f; + uint32 ul; + } tmp; + + switch (false) { + case false:; + case (sizeof(tmp) == 4 && sizeof(tmp.f) == 4 && sizeof(tmp.ul) == 4):; + }; + tmp.f = f; + return tmp.ul; +} + +inline vec_t BitsToFloat(uint32 i) { + union Convertor_t { + vec_t f; + uint32 ul; + } tmp; + switch (false) { + case false:; + case (sizeof(tmp) == 4 && sizeof(tmp.f) == 4 && sizeof(tmp.ul) == 4):; + }; + tmp.ul = i; + return tmp.f; +} + +inline bool IsFinite(const vec_t &f) { +#ifdef _GAMECONSOLE + return f == f && fabs(f) <= FLT_MAX; +#else + return ((FloatBits(f) & 0x7F800000) != 0x7F800000); +#endif +} + +#if defined(_WIN32) + +#include + +// Just use prototype from math.h +#ifdef __cplusplus +extern "C" { +#endif +_Check_return_ double __cdecl fabs(_In_ double); +//_CRT_JIT_INTRINSIC _CRTIMP float __cdecl fabsf( __in float _X); +_Check_return_ float __cdecl fabsf(_In_ float); +#ifdef __cplusplus +} +#endif + +// In win32 try to use the intrinsic fabs so the optimizer can do it's thing +// inline in the code +#pragma intrinsic(fabs) +// Also, alias float make positive to use fabs, too +// NOTE: Is there a perf issue with double<->float conversion? +inline float FloatMakePositive(vec_t f) { return fabsf(f); } +#else +inline float FloatMakePositive(vec_t f) { + return fabsf(f); // was since 2002: BitsToFloat( FloatBits(f) & 0x7FFFFFFF ); + // fixed in 2010 +} +#endif + +inline float FloatNegate(vec_t f) { + return -f; // BitsToFloat( FloatBits(f) ^ 0x80000000 ); +} + +#define FLOAT32_NAN_BITS (uint32)0x7FC00000 // not a number! +#define FLOAT32_NAN BitsToFloat(FLOAT32_NAN_BITS) + +#define VEC_T_NAN FLOAT32_NAN + +#endif + +inline float FloatMakeNegative(vec_t f) { + return -fabsf(f); // was since 2002: BitsToFloat( FloatBits(f) | 0x80000000 + // ); fixed in 2010 +} + +// FIXME: why are these here? Hardly anyone actually needs them. +struct color24 { + ::byte r, g, b; +}; + +typedef struct alignas(const unsigned) color32_s { + bool operator!=(const struct color32_s &other) const; + ::byte r, g, b, a; + + // assign and copy by using the whole register rather + // than byte-by-byte copy. (No, the compiler is not + // smart enough to do this for you. /FAcs if you + // don't believe me.) + inline unsigned *asInt() { return reinterpret_cast(this); } + inline const unsigned *asInt(void) const { + return reinterpret_cast(this); + } + // This thing is in a union elsewhere, and union members can't have assignment + // operators, so you have to explicitly assign using this, or be slow. SUCK. + inline void Copy(const color32_s &rhs) { *asInt() = *rhs.asInt(); } + +} color32; + +inline bool color32::operator!=(const color32 &other) const { + return r != other.r || g != other.g || b != other.b || a != other.a; +} + +struct colorVec { + unsigned r, g, b, a; +}; + +#ifndef NOTE_UNUSED +#define NOTE_UNUSED(arg) ((void)arg) // for pesky compiler / lint warnings +#endif +#ifdef __cplusplus + +struct vrect_t { + int x, y, width, height; + vrect_t *pnext; +}; + +#endif + +//----------------------------------------------------------------------------- +// MaterialRect_t struct - used for DrawDebugText +//----------------------------------------------------------------------------- +struct Rect_t { + int x, y; + int width, height; +}; + +struct Rect3D_t { + int x, y, z; + int width, height, depth; + + FORCEINLINE Rect3D_t(int nX, int nY, int nZ, int nWidth, int nHeight, + int nDepth) { + x = nX; + y = nY; + z = nZ; + width = nWidth; + height = nHeight; + depth = nDepth; + } + + FORCEINLINE Rect3D_t() = default; +}; + +//----------------------------------------------------------------------------- +// Interval, used by soundemittersystem + the game +//----------------------------------------------------------------------------- +struct interval_t { + float start; + float range; +}; + +//----------------------------------------------------------------------------- +// Declares a type-safe handle type; you can't assign one handle to the next +//----------------------------------------------------------------------------- + +// 32-bit pointer handles. + +// Typesafe 8-bit and 16-bit handles. +template +class CBaseIntHandle { + public: + inline bool operator==(const CBaseIntHandle &other) { + return m_Handle == other.m_Handle; + } + inline bool operator!=(const CBaseIntHandle &other) { + return m_Handle != other.m_Handle; + } + + // Only the code that doles out these handles should use these functions. + // Everyone else should treat them as a transparent type. + inline HandleType GetHandleValue() { return m_Handle; } + inline void SetHandleValue(HandleType val) { m_Handle = val; } + + typedef HandleType HANDLE_TYPE; + + protected: + HandleType m_Handle; +}; + +template +class CIntHandle16 : public CBaseIntHandle { + public: + inline CIntHandle16() {} + + static inline CIntHandle16 MakeHandle(HANDLE_TYPE val) { + return CIntHandle16(val); + } + + protected: + inline CIntHandle16(HANDLE_TYPE val) { m_Handle = val; } +}; + +template +class CIntHandle32 : public CBaseIntHandle { + public: + inline CIntHandle32() {} + + static inline CIntHandle32 MakeHandle(HANDLE_TYPE val) { + return CIntHandle32(val); + } + + protected: + inline CIntHandle32(HANDLE_TYPE val) { m_Handle = val; } +}; + +// NOTE: This macro is the same as windows uses; so don't change the guts of it +#define DECLARE_HANDLE_16BIT(name) \ + typedef CIntHandle16 name; +#define DECLARE_HANDLE_32BIT(name) \ + typedef CIntHandle32 name; + +#define DECLARE_POINTER_HANDLE(name) \ + struct name##__ { \ + int unused; \ + }; \ + typedef struct name##__ *name +#define FORWARD_DECLARE_HANDLE(name) typedef struct name##__ *name + +#define DECLARE_DERIVED_POINTER_HANDLE(_name, _basehandle) \ + struct _name##__ : public _basehandle##__ {}; \ + typedef struct _name##__ *_name +#define DECLARE_ALIASED_POINTER_HANDLE(_name, _alias) \ + typedef struct _alias##__ *name + +// @TODO: Find a better home for this +#if !defined(_STATIC_LINKED) && !defined(PUBLISH_DLL_SUBSYSTEM) +// for platforms built with dynamic linking, the dll interface does not need +// spoofing +#define PUBLISH_DLL_SUBSYSTEM() +#endif + +#define UID_PREFIX generated_id_ +#define UID_CAT1(a, c) a##c +#define UID_CAT2(a, c) UID_CAT1(a, c) +#define EXPAND_CONCAT(a, c) UID_CAT1(a, c) +#ifdef _MSC_VER +#define UNIQUE_ID UID_CAT2(UID_PREFIX, __COUNTER__) +#else +#define UNIQUE_ID UID_CAT2(UID_PREFIX, __LINE__) +#endif + +#define _MKSTRING(arg) #arg +#define MKSTRING(arg) _MKSTRING(arg) + +// this allows enumerations to be used as flags, and still remain type-safe! +#define DEFINE_ENUM_BITWISE_OPERATORS(Type) \ + inline Type operator|(Type a, Type b) { return Type(int(a) | int(b)); } \ + inline Type operator&(Type a, Type b) { return Type(int(a) & int(b)); } \ + inline Type operator^(Type a, Type b) { return Type(int(a) ^ int(b)); } \ + inline Type operator<<(Type a, int b) { return Type(int(a) << b); } \ + inline Type operator>>(Type a, int b) { return Type(int(a) >> b); } \ + inline Type &operator|=(Type &a, Type b) { return a = a | b; } \ + inline Type &operator&=(Type &a, Type b) { return a = a & b; } \ + inline Type &operator^=(Type &a, Type b) { return a = a ^ b; } \ + inline Type &operator<<=(Type &a, int b) { return a = a << b; } \ + inline Type &operator>>=(Type &a, int b) { return a = a >> b; } \ + inline Type operator~(Type a) { return Type(~int(a)); } + +// defines increment/decrement operators for enums for easy iteration +#define DEFINE_ENUM_INCREMENT_OPERATORS(Type) \ + inline Type &operator++(Type &a) { return a = Type(int(a) + 1); } \ + inline Type &operator--(Type &a) { return a = Type(int(a) - 1); } \ + inline Type operator++(Type &a, int) { \ + Type t = a; \ + ++a; \ + return t; \ + } \ + inline Type operator--(Type &a, int) { \ + Type t = a; \ + --a; \ + return t; \ + } + +// Max 2 player splitscreen in portal (don't merge this back), saves a bunch of +// memory [8/31/2010 tom] +#define MAX_SPLITSCREEN_CLIENT_BITS 1 +// this should == MAX_JOYSTICKS in InputEnums.h +#define MAX_SPLITSCREEN_CLIENTS (1 << MAX_SPLITSCREEN_CLIENT_BITS) // 2 + +#include "tier0/valve_on.h" + +#endif // VPC_TIER0_BASETYPES_H_ diff --git a/public/tier0/commonmacros.h b/public/tier0/commonmacros.h new file mode 100644 index 0000000..242d525 --- /dev/null +++ b/public/tier0/commonmacros.h @@ -0,0 +1,40 @@ +// Copyright Valve Corporation, All rights reserved. +// +// This should contain ONLY general purpose macros that are +// appropriate for use in engine/launcher/all tools + +#ifndef VPC_TIER0_COMMONMACROS_H_ +#define VPC_TIER0_COMMONMACROS_H_ + +// Makes a 4-byte "packed ID" int out of 4 characters +#define MAKEID(d, c, b, a) \ + (((int)(a) << 24) | ((int)(b) << 16) | ((int)(c) << 8) | ((int)(d))) + +// Compares a string with a 4-byte packed ID constant +#define STRING_MATCHES_ID(p, id) ((*((int *)(p)) == (id)) ? true : false) +#define ID_TO_STRING(id, p) \ + ((p)[3] = (((id) >> 24) & 0xFF), (p)[2] = (((id) >> 16) & 0xFF), \ + (p)[1] = (((id) >> 8) & 0xFF), (p)[0] = (((id) >> 0) & 0xFF)) + +#define V_ARRAYSIZE(p) (sizeof(p) / sizeof(p[0])) + +#define SETBITS(iBitVector, bits) ((iBitVector) |= (bits)) +#define CLEARBITS(iBitVector, bits) ((iBitVector) &= ~(bits)) +#define FBitSet(iBitVector, bits) ((iBitVector) & (bits)) + +inline bool IsPowerOfTwo(int value) { return (value & (value - 1)) == 0; } + +#ifndef REFERENCE +#define REFERENCE(arg) ((void)arg) +#endif + +// Wraps the integer in quotes, allowing us to form constant strings with +// it +#define CONST_INTEGER_AS_STRING(x) #x +//__LINE__ can only be converted to an actual number by going through +// this, otherwise the output is literally "__LINE__" +#define __HACK_LINE_AS_STRING__(x) CONST_INTEGER_AS_STRING(x) +// Gives you the line number in constant string form +#define __LINE__AS_STRING __HACK_LINE_AS_STRING__(__LINE__) + +#endif // VPC_TIER0_COMMONMACROS_H_ diff --git a/public/tier0/dbg.h b/public/tier0/dbg.h new file mode 100644 index 0000000..36bfee0 --- /dev/null +++ b/public/tier0/dbg.h @@ -0,0 +1,736 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_DBG_H_ +#define VPC_TIER0_DBG_H_ + +#include "tier0/platform.h" +#include "tier0/basetypes.h" +#include "dbgflag.h" +#include "logging.h" + +#include +#include +#include + +//----------------------------------------------------------------------------- +// dll export stuff +//----------------------------------------------------------------------------- + +class Color; + +//----------------------------------------------------------------------------- +// Usage model for the Dbg library +// +// 1. Assertions. +// +// Assertions are used to detect and warn about invalid states +// +// To use an assertion, use +// +// Assert( (f == 5) ); +// AssertMsg( (f == 5), ("F needs to be %d here!\n", 5) ); +// AssertFunc( (f == 5), BadFunc() ); +// AssertEquals( f, 5 ); +// AssertFloatEquals( f, 5.0f, 1e-3 ); +// +// The first will simply report that an assertion failed on a particular +// code file and line. The second version will display a print-f formatted +// message along with the file and line, the third will display a generic +// message and will also cause the function BadFunc to be executed, and the +// last two will report an error if f is not equal to 5 (the last one asserts +// within a particular tolerance). +// +// 2. Code activation +// +// To cause code to be run only in debug builds, use DBG_CODE: +// An example is below. +// +// DBG_CODE( +// { +// int x = 5; +// ++x; +// } +// ); +// +// Code can be activated based on the dynamic spew groups also. Use +// +// DBG_DCODE( "group", level, +// { int x = 5; ++x; } +// ); +// +// 3. Breaking into the debugger. +// +// To cause an unconditional break into the debugger in debug builds only, use +// DBG_BREAK +// +// DBG_BREAK(); +// +// You can force a break in any build (release or debug) using +// +// DebuggerBreak(); +//----------------------------------------------------------------------------- + +PLATFORM_INTERFACE void _ExitOnFatalAssert(const tchar *pFile, int line); + +#if defined(DBGFLAG_STRINGS_STRIP) +#define DbgFlagMacro_ExitOnFatalAssert(pFile, line) _ExitOnFatalAssert("", 0) +#else +#define DbgFlagMacro_ExitOnFatalAssert(pFile, line) \ + _ExitOnFatalAssert(pFile, line) +#endif + +PLATFORM_INTERFACE bool ShouldUseNewAssertDialog(); + +PLATFORM_INTERFACE bool SetupWin32ConsoleIO(); + +// Returns true if they want to break in the debugger. +PLATFORM_INTERFACE bool DoNewAssertDialog(const tchar *pFile, int line, + const tchar *pExpression); + +#if defined(DBGFLAG_STRINGS_STRIP) +#define DbgFlagMacro_DoNewAssertDialog(pFile, line, pExpression) \ + DoNewAssertDialog("", 0, "") +#else +#define DbgFlagMacro_DoNewAssertDialog(pFile, line, pExpression) \ + DoNewAssertDialog(pFile, line, pExpression) +#endif + +/* Used to define macros, never use these directly. */ + +#ifdef _PREFAST_ +// When doing /analyze builds define the assert macros to be __analysis_assume. +// This tells the compiler to assume that the condition is true, which helps to +// suppress many warnings. This define is done in debug and release builds, but +// debug builds should be preferred for static analysis because some asserts are +// compiled out in release. The unfortunate !! is necessary because otherwise +// /analyze is incapable of evaluating all of the logical expressions that the +// regular compiler can handle. +#define _AssertMsg(_exp, _msg, _executeExp, _bFatal) __analysis_assume(!!(_exp)) +#define _AssertMsgOnce(_exp, _msg, _bFatal) __analysis_assume(!!(_exp)) +// Force asserts on for /analyze so that we get a __analysis_assume of all of +// the constraints. +#define DBGFLAG_ASSERT +#define DBGFLAG_ASSERTFATAL +#else +#define _AssertMsg(_exp, _msg, _executeExp, _bFatal) \ + do { \ + if (!(_exp)) { \ + LoggingResponse_t ret = \ + Log_Assert("%s (%d) : %s\n", __TFILE__, __LINE__, _msg); \ + _executeExp; \ + if (ret == LR_DEBUGGER) { \ + if (!ShouldUseNewAssertDialog() || \ + DbgFlagMacro_DoNewAssertDialog(__TFILE__, __LINE__, _msg)) \ + DebuggerBreak(); \ + if (_bFatal) DbgFlagMacro_ExitOnFatalAssert(__TFILE__, __LINE__); \ + } \ + } \ + } while (0) + +#define _AssertMsgOnce(_exp, _msg, _bFatal) \ + do { \ + static bool fAsserted; \ + if (!fAsserted) { \ + _AssertMsg(_exp, _msg, (fAsserted = true), _bFatal); \ + } \ + } while (0) +#endif + +/* Spew macros... */ + +// AssertFatal macros +// AssertFatal is used to detect an unrecoverable error condition. +// If enabled, it may display an assert dialog (if DBGFLAG_ASSERTDLG is turned +// on or running under the debugger), and always terminates the application + +#ifdef DBGFLAG_ASSERTFATAL + +#define AssertFatal(_exp) \ + _AssertMsg(_exp, _T("Assertion Failed: ") _T(#_exp), ((void)0), true) +#define AssertFatalOnce(_exp) \ + _AssertMsgOnce(_exp, _T("Assertion Failed: ") _T(#_exp), true) +#define AssertFatalMsg(_exp, _msg) _AssertMsg(_exp, _msg, ((void)0), true) +#define AssertFatalMsgOnce(_exp, _msg) _AssertMsgOnce(_exp, _msg, true) +#define AssertFatalFunc(_exp, _f) _AssertMsg( _exp, _T("Assertion Failed: " _T(#_exp), _f, true ) +#define AssertFatalEquals(_exp, _expectedValue) \ + AssertFatalMsg2((_exp) == (_expectedValue), _T("Expected %d but got %d!"), \ + (_expectedValue), (_exp)) +#define AssertFatalFloatEquals(_exp, _expectedValue, _tol) \ + AssertFatalMsg2(fabs((_exp) - (_expectedValue)) <= (_tol), \ + _T("Expected %f but got %f!"), (_expectedValue), (_exp)) +#define VerifyFatal(_exp) AssertFatal(_exp) +#define VerifyEqualsFatal(_exp, _expectedValue) \ + AssertFatalEquals(_exp, _expectedValue) + +#define AssertFatalMsg1(_exp, _msg, a1) \ + AssertFatalMsg(_exp, (const tchar *)(CDbgFmtMsg(_msg, a1))) +#define AssertFatalMsg2(_exp, _msg, a1, a2) \ + AssertFatalMsg(_exp, (const tchar *)(CDbgFmtMsg(_msg, a1, a2))) +#define AssertFatalMsg3(_exp, _msg, a1, a2, a3) \ + AssertFatalMsg(_exp, (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3))) +#define AssertFatalMsg4(_exp, _msg, a1, a2, a3, a4) \ + AssertFatalMsg(_exp, (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3, a4))) +#define AssertFatalMsg5(_exp, _msg, a1, a2, a3, a4, a5) \ + AssertFatalMsg(_exp, (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3, a4, a5))) +#define AssertFatalMsg6(_exp, _msg, a1, a2, a3, a4, a5, a6) \ + AssertFatalMsg(_exp, \ + (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3, a4, a5, a6))) +#define AssertFatalMsg6(_exp, _msg, a1, a2, a3, a4, a5, a6) \ + AssertFatalMsg(_exp, \ + (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3, a4, a5, a6))) +#define AssertFatalMsg7(_exp, _msg, a1, a2, a3, a4, a5, a6, a7) \ + AssertFatalMsg( \ + _exp, (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3, a4, a5, a6, a7))) +#define AssertFatalMsg8(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8) \ + AssertFatalMsg( \ + _exp, (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3, a4, a5, a6, a7, a8))) +#define AssertFatalMsg9(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ + AssertFatalMsg(_exp, (const tchar *)(CDbgFmtMsg(_msg, a1, a2, a3, a4, a5, \ + a6, a7, a8, a9))) + +#else // DBGFLAG_ASSERTFATAL + +#define AssertFatal(_exp) ((void)0) +#define AssertFatalOnce(_exp) ((void)0) +#define AssertFatalMsg(_exp, _msg) ((void)0) +#define AssertFatalMsgOnce(_exp, _msg) ((void)0) +#define AssertFatalFunc(_exp, _f) ((void)0) +#define AssertFatalEquals(_exp, _expectedValue) ((void)0) +#define AssertFatalFloatEquals(_exp, _expectedValue, _tol) ((void)0) +#define VerifyFatal(_exp) (_exp) +#define VerifyEqualsFatal(_exp, _expectedValue) (_exp) + +#define AssertFatalMsg1(_exp, _msg, a1) ((void)0) +#define AssertFatalMsg2(_exp, _msg, a1, a2) ((void)0) +#define AssertFatalMsg3(_exp, _msg, a1, a2, a3) ((void)0) +#define AssertFatalMsg4(_exp, _msg, a1, a2, a3, a4) ((void)0) +#define AssertFatalMsg5(_exp, _msg, a1, a2, a3, a4, a5) ((void)0) +#define AssertFatalMsg6(_exp, _msg, a1, a2, a3, a4, a5, a6) ((void)0) +#define AssertFatalMsg6(_exp, _msg, a1, a2, a3, a4, a5, a6) ((void)0) +#define AssertFatalMsg7(_exp, _msg, a1, a2, a3, a4, a5, a6, a7) ((void)0) +#define AssertFatalMsg8(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8) ((void)0) +#define AssertFatalMsg9(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ + ((void)0) + +#endif // DBGFLAG_ASSERTFATAL + +// lightweight assert macros: in theory, can be run in release without slowing +// it down +#if defined(_CERT) || defined(_RETAIL) +#define AssertAligned(PTR) +#else +#if defined(_X360) +#define AssertAligned(PTR) \ + __twnei(intp(PTR) & 0xF, \ + 0) // trap if not equal to immediate value; unsigned comparison +#elif defined(DBGFLAG_ASSERT) +#define AssertAligned(adr) Assert((((intp)(adr)) & 0xf) == 0) +#else +#define AssertAligned(PTR) +#endif +#endif + +// Assert macros +// Assert is used to detect an important but survivable error. +// It's only turned on when DBGFLAG_ASSERT is true. + +#ifdef DBGFLAG_ASSERT + +#define Assert(_exp) \ + _AssertMsg(_exp, _T("Assertion Failed: ") _T(#_exp), ((void)0), false) +#define AssertMsg_(_exp, _msg) _AssertMsg(_exp, _msg, ((void)0), false) +#define AssertOnce(_exp) \ + _AssertMsgOnce(_exp, _T("Assertion Failed: ") _T(#_exp), false) +#define AssertMsgOnce(_exp, _msg) _AssertMsgOnce(_exp, _msg, false) +#define AssertFunc(_exp, _f) \ + _AssertMsg(_exp, _T("Assertion Failed: ") _T(#_exp), _f, false) +#define AssertEquals(_exp, _expectedValue) \ + AssertMsg2((_exp) == (_expectedValue), _T("Expected %d but got %d!"), \ + (_expectedValue), (_exp)) +#define AssertFloatEquals(_exp, _expectedValue, _tol) \ + AssertMsg2(fabs((_exp) - (_expectedValue)) <= (_tol), \ + _T("Expected %f but got %f!"), (_expectedValue), (_exp)) +#define Verify(_exp) Assert(_exp) +#define VerifyEquals(_exp, _expectedValue) AssertEquals(_exp, _expectedValue) + +#define AssertMsg(_exp, _msg) AssertMsg_(_exp, _T(_msg)) +#define AssertMsg1(_exp, _msg, a1) \ + AssertMsg_(_exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1))) +#define AssertMsg2(_exp, _msg, a1, a2) \ + AssertMsg_(_exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2))) +#define AssertMsg3(_exp, _msg, a1, a2, a3) \ + AssertMsg_(_exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2, a3))) +#define AssertMsg4(_exp, _msg, a1, a2, a3, a4) \ + AssertMsg_(_exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2, a3, a4))) +#define AssertMsg5(_exp, _msg, a1, a2, a3, a4, a5) \ + AssertMsg_(_exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2, a3, a4, a5))) +#define AssertMsg6(_exp, _msg, a1, a2, a3, a4, a5, a6) \ + AssertMsg_(_exp, \ + (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2, a3, a4, a5, a6))) +#define AssertMsg7(_exp, _msg, a1, a2, a3, a4, a5, a6, a7) \ + AssertMsg_( \ + _exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2, a3, a4, a5, a6, a7))) +#define AssertMsg8(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8) \ + AssertMsg_(_exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2, a3, a4, a5, \ + a6, a7, a8))) +#define AssertMsg9(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ + AssertMsg_(_exp, (const tchar *)(CDbgFmtMsg(_T(_msg), a1, a2, a3, a4, a5, \ + a6, a7, a8, a9))) + +#else // DBGFLAG_ASSERT + +#define Assert(_exp) ((void)0) +#define AssertOnce(_exp) ((void)0) +#define AssertMsg(_exp, _msg) ((void)0) +#define AssertMsgOnce(_exp, _msg) ((void)0) +#define AssertFunc(_exp, _f) ((void)0) +#define AssertEquals(_exp, _expectedValue) ((void)0) +#define AssertFloatEquals(_exp, _expectedValue, _tol) ((void)0) +#define Verify(_exp) (_exp) +#define VerifyEquals(_exp, _expectedValue) (_exp) + +#define AssertMsg1(_exp, _msg, a1) ((void)0) +#define AssertMsg2(_exp, _msg, a1, a2) ((void)0) +#define AssertMsg3(_exp, _msg, a1, a2, a3) ((void)0) +#define AssertMsg4(_exp, _msg, a1, a2, a3, a4) ((void)0) +#define AssertMsg5(_exp, _msg, a1, a2, a3, a4, a5) ((void)0) +#define AssertMsg6(_exp, _msg, a1, a2, a3, a4, a5, a6) ((void)0) +#define AssertMsg6(_exp, _msg, a1, a2, a3, a4, a5, a6) ((void)0) +#define AssertMsg7(_exp, _msg, a1, a2, a3, a4, a5, a6, a7) ((void)0) +#define AssertMsg8(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8) ((void)0) +#define AssertMsg9(_exp, _msg, a1, a2, a3, a4, a5, a6, a7, a8, a9) ((void)0) + +#endif // DBGFLAG_ASSERT + +#define STRINGIFY_INTERNAL(x) #x +#define STRINGIFY(x) STRINGIFY_INTERNAL(x) + +#define FILE_LINE_FUNCTION_STRING \ + __FILE__ "(" STRINGIFY(__LINE__) "):" __FUNCTION__ ":" +#define FILE_LINE_STRING __FILE__ "(" STRINGIFY(__LINE__) "):" +#define FUNCTION_LINE_STRING __FUNCTION__ "(" STRINGIFY(__LINE__) "): " + +////////////////////////////////////////////////////////////////////////// +// Legacy Logging System +////////////////////////////////////////////////////////////////////////// + +// Channels which map the legacy logging system to the new system. + +// Channel for all default Msg/Warning/Error commands. +DECLARE_LOGGING_CHANNEL(LOG_GENERAL); +// Channel for all asserts. +DECLARE_LOGGING_CHANNEL(LOG_ASSERT); +// Channel for all ConMsg and ConColorMsg commands. +DECLARE_LOGGING_CHANNEL(LOG_CONSOLE); +// Channel for all DevMsg and DevWarning commands with level < 2. +DECLARE_LOGGING_CHANNEL(LOG_DEVELOPER); +// Channel for ConDMsg commands. +DECLARE_LOGGING_CHANNEL(LOG_DEVELOPER_CONSOLE); +// Channel for all DevMsg and DevWarning commands with level >= 2. +DECLARE_LOGGING_CHANNEL(LOG_DEVELOPER_VERBOSE); + +// Legacy logging functions + +PLATFORM_INTERFACE void Error(const tchar *pMsg, ...) FMTFUNCTION(1, 2); +PLATFORM_INTERFACE void Error_SpewCallStack(int iMaxCallStackLength, + const tchar *pMsg, ...) + FMTFUNCTION(2, 3); + +#if defined(DBGFLAG_STRINGS_STRIP) && !defined(TIER0_DLL_EXPORT) + +#define Msg(...) ((void)0) +#define Warning(...) ((void)0) +#define Warning_SpewCallStack(...) ((void)0) +#define DevMsg(...) ((void)0) +#define DevWarning(...) ((void)0) +#define ConColorMsg(...) ((void)0) +#define ConMsg(...) ((void)0) +#define ConDMsg(...) ((void)0) +#define COM_TimestampedLog(...) ((void)0) + +#else // #if defined( DBGFLAG_STRINGS_STRIP ) && !defined( TIER0_DLL_EXPORT ) + +PLATFORM_INTERFACE void Msg(const tchar *pMsg, ...); +PLATFORM_INTERFACE void Warning(const tchar *pMsg, ...) FMTFUNCTION(1, 2); +PLATFORM_INTERFACE void Warning_SpewCallStack(int iMaxCallStackLength, + const tchar *pMsg, ...) + FMTFUNCTION(2, 3); + +#ifdef _PS3 + +PLATFORM_OVERLOAD void DevMsg(int level, const tchar *pMsg, ...) + FMTFUNCTION(2, 3); +PLATFORM_OVERLOAD void DevWarning(int level, const tchar *pMsg, ...) + FMTFUNCTION(2, 3); + +PLATFORM_INTERFACE void DevMsg(const tchar *pMsg, ...) FMTFUNCTION(1, 2); +PLATFORM_INTERFACE void DevWarning(const tchar *pMsg, ...) FMTFUNCTION(1, 2); + +PLATFORM_INTERFACE void ConColorMsg(const Color &clr, const tchar *pMsg, ...) + FMTFUNCTION(2, 3); +PLATFORM_INTERFACE void ConMsg(const tchar *pMsg, ...) FMTFUNCTION(1, 2); + +#else // !_PS3 + +PLATFORM_INTERFACE void DevMsg(int level, const tchar *pMsg, ...) + FMTFUNCTION(2, 3); +PLATFORM_INTERFACE void DevWarning(int level, const tchar *pMsg, ...) + FMTFUNCTION(2, 3); + +PLATFORM_OVERLOAD void DevMsg(const tchar *pMsg, ...) FMTFUNCTION(1, 2); +PLATFORM_OVERLOAD void DevWarning(const tchar *pMsg, ...) FMTFUNCTION(1, 2); + +PLATFORM_OVERLOAD void ConColorMsg(const Color &clr, const tchar *pMsg, ...) + FMTFUNCTION(2, 3); +PLATFORM_OVERLOAD void ConMsg(const tchar *pMsg, ...) FMTFUNCTION(1, 2); + +#endif // _PS3 + +PLATFORM_INTERFACE void ConDMsg(const tchar *pMsg, ...) FMTFUNCTION(1, 2); + +PLATFORM_INTERFACE void COM_TimestampedLog(char const *fmt, ...) + FMTFUNCTION(1, 2); + +#endif // #if defined( DBGFLAG_STRINGS_STRIP ) && !defined( TIER0_DLL_EXPORT ) + +// You can use this macro like a runtime assert macro. +// If the condition fails, then Error is called with the message. This macro is +// called like AssertMsg, where msg must be enclosed in parenthesis: +// +// ErrorIfNot( bCondition, ("a b c %d %d %d", 1, 2, 3) ); +#define ErrorIfNot(condition, msg) \ + if ((condition)) \ + ; \ + else { \ + Error msg; \ + } + +#ifdef _DEBUG +#define DebugMsg(...) DevMsg(__VA_ARGS__) +#else +#define DebugMsg(...) +#endif + +// @TODO: these callstack spew functions are currently disabled in the new +// logging system. Need to add support for these if desired. +PLATFORM_INTERFACE void _Warning_AlwaysSpewCallStack_Enable(bool bEnable); +PLATFORM_INTERFACE void _Warning_AlwaysSpewCallStack_Length( + int iMaxCallStackLength); + +PLATFORM_INTERFACE void _Error_AlwaysSpewCallStack_Enable(bool bEnable); +PLATFORM_INTERFACE void _Error_AlwaysSpewCallStack_Length( + int iMaxCallStackLength); + +/* Code macros, debugger interface */ + +#ifdef _DEBUG + +#define DBG_CODE(_code) \ + if (0) \ + ; \ + else { \ + _code \ + } +#define DBG_CODE_NOSCOPE(_code) _code +#define DBG_DCODE(_g, _l, _code) \ + if (IsSpewActive(_g, _l)) { \ + _code \ + } else { \ + } +#define DBG_BREAK() DebuggerBreak() /* defined in platform.h */ + +#else /* not _DEBUG */ + +#define DBG_CODE(_code) ((void)0) +#define DBG_CODE_NOSCOPE(_code) +#define DBG_DCODE(_g, _l, _code) ((void)0) +#define DBG_BREAK() ((void)0) + +#endif /* _DEBUG */ + +//----------------------------------------------------------------------------- +// Macro to assist in asserting constant invariants during compilation + +#ifdef _DEBUG +#define COMPILE_TIME_ASSERT(pred) static_assert(pred) +#define ASSERT_INVARIANT(pred) \ + static void UNIQUE_ID() { COMPILE_TIME_ASSERT(pred) } +#else +#define COMPILE_TIME_ASSERT(pred) +#define ASSERT_INVARIANT(pred) +#endif + +#ifdef _DEBUG +template +inline DEST_POINTER_TYPE assert_cast(SOURCE_POINTER_TYPE *pSource) { + Assert(static_cast(pSource) == + dynamic_cast(pSource)); + return static_cast(pSource); +} +#else +#define assert_cast static_cast +#endif + +//----------------------------------------------------------------------------- +// Templates to assist in validating pointers: + +// Have to use these stubs so we don't have to include windows.h here. +PLATFORM_INTERFACE void _AssertValidReadPtr(void *ptr, int count = 1); +PLATFORM_INTERFACE void _AssertValidWritePtr(void *ptr, int count = 1); +PLATFORM_INTERFACE void _AssertValidReadWritePtr(void *ptr, int count = 1); +PLATFORM_INTERFACE void _AssertValidStringPtr(const tchar *ptr, int maxchar); + +#ifdef DBGFLAG_ASSERT +inline void AssertValidStringPtr(const tchar *ptr, int maxchar = 0xFFFFFF) { + _AssertValidStringPtr(ptr, maxchar); +} +template +inline void AssertValidReadPtr(T *ptr, int count = 1) { + _AssertValidReadPtr((void *)ptr, count); +} +template +inline void AssertValidWritePtr(T *ptr, int count = 1) { + _AssertValidWritePtr((void *)ptr, count); +} +template +inline void AssertValidReadWritePtr(T *ptr, int count = 1) { + _AssertValidReadWritePtr((void *)ptr, count); +} +#define AssertValidThis() AssertValidReadWritePtr(this, sizeof(*this)) + +#else + +inline void AssertValidStringPtr(const tchar *ptr, int maxchar = 0xFFFFFF) {} +template +inline void AssertValidReadPtr(T *ptr, int count = 1) {} +template +inline void AssertValidWritePtr(T *ptr, int count = 1) {} +template +inline void AssertValidReadWritePtr(T *ptr, int count = 1) {} +#define AssertValidThis() +#endif + +//----------------------------------------------------------------------------- +// Macro to protect functions that are not reentrant + +#ifdef _DEBUG +class CReentryGuard { + public: + CReentryGuard(int *pSemaphore) : m_pSemaphore(pSemaphore) { + ++(*m_pSemaphore); + } + + ~CReentryGuard() { --(*m_pSemaphore); } + + private: + int *m_pSemaphore; +}; + +#define ASSERT_NO_REENTRY() \ + static int fSemaphore##__LINE__; \ + Assert(!fSemaphore##__LINE__); \ + CReentryGuard ReentryGuard##__LINE__(&fSemaphore##__LINE__) +#else +#define ASSERT_NO_REENTRY() +#endif + +// Tier0 uses these for string functions since this abstraction is normally done +// in tier1. +#ifdef POSIX +#define Tier0Internal_sntprintf snprintf +#define Tier0Internal_vsntprintf vsnprintf +#define Tier0Internal_vsnprintf vsnprintf +#else +#define Tier0Internal_sntprintf _sntprintf +#define Tier0Internal_vsntprintf _vsntprintf +#define Tier0Internal_vsnprintf _vsnprintf +#endif + +//----------------------------------------------------------------------------- +// +// Purpose: Inline string formatter +// + +#include "tier0/valve_off.h" +class CDbgFmtMsg { + public: + CDbgFmtMsg(const tchar *pszFormat, ...) { + va_list arg_ptr; + + va_start(arg_ptr, pszFormat); + Tier0Internal_vsntprintf(m_szBuf, sizeof(m_szBuf) - 1, pszFormat, arg_ptr); + va_end(arg_ptr); + + m_szBuf[sizeof(m_szBuf) - 1] = 0; + } + + operator const tchar *() const { return m_szBuf; } + + private: + tchar m_szBuf[256]; +}; +#include "tier0/valve_on.h" + +//----------------------------------------------------------------------------- +// +// Purpose: Embed debug info in each file. +// +#if defined(_WIN32) && !defined(_X360) + +#ifdef _DEBUG +#pragma comment(compiler) +#endif + +#endif + +//----------------------------------------------------------------------------- +// +// Purpose: Wrap around a variable to create a simple place to put a breakpoint +// + +#ifdef _DEBUG + +template +class CDataWatcher { + public: + const Type &operator=(const Type &val) { return Set(val); } + + const Type &operator=(const CDataWatcher &val) { + return Set(val.m_Value); + } + + const Type &Set(const Type &val) { + // Put your breakpoint here + m_Value = val; + return m_Value; + } + + Type &GetForModify() { return m_Value; } + + const Type &operator+=(const Type &val) { return Set(m_Value + val); } + + const Type &operator-=(const Type &val) { return Set(m_Value - val); } + + const Type &operator/=(const Type &val) { return Set(m_Value / val); } + + const Type &operator*=(const Type &val) { return Set(m_Value * val); } + + const Type &operator^=(const Type &val) { return Set(m_Value ^ val); } + + const Type &operator|=(const Type &val) { return Set(m_Value | val); } + + const Type &operator++() { return (*this += 1); } + + Type operator--() { return (*this -= 1); } + + Type operator++(int) // postfix version.. + { + Type val = m_Value; + (*this += 1); + return val; + } + + Type operator--(int) // postfix version.. + { + Type val = m_Value; + (*this -= 1); + return val; + } + + // For some reason the compiler only generates type conversion warnings for + // this operator when used like CNetworkVarBase = 0x1 (it + // warns about converting from an int to an unsigned char). + template + const Type &operator&=(C val) { + return Set(m_Value & val); + } + + operator const Type &() const { return m_Value; } + + const Type &Get() const { return m_Value; } + + const Type *operator->() const { return &m_Value; } + + Type m_Value; +}; + +#else + +template +class CDataWatcher { + private: + CDataWatcher(); // refuse to compile in non-debug builds +}; + +#endif + +// Code for programmatically setting/unsetting hardware breakpoints (there's +// probably a 360 and +#ifdef IS_WINDOWS_PC + +typedef void *HardwareBreakpointHandle_t; + +enum EHardwareBreakpointType { + BREAKPOINT_EXECUTE = 0, + BREAKPOINT_WRITE, + BREAKPOINT_READWRITE, +}; + +enum EHardwareBreakpointSize { + BREAKPOINT_SIZE_1 = 1, + BREAKPOINT_SIZE_2 = 2, + BREAKPOINT_SIZE_4 = 4, + BREAKPOINT_SIZE_8 = 8, +}; + +PLATFORM_INTERFACE HardwareBreakpointHandle_t +SetHardwareBreakpoint(EHardwareBreakpointType eType, + EHardwareBreakpointSize eSize, const void *pvLocation); +PLATFORM_INTERFACE bool ClearHardwareBreakpoint( + HardwareBreakpointHandle_t handle); + +class CHardwareBreakPointScopeGuard { + public: + CHardwareBreakPointScopeGuard( + const void *pvLocation, size_t nLocationSize, + EHardwareBreakpointType eType = BREAKPOINT_WRITE) { + EHardwareBreakpointSize eSize = BREAKPOINT_SIZE_4; + switch (nLocationSize) { + case 1: + eSize = BREAKPOINT_SIZE_1; + break; + case 2: + eSize = BREAKPOINT_SIZE_2; + break; + case 4: + eSize = BREAKPOINT_SIZE_4; //-V1048 + break; + case 8: + eSize = BREAKPOINT_SIZE_8; + break; + default: + Warning( + _T( "SetHardwareBreakpoint can only work with 1, 2, 4 or 8 byte data fields." )); + break; + } + + m_hBreakPoint = SetHardwareBreakpoint(eType, eSize, pvLocation); + m_bActive = m_hBreakPoint != (HardwareBreakpointHandle_t)0; + } + + ~CHardwareBreakPointScopeGuard() { Release(); } + + void Release() { + if (!m_bActive) return; + ClearHardwareBreakpoint(m_hBreakPoint); + } + + private: + bool m_bActive; + HardwareBreakpointHandle_t m_hBreakPoint; +}; + +#endif // IS_WINDOWS_PC +//----------------------------------------------------------------------------- + +#endif // VPC_TIER0_DBG_H_ diff --git a/public/tier0/dbgflag.h b/public/tier0/dbgflag.h new file mode 100644 index 0000000..5744fa0 --- /dev/null +++ b/public/tier0/dbgflag.h @@ -0,0 +1,69 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: This file sets all of our debugging flags. It should be called +// before all other header files. + +#ifndef VPC_TIER0_DBGFLAG_H_ +#define VPC_TIER0_DBGFLAG_H_ + +// Here are all the flags we support: +// DBGFLAG_MEMORY: Enables our memory debugging system, which +// overrides malloc & free DBGFLAG_MEMORY_NEWDEL: Enables new / delete +// tracking for memory debug system. Requires DBGFLAG_MEMORY to be enabled. +// DBGFLAG_VALIDATE: Enables our recursive validation system for +// checking integrity and memory leaks +// DBGFLAG_ASSERT: Turns Assert on or off (when off, it isn't +// compiled at all) +// DBGFLAG_ASSERTFATAL: Turns AssertFatal on or off (when off, it isn't +// compiled at all) +// DBGFLAG_ASSERTDLG: Turns assert dialogs on or off and debug breaks +// on or off when not under the debugger. +// (Dialogs will always be on +//when process is being debugged.) +// DBGFLAG_STRINGS: Turns on hardcore string validation (slow but +// safe) + +#undef DBGFLAG_MEMORY +#undef DBGFLAG_MEMORY_NEWDEL +#undef DBGFLAG_VALIDATE +#undef DBGFLAG_ASSERT +#undef DBGFLAG_ASSERTFATAL +#undef DBGFLAG_ASSERTDLG +#undef DBGFLAG_STRINGS + +//----------------------------------------------------------------------------- +// Default flags for debug builds +//----------------------------------------------------------------------------- +#if defined(_DEBUG) && !defined(PS3MEMOVERRIDEWRAP) + +#define DBGFLAG_MEMORY +#ifdef _SERVER // only enable new & delete tracking for server; on client it + // conflicts with CRT mem leak tracking +#define DBGFLAG_MEMORY_NEWDEL +#endif +#ifdef STEAM +#define DBGFLAG_VALIDATE +#endif +#define DBGFLAG_ASSERT +#define DBGFLAG_ASSERTFATAL +#define DBGFLAG_ASSERTDLG +#define DBGFLAG_STRINGS + +//----------------------------------------------------------------------------- +// Default flags for release builds +//----------------------------------------------------------------------------- +#else // _DEBUG +#ifdef STEAM +#define DBGFLAG_ASSERT +#endif +#define DBGFLAG_ASSERTFATAL // note: fatal asserts are enabled in release + // builds +#define DBGFLAG_ASSERTDLG + +#endif // _DEBUG + +#if defined(_CERT) +#define DBGFLAG_STRINGS_STRIP +#endif + +#endif // VPC_TIER0_DBGFLAG_H_ diff --git a/public/tier0/etwprof.h b/public/tier0/etwprof.h new file mode 100644 index 0000000..eb5acbb --- /dev/null +++ b/public/tier0/etwprof.h @@ -0,0 +1,155 @@ +// Copyright (c) Valve Corporation, All rights reserved. +// +// ETW (Event Tracing for Windows) profiling helpers. +// This allows easy insertion of Generic Event markers into ETW/xperf tracing +// which then aids in analyzing the traces and finding performance problems. +// The usage patterns are to use ETWBegin and ETWEnd (typically through the +// convenience class CETWScope) to bracket time-consuming operations. In +// addition ETWFrameMark marks the beginning of each frame, and ETWMark can be +// used to mark other notable events. More event types and providers can be +// added as needed. When recording xperf profiles add Valve-Main+Valve-FrameRate +// to the list of user-mode providers and be sure to register the providers with +// this sequence of commands: +// xcopy /y game\bin\tier0.dll %temp% +// wevtutil um src\tier0\ValveETWProvider.man +// wevtutil im src\tier0\ValveETWProvider.man + +#ifndef VPC_TIER0_ETWPROF_H_ +#define VPC_TIER0_ETWPROF_H_ + +#include "tier0/platform.h" + +#ifdef IS_WINDOWS_PC +// ETW support should be compiled in for all Windows PC platforms. It isn't +// supported on Windows XP but that is determined at run-time. +#if !defined(STANDALONE_VPC) && !defined(DISABLE_ETW) +#define ETW_MARKS_ENABLED +#endif +#endif + +#ifdef ETW_MARKS_ENABLED + +// Insert a single event to mark a point in an ETW trace. The return value is a +// 64-bit time stamp. +PLATFORM_INTERFACE int64 ETWMark(const char *pMessage); +// Optionally do full printf formatting of the mark string. This will be more +// expensive, but only when tracing is enabled. +PLATFORM_INTERFACE int64 +ETWMarkPrintf(PRINTF_FORMAT_STRING const char *pMessage, ...) FMTFUNCTION(1, 2); +// Optionally specify one to four floats. They will show up in separate columns +// in summary tables to allow sorting and easier transfer to spreadsheets. +PLATFORM_INTERFACE void ETWMark1F(const char *pMessage, float data1); +PLATFORM_INTERFACE void ETWMark2F(const char *pMessage, float data1, + float data2); +PLATFORM_INTERFACE void ETWMark3F(const char *pMessage, float data1, + float data2, float data3); +PLATFORM_INTERFACE void ETWMark4F(const char *pMessage, float data1, + float data2, float data3, float data4); +// Optionally specify one to four ints. They will show up in separate columns in +// summary tables to allow sorting and easier transfer to spreadsheets. +PLATFORM_INTERFACE void ETWMark1I(const char *pMessage, int data1); +PLATFORM_INTERFACE void ETWMark2I(const char *pMessage, int data1, int data2); +PLATFORM_INTERFACE void ETWMark3I(const char *pMessage, int data1, int data2, + int data3); +PLATFORM_INTERFACE void ETWMark4I(const char *pMessage, int data1, int data2, + int data3, int data4); +// Optionally specify one to two strings. They will show up in separate columns +// in summary tables to allow sorting and easier transfer to spreadsheets. +PLATFORM_INTERFACE void ETWMark1S(const char *pMessage, const char *data1); +PLATFORM_INTERFACE void ETWMark2S(const char *pMessage, const char *data1, + const char *data2); + +// Insert a begin event to mark the start of some work. The return value is a +// 64-bit time stamp which should be passed to the corresponding ETWEnd +// function. +PLATFORM_INTERFACE int64 ETWBegin(const char *pMessage); + +// Insert a paired end event to mark the end of some work. +PLATFORM_INTERFACE int64 ETWEnd(const char *pMessage, int64 nStartTime); + +// Mark the start of the next render frame. bIsServerProcess must be passed +// in consistently for a particular process. +PLATFORM_INTERFACE void ETWRenderFrameMark(bool bIsServerProcess); +// Mark the start of the next simulation frame. bIsServerProcess must be passed +// in consistently for a particular process. +PLATFORM_INTERFACE void ETWSimFrameMark(bool bIsServerProcess); +// Return the frame number recorded in the ETW trace -- useful for +// synchronization other profile information to the ETW trace. +PLATFORM_INTERFACE int ETWGetRenderFrameNumber(); + +PLATFORM_INTERFACE void ETWMouseDown(int nWhichButton, int nX, int nY); +PLATFORM_INTERFACE void ETWMouseUp(int nWhichButton, int nX, int nY); +PLATFORM_INTERFACE void ETWKeyDown(int nScanCode, int nVirtualCode, + const char *pChar); + +PLATFORM_INTERFACE void ETWSendPacket(const char *pTo, int nWireSize, + int nOutSequenceNR, + int nOutSequenceNrAck); +PLATFORM_INTERFACE void ETWThrottled(); +PLATFORM_INTERFACE void ETWReadPacket(const char *pFrom, int nWireSize, + int nInSequenceNR, int nOutSequenceNRAck); + +// This class calls the ETW Begin and End functions in order to insert a +// pair of events to bracket some work. +class CETWScope { + public: + CETWScope(const char *pMessage) : m_pMessage(pMessage) { + m_nStartTime = ETWBegin(pMessage); + } + ~CETWScope() { ETWEnd(m_pMessage, m_nStartTime); } + + private: + // Private and unimplemented to disable copying. + CETWScope(const CETWScope &rhs); + CETWScope &operator=(const CETWScope &rhs); + + const char *m_pMessage; + int64 m_nStartTime; +}; + +#else + +inline int64 ETWMark(const char *) { return 0; } +inline int64 ETWMarkPrintf(const char *, ...) { return 0; } +inline void ETWMark1F(const char *, float) {} +inline void ETWMark2F(const char *, float, float) {} +inline void ETWMark3F(const char *, float, float, float) {} +inline void ETWMark4F(const char *, float, float, float, float) {} +inline void ETWMark1I(const char *, int) {} +inline void ETWMark2I(const char *, int, int) {} +inline void ETWMark3I(const char *, int, int, int) {} +inline void ETWMark4I(const char *, int, int, int, int) {} +// Optionally specify one to two strings. They will show up in separate columns +// in summary tables to allow sorting and easier transfer to spreadsheets. +inline void ETWMark1S(const char *, const char *) {} +inline void ETWMark2S(const char *, const char *, const char *) {} + +inline int64 ETWBegin(const char *) { return 0; } +inline int64 ETWEnd(const char *, int64) { return 0; } +inline void ETWRenderFrameMark(bool) {} +inline void ETWSimFrameMark(bool) {} +inline int ETWGetRenderFrameNumber() { return 0; } + +inline void ETWMouseDown(int, int, int) {} +inline void ETWMouseUp(int, int, int) {} +inline void ETWKeyDown(int, int, const char *) {} + +inline void ETWSendPacket(const char *, int, int, int) {} +inline void ETWThrottled() {} +inline void ETWReadPacket(const char *, int, int, int) {} + +// This class calls the ETW Begin and End functions in order to insert a +// pair of events to bracket some work. +class CETWScope { + public: + CETWScope(const char *) {} + + private: + // Private and unimplemented to disable copying. + CETWScope(const CETWScope &rhs); + CETWScope &operator=(const CETWScope &rhs); +}; + +#endif + +#endif // VPC_TIER0_ETWPROF_H_ diff --git a/public/tier0/eventmasks.h b/public/tier0/eventmasks.h new file mode 100644 index 0000000..30d6263 --- /dev/null +++ b/public/tier0/eventmasks.h @@ -0,0 +1,392 @@ +// Copyright Valve Corporation, All rights reserved. + +#pragma once + +typedef union EVENT_MASK(TC_deliver_mode) { + struct { + uint16 DD : 1; // both logical processors in deliver mode }, + uint16 DB : 1; // logical processor 0 in deliver mode, 1 in build mode }, + uint16 DI : 1; // logical processor 0 in deliver mode, 1 is inactive }, + uint16 BD : 1; // logical processor 0 in build mode, 1 in deliver mode }, + uint16 BB : 1; // both logical processors in build mode }, + uint16 BI : 1; // logical processor 0 in build mode, 1 is inactive }, + uint16 ID : 1; // logical processor 0 is inactive, 1 in deliver mode }, + uint16 IB : 1; // logical processor 0 is inactive, 1 in build mode } + }; + uint16 flat; +} EVENT_MASK(TC_deliver_mode); + +typedef union EVENT_MASK(BPU_fetch_request) { + struct { + uint16 TCMISS : 1; // Trace cache lookup miss }, + }; + uint16 flat; +} EVENT_MASK(BPU_fetch_request); + +typedef union EVENT_MASK(ITLB_reference) { + struct { + uint16 HIT : 1; // ITLB hit }, + uint16 MISS : 1; // ITLB miss }, + uint16 HIT_UC : 1; // Uncacheable ITLB hit } + }; + uint16 flat; +} EVENT_MASK(ITLB_reference); + +typedef union EVENT_MASK(memory_cancel) { + struct { + uint16 dummy : 2; + + uint16 ST_RB_FULL : 1; // Replayed because no store request buffer is + // available }, + uint16 _64K_CONF : 1; // Conflicts due to 64K aliasing } + }; + uint16 flat; +} EVENT_MASK(memory_cancel); + +typedef union EVENT_MASK(memory_complete) { + struct { + uint16 LSC : 1; // Load split completed, excluding UC/WC loads }, + uint16 SSC : 1; // Any split stores completed } } + }; + uint16 flat; +} EVENT_MASK(memory_complete); + +typedef union EVENT_MASK(load_port_replay) { + struct { + uint16 dummy : 1; + uint16 SPLIT_LD : 1; // Split load } } + }; + uint16 flat; +} EVENT_MASK(load_port_replay); + +typedef union EVENT_MASK(store_port_replay) { + struct { + uint16 dummy0 : 1; + uint16 SPLIT_ST : 1; // Split store } } + }; + uint16 flat; +} EVENT_MASK(store_port_replay); + +typedef union EVENT_MASK(MOB_load_replay) { + struct { + uint16 dummy0 : 1; + + uint16 NO_STA : 1; // Replayed because of unknown store address }, + + uint16 dummy2 : 1; + + uint16 NO_STD : 1; // Replayed because of unknown store data }, + uint16 PARTIAL_DATA : 1; // Replayed because of partially overlapped data + // access between the load and store operations }, + uint16 UNALGN_ADDR : 1; // Replayed because the lower 4 bits of the linear + // address do not match between the load and store + // operations } } + }; + uint16 flat; +} EVENT_MASK(MOB_load_replay); + +typedef union EVENT_MASK(page_walk_type) { + struct { + uint16 DTMISS : 1; // Page walk for a data TLB miss }, + uint16 ITMISS : 1; // Page walk for an instruction TLB miss } } + }; + uint16 flat; +} EVENT_MASK(page_walk_type); + +typedef union EVENT_MASK(BSQ_cache_reference) { + struct { + uint16 RD_2ndL_HITS : 1; // Read 2nd level cache hit Shared }, + uint16 RD_2ndL_HITE : 1; // Read 2nd level cache hit Exclusive }, + uint16 RD_2ndL_HITM : 1; // Read 2nd level cache hit Modified }, + uint16 RD_3rdL_HITS : 1; // Read 3rd level cache hit Shared }, + uint16 RD_3rdL_HITE : 1; // Read 3rd level cache hit Exclusive }, + uint16 RD_3rdL_HITM : 1; // Read 3rd level cache hit Modified }, + uint16 dummy6 : 1; + uint16 dummy7 : 1; + uint16 RD_2ndL_MISS : 1; // Read 2nd level cache miss }, + uint16 RD_3rdL_MISS : 1; // Read 3rd level cache miss }, + uint16 WR_2ndL_MISS : 1; // Writeback lookup from DAC misses the 2nd level + // cache } } + }; + uint16 flat; +} EVENT_MASK(BSQ_cache_reference); + +typedef union EVENT_MASK(IOQ) { + struct { + uint16 bit0 : 1; // bus request type (use 00001 for invalid or default) + uint16 bit1 : 1; // + uint16 bit2 : 1; // + uint16 bit3 : 1; // + uint16 bit4 : 1; // + uint16 ALL_READ : 1; // Count read entries }, + uint16 ALL_WRITE : 1; // Count write entries }, + uint16 MEM_UC : 1; // Count UC memory access entries }, + uint16 MEM_WC : 1; // Count WC memory access entries }, + uint16 MEM_WT : 1; // Count WT memory access entries }, + uint16 MEM_WP : 1; // Count WP memory access entries }, + uint16 MEM_WB : 1; // Count WB memory access entries }, + uint16 dummy12 : 1; + + uint16 OWN : 1; // Count own store requests }, + uint16 OTHER : 1; // Count other and DMA store requests }, + uint16 PREFETCH : 1; // Include HW and SW prefetch requests } } + }; + uint16 flat; +} EVENT_MASK(IOQ); + +typedef union EVENT_MASK(FSB_data_activity) { + struct { + /* DRDY_OWN is mutually exclusive with DRDY_OTHER */ + /* DBSY_OWN is mutually exclusive with DBSY_OTHER */ + uint16 + DRDY_DRV : 1; // Count when this processor drives data onto the bus }, + uint16 + DRDY_OWN : 1; // Count when this processor reads data from the bus }, + uint16 DRDY_OTHER : 1; // Count when data is on the bus but not being + // sampled by the processor }, + uint16 DBSY_DRV : 1; // Count when this processor reserves the bus for + // driving data }, + uint16 DBSY_OWN : 1; // Count when this processor reserves the bus for + // sampling data }, + uint16 DBSY_OTHER : 1; // Count when the bus is reserved for driving data + // this processor will not sample } } + }; + uint16 flat; +} EVENT_MASK(FSB_data_activity); + +typedef union EVENT_MASK(BSQ) { + struct { + uint16 REQ_TYPE0 : 1; // Request type encoding bit 0 }, + uint16 REQ_TYPE1 : 1; // Request type encoding bit 1 }, + uint16 REQ_LEN0 : 1; // Request length encoding bit 0 }, + uint16 REQ_LEN1 : 1; // Request length encoding bit 1 }, + uint16 dummy4 : 1; + uint16 REQ_IO_TYPE : 1; // Request type is input or output }, + uint16 REQ_LOCK_TYPE : 1; // Request type is bus lock }, + uint16 REQ_CACHE_TYPE : 1; // Request type is cacheable }, + uint16 REQ_SPLIT_TYPE : 1; // Request type is a bus 8-byte chunk split + // across 8-byte boundary }, + uint16 REQ_DEM_TYPE : 1; // Request type is a demand (1) or prefetch (0) }, + uint16 REQ_ORD_TYPE : 1; // Request is an ordered type }, + uint16 MEM_TYPE0 : 1; // Memory type encoding bit 0 }, + uint16 MEM_TYPE1 : 1; // Memory type encoding bit 1 }, + uint16 MEM_TYPE2 : 1; // Memory type encoding bit 2 } } + }; + uint16 flat; +} EVENT_MASK(BSQ); + +typedef union EVENT_MASK(firm_uop) { + struct { + uint16 dummy15 : 15; + uint16 ALL : 1; // count all uops of this type } } + }; + uint16 flat; +} EVENT_MASK(firm_uop); + +typedef union EVENT_MASK(TC_misc) { + struct { + uint16 dymmy4 : 4; + uint16 FLUSH : 1; // Number of flushes } } + }; + uint16 flat; +} EVENT_MASK(TC_misc); + +typedef union EVENT_MASK(global_power_events) { + struct { + uint16 Running : 1; // The processor is active } } + }; + uint16 flat; +} EVENT_MASK(global_power_events); + +typedef union EVENT_MASK(tc_ms_xfer) { + struct { + uint16 CISC : 1; // A TC to MS transfer ocurred } } + }; + uint16 flat; +} EVENT_MASK(tc_ms_xfer); + +typedef union EVENT_MASK(uop_queue_writes) { + struct { + uint16 FROM_TC_BUILD : 1; // uops written from TC build mode + uint16 FROM_TC_DELIVER : 1; // uops written from TC deliver mode + uint16 FROM_ROM : 1; // uops written from microcode ROM } } + }; + uint16 flat; +} EVENT_MASK(uop_queue_writes); + +typedef union EVENT_MASK(branch_type) { + struct { + uint16 dummy : 1; + uint16 CONDITIONAL : 1; // Conditional jumps + uint16 CALL : 1; // Direct or indirect call + uint16 RETURN : 1; // Return branches + uint16 INDIRECT : 1; // Returns, indirect calls, or indirect jumps + }; + uint16 flat; +} EVENT_MASK(branch_type); + +typedef union EVENT_MASK(resource_stall) { + struct { + uint16 dummy1 : 5; + uint16 SBFULL : 1; // A Stall due to lack of store buffers } } + }; + uint16 flat; +} EVENT_MASK(resource_stall); + +typedef union EVENT_MASK(WC_Buffer) { + struct { + uint16 WCB_EVICTS : 1; // all causes }, + uint16 WCB_FULL_EVICT : 1; // no WC buffer is available }, + /* XXX: 245472-011 no longer lists bit 2, but that looks like + a table formatting error. Keeping it for now. */ + uint16 + WCB_HITM_EVICT : 1; // store encountered a Hit Modified condition } } + }; + uint16 flat; +} EVENT_MASK(WC_Buffer); + +typedef union EVENT_MASK(b2b_cycles) { + struct { + uint16 dummy0 : 1; + uint16 bit1 : 1; // + uint16 bit2 : 1; // + uint16 bit3 : 1; // + uint16 bit4 : 1; // + uint16 bit5 : 1; // + uint16 bit6 : 1; // + }; + uint16 flat; +} EVENT_MASK(b2b_cycles); + +typedef union EVENT_MASK(bnr) { + struct { + uint16 bit0 : 1; // + uint16 bit1 : 1; // + uint16 bit2 : 1; // + }; + uint16 flat; +} EVENT_MASK(bnr); + +typedef union EVENT_MASK(snoop) { + struct { + uint16 dummy0 : 1; + uint16 dummy1 : 1; + + uint16 bit2 : 1; // + uint16 dummy3 : 1; // + uint16 dummy4 : 1; // + uint16 dummy5 : 1; // + uint16 bit6 : 1; // + uint16 bit7 : 1; // + }; + uint16 flat; +} EVENT_MASK(snoop); + +typedef union EVENT_MASK(response) { + struct { + uint16 dummy0 : 1; // + uint16 bit1 : 1; // + uint16 bit2 : 1; // + uint16 dummy3 : 1; // + uint16 dummy4 : 1; // + uint16 dummy5 : 1; // + uint16 dummy6 : 1; // + uint16 dummy7 : 1; // + uint16 bit8 : 1; // + uint16 bit9 : 1; // + }; + uint16 flat; +} EVENT_MASK(response); + +typedef union EVENT_MASK(nbogus_bogus) { + struct { + uint16 NBOGUS : 1; // The marked uops are not bogus + uint16 BOGUS : 1; // The marked uops are bogus + }; + uint16 flat; +} EVENT_MASK(nbogus_bogus); + +typedef union EVENT_MASK(execution_event) { + struct { + uint16 NBOGUS0 : 1; // non-bogus uops with tag bit 0 set }, + uint16 NBOGUS1 : 1; // non-bogus uops with tag bit 1 set }, + uint16 NBOGUS2 : 1; // non-bogus uops with tag bit 2 set }, + uint16 NBOGUS3 : 1; // non-bogus uops with tag bit 3 set }, + uint16 BOGUS0 : 1; // bogus uops with tag bit 0 set }, + uint16 BOGUS1 : 1; // bogus uops with tag bit 1 set }, + uint16 BOGUS2 : 1; // bogus uops with tag bit 2 set }, + uint16 BOGUS3 : 1; // bogus uops with tag bit 3 set } } + }; + uint16 flat; +} EVENT_MASK(execution_event); + +typedef union EVENT_MASK(instr_retired) { + struct { + uint16 NBOGUSNTAG : 1; // Non-bogus instructions that are not tagged }, + uint16 NBOGUSTAG : 1; // Non-bogus instructions that are tagged }, + uint16 BOGUSNTAG : 1; // Bogus instructions that are not tagged }, + uint16 BOGUSTAG : 1; // Bogus instructions that are tagged } } + }; + uint16 flat; +} EVENT_MASK(instr_retired); + +typedef union EVENT_MASK(uop_type) { + struct { + uint16 dummy0 : 1; + uint16 TAGLOADS : 1; // The uop is a load operation }, + uint16 TAGSTORES : 1; // The uop is a store operation } } + }; + uint16 flat; +} EVENT_MASK(uop_type); + +typedef union EVENT_MASK(branch_retired) { + struct { + uint16 MMNP : 1; // Branch Not-taken Predicted + uint16 MMNM : 1; // Branch Not-taken Mispredicted + uint16 MMTP : 1; // Branch Taken Predicted + uint16 MMTM : 1; // Branch Taken Mispredicted + }; + uint16 flat; +} EVENT_MASK(branch_retired); + +typedef union EVENT_MASK(mispred_branch_retired) { + struct { + uint16 NBOGUS : 1; // The retired branch is not bogus } } + }; + uint16 flat; +} EVENT_MASK(mispred_branch_retired); + +typedef union EVENT_MASK(x87_assist) { + struct { + uint16 FPSU : 1; // FP stack underflow }, + uint16 FPSO : 1; // FP stack overflow }, + uint16 POAO : 1; // x87 output overflow }, + uint16 POAU : 1; // x87 output underflow }, + uint16 PREA : 1; // x87 input assist } } + }; + uint16 flat; +} EVENT_MASK(x87_assist); + +typedef union EVENT_MASK(machine_clear) { + struct { + uint16 CLEAR : 1; // Count a portion of the cycles when the machine is + // cleared }, + uint16 dummy1 : 1; + uint16 MOCLEAR : 1; // Count clears due to memory ordering issues }, + uint16 dummy3 : 1; + uint16 dummy4 : 1; + uint16 dummy5 : 1; + + uint16 SMCLEAR : 1; // Count clears due to self-modifying code issues } } + }; + uint16 flat; +} EVENT_MASK(machine_clear); + +typedef union EVENT_MASK(x87_SIMD_moves_uop) { + struct { + uint16 dummy3 : 3; + uint16 ALLP0 : 1; // Count all x87/SIMD store/move uops }, + uint16 ALLP2 : 1; // count all x87/SIMD load uops } } + }; + uint16 flat; +} EVENT_MASK(x87_SIMD_moves_uop); diff --git a/public/tier0/eventmodes.h b/public/tier0/eventmodes.h new file mode 100644 index 0000000..553c648 --- /dev/null +++ b/public/tier0/eventmodes.h @@ -0,0 +1,1094 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_EVENTMODES_H_ +#define VPC_TIER0_EVENTMODES_H_ + +/* + + + + Event Modes to choose from: + + P4Event_TC_deliver_mode + + P4Event_BPU_fetch_request + + P4Event_ITLB_reference + + P4Event_memory_cancel + + P4Event_memory_complete + + P4Event_load_port_replay + + P4Event_store_port_replay + + P4Event_MOB_load_replay + + P4Event_page_walk_type + + P4Event_BSQ_cache_reference + + P4Event_IOQ_allocation + + P4Event_IOQ_active_entries + + P4Event_FSB_data_activity + + P4Event_BSQ_allocation + + P4Event_BSQ_active_entries + + P4Event_SSE_input_assist + + P4Event_packed_SP_uop + + P4Event_packed_DP_uop + + P4Event_scalar_SP_uop + + P4Event_scalar_DP_uop + + P4Event_64bit_MMX_uop + + P4Event_128bit_MMX_uop + + P4Event_x87_FP_uop + + P4Event_x87_SIMD_moves_uop + + P4Event_TC_misc + + P4Event_global_power_events + + P4Event_tc_ms_xfer + + P4Event_uop_queue_writes + + P4Event_retired_mispred_branch_type + + P4Event_retired_branch_type + + P4Event_resource_stall + + P4Event_WC_Buffer + + P4Event_b2b_cycles + + P4Event_bnr + + P4Event_snoop + + P4Event_response + + P4Event_front_end_event + + P4Event_execution_event + + P4Event_replay_event + + P4Event_instr_retired + + P4Event_uops_retired + + P4Event_uop_type + + P4Event_branch_retired + + P4Event_mispred_branch_retired + + P4Event_x87_assist + + P4Event_machine_clear + + +*/ + +class P4P4Event_TC_deliver_mode : public P4BaseEvent { + public: + EVENT_MASK(TC_deliver_mode) * eventMask; + + P4P4Event_TC_deliver_mode() { + eventMask = (EVENT_MASK(TC_deliver_mode) *)&m_eventMask; + + escr.ESCREventSelect = 0x01; + cccr.CCCRSelect = 0x01; + //// eventType = EVENT_TYPE(TC_deliver_mode); + description = _T("TC_deliver_mode"); + UseCounter4(); + } + + void UseCounter4() { + SetCounter(4); + ; + } + void UseCounter5() { SetCounter(5); } + void UseCounter6() { SetCounter(6); } + void UseCounter7() { SetCounter(7); } +}; + +class P4P4Event_BPU_fetch_request : public P4BaseEvent + +{ + public: + EVENT_MASK(BPU_fetch_request) * eventMask; + + P4P4Event_BPU_fetch_request() { + eventMask = (EVENT_MASK(BPU_fetch_request) *)&m_eventMask; + + escr.ESCREventSelect = 0x03; + cccr.CCCRSelect = 0x00; + // eventType = EVENT_TYPE(BPU_fetch_request); + description = _T("BPU_fetch_request"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4P4Event_ITLB_reference : public P4BaseEvent + +{ + public: + EVENT_MASK(ITLB_reference) * eventMask; + + P4P4Event_ITLB_reference() { + eventMask = (EVENT_MASK(ITLB_reference) *)&m_eventMask; + + escr.ESCREventSelect = 0x18; + cccr.CCCRSelect = 0x03; + // eventType=EVENT_TYPE(ITLB_reference); + description = _T("ITLB_reference"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_memory_cancel : public P4BaseEvent + +{ + public: + EVENT_MASK(memory_cancel) * eventMask; + + P4Event_memory_cancel() { + eventMask = (EVENT_MASK(memory_cancel) *)&m_eventMask; + + escr.ESCREventSelect = 0x02; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(memory_cancel); + description = _T("memory_cancel"); + UseCounter8(); + } + + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_memory_complete : public P4BaseEvent + +{ + public: + EVENT_MASK(memory_complete) * eventMask; + + P4Event_memory_complete() { + eventMask = (EVENT_MASK(memory_complete) *)&m_eventMask; + + escr.ESCREventSelect = 0x08; + cccr.CCCRSelect = 0x02; + // eventType=EVENT_TYPE(memory_complete); + description = _T("memory_complete"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_load_port_replay : public P4BaseEvent + +{ + public: + EVENT_MASK(load_port_replay) * eventMask; + + P4Event_load_port_replay() { + eventMask = (EVENT_MASK(load_port_replay) *)&m_eventMask; + + escr.ESCREventSelect = 0x04; + cccr.CCCRSelect = 0x02; + // eventType=EVENT_TYPE(load_port_replay); + description = _T("load_port_replay"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_store_port_replay : public P4BaseEvent + +{ + public: + EVENT_MASK(store_port_replay) * eventMask; + + P4Event_store_port_replay() { + eventMask = (EVENT_MASK(store_port_replay) *)&m_eventMask; + + escr.ESCREventSelect = 0x05; + cccr.CCCRSelect = 0x02; + // eventType=EVENT_TYPE(store_port_replay); + description = _T("store_port_replay"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_MOB_load_replay : public P4BaseEvent + +{ + public: + EVENT_MASK(MOB_load_replay) * eventMask; + + P4Event_MOB_load_replay() { + eventMask = (EVENT_MASK(MOB_load_replay) *)&m_eventMask; + + escr.ESCREventSelect = 0x03; + cccr.CCCRSelect = 0x02; + // eventType=EVENT_TYPE(MOB_load_replay); + description = _T("MOB_load_replay"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_page_walk_type : public P4BaseEvent + +{ + public: + EVENT_MASK(page_walk_type) * eventMask; + + P4Event_page_walk_type() { + eventMask = (EVENT_MASK(page_walk_type) *)&m_eventMask; + + escr.ESCREventSelect = 0x01; + cccr.CCCRSelect = 0x04; + // eventType=EVENT_TYPE(page_walk_type); + description = _T("page_walk_type"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_BSQ_cache_reference : public P4BaseEvent + +{ + public: + EVENT_MASK(BSQ_cache_reference) * eventMask; + + P4Event_BSQ_cache_reference() { + eventMask = (EVENT_MASK(BSQ_cache_reference) *)&m_eventMask; + + escr.ESCREventSelect = 0x0C; + cccr.CCCRSelect = 0x07; + // eventType=EVENT_TYPE(BSQ_cache_reference); + description = _T("BSQ_cache_reference"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_IOQ_allocation : public P4BaseEvent + +{ + public: + EVENT_MASK(IOQ) * eventMask; + + P4Event_IOQ_allocation() { + eventMask = (EVENT_MASK(IOQ) *)&m_eventMask; + + escr.ESCREventSelect = 0x03; + cccr.CCCRSelect = 0x06; + // eventType=EVENT_TYPE(IOQ); + description = _T("IOQ_allocation"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_IOQ_active_entries : public P4BaseEvent + +{ + public: + EVENT_MASK(IOQ) * eventMask; + + P4Event_IOQ_active_entries() { + eventMask = (EVENT_MASK(IOQ) *)&m_eventMask; + + escr.ESCREventSelect = 0x1A; + cccr.CCCRSelect = 0x06; + // eventType=EVENT_TYPE(IOQ); + description = _T("IOQ_active_entries"); + UseCounter2(); + } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_FSB_data_activity : public P4BaseEvent + +{ + public: + EVENT_MASK(FSB_data_activity) * eventMask; + + P4Event_FSB_data_activity() { + eventMask = (EVENT_MASK(FSB_data_activity) *)&m_eventMask; + + escr.ESCREventSelect = 0x17; + cccr.CCCRSelect = 0x06; + // eventType=EVENT_TYPE(FSB_data_activity); + description = _T("FSB_data_activity"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_BSQ_allocation : public P4BaseEvent + +{ + public: + EVENT_MASK(BSQ) * eventMask; + + P4Event_BSQ_allocation() { + eventMask = (EVENT_MASK(BSQ) *)&m_eventMask; + + escr.ESCREventSelect = 0x05; + cccr.CCCRSelect = 0x07; + // eventType=EVENT_TYPE(BSQ); + description = _T("BSQ_allocation"); + UseCounter0(); + } + + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } +}; +class P4Event_BSQ_active_entries : public P4BaseEvent + +{ + public: + EVENT_MASK(BSQ) * eventMask; + + P4Event_BSQ_active_entries() { + eventMask = (EVENT_MASK(BSQ) *)&m_eventMask; + + escr.ESCREventSelect = 0x06; + cccr.CCCRSelect = 0x07; + // eventType=EVENT_TYPE(BSQ); + description = _T("bsq_active_entries"); + UseCounter2(); + } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_SSE_input_assist : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_SSE_input_assist() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x34; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("SSE_input_assist"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_packed_SP_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_packed_SP_uop() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x08; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("packed_SP_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_packed_DP_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_packed_DP_uop() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x0C; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("packed_DP_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_scalar_SP_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_scalar_SP_uop() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x0A; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("scalar_SP_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_scalar_DP_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_scalar_DP_uop() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x0E; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("scalar_DP_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_64bit_MMX_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_64bit_MMX_uop() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x02; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("64bit_MMX_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_128bit_MMX_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_128bit_MMX_uop() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x1A; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("128bit_MMX_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_x87_FP_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(firm_uop) * eventMask; + + P4Event_x87_FP_uop() { + eventMask = (EVENT_MASK(firm_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x04; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(firm_uop); + description = _T("x87_FP_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_x87_SIMD_moves_uop : public P4BaseEvent + +{ + public: + EVENT_MASK(x87_SIMD_moves_uop) * eventMask; + + P4Event_x87_SIMD_moves_uop() { + eventMask = (EVENT_MASK(x87_SIMD_moves_uop) *)&m_eventMask; + + escr.ESCREventSelect = 0x2E; + cccr.CCCRSelect = 0; + // eventType=EVENT_TYPE(x87_SIMD_moves_uop); + description = _T("x87_SIMD_moves_uop"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_TC_misc : public P4BaseEvent + +{ + public: + EVENT_MASK(TC_misc) * eventMask; + + P4Event_TC_misc() { + eventMask = (EVENT_MASK(TC_misc) *)&m_eventMask; + + escr.ESCREventSelect = 0x06; + cccr.CCCRSelect = 0x01; + // eventType=EVENT_TYPE(TC_misc); + description = _T("TC_misc"); + UseCounter4(); + } + + void UseCounter4() { + SetCounter(4); + ; + } + void UseCounter5() { SetCounter(5); } + void UseCounter6() { SetCounter(6); } + void UseCounter7() { SetCounter(7); } +}; +class P4Event_global_power_events : public P4BaseEvent + +{ + public: + EVENT_MASK(global_power_events) * eventMask; + + P4Event_global_power_events() { + eventMask = (EVENT_MASK(global_power_events) *)&m_eventMask; + + escr.ESCREventSelect = 0x13; + cccr.CCCRSelect = 0x06; + // eventType=EVENT_TYPE(global_power_events); + description = _T("global_power_events"); + UseCounter0(); + } + + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_tc_ms_xfer : public P4BaseEvent + +{ + public: + EVENT_MASK(tc_ms_xfer) * eventMask; + + P4Event_tc_ms_xfer() { + eventMask = (EVENT_MASK(tc_ms_xfer) *)&m_eventMask; + + escr.ESCREventSelect = 0x05; + cccr.CCCRSelect = 0x00; + // eventType=EVENT_TYPE(tc_ms_xfer); + description = _T("tc_ms_xfer"); + UseCounter4(); + } + + void UseCounter4() { + SetCounter(4); + ; + } + void UseCounter5() { SetCounter(5); } + void UseCounter6() { SetCounter(6); } + void UseCounter7() { SetCounter(7); } +}; +class P4Event_uop_queue_writes : public P4BaseEvent + +{ + public: + EVENT_MASK(uop_queue_writes) * eventMask; + + P4Event_uop_queue_writes() { + eventMask = (EVENT_MASK(uop_queue_writes) *)&m_eventMask; + + escr.ESCREventSelect = 0x09; + cccr.CCCRSelect = 0x00; + // eventType=EVENT_TYPE(uop_queue_writes); + description = _T("uop_queue_writes"); + UseCounter4(); + } + + void UseCounter4() { + SetCounter(4); + ; + } + void UseCounter5() { SetCounter(5); } + void UseCounter6() { SetCounter(6); } + void UseCounter7() { SetCounter(7); } +}; +class P4Event_retired_mispred_branch_type : public P4BaseEvent + +{ + public: + EVENT_MASK(branch_type) * eventMask; + + P4Event_retired_mispred_branch_type() { + eventMask = (EVENT_MASK(branch_type) *)&m_eventMask; + + escr.ESCREventSelect = 0x05; + cccr.CCCRSelect = 0x02; + // eventType=EVENT_TYPE(branch_type); + description = _T("retired_mispred_branch_type"); + UseCounter4(); + } + + void UseCounter4() { + SetCounter(4); + ; + } + void UseCounter5() { SetCounter(5); } + void UseCounter6() { SetCounter(6); } + void UseCounter7() { SetCounter(7); } +}; +class P4Event_retired_branch_type : public P4BaseEvent + +{ + public: + EVENT_MASK(branch_type) * eventMask; + + P4Event_retired_branch_type() { + eventMask = (EVENT_MASK(branch_type) *)&m_eventMask; + + escr.ESCREventSelect = 0x04; + cccr.CCCRSelect = 0x04; + // eventType=EVENT_TYPE(branch_type); + description = _T("retired_branch_type"); + UseCounter4(); + } + + void UseCounter4() { + SetCounter(4); + ; + } + void UseCounter5() { SetCounter(5); } + void UseCounter6() { SetCounter(6); } + void UseCounter7() { SetCounter(7); } +}; +class P4Event_resource_stall : public P4BaseEvent + +{ + public: + EVENT_MASK(resource_stall) * eventMask; + + P4Event_resource_stall() { + eventMask = (EVENT_MASK(resource_stall) *)&m_eventMask; + + escr.ESCREventSelect = 0x01; + cccr.CCCRSelect = 0x02; + // eventType=EVENT_TYPE(resource_stall); + description = _T("resource_stall"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_WC_Buffer : public P4BaseEvent + +{ + public: + EVENT_MASK(WC_Buffer) * eventMask; + + P4Event_WC_Buffer() { + eventMask = (EVENT_MASK(WC_Buffer) *)&m_eventMask; + + escr.ESCREventSelect = 0x05; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(WC_Buffer); + description = _T("WC_Buffer"); + UseCounter8(); + } + void UseCounter8() { SetCounter(8); } + void UseCounter9() { SetCounter(9); } + void UseCounter10() { SetCounter(10); } + void UseCounter11() { SetCounter(11); } +}; +class P4Event_b2b_cycles : public P4BaseEvent + +{ + public: + EVENT_MASK(b2b_cycles) * eventMask; + + P4Event_b2b_cycles() { + eventMask = (EVENT_MASK(b2b_cycles) *)&m_eventMask; + + escr.ESCREventSelect = 0x16; + cccr.CCCRSelect = 0x03; + // eventType=EVENT_TYPE(b2b_cycles); + description = _T("b2b_cycles"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_bnr : public P4BaseEvent + +{ + public: + EVENT_MASK(bnr) * eventMask; + + P4Event_bnr() { + eventMask = (EVENT_MASK(bnr) *)&m_eventMask; + + escr.ESCREventSelect = 0x08; + cccr.CCCRSelect = 0x03; + // eventType=EVENT_TYPE(bnr); + description = _T("bnr"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_snoop : public P4BaseEvent + +{ + public: + EVENT_MASK(snoop) * eventMask; + + P4Event_snoop() { + eventMask = (EVENT_MASK(snoop) *)&m_eventMask; + + escr.ESCREventSelect = 0x06; + cccr.CCCRSelect = 0x03; + // eventType=EVENT_TYPE(snoop); + description = _T("snoop"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_response : public P4BaseEvent + +{ + public: + EVENT_MASK(response) * eventMask; + + P4Event_response() { + eventMask = (EVENT_MASK(response) *)&m_eventMask; + + escr.ESCREventSelect = 0x04; + cccr.CCCRSelect = 0x03; + // eventType=EVENT_TYPE(response); + description = _T("response"); + UseCounter0(); + } + void UseCounter0() { SetCounter(0); } + void UseCounter1() { SetCounter(1); } + void UseCounter2() { SetCounter(2); } + void UseCounter3() { SetCounter(3); } +}; +class P4Event_front_end_event : public P4BaseEvent + +{ + public: + EVENT_MASK(nbogus_bogus) * eventMask; + + P4Event_front_end_event() { + eventMask = (EVENT_MASK(nbogus_bogus) *)&m_eventMask; + + escr.ESCREventSelect = 0x08; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(nbogus_bogus); + description = _T("front_end_event"); + UseCounter12(); + } + + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_execution_event : public P4BaseEvent + +{ + public: + EVENT_MASK(execution_event) * eventMask; + + P4Event_execution_event() { + eventMask = (EVENT_MASK(execution_event) *)&m_eventMask; + + escr.ESCREventSelect = 0x0C; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(execution_event); + description = _T("execution_event"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_replay_event : public P4BaseEvent + +{ + public: + EVENT_MASK(nbogus_bogus) * eventMask; + + P4Event_replay_event() { + eventMask = (EVENT_MASK(nbogus_bogus) *)&m_eventMask; + + escr.ESCREventSelect = 0x09; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(nbogus_bogus); + description = _T("replay_event"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_instr_retired : public P4BaseEvent + +{ + public: + EVENT_MASK(instr_retired) * eventMask; + + P4Event_instr_retired() { + eventMask = (EVENT_MASK(instr_retired) *)&m_eventMask; + + escr.ESCREventSelect = 0x02; + cccr.CCCRSelect = 0x04; + // eventType=EVENT_TYPE(instr_retired); + description = _T("instr_retired"); + UseCounter12(); + } + + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_uops_retired : public P4BaseEvent + +{ + public: + EVENT_MASK(nbogus_bogus) * eventMask; + + P4Event_uops_retired() { + eventMask = (EVENT_MASK(nbogus_bogus) *)&m_eventMask; + + escr.ESCREventSelect = 0x01; + cccr.CCCRSelect = 0x04; + // eventType=EVENT_TYPE(nbogus_bogus); + description = _T("uops_retired"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_uop_type : public P4BaseEvent + +{ + public: + EVENT_MASK(uop_type) * eventMask; + + P4Event_uop_type() { + eventMask = (EVENT_MASK(uop_type) *)&m_eventMask; + + escr.ESCREventSelect = 0x02; + cccr.CCCRSelect = 0x02; + // eventType=EVENT_TYPE(uop_type); + description = _T("uop_type"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_branch_retired : public P4BaseEvent + +{ + public: + EVENT_MASK(branch_retired) * eventMask; + + P4Event_branch_retired() { + eventMask = (EVENT_MASK(branch_retired) *)&m_eventMask; + + escr.ESCREventSelect = 0x06; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(branch_retired); + description = _T("branch_retired"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_mispred_branch_retired : public P4BaseEvent + +{ + public: + EVENT_MASK(mispred_branch_retired) * eventMask; + + P4Event_mispred_branch_retired() { + eventMask = (EVENT_MASK(mispred_branch_retired) *)&m_eventMask; + + escr.ESCREventSelect = 0x03; + cccr.CCCRSelect = 0x04; + // eventType=EVENT_TYPE(mispred_branch_retired); + description = _T("mispred_branch_retired"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_x87_assist : public P4BaseEvent + +{ + public: + EVENT_MASK(x87_assist) * eventMask; + + P4Event_x87_assist() { + eventMask = (EVENT_MASK(x87_assist) *)&m_eventMask; + + escr.ESCREventSelect = 0x03; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(x87_assist); + description = _T("x87_assist"); + UseCounter12(); + } + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; +class P4Event_machine_clear : public P4BaseEvent + +{ + public: + EVENT_MASK(machine_clear) * eventMask; + + P4Event_machine_clear() { + eventMask = (EVENT_MASK(machine_clear) *)&m_eventMask; + escr.ESCREventSelect = 0x02; + cccr.CCCRSelect = 0x05; + // eventType=EVENT_TYPE(machine_clear); + description = _T("machine_clear"); + UseCounter12(); + } + + void UseCounter12() { SetCounter(12); } + void UseCounter13() { SetCounter(13); } + + void UseCounter14() { SetCounter(14); } + void UseCounter15() { SetCounter(15); } + + void UseCounter16() { SetCounter(16); } + void UseCounter17() { SetCounter(17); } +}; + +#endif // VPC_TIER0_EVENTMODES_H_ diff --git a/public/tier0/fasttimer.h b/public/tier0/fasttimer.h new file mode 100644 index 0000000..893699e --- /dev/null +++ b/public/tier0/fasttimer.h @@ -0,0 +1,515 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_FASTTIMER_H_ +#define VPC_TIER0_FASTTIMER_H_ + +#include + +#include "tier0/platform.h" + +#ifdef _PS3 +#include "sys/sys_time.h" +#else +inline uint64 sys_time_get_timebase_frequency() { + DebuggerBreak(); // Error("sys_time_get_timebase_frequency called on non-PS3 + // platform."); + return 1; // this function should never ever be called. +} +#endif + +PLATFORM_INTERFACE uint64 g_ClockSpeed; +PLATFORM_INTERFACE unsigned long g_dwClockSpeed; + +PLATFORM_INTERFACE double g_ClockSpeedMicrosecondsMultiplier; +PLATFORM_INTERFACE double g_ClockSpeedMillisecondsMultiplier; +PLATFORM_INTERFACE double g_ClockSpeedSecondsMultiplier; + +#ifdef COMPILER_MSVC64 +extern "C" { +unsigned __int64 __rdtsc(); +} + +#pragma intrinsic(__rdtsc) +#endif + +class CCycleCount { + friend class CFastTimer; + + public: + CCycleCount(); + CCycleCount(uint64 cycles); + + void Sample(); // Sample the clock. This takes about 34 clocks to execute (or + // 26,000 calls per millisecond on a P900). + + void Init(); // Set to zero. + void Init(float initTimeMsec); + void Init(double initTimeMsec) { Init((float)initTimeMsec); } + void Init(uint64 cycles); + bool IsLessThan(CCycleCount const &other) const; // Compare two counts. + + // Convert to other time representations. These functions are slow, so it's + // preferable to call them during display rather than inside a timing block. + unsigned long GetCycles() const; + uint64 GetLongCycles() const; + + unsigned long GetMicroseconds() const; + uint64 GetUlMicroseconds() const; + double GetMicrosecondsF() const; + void SetMicroseconds(unsigned long nMicroseconds); + + unsigned long GetMilliseconds() const; + double GetMillisecondsF() const; + + double GetSeconds() const; + + CCycleCount &operator+=(CCycleCount const &other); + + // dest = rSrc1 + rSrc2 + static void Add(CCycleCount const &rSrc1, CCycleCount const &rSrc2, + CCycleCount &dest); // Add two samples together. + + // dest = rSrc1 - rSrc2 + static void Sub(CCycleCount const &rSrc1, CCycleCount const &rSrc2, + CCycleCount &dest); // Add two samples together. + + static uint64 GetTimestamp(); + + uint64 m_Int64; +}; + +class CClockSpeedInit { + public: + CClockSpeedInit() { Init(); } + + static void Init() { + const CPUInformation &pi = GetCPUInformation(); + + g_ClockSpeed = pi.m_Speed; + + if constexpr (IsX360()) { + // cycle counter runs as doc'd at 1/64 Xbox 3.2GHz clock speed, thus 50 + // Mhz + g_ClockSpeed = pi.m_Speed / 64L; + } + + if constexpr (IsPS3()) { + // CPU clock rate is totally unrelated to time base register frequency on + // PS3 + g_ClockSpeed = sys_time_get_timebase_frequency(); + } + + g_dwClockSpeed = (unsigned long)g_ClockSpeed; + + g_ClockSpeedMicrosecondsMultiplier = 1000000.0 / (double)g_ClockSpeed; + g_ClockSpeedMillisecondsMultiplier = 1000.0 / (double)g_ClockSpeed; + g_ClockSpeedSecondsMultiplier = 1.0f / (double)g_ClockSpeed; + } +}; + +class CFastTimer { + public: + // These functions are fast to call and should be called from your sampling + // code. + void Start(); + void End(); + // Get the elapsed time between Start and End calls. + const CCycleCount &GetDuration() const; + // Call without ending. Not that cheap. + CCycleCount GetDurationInProgress() const; + + // Return number of cycles per second on this processor. + static inline unsigned long GetClockSpeed(); + + private: + CCycleCount m_Duration; +#ifdef DEBUG_FASTTIMER + bool m_bRunning; // Are we currently running? +#endif +}; + +// This is a helper class that times whatever block of code it's in +class CTimeScope { + public: + CTimeScope(CFastTimer *pTimer); + ~CTimeScope(); + + private: + CFastTimer *m_pTimer; +}; + +inline CTimeScope::CTimeScope(CFastTimer *pTotal) { + m_pTimer = pTotal; + m_pTimer->Start(); +} + +inline CTimeScope::~CTimeScope() { m_pTimer->End(); } + +// This is a helper class that times whatever block of code it's in and +// adds the total (int microseconds) to a global counter. +class CTimeAdder { + public: + CTimeAdder(CCycleCount *pTotal); + ~CTimeAdder(); + + void End(); + + private: + CCycleCount *m_pTotal; + CFastTimer m_Timer; +}; + +inline CTimeAdder::CTimeAdder(CCycleCount *pTotal) { + m_pTotal = pTotal; + m_Timer.Start(); +} + +inline CTimeAdder::~CTimeAdder() { End(); } + +inline void CTimeAdder::End() { + if (m_pTotal) { + m_Timer.End(); + *m_pTotal += m_Timer.GetDuration(); + m_pTotal = 0; + } +} + +// -------------------------------------------------------------------------- // +// Simple tool to support timing a block of code, and reporting the results on +// program exit or at each iteration +// +// Macros used because dbg.h uses this header, thus Msg() is unavailable +// -------------------------------------------------------------------------- // + +#define PROFILE_SCOPE(name) \ + class C##name##ACC : public CAverageCycleCounter { \ + public: \ + ~C##name##ACC() { \ + Msg("%-48s: %6.3f avg (%8.1f total, %7.3f peak, %5d iters)\n", #name, \ + GetAverageMilliseconds(), GetTotalMilliseconds(), \ + GetPeakMilliseconds(), GetIters()); \ + } \ + }; \ + static C##name##ACC name##_ACC; \ + CAverageTimeMarker name##_ATM(&name##_ACC) + +#define TIME_SCOPE(name) \ + class CTimeScopeMsg_##name { \ + public: \ + CTimeScopeMsg_##name() { m_Timer.Start(); } \ + ~CTimeScopeMsg_##name() { \ + m_Timer.End(); \ + Msg(#name "time: %.4fms\n", m_Timer.GetDuration().GetMillisecondsF()); \ + } \ + \ + private: \ + CFastTimer m_Timer; \ + } name##_TSM; + +// -------------------------------------------------------------------------- // + +class CAverageCycleCounter { + public: + CAverageCycleCounter(); + + void Init(); + void MarkIter(const CCycleCount &duration); + + unsigned GetIters() const; + + double GetAverageMilliseconds() const; + double GetTotalMilliseconds() const; + double GetPeakMilliseconds() const; + + private: + unsigned m_nIters; + CCycleCount m_Total; + CCycleCount m_Peak; + const tchar *m_pszName; + bool m_fReport; +}; + +// -------------------------------------------------------------------------- // + +class CAverageTimeMarker { + public: + CAverageTimeMarker(CAverageCycleCounter *pCounter); + ~CAverageTimeMarker(); + + private: + CAverageCycleCounter *m_pCounter; + CFastTimer m_Timer; +}; + +// -------------------------------------------------------------------------- // +// CCycleCount inlines. +// -------------------------------------------------------------------------- // + +inline CCycleCount::CCycleCount() { Init((uint64)0); } + +inline CCycleCount::CCycleCount(uint64 cycles) { Init(cycles); } + +inline void CCycleCount::Init() { Init((uint64)0); } + +inline void CCycleCount::Init(float initTimeMsec) { + if (g_ClockSpeedMillisecondsMultiplier > 0) + Init((uint64)(initTimeMsec / g_ClockSpeedMillisecondsMultiplier)); + else + Init((uint64)0); +} + +inline void CCycleCount::Init(uint64 cycles) { m_Int64 = cycles; } + +inline void CCycleCount::Sample() { +#ifdef COMPILER_MSVC64 + unsigned __int64 *pSample = (unsigned __int64 *)&m_Int64; + *pSample = __rdtsc(); + // Msg( "Sample = %I64x", pSample ); +#elif defined(_X360) + // only need lower 32 bits, avoids doc'd read bug and 32 bit rollover is in 85 + // seconds + m_Int64 = (uint64)__mftb32(); + // scale back up, needs to be viewed as 1 cycle/clock +#elif defined(_PS3) + // only need lower 32 bits, avoids doc'd read bug and 32 bit rollover is in 85 + // seconds + m_Int64 = (uint64)__mftb(); + // scale back up, needs to be viewed as 1 cycle/clock +#elif defined(__GNUC__) + union { + unsigned long *pSample; + uint64 *pInt64; + } tmp; + tmp.pInt64 = &m_Int64; + __asm__ __volatile__( + "rdtsc\n\t" + "movl %%eax, (%0)\n\t" + "movl %%edx, 4(%0)\n\t" + : /* no output regs */ + : "D"(tmp.pSample) + : "%eax", "%edx"); +#elif defined(_WIN32) + unsigned long *pSample = (unsigned long *)&m_Int64; + __asm + { + // force the cpu to synchronize the instruction queue + // NJS: CPUID can really impact performance in tight loops. + // cpuid + // cpuid + // cpuid + mov ecx, pSample + rdtsc + mov [ecx], eax + mov [ecx+4], edx + } +#elif defined(POSIX) + unsigned long *pSample = (unsigned long *)&m_Int64; + __asm__ __volatile__( + "rdtsc\n\t" + "movl %%eax, (%0)\n\t" + "movl %%edx, 4(%0)\n\t" + : /* no output regs */ + : "D"(pSample) + : "%eax", "%edx"); +#endif +} + +inline CCycleCount &CCycleCount::operator+=(CCycleCount const &other) { + m_Int64 += other.m_Int64; + return *this; +} + +inline void CCycleCount::Add(CCycleCount const &rSrc1, CCycleCount const &rSrc2, + CCycleCount &dest) { + dest.m_Int64 = rSrc1.m_Int64 + rSrc2.m_Int64; +} + +inline void CCycleCount::Sub(CCycleCount const &rSrc1, CCycleCount const &rSrc2, + CCycleCount &dest) { + dest.m_Int64 = rSrc1.m_Int64 - rSrc2.m_Int64; +} + +inline uint64 CCycleCount::GetTimestamp() { + CCycleCount c; + c.Sample(); + return c.GetLongCycles(); +} + +inline bool CCycleCount::IsLessThan(CCycleCount const &other) const { + return m_Int64 < other.m_Int64; +} + +inline unsigned long CCycleCount::GetCycles() const { + return (unsigned long)m_Int64; +} + +inline uint64 CCycleCount::GetLongCycles() const { return m_Int64; } + +inline unsigned long CCycleCount::GetMicroseconds() const { + return (unsigned long)((m_Int64 * 1000000) / g_ClockSpeed); +} + +inline uint64 CCycleCount::GetUlMicroseconds() const { + return ((m_Int64 * 1000000) / g_ClockSpeed); +} + +inline double CCycleCount::GetMicrosecondsF() const { + return (double)(m_Int64 * g_ClockSpeedMicrosecondsMultiplier); +} + +inline void CCycleCount::SetMicroseconds(unsigned long nMicroseconds) { + m_Int64 = ((uint64)nMicroseconds * g_ClockSpeed) / 1000000; +} + +inline unsigned long CCycleCount::GetMilliseconds() const { + return (unsigned long)((m_Int64 * 1000) / g_ClockSpeed); +} + +inline double CCycleCount::GetMillisecondsF() const { + return (double)(m_Int64 * g_ClockSpeedMillisecondsMultiplier); +} + +inline double CCycleCount::GetSeconds() const { + return (double)(m_Int64 * g_ClockSpeedSecondsMultiplier); +} + +// -------------------------------------------------------------------------- // +// CFastTimer inlines. +// -------------------------------------------------------------------------- // +inline void CFastTimer::Start() { + m_Duration.Sample(); +#ifdef DEBUG_FASTTIMER + m_bRunning = true; +#endif +} + +inline void CFastTimer::End() { + CCycleCount cnt; + cnt.Sample(); + if (IsX360()) { + // have to handle rollover, hires timer is only accurate to 32 bits + // more than one overflow should not have occurred, otherwise caller should + // use a slower timer + if ((uint64)cnt.m_Int64 <= (uint64)m_Duration.m_Int64) { + // rollover occurred + cnt.m_Int64 += 0x100000000LL; + } + } + + m_Duration.m_Int64 = cnt.m_Int64 - m_Duration.m_Int64; + +#ifdef DEBUG_FASTTIMER + m_bRunning = false; +#endif +} + +inline CCycleCount CFastTimer::GetDurationInProgress() const { + CCycleCount cnt; + cnt.Sample(); + if (IsX360()) { + // have to handle rollover, hires timer is only accurate to 32 bits + // more than one overflow should not have occurred, otherwise caller should + // use a slower timer + if ((uint64)cnt.m_Int64 <= (uint64)m_Duration.m_Int64) { + // rollover occurred + cnt.m_Int64 += 0x100000000LL; + } + } + + CCycleCount result; + result.m_Int64 = cnt.m_Int64 - m_Duration.m_Int64; + + return result; +} + +inline unsigned long CFastTimer::GetClockSpeed() { return g_dwClockSpeed; } + +inline CCycleCount const &CFastTimer::GetDuration() const { +#ifdef DEBUG_FASTTIMER + assert(!m_bRunning); +#endif + return m_Duration; +} + +// -------------------------------------------------------------------------- // +// CAverageCycleCounter inlines + +inline CAverageCycleCounter::CAverageCycleCounter() + : m_nIters(0), m_fReport(false), m_pszName(nullptr) {} + +inline void CAverageCycleCounter::Init() { + m_Total.Init(); + m_Peak.Init(); + m_nIters = 0; +} + +inline void CAverageCycleCounter::MarkIter(const CCycleCount &duration) { + ++m_nIters; + m_Total += duration; + if (m_Peak.IsLessThan(duration)) m_Peak = duration; +} + +inline unsigned CAverageCycleCounter::GetIters() const { return m_nIters; } + +inline double CAverageCycleCounter::GetAverageMilliseconds() const { + if (m_nIters) + return (m_Total.GetMillisecondsF() / (double)m_nIters); + else + return 0; +} + +inline double CAverageCycleCounter::GetTotalMilliseconds() const { + return m_Total.GetMillisecondsF(); +} + +inline double CAverageCycleCounter::GetPeakMilliseconds() const { + return m_Peak.GetMillisecondsF(); +} + +// -------------------------------------------------------------------------- // + +inline CAverageTimeMarker::CAverageTimeMarker(CAverageCycleCounter *pCounter) { + m_pCounter = pCounter; + m_Timer.Start(); +} + +inline CAverageTimeMarker::~CAverageTimeMarker() { + m_Timer.End(); + m_pCounter->MarkIter(m_Timer.GetDuration()); +} + +// CLimitTimer +// Use this to time whether a desired interval of time has passed. It's +// extremely fast to check while running. +class CLimitTimer { + public: + void SetLimit(uint64 m_cMicroSecDuration); + bool BLimitReached(void); + + private: + uint64 m_lCycleLimit; +}; + +//----------------------------------------------------------------------------- +// Purpose: Initializes the limit timer with a period of time to measure. +// Input : cMicroSecDuration - How long a time period to measure +//----------------------------------------------------------------------------- +inline void CLimitTimer::SetLimit(uint64 m_cMicroSecDuration) { + uint64 dlCycles = + ((uint64)m_cMicroSecDuration * (uint64)g_dwClockSpeed) / (uint64)1000000L; + CCycleCount cycleCount; + cycleCount.Sample(); + m_lCycleLimit = cycleCount.GetLongCycles() + dlCycles; +} + +//----------------------------------------------------------------------------- +// Purpose: Determines whether our specified time period has passed +// Output: true if at least the specified time period has passed +//----------------------------------------------------------------------------- +inline bool CLimitTimer::BLimitReached() { + CCycleCount cycleCount; + cycleCount.Sample(); + return (cycleCount.GetLongCycles() >= m_lCycleLimit); +} + +#endif // VPC_TIER0_FASTTIMER_H_ diff --git a/public/tier0/ia32detect.h b/public/tier0/ia32detect.h new file mode 100644 index 0000000..5244e55 --- /dev/null +++ b/public/tier0/ia32detect.h @@ -0,0 +1,273 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_IA32DETECT_H_ +#define VPC_TIER0_IA32DETECT_H_ + +#ifdef _MSC_VER +#include +#endif + +inline void cpuid(int *info, int function_id) noexcept { +#if defined(__clang__) || defined(__GNUC__) +#if defined(_M_X64) || defined(__amd64__) + asm volatile( + "movq\t%%rbx, %%rsi\n\t" + "cpuid\n\t" + "xchgq\t%%rbx, %%rsi\n\t" + : "=a"(info[0]), "=S"(info[1]), "=c"(info[2]), "=d"(info[3]) + : "a"(function_id)); + return info; +#else +#error "Please add cpuid support for your arhitecture." + return info; +#endif // defined(_M_X64) || defined(__amd64__) +#elif defined(_MSC_VER) + __cpuid(info, function_id); +#endif +} + +// This section from http://iss.cs.cornell.edu/ia32.htm +typedef unsigned bit; + +enum CPUVendor { INTEL, AMD, UNKNOWN_VENDOR }; +class ia32detect { + public: + enum type_t { type_OEM, type_OverDrive, type_Dual, type_reserved }; + + enum brand_t { + brand_na, + brand_Celeron, + brand_PentiumIII, + brand_PentiumIIIXeon, + brand_reserved1, + brand_reserved2, + brand_PentiumIIIMobile, + brand_reserved3, + brand_Pentium4, + brand_invalid + }; + +#pragma pack(push, 1) + + struct version_t { + bit Stepping : 4; + bit Model : 4; + bit Family : 4; + bit Type : 2; + bit Reserved1 : 2; + bit XModel : 4; + bit XFamily : 8; + bit Reserved2 : 4; + }; + + struct misc_t { + ::byte Brand; + ::byte CLFLUSH; + ::byte Reserved; + ::byte APICId; + }; + + struct feature_t { + bit FPU : 1; // Floating Point Unit On-Chip + bit VME : 1; // Virtual 8086 Mode Enhancements + bit DE : 1; // Debugging Extensions + bit PSE : 1; // Page Size Extensions + bit TSC : 1; // Time Stamp Counter + bit MSR : 1; // Model Specific Registers + bit PAE : 1; // Physical Address Extension + bit MCE : 1; // Machine Check Exception + bit CX8 : 1; // CMPXCHG8 Instruction + bit APIC : 1; // APIC On-Chip + bit Reserved1 : 1; + bit SEP : 1; // SYSENTER and SYSEXIT instructions + bit MTRR : 1; // Memory Type Range Registers + bit PGE : 1; // PTE Global Bit + bit MCA : 1; // Machine Check Architecture + bit CMOV : 1; // Conditional Move Instructions + bit PAT : 1; // Page Attribute Table + bit PSE36 : 1; // 32-bit Page Size Extension + bit PSN : 1; // Processor Serial Number + bit CLFSH : 1; // CLFLUSH Instruction + bit Reserved2 : 1; + bit DS : 1; // Debug Store + bit ACPI : 1; // Thermal Monitor and Software Controlled Clock Facilities + bit MMX : 1; // Intel MMX Technology + bit FXSR : 1; // FXSAVE and FXRSTOR Instructions + bit SSE : 1; // Intel SSE Technology + bit SSE2 : 1; // Intel SSE2 Technology + bit SS : 1; // Self Snoop + bit HTT : 1; // Hyper Threading + bit TM : 1; // Thermal Monitor + bit Reserved3 : 1; + bit PBE : 1; // Pending Brk. EN. + }; + +#pragma pack(pop) + + tstring vendor_name; + CPUVendor vendor; + tstring brand; + version_t version; + misc_t misc; + feature_t feature; + ::byte *cache; + + ia32detect() { + cache = 0; + uint32 m = init0(); + + uint32 *d = new uint32[m * 4]; + + for (uint32 i = 1; i <= m; i++) { + cpuid((int *)(d + (i - 1) * 4), i); + } + + if (m >= 1) init1(d); + + if (m >= 2) init2(d[4] & 0xFF); + + delete[] d; + + init0x80000000(); + + //----------------------------------------------------------------------- + // Get the vendor of the processor + //----------------------------------------------------------------------- + if (_tcscmp(vendor_name.c_str(), _T("GenuineIntel")) == 0) { + vendor = INTEL; + + } else if (_tcscmp(vendor_name.c_str(), _T("AuthenticAMD")) == 0) { + vendor = AMD; + + } else { + vendor = UNKNOWN_VENDOR; + } + } + + const tstring version_text() const { + tchar b[128]; + + _stprintf(b, _T("%u.%u.%u %s XVersion(%u.%u)"), version.Family, + version.Model, version.Stepping, type_text(), version.XFamily, + version.XModel); + + return tstring(b); + } + + protected: + const tchar *type_text() const { + static const tchar *text[] = {_T("Intel OEM Processor"), + _T("Intel OverDrive(R) Processor"), + _T("Intel Dual Processor"), _T("reserved")}; + + return text[version.Type]; + } + + const tstring brand_text() const { + static const tchar *text[] = {_T("n/a"), + _T("Celeron"), + _T("Pentium III"), + _T("Pentium III Xeon"), + _T("reserved (4)"), + _T("reserved (5)"), + _T("Pentium III Mobile"), + _T("reserved (7)"), + _T("Pentium 4")}; + + if (misc.Brand < brand_invalid) + return tstring(text[misc.Brand]); + else { + tchar b[32]; + + _stprintf(b, _T("Brand %d (Update)"), misc.Brand); + + return tstring(b); + } + } + + private: + uint32 init0() { + int data[4]; + char vendor_id[13]; + + memset(vendor_id, 0, sizeof(vendor_id)); + cpuid(data, 0); + + memcpy(vendor_id + 0, &data[1], sizeof(data[1])); + memcpy(vendor_id + 4, &data[3], sizeof(data[3])); + memcpy(vendor_id + 8, &data[2], sizeof(data[2])); + vendor_id[12] = '\0'; + + uint32 m = data[0]; + vendor_name = vendor_id; + + return m; + } + + void init1(uint32 *d) { + version = *(version_t *)&d[0]; + misc = *(misc_t *)&d[1]; + feature = *(feature_t *)&d[3]; + } + + void process2(uint32 d, bool c[]) { + if ((d & 0x80000000) == 0) + for (int i = 0; i < 32; i += 8) c[(d >> i) & 0xFF] = true; + } + + void init2(::byte count) { + uint32 d[4]; + bool c[256]; + + for (int ci1 = 0; ci1 < 256; ci1++) c[ci1] = false; + + for (int i = 0; i < count; i++) { + cpuid((int *)d, 2); + + if (i == 0) d[0] &= 0xFFFFFF00; + + process2(d[0], c); + process2(d[1], c); + process2(d[2], c); + process2(d[3], c); + } + + int m = 0; + + for (int ci2 = 0; ci2 < 256; ci2++) + if (c[ci2]) m++; + + cache = new ::byte[m]; + + m = 0; + + for (::byte ci3 = 1; ci3 != 0; ci3++) + if (c[ci3]) cache[m++] = ci3; + + cache[m] = 0; + } + + void init0x80000000() { + uint32 m; + + int data[4]; + cpuid(data, 0x80000000); + + m = data[0]; + + if ((m & 0x80000000) != 0) { + uint32 *d = new uint32[(m - 0x80000000) * 4]; + + for (uint32 i = 0x80000001; i <= m; i++) { + cpuid((int *)(d + (i - 0x80000001) * 4), i); + } + + if (m >= 0x80000002) brand = (tchar *)(d + 4); + + // note the assignment to brand above does a copy, we need to delete + delete[] d; + } + } +}; + +#endif // VPC_TIER0_IA32DETECT_H_ diff --git a/public/tier0/icommandline.h b/public/tier0/icommandline.h new file mode 100644 index 0000000..ac63c27 --- /dev/null +++ b/public/tier0/icommandline.h @@ -0,0 +1,55 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_ICOMMANDLINE_H_ +#define VPC_TIER0_ICOMMANDLINE_H_ + +#include "tier0/platform.h" + +// Purpose: Interface to engine command line +abstract_class ICommandLine { + public: + virtual void CreateCmdLine(const char *commandline) = 0; + virtual void CreateCmdLine(int argc, char **argv) = 0; + virtual const char *GetCmdLine(void) const = 0; + + // Check whether a particular parameter exists + virtual const char *CheckParm(const char *psz, const char **ppszValue = 0) + const = 0; + virtual void RemoveParm(const char *parm) = 0; + virtual void AppendParm(const char *pszParm, const char *pszValues) = 0; + + // Returns the argument after the one specified, or the default if not found + virtual const char *ParmValue(const char *psz, const char *pDefaultVal = 0) + const = 0; + virtual int ParmValue(const char *psz, int nDefaultVal) const = 0; + virtual float ParmValue(const char *psz, float flDefaultVal) const = 0; + + // Gets at particular parameters + virtual int ParmCount() const = 0; + virtual int FindParm(const char *psz) const = 0; // Returns 0 if not found. + virtual const char *GetParm(int nIndex) const = 0; + + // copies the string passwed + virtual void SetParm(int nIndex, char const *pNewParm) = 0; +}; + +//----------------------------------------------------------------------------- +// Gets a singleton to the commandline interface +// NOTE: The #define trickery here is necessary for backwards compat: +// this interface used to lie in the vstdlib library. +//----------------------------------------------------------------------------- +PLATFORM_INTERFACE ICommandLine *CommandLine(); + +//----------------------------------------------------------------------------- +// Process related functions +//----------------------------------------------------------------------------- +PLATFORM_INTERFACE const tchar *Plat_GetCommandLine(); + +#ifndef _WIN32 +// helper function for OS's that don't have a ::GetCommandLine() call +PLATFORM_INTERFACE void Plat_SetCommandLine(const char *cmdLine); +#endif + +PLATFORM_INTERFACE const char *Plat_GetCommandLineA(); + +#endif // VPC_TIER0_ICOMMANDLINE_H_ diff --git a/public/tier0/ioctlcodes.h b/public/tier0/ioctlcodes.h new file mode 100644 index 0000000..05b2ef0 --- /dev/null +++ b/public/tier0/ioctlcodes.h @@ -0,0 +1,25 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_IOCTLCODES_H_ +#define VPC_TIER0_IOCTLCODES_H_ + +#ifdef _WIN32 +#include +#endif // _WIN32 + +// Define the IOCTL codes we will use. The IOCTL code contains a command +// identifier, plus other information about the device, the type of access +// with which the file must have been opened, and the type of buffering. + +// Device type -- in the "User Defined" range." +#define DEVICE_FILE_TYPE 40000 + +// The IOCTL function codes from 0x800 to 0xFFF are for customer use. + +#define IOCTL_WRITE_MSR \ + CTL_CODE(DEVICE_FILE_TYPE, 0x900, METHOD_BUFFERED, FILE_READ_ACCESS) + +#define IOCTL_READ_MSR \ + CTL_CODE(DEVICE_FILE_TYPE, 0x901, METHOD_BUFFERED, FILE_READ_ACCESS) + +#endif // VPC_TIER0_IOCTLCODES_H_ diff --git a/public/tier0/k8performancecounters.h b/public/tier0/k8performancecounters.h new file mode 100644 index 0000000..c77bfa2 --- /dev/null +++ b/public/tier0/k8performancecounters.h @@ -0,0 +1,1447 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_K8PERFORMANCECOUNTERS_H_ +#define VPC_TIER0_K8PERFORMANCECOUNTERS_H_ + +/* + * AMD K8 events. + * + */ + +#ifdef COMPILER_MSVC64 +extern "C" { +unsigned __int64 __readpmc(unsigned long); +} + +#pragma intrinsic(__readpmc) +#endif + +typedef union EVENT_MASK(NULL_MASK) { + // no tests defined + uint16 flat; +} EVENT_MASK(NULL_MASK); + +#define MSR_K8_EVNTSEL0 0xC0010000 /* .. 0xC0010003 */ +#define MSR_K8_PERFCTR0 0xC0010004 /* .. 0xC0010007 */ + +#pragma pack(push, 1) + +// access to these bits is through the methods +typedef union PerfEvtSel { + struct { + uint64 EventMask : 8; + + uint64 UnitMask : 8; + uint64 USR : 1; + uint64 OS : 1; + uint64 Edge : 1; + uint64 PC : 1; + uint64 INTAPIC : 1; + uint64 Reserved21 : 1; + uint64 Enable : 1; + uint64 Complement : 1; // aka INV + uint64 Threshold : 8; // aka CounterMask + uint64 Reserver32 : 32; + }; + uint64 flat; + +} PerfEvtSel; + +enum UnitEncode { FP, LS, DC, BU, IC, UE_Unknown, FR, NB }; + +// Turn off the no return value warning in ReadCounter. +#define k8NUM_COUNTERS 4 +class k8BaseEvent { + public: + PME *pme; + + PerfEvtSel eventSelect[k8NUM_COUNTERS]; + + unsigned short m_eventMask; + int event_id; + const tchar *name; + tchar revRequired; + int eventSelectNum; + UnitEncode unitEncode; + + void SetCounter(int n) { + if (n < 0) + n = 0; + else if (n > 3) + n = 3; + eventSelectNum = n; + } + k8BaseEvent() { + pme = PME::Instance(); + + for (int i = 0; i < k8NUM_COUNTERS; i++) { + eventSelect[i].flat = 0; + } + eventSelectNum = 0; + unitEncode = UE_Unknown; + + m_eventMask = 0; + event_id = 0; + name = 0; + revRequired = 'A'; + } + + void SetCaptureMode(PrivilegeCapture priv) { + PerfEvtSel &select = eventSelect[eventSelectNum]; + StopCounter(); + + switch (priv) { + case OS_Only: + select.USR = 0; + select.OS = 1; + break; + + case USR_Only: + select.USR = 1; + select.OS = 0; + break; + + case OS_and_USR: + select.USR = 1; + select.OS = 1; + break; + } + + select.UnitMask = m_eventMask; + select.EventMask = event_id; + + int selectPort = MSR_K8_EVNTSEL0 + eventSelectNum; + pme->WriteMSR(selectPort, select.flat); + } + + void SetFiltering(CompareState compareEnable, CompareMethod compareMethod, + uint8 threshold, EdgeState edgeEnable) { + PerfEvtSel &select = eventSelect[eventSelectNum]; + + StopCounter(); + + if (compareEnable == CompareDisable) + select.Threshold = 0; + else + select.Threshold = threshold; + + select.Complement = compareMethod; + + select.Edge = edgeEnable; + + int selectPort = MSR_K8_EVNTSEL0 + eventSelectNum; + pme->WriteMSR(selectPort, select.flat); + } + + void StartCounter() { + PerfEvtSel &select = eventSelect[eventSelectNum]; + + select.Enable = 1; + int selectPort = MSR_K8_EVNTSEL0 + eventSelectNum; + + pme->WriteMSR(selectPort, select.flat); + } + void StopCounter() { + PerfEvtSel &select = eventSelect[eventSelectNum]; + select.Enable = 0; + int selectPort = MSR_K8_EVNTSEL0 + eventSelectNum; + + pme->WriteMSR(selectPort, select.flat); + } + + void ClearCounter() { + int counterPort = MSR_K8_PERFCTR0 + eventSelectNum; + + pme->WriteMSR(counterPort, 0ui64); // clear + } + + void WriteCounter(int64 value) { + int counterPort = MSR_K8_PERFCTR0 + eventSelectNum; + pme->WriteMSR(counterPort, value); // clear + } + + int64 ReadCounter() { +#if PME_DEBUG + PerfEvtSel &select = eventSelect[eventSelectNum]; + + if (select.USR == 0 && select.OS == 0) + return -1; // no area to collect, use SetCaptureMode + + if (select.EventMask == 0) return -2; // no event mask set + + if (eventSelectNum < 0 || eventSelectNum > 3) + return -3; // counter not legal + + // check revision + +#endif + + // ReadMSR should work here too, but RDPMC should be faster + // ReadMSR(counterPort, int64); + + // we need to copy this into a temp for some reason +#ifdef COMPILER_MSVC64 + return __readpmc((unsigned long)eventSelectNum); +#else + int temp = eventSelectNum; + _asm + { + mov ecx, temp + RDPMC + } +#endif + } +}; + +typedef union EVENT_MASK(k8_dispatched_fpu_ops) { + // event 0 + struct { + uint16 AddPipeOps : 1; // Add pipe ops excluding junk ops" }, + uint16 MulPipeOps : 1; // Multiply pipe ops excluding junk ops" },, + uint16 StoreOps : 1; // Store pipe ops excluding junk ops" }, + uint16 AndPipeOpsJunk : 1; // Add pipe junk ops" },, + uint16 MulPipeOpsJunk : 1; // Multiply pipe junk ops" }, + uint16 StoreOpsJunk : 1; // Store pipe junk ops" } } + }; + uint16 flat; +} EVENT_MASK(k8_dispatched_fpu_ops); + +class k8Event_DISPATCHED_FPU_OPS : public k8BaseEvent { + public: + k8Event_DISPATCHED_FPU_OPS() { + eventMask = (EVENT_MASK(k8_dispatched_fpu_ops) *)&m_eventMask; + + event_id = 0x00; + unitEncode = FP; + name = _T("Dispatched FPU ops"); + revRequired = 'B'; + } + EVENT_MASK(k8_dispatched_fpu_ops) * eventMask; +}; + +////////////////////////////////////////////////////////// + +class k8Event_NO_FPU_OPS : public k8BaseEvent { + public: + k8Event_NO_FPU_OPS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + event_id = 0x01; + unitEncode = FP; + + name = _T("Cycles with no FPU ops retired"); + revRequired = 'B'; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +////////////////////////////////////////////////////////// + +class k8Event_FAST_FPU_OPS : public k8BaseEvent { + public: + k8Event_FAST_FPU_OPS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + event_id = 0x02; + unitEncode = FP; + + name = _T("Dispatched FPU ops that use the fast flag interface"); + revRequired = 'B'; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +////////////////////////////////////////////////////////// + +typedef union EVENT_MASK(k8_segment_register_load) { + struct { + uint16 ES : 1; + uint16 CS : 1; + uint16 SS : 1; + uint16 DS : 1; + uint16 FS : 1; + uint16 GS : 1; + uint16 HS : 1; + }; + uint16 flat; +} EVENT_MASK(k8_segment_register_load); + +class k8Event_SEG_REG_LOAD : public k8BaseEvent { + public: + k8Event_SEG_REG_LOAD() { + eventMask = (EVENT_MASK(k8_segment_register_load) *)&m_eventMask; + name = _T("Segment register load"); + event_id = 0x20; + unitEncode = LS; + } + EVENT_MASK(k8_segment_register_load) * eventMask; +}; + +class k8Event_SELF_MODIFY_RESYNC : public k8BaseEvent { + public: + k8Event_SELF_MODIFY_RESYNC() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Microarchitectural resync caused by self modifying code"); + event_id = 0x21; + unitEncode = LS; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_LS_RESYNC_BY_SNOOP : public k8BaseEvent { + public: + k8Event_LS_RESYNC_BY_SNOOP() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + event_id = 0x22; + unitEncode = LS; + + name = _T("Microarchitectural resync caused by snoop"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_LS_BUFFER_FULL : public k8BaseEvent { + public: + k8Event_LS_BUFFER_FULL() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("LS Buffer 2 Full"); + event_id = 0x23; + unitEncode = LS; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +typedef union EVENT_MASK(k8_locked_op) { + struct { + uint16 NumLockInstr : 1; // Number of lock instructions executed + uint16 NumCyclesInRequestGrant : 1; // Number of cycles spent in the lock + // request/grant stage + + uint16 NumCyclesForLock : 1; + /*Number of cycles a lock takes to complete once it is + non-speculative and is the oldest load/store operation + (non-speculative cycles in Ls2 entry 0)*/ + }; + uint16 flat; + +} EVENT_MASK(k8_locked_op); + +class k8Event_LOCKED_OP : public k8BaseEvent { + public: + EVENT_MASK(k8_locked_op) * eventMask; + + k8Event_LOCKED_OP() { + eventMask = (EVENT_MASK(k8_locked_op) *)&m_eventMask; + name = _T("Locked operation"); + event_id = 0x24; + unitEncode = LS; + + revRequired = 'C'; + } +}; + +class k8Event_OP_LATE_CANCEL : public k8BaseEvent { + public: + k8Event_OP_LATE_CANCEL() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Microarchitectural late cancel of an operation"); + event_id = 0x25; + unitEncode = LS; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("OP_LATE_CANCEL"); +}; +class k8Event_CFLUSH_RETIRED : public k8BaseEvent { + public: + k8Event_CFLUSH_RETIRED() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Retired CFLUSH instructions"); + event_id = 0x26; + unitEncode = LS; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("CFLUSH_RETIRED"); +}; +class k8Event_CPUID_RETIRED : public k8BaseEvent { + public: + k8Event_CPUID_RETIRED() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Retired CPUID instructions"); + event_id = 0x27; + unitEncode = LS; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("CPUID_RETIRED"); +}; + +typedef union EVENT_MASK(k8_cache) { + struct { + uint16 Invalid : 1; + uint16 Exclusive : 1; + uint16 Shared : 1; + uint16 Owner : 1; + uint16 Modified : 1; + }; + uint16 flat; + +} EVENT_MASK(k8_cache); +/* 0x40-0x47: from K7 official event set */ + +class k8Event_DATA_CACHE_ACCESSES : public k8BaseEvent { + k8Event_DATA_CACHE_ACCESSES() { + event_id = 0x40; + unitEncode = DC; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + //_T("DATA_CACHE_ACCESSES"), + name = _T("Data cache accesses"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_DATA_CACHE_MISSES : public k8BaseEvent { + k8Event_DATA_CACHE_MISSES() { + event_id = 0x41; + unitEncode = DC; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + //_T("DATA_CACHE_MISSES"), + name = _T("Data cache misses"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_DATA_CACHE_REFILLS_FROM_L2 : public k8BaseEvent { + k8Event_DATA_CACHE_REFILLS_FROM_L2() { + event_id = 0x42; + unitEncode = DC; + + eventMask = (EVENT_MASK(k8_cache) *)&m_eventMask; + + name = _T("Data cache refills from L2"); + } + EVENT_MASK(k8_cache) * eventMask; +}; + +class k8Event_DATA_CACHE_REFILLS_FROM_SYSTEM : public k8BaseEvent { + k8Event_DATA_CACHE_REFILLS_FROM_SYSTEM() { + event_id = 0x43; + unitEncode = DC; + + eventMask = (EVENT_MASK(k8_cache) *)&m_eventMask; + + // UM(k7_um_moesi), + //_T("DATA_CACHE_REFILLS_FROM_SYSTEM"), + name = _T("Data cache refills from system"); + } + EVENT_MASK(k8_cache) * eventMask; +}; + +class k8Event_DATA_CACHE_WRITEBACKS : public k8BaseEvent { + k8Event_DATA_CACHE_WRITEBACKS() { + event_id = 0x44; + unitEncode = DC; + + eventMask = (EVENT_MASK(k8_cache) *)&m_eventMask; + + // UM(k7_um_moesi), + //_T("DATA_CACHE_WRITEBACKS"), + name = _T("Data cache writebacks"); + } + EVENT_MASK(k8_cache) * eventMask; +}; + +class k8Event_L1_DTLB_MISSES_AND_L2_DTLB_HITS : public k8BaseEvent { + k8Event_L1_DTLB_MISSES_AND_L2_DTLB_HITS() { + event_id = 0x45; + unitEncode = DC; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + name = _T("L1 DTLB misses and L2 DTLB hits"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_L1_AND_L2_DTLB_MISSES : public k8BaseEvent { + k8Event_L1_AND_L2_DTLB_MISSES() { + event_id = 0x46; + unitEncode = DC; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + name = _T("L1 and L2 DTLB misses"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_MISALIGNED_DATA_REFERENCES : public k8BaseEvent { + k8Event_MISALIGNED_DATA_REFERENCES() { + event_id = 0x47; + unitEncode = DC; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + // NULL, _T("MISALIGNED_DATA_REFERENCES"), + name = _T("Misaligned data references"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_ACCESS_CANCEL_LATE : public k8BaseEvent { + public: + k8Event_ACCESS_CANCEL_LATE() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Microarchitectural late cancel of an access"); + event_id = 0x48; + unitEncode = DC; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("ACCESS_CANCEL_LATE"); +}; +class k8Event_ACCESS_CANCEL_EARLY : public k8BaseEvent { + public: + k8Event_ACCESS_CANCEL_EARLY() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Microarchitectural early cancel of an access"); + event_id = 0x49; + unitEncode = DC; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("ACCESS_CANCEL_EARLY"); +}; +typedef union EVENT_MASK(k8_ecc) { + struct { + uint16 ScrubberError : 1; // Scrubber error" }, + uint16 PiggybackScrubberErrors : 1; // Piggyback scrubber errors" } } + }; + uint16 flat; + +} EVENT_MASK(k8_ecc); + +class k8Event_ECC_BIT_ERR : public k8BaseEvent { + public: + k8Event_ECC_BIT_ERR() { + eventMask = (EVENT_MASK(k8_ecc) *)&m_eventMask; + name = _T("One bit ECC error recorded found by scrubber"); + event_id = 0x4A; + unitEncode = DC; + } + EVENT_MASK(k8_ecc) * eventMask; + // name = _T("ECC_BIT_ERR"); +}; + +// 4B +typedef union EVENT_MASK(k8_distpatch_prefetch_instructions) { + struct { + uint16 Load : 1; + uint16 Store : 1; + uint16 NTA : 1; + }; + uint16 flat; + +} EVENT_MASK(k8_distpatch_prefetch_instructions); + +class k8Event_DISPATCHED_PRE_INSTRS : public k8BaseEvent { + public: + k8Event_DISPATCHED_PRE_INSTRS() { + eventMask = (EVENT_MASK(k8_distpatch_prefetch_instructions) *)&m_eventMask; + name = _T("Dispatched prefetch instructions"); + event_id = 0x4B; + unitEncode = DC; + } + EVENT_MASK(k8_distpatch_prefetch_instructions) * eventMask; + // name = _T("DISPATCHED_PRE_INSTRS"); + + /* 0x4C: added in Revision C */ +}; + +typedef union EVENT_MASK(k8_lock_accesses) { + struct { + uint16 DcacheAccesses : 1; // Number of dcache accesses by lock + // instructions" }, + uint16 + DcacheMisses : 1; // Number of dcache misses by lock instructions" } } + }; + uint16 flat; + +} EVENT_MASK(k8_lock_accesses); + +class k8Event_LOCK_ACCESSES : public k8BaseEvent { + public: + k8Event_LOCK_ACCESSES() { + eventMask = (EVENT_MASK(k8_lock_accesses) *)&m_eventMask; + name = _T("DCACHE accesses by locks"); + event_id = 0x4C; + unitEncode = DC; + + revRequired = 'C'; + } + EVENT_MASK(k8_lock_accesses) * eventMask; +}; + +class k8Event_CYCLES_PROCESSOR_IS_RUNNING : public k8BaseEvent { + public: + k8Event_CYCLES_PROCESSOR_IS_RUNNING() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Cycles processor is running (not in HLT or STPCLK)"); + event_id = 0x76; + unitEncode = BU; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("CYCLES_PROCESSOR_IS_RUNNING"); /* undocumented *; +}; + +typedef union EVENT_MASK(k8_internal_L2_request) { + struct { + uint16 ICFill : 1; // IC fill" }, + uint16 DCFill : 1; // DC fill" }, + uint16 TLBReload : 1; // TLB reload" }, + uint16 TagSnoopRequest : 1; // Tag snoop request" }, + uint16 CancelledRequest : 1; // Cancelled request" } } + }; + uint16 flat; + +} EVENT_MASK(k8_internal_L2_request); + +class k8Event_BU_INT_L2_REQ : public k8BaseEvent { + public: + k8Event_BU_INT_L2_REQ() { + eventMask = (EVENT_MASK(k8_internal_L2_request) *)&m_eventMask; + name = _T("Internal L2 request"); + unitEncode = BU; + event_id = 0x7D; + } + + EVENT_MASK(k8_internal_L2_request) * eventMask; +}; + +// name = _T("BU_INT_L2_REQ"); + +// 7E +typedef union EVENT_MASK(k8_fill_request_missed_L2) { + struct { + uint16 ICFill : 1; // IC fill" }, + uint16 DCFill : 1; // DC fill" }, + uint16 TLBReload : 1; // TLB reload" }, + }; + uint16 flat; + +} EVENT_MASK(k8_fill_request_missed_L2); + +class k8Event_BU_FILL_REQ : public k8BaseEvent { + public: + k8Event_BU_FILL_REQ() { + eventMask = (EVENT_MASK(k8_fill_request_missed_L2) *)&m_eventMask; + name = _T("Fill request that missed in L2"); + event_id = 0x7E; + unitEncode = BU; + } + EVENT_MASK(k8_fill_request_missed_L2) * eventMask; + // name = _T("BU_FILL_REQ"); +}; + +// 7F +typedef union EVENT_MASK(k8_fill_into_L2) { + struct { + uint16 DirtyL2Victim : 1; // Dirty L2 victim + uint16 VictimFromL2 : 1; // Victim from L2 + }; + uint16 flat; + +} EVENT_MASK(k8_fill_into_L2); + +class k8Event_BU_FILL_L2 : public k8BaseEvent { + public: + k8Event_BU_FILL_L2() { + eventMask = (EVENT_MASK(k8_fill_into_L2) *)&m_eventMask; + name = _T("Fill into L2"); + event_id = 0x7F; + unitEncode = BU; + } + EVENT_MASK(k8_fill_into_L2) * eventMask; + // name = _T("BU_FILL_L2"); +}; + +class k8Event_INSTRUCTION_CACHE_FETCHES : public k8BaseEvent { + public: + k8Event_INSTRUCTION_CACHE_FETCHES() { + event_id = 0x80; + unitEncode = IC; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + name = _T("Instruction cache fetches"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_INSTRUCTION_CACHE_MISSES : public k8BaseEvent { + public: + k8Event_INSTRUCTION_CACHE_MISSES() { + event_id = 0x81; + unitEncode = IC; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + // 0xF, NULL, _T("INSTRUCTION_CACHE_MISSES"), + name = _T("Instruction cache misses"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_IC_REFILL_FROM_L2 : public k8BaseEvent { + public: + k8Event_IC_REFILL_FROM_L2() { + eventMask = (EVENT_MASK(k8_cache) *)&m_eventMask; + name = _T("Refill from L2"); + event_id = 0x82; + unitEncode = IC; + } + EVENT_MASK(k8_cache) * eventMask; + // name = _T("IC_REFILL_FROM_L2"); +}; +class k8Event_IC_REFILL_FROM_SYS : public k8BaseEvent { + public: + k8Event_IC_REFILL_FROM_SYS() { + eventMask = (EVENT_MASK(k8_cache) *)&m_eventMask; + name = _T("Refill from system"); + event_id = 0x83; + unitEncode = IC; + } + EVENT_MASK(k8_cache) * eventMask; + // name = _T("IC_REFILL_FROM_SYS"); +}; +class k8Event_L1_ITLB_MISSES_AND_L2_ITLB_HITS : public k8BaseEvent { + public: + k8Event_L1_ITLB_MISSES_AND_L2_ITLB_HITS() { + event_id = 0x84; + unitEncode = IC; + + name = _T("L1 ITLB misses (and L2 ITLB hits)"); + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_L1_AND_L2_ITLB_MISSES : public k8BaseEvent { + public: + k8Event_L1_AND_L2_ITLB_MISSES() { + event_id = 0x85; + unitEncode = IC; + + name = _T("(L1 and) L2 ITLB misses"); + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_IC_RESYNC_BY_SNOOP : public k8BaseEvent { + public: + k8Event_IC_RESYNC_BY_SNOOP() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + event_id = 0x86; + unitEncode = IC; + + name = _T("Microarchitectural resync caused by snoop"); + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("IC_RESYNC_BY_SNOOP"); + /* similar to 0x22; but IC unit instead of LS unit */ +}; +class k8Event_IC_FETCH_STALL : public k8BaseEvent { + public: + k8Event_IC_FETCH_STALL() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Instruction fetch stall"); + event_id = 0x87; + unitEncode = IC; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("IC_FETCH_STALL"); +}; +class k8Event_IC_STACK_HIT : public k8BaseEvent { + public: + k8Event_IC_STACK_HIT() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Return stack hit"); + event_id = 0x88; + unitEncode = IC; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("IC_STACK_HIT"); +}; +class k8Event_IC_STACK_OVERFLOW : public k8BaseEvent { + public: + k8Event_IC_STACK_OVERFLOW() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Return stack overflow"); + event_id = 0x89; + unitEncode = IC; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("IC_STACK_OVERFLOW"); +}; + +/* 0xC0-0xC7: from K7 official event set */ +class k8Event_RETIRED_INSTRUCTIONS : public k8BaseEvent { + public: + k8Event_RETIRED_INSTRUCTIONS() { + event_id = 0xC0; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + + // 0xF, NULL, _T("RETIRED_INSTRUCTIONS"), + name = + _T("Retired instructions (includes exceptions, interrupts, resyncs)"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_RETIRED_OPS : public k8BaseEvent { + public: + k8Event_RETIRED_OPS() { + event_id = 0xC1; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + // 0xF, NULL, _T("RETIRED_OPS"), + name = _T("Retired Ops"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_RETIRED_BRANCHES : public k8BaseEvent { + public: + k8Event_RETIRED_BRANCHES() { + event_id = 0xC2; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + // 0xF, NULL, _T("RETIRED_BRANCHES"), + name = + _T("Retired branches (conditional, unconditional, exceptions, ") + _T("interrupts)"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_RETIRED_BRANCHES_MISPREDICTED : public k8BaseEvent { + public: + k8Event_RETIRED_BRANCHES_MISPREDICTED() { + event_id = 0xC3; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + // 0xF, NULL, _T("RETIRED_BRANCHES_MISPREDICTED"), + name = _T("Retired branches mispredicted"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_RETIRED_TAKEN_BRANCHES : public k8BaseEvent { + public: + k8Event_RETIRED_TAKEN_BRANCHES() { + event_id = 0xC4; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + // 0xF, NULL, _T("RETIRED_TAKEN_BRANCHES"), + name = _T("Retired taken branches"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_RETIRED_TAKEN_BRANCHES_MISPREDICTED : public k8BaseEvent { + public: + k8Event_RETIRED_TAKEN_BRANCHES_MISPREDICTED() { + event_id = 0xC5; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + // 0xF, NULL, _T("RETIRED_TAKEN_BRANCHES_MISPREDICTED"), + name = _T("Retired taken branches mispredicted"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_RETIRED_FAR_CONTROL_TRANSFERS : public k8BaseEvent { + public: + k8Event_RETIRED_FAR_CONTROL_TRANSFERS() { + event_id = 0xC6; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + // 0xF, NULL, _T("RETIRED_FAR_CONTROL_TRANSFERS"), + name = _T("Retired far control transfers"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_RETIRED_RESYNC_BRANCHES : public k8BaseEvent { + public: + k8Event_RETIRED_RESYNC_BRANCHES() { + event_id = 0xC7; + unitEncode = FR; + + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + // 0xF, NULL, _T("RETIRED_RESYNC_BRANCHES"), + name = + _T("Retired resync branches (only non-control transfer branches ") + _T("counted)"); + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_RETIRED_NEAR_RETURNS : public k8BaseEvent { + public: + k8Event_RETIRED_NEAR_RETURNS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Retired near returns"); + event_id = 0xC8; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_RETIRED_RETURNS_MISPREDICT : public k8BaseEvent { + public: + k8Event_RETIRED_RETURNS_MISPREDICT() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Retired near returns mispredicted"); + event_id = 0xC9; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("RETIRED_RETURNS_MISPREDICT"); +}; +class k8Event_RETIRED_BRANCH_MISCOMPARE : public k8BaseEvent { + public: + k8Event_RETIRED_BRANCH_MISCOMPARE() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Retired taken branches mispredicted due to address miscompare"); + event_id = 0xCA; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("RETIRED_BRANCH_MISCOMPARE"); +}; + +/* Revision B and later */ + +typedef union EVENT_MASK(k8_retired_fpu_instr) { + struct { + uint16 DirtyL2Victim : 1; // x87 instructions + uint16 CombinedMMX_3DNow : 1; // Combined MMX & 3DNow! instructions" }, + uint16 CombinedPackedSSE_SSE2 : 1; // Combined packed SSE and SSE2 + // instructions" }, + uint16 CombinedScalarSSE_SSE2 : 1; // Combined scalar SSE and SSE2 + // instructions" } } + }; + uint16 flat; + +} EVENT_MASK(k8_retired_fpu_instr); + +class k8Event_RETIRED_FPU_INSTRS : public k8BaseEvent { + public: + k8Event_RETIRED_FPU_INSTRS() { + eventMask = (EVENT_MASK(k8_retired_fpu_instr) *)&m_eventMask; + event_id = 0xCB; + unitEncode = FR; + + name = _T("Retired FPU instructions"); + revRequired = 'B'; + } + EVENT_MASK(k8_retired_fpu_instr) * eventMask; + /* Revision B and later */ +}; + +// CC +typedef union EVENT_MASK(k8_retired_fastpath_double_op_instr) { + struct { + uint16 LowOpPosition0 : 1; // With low op in position 0" }, + uint16 LowOpPosition1 : 1; // With low op in position 1" }, + uint16 LowOpPosition2 : 1; // With low op in position 2" } } + }; + uint16 flat; + +} EVENT_MASK(k8_retired_fastpath_double_op_instr); + +class k8Event_RETIRED_FASTPATH_INSTRS : public k8BaseEvent { + public: + k8Event_RETIRED_FASTPATH_INSTRS() { + eventMask = (EVENT_MASK(k8_retired_fastpath_double_op_instr) *)&m_eventMask; + event_id = 0xCC; + unitEncode = FR; + + name = _T("Retired fastpath double op instructions"); + revRequired = 'B'; + } + EVENT_MASK(k8_retired_fastpath_double_op_instr) * eventMask; +}; + +class k8Event_INTERRUPTS_MASKED_CYCLES : public k8BaseEvent { + public: + k8Event_INTERRUPTS_MASKED_CYCLES() { + event_id = 0xCD; + unitEncode = FR; + + // 0xF, NULL, _T("INTERRUPTS_MASKED_CYCLES"), + name = _T("Interrupts masked cycles (IF=0)"); + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_INTERRUPTS_MASKED_WHILE_PENDING_CYCLES : public k8BaseEvent { + public: + k8Event_INTERRUPTS_MASKED_WHILE_PENDING_CYCLES() { + event_id = 0xCE; + unitEncode = FR; + + // 0xF, NULL, _T("INTERRUPTS_MASKED_WHILE_PENDING_CYCLES"), + name = _T("Interrupts masked while pending cycles (INTR while IF=0)"); + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; +class k8Event_NUMBER_OF_TAKEN_HARDWARE_INTERRUPTS : public k8BaseEvent { + public: + k8Event_NUMBER_OF_TAKEN_HARDWARE_INTERRUPTS() { + event_id = 0xCF; + unitEncode = FR; + + // 0xF, NULL, _T("NUMBER_OF_TAKEN_HARDWARE_INTERRUPTS"), + name = _T("Number of taken hardware interrupts"); + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + } + EVENT_MASK(NULL_MASK) * eventMask; +}; + +class k8Event_DECODER_EMPTY : public k8BaseEvent { + public: + k8Event_DECODER_EMPTY() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Nothing to dispatch (decoder empty)"); + event_id = 0xD0; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DECODER_EMPTY"); +}; +class k8Event_DISPATCH_STALLS : public k8BaseEvent { + public: + k8Event_DISPATCH_STALLS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stalls (events 0xD2-0xDA combined)"); + event_id = 0xD1; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALLS"); +}; +class k8Event_DISPATCH_STALL_FROM_BRANCH_ABORT : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_FROM_BRANCH_ABORT() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall from branch abort to retire"); + event_id = 0xD2; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_FROM_BRANCH_ABORT"); +}; +class k8Event_DISPATCH_STALL_SERIALIZATION : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_SERIALIZATION() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall for serialization"); + event_id = 0xD3; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_SERIALIZATION"); +}; +class k8Event_DISPATCH_STALL_SEG_LOAD : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_SEG_LOAD() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall for segment load"); + event_id = 0xD4; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_SEG_LOAD"); +}; +class k8Event_DISPATCH_STALL_REORDER_BUFFER : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_REORDER_BUFFER() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall when reorder buffer is full"); + event_id = 0xD5; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_REORDER_BUFFER"); +}; +class k8Event_DISPATCH_STALL_RESERVE_STATIONS : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_RESERVE_STATIONS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall when reservation stations are full"); + event_id = 0xD6; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_RESERVE_STATIONS"); +}; +class k8Event_DISPATCH_STALL_FPU : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_FPU() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall when FPU is full"); + event_id = 0xD7; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_FPU"); +}; +class k8Event_DISPATCH_STALL_LS : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_LS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall when LS is full"); + event_id = 0xD8; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_LS"); +}; +class k8Event_DISPATCH_STALL_QUIET_WAIT : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_QUIET_WAIT() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Dispatch stall when waiting for all to be quiet"); + event_id = 0xD9; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_QUIET_WAIT"); +}; +class k8Event_DISPATCH_STALL_PENDING : public k8BaseEvent { + public: + k8Event_DISPATCH_STALL_PENDING() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = + _T("Dispatch stall when far control transfer or resync branch is ") + _T("pending"); + event_id = 0xDA; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DISPATCH_STALL_PENDING"); +}; + +typedef union EVENT_MASK(k8_fpu_exceptions) { + struct { + uint16 x87ReclassMicrofaults : 1; // x87 reclass microfaults" }, + uint16 SSERetypeMicrofaults : 1; // SSE retype microfaults" }, + uint16 SSEReclassMicrofaults : 1; // SSE reclass microfaults" }, + uint16 SSE_x87Microtraps : 1; // SSE and x87 microtraps" } } + }; + uint16 flat; + +} EVENT_MASK(k8_fpu_exceptions); + +class k8Event_FPU_EXCEPTIONS : public k8BaseEvent { + public: + k8Event_FPU_EXCEPTIONS() { + eventMask = (EVENT_MASK(k8_fpu_exceptions) *)&m_eventMask; + event_id = 0xDB; + unitEncode = FR; + + name = _T("FPU exceptions"); + revRequired = 'B'; + } + EVENT_MASK(k8_fpu_exceptions) * eventMask; + // name = _T("FPU_EXCEPTIONS"); + /* Revision B and later */ +}; +class k8Event_DR0_BREAKPOINTS : public k8BaseEvent { + public: + k8Event_DR0_BREAKPOINTS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Number of breakpoints for DR0"); + event_id = 0xDC; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DR0_BREAKPOINTS"); +}; +class k8Event_DR1_BREAKPOINTS : public k8BaseEvent { + public: + k8Event_DR1_BREAKPOINTS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Number of breakpoints for DR1"); + event_id = 0xDD; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DR1_BREAKPOINTS"); +}; +class k8Event_DR2_BREAKPOINTS : public k8BaseEvent { + public: + k8Event_DR2_BREAKPOINTS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Number of breakpoints for DR2"); + event_id = 0xDE; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DR2_BREAKPOINTS"); +}; +class k8Event_DR3_BREAKPOINTS : public k8BaseEvent { + public: + k8Event_DR3_BREAKPOINTS() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Number of breakpoints for DR3"); + event_id = 0xDF; + unitEncode = FR; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DR3_BREAKPOINTS"); +}; + +// E0 +typedef union EVENT_MASK(k8_page_access_event) { + struct { + uint16 PageHit : 1; // Page hit" }, + uint16 PageMiss : 1; // Page miss" }, + uint16 PageConflict : 1; // Page conflict" } } + }; + uint16 flat; + +} EVENT_MASK(k8_page_access_event); + +class k8Event_MEM_PAGE_ACCESS : public k8BaseEvent { + public: + k8Event_MEM_PAGE_ACCESS() { + eventMask = (EVENT_MASK(k8_page_access_event) *)&m_eventMask; + name = _T("Memory controller page access"); + event_id = 0xE0; + unitEncode = NB; + } + EVENT_MASK(k8_page_access_event) * eventMask; + // name = _T("MEM_PAGE_ACCESS"); +}; +class k8Event_MEM_PAGE_TBL_OVERFLOW : public k8BaseEvent { + public: + k8Event_MEM_PAGE_TBL_OVERFLOW() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Memory controller page table overflow"); + event_id = 0xE1; + unitEncode = NB; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("MEM_PAGE_TBL_OVERFLOW"); +}; +class k8Event_DRAM_SLOTS_MISSED : public k8BaseEvent { + public: + k8Event_DRAM_SLOTS_MISSED() { + eventMask = (EVENT_MASK(NULL_MASK) *)&m_eventMask; + name = _T("Memory controller DRAM command slots missed (in MemClks)"); + event_id = 0xE2; + unitEncode = NB; + } + EVENT_MASK(NULL_MASK) * eventMask; + // name = _T("DRAM_SLOTS_MISSED"); +}; + +// e3 +typedef union EVENT_MASK(k8_turnaround) { + struct { + uint16 DIMMTurnaround : 1; // DIMM turnaround" }, + uint16 ReadToWriteTurnaround : 1; // Read to write turnaround" }, + uint16 WriteToReadTurnaround : 1; // Write to read turnaround" } } + }; + uint16 flat; + +} EVENT_MASK(k8_turnaround); + +class k8Event_MEM_TURNAROUND : public k8BaseEvent { + public: + k8Event_MEM_TURNAROUND() { + eventMask = (EVENT_MASK(k8_turnaround) *)&m_eventMask; + name = _T("Memory controller turnaround"); + event_id = 0xE3; + unitEncode = NB; + } + EVENT_MASK(k8_turnaround) * eventMask; + // name = _T("MEM_TURNAROUND"); +}; + +// E4 +typedef union EVENT_MASK(k8_bypass_counter_saturation) { + struct { + uint16 MEM_HighPriorityBypass : 1; // Memory controller high priority + // bypass" }, + uint16 + MEM_LowPriorityBypass : 1; // Memory controller low priority bypass" }, + uint16 DRAM_InterfaceBypass : 1; // DRAM controller interface bypass" }, + uint16 DRAM_QueueBypass : 1; // DRAM controller queue bypass" } } + }; + uint16 flat; + +} EVENT_MASK(k8_bypass_counter_saturation); + +class k8Event_MEM_BYPASS_SAT : public k8BaseEvent { + public: + k8Event_MEM_BYPASS_SAT() { + eventMask = (EVENT_MASK(k8_bypass_counter_saturation) *)&m_eventMask; + name = _T("Memory controller bypass counter saturation"); + event_id = 0xE4; + unitEncode = NB; + } + EVENT_MASK(k8_bypass_counter_saturation) * eventMask; + // name = _T("MEM_BYPASS_SAT"); +}; + +// EB +typedef union EVENT_MASK(k8_sized_commands) { + struct { + uint16 NonPostWrSzByte : 1; // NonPostWrSzByte" }, + uint16 NonPostWrSzDword : 1; // NonPostWrSzDword" }, + uint16 PostWrSzByte : 1; // PostWrSzByte" }, + uint16 PostWrSzDword : 1; // PostWrSzDword" }, + uint16 RdSzByte : 1; // RdSzByte" }, + uint16 RdSzDword : 1; // RdSzDword" }, + uint16 RdModWr : 1; // RdModWr" } } + }; + uint16 flat; + +} EVENT_MASK(k8_sized_commands); + +class k8Event_SIZED_COMMANDS : public k8BaseEvent { + public: + k8Event_SIZED_COMMANDS() { + eventMask = (EVENT_MASK(k8_sized_commands) *)&m_eventMask; + name = _T("Sized commands"); + event_id = 0xEB; + unitEncode = NB; + } + EVENT_MASK(k8_sized_commands) * eventMask; + // name = _T("SIZED_COMMANDS"); +}; + +typedef union EVENT_MASK(k8_probe_result) { + struct { + uint16 ProbeMiss : 1; // Probe miss" }, + uint16 ProbeHit : 1; // Probe hit" }, + uint16 ProbeHitDirtyWithoutMemoryCancel : 1; // Probe hit dirty without + // memory cancel" }, + uint16 ProbeHitDirtyWithMemoryCancel : 1; // Probe hit dirty with memory + // cancel" } } + uint16 UpstreamDisplayRefreshReads : 1; // Rev D and later + uint16 UpstreamNonDisplayRefreshReads : 1; // Rev D and later + uint16 UpstreamWrites : 1; // Rev D and later + }; + uint16 flat; + +} EVENT_MASK(k8_probe_result); + +class k8Event_PROBE_RESULT : public k8BaseEvent { + public: + k8Event_PROBE_RESULT() { + eventMask = (EVENT_MASK(k8_probe_result) *)&m_eventMask; + name = _T("Probe result"); + event_id = 0xEC; + unitEncode = NB; + } + EVENT_MASK(k8_probe_result) * eventMask; + // name = _T("PROBE_RESULT"); +}; + +typedef union EVENT_MASK(k8_ht) { + struct { + uint16 CommandSent : 1; // Command sent" }, + uint16 DataSent : 1; // Data sent" }, + uint16 BufferReleaseSent : 1; // Buffer release sent" + uint16 NopSent : 1; // Nop sent" } } + }; + uint16 flat; + +} EVENT_MASK(k8_ht); + +class k8Event_HYPERTRANSPORT_BUS0_WIDTH : public k8BaseEvent { + public: + k8Event_HYPERTRANSPORT_BUS0_WIDTH() { + eventMask = (EVENT_MASK(k8_ht) *)&m_eventMask; + name = _T("Hypertransport (tm) bus 0 bandwidth"); + event_id = 0xF6; + unitEncode = NB; + } + EVENT_MASK(k8_ht) * eventMask; + // name = _T("HYPERTRANSPORT_BUS0_WIDTH"); +}; +class k8Event_HYPERTRANSPORT_BUS1_WIDTH : public k8BaseEvent { + public: + k8Event_HYPERTRANSPORT_BUS1_WIDTH() { + eventMask = (EVENT_MASK(k8_ht) *)&m_eventMask; + name = _T("Hypertransport (tm) bus 1 bandwidth"); + event_id = 0xF7; + unitEncode = NB; + } + EVENT_MASK(k8_ht) * eventMask; + // name = _T("HYPERTRANSPORT_BUS1_WIDTH"); +}; +class k8Event_HYPERTRANSPORT_BUS2_WIDTH : public k8BaseEvent { + public: + k8Event_HYPERTRANSPORT_BUS2_WIDTH() { + eventMask = (EVENT_MASK(k8_ht) *)&m_eventMask; + name = _T("Hypertransport (tm) bus 2 bandwidth"); + event_id = 0xF8; + unitEncode = NB; + } + EVENT_MASK(k8_ht) * eventMask; + // name = _T("HYPERTRANSPORT_BUS2_WIDTH"); +}; + +// +// typedef union EVENT_MASK( perfctr_event_set k8_common_event_set) +//{ +// +// .cpu_type = PERFCTR_X86_AMD_K8, +// .event_prefix = _T("K8_"), +// .include = &k7_official_event_set, +// .nevents = ARRAY_SIZE(k8_common_events), +// .events = k8_common_events, +//}EVENT_MASK( perfctr_event_set k8_common_event_set); +// +// typedef union EVENT_MASK( perfctr_event k8_events[]) +//{ +// +// { 0x24, 0xF, UM(NULL), _T("LOCKED_OP"), /* unit mask changed in Rev. C */ +// _T("Locked operation") }, +//}EVENT_MASK( perfctr_event k8_events[]); + +// const struct perfctr_event_set perfctr_k8_event_set) +//{ +// +// .cpu_type = PERFCTR_X86_AMD_K8, +// .event_prefix = _T("K8_"), +// .include = &k8_common_event_set, +// .nevents = ARRAY_SIZE(k8_events), +// .events = k8_events, +//}; +// +/* + * K8 Revision C. Starts at CPUID 0xF58 for Opteron/Athlon64FX and + * CPUID 0xF48 for Athlon64. (CPUID 0xF51 is Opteron Revision B3.) + */ + +// +// typedef union EVENT_MASK( k8_lock_accesses) +//{ +// struct +// { +// uint16 DcacheAccesses:1; // Number of dcache accesses by lock +// instructions" }, uint16 DcacheMisses:1; // Number of dcache +// misses by lock instructions" } } +// }; +// uint16 flat; +// +//}EVENT_MASK( k8_lock_accesses); +// + +#endif // VPC_TIER0_K8PERFORMANCECOUNTERS_H_ diff --git a/public/tier0/l2cache.h b/public/tier0/l2cache.h new file mode 100644 index 0000000..3454f5b --- /dev/null +++ b/public/tier0/l2cache.h @@ -0,0 +1,34 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_CL2CACHE_H_ +#define VPC_TIER0_CL2CACHE_H_ + +class P4Event_BSQ_cache_reference; + +class CL2Cache { + public: + CL2Cache(); + ~CL2Cache(); + + void Start(void); + void End(void); + + //------------------------------------------------------------------------- + // GetL2CacheMisses + //------------------------------------------------------------------------- + int GetL2CacheMisses(void) { return m_iL2CacheMissCount; } + +#ifdef DBGFLAG_VALIDATE + void Validate(CValidator &validator, + tchar *pchName); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + + private: + P4Event_BSQ_cache_reference *m_pL2CacheEvent; + int64 m_i64Start; + int64 m_i64End; + int m_nID; + int m_iL2CacheMissCount; +}; + +#endif // VPC_TIER0_CL2CACHE_H_ diff --git a/public/tier0/logging.h b/public/tier0/logging.h new file mode 100644 index 0000000..3e5cd6e --- /dev/null +++ b/public/tier0/logging.h @@ -0,0 +1,700 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Logging system declarations. +// +// The logging system is a channel-based output mechanism which allows +// subsystems to route their text/diagnostic output to various listeners +// +// The logging system is a channel-based mechanism for all code (engine, +// mod, tool) across all platforms to output information, warnings, errors, etc. +// +// This system supersedes the existing Msg(), Warning(), Error(), DevMsg(), +// ConMsg() etc. functions. There are channels defined in the new system +// through which all old messages are routed; see LOG_GENERAL, LOG_CONSOLE, +// LOG_DEVELOPER, etc. +// +// To use the system, simply call one of the predefined macros: +// +// Log_Msg(ChannelID, [Color], Message, ...) +// Log_Warning(ChannelID, [Color], Message, ...) +// Log_Error(ChannelID, [Color], Message, ...) +// +// A ChannelID is typically created by defining a logging channel with +// the log channel macros: +// +// DEFINE_LOGGING_CHANNEL_NO_TAGS(LOG_ChannelName, "ChannelName", [Flags], +// [MinimumSeverity], [Color]); +// +// or +// +// BEGIN_DEFINE_LOGGING_CHANNEL(LOG_ChannelName, "ChannelName", +// [Flags], [MinimumSeverity], [Color] ); ADD_LOGGING_CHANNEL_TAG( "Tag1" ); +// ADD_LOGGING_CHANNEL_TAG( "Tag2" ); +// END_DEFINE_LOGGING_CHANNEL(); +// +// These macros create a global channel ID variable with the name specified by +// the first parameter (in this example, LOG_ChannelName). This channel ID can +// be used by various LoggingSystem_** functions to manipulate the channel +// settings. +// +// The optional [Flags] parameter is an OR'd together set of +// LoggingChannelFlags_t values (default: 0).// The optional [MinimumSeverity] +// parameter is the lowest threshold above which messages will be processed +// (inclusive). The default is LS_MESSAGE, which results in all messages, +// warnings, and errors being logged. +// +// Variadic parameters to the Log_** functions will be ignored if a channel is +// not enabled for a given severity (for performance reasons). +// +// Logging channels can have their minimum severity modified by name, ID, or +// tag. +// +// Logging channels are not hierarchical since there are situations in which a +// channel needs to belong to multiple hierarchies. Use tags to create +// categories or shallow hierarchies. +// +// TODO: Feature wishlist: +// 1) Callstack logging support +// 2) Registering dynamic channels and unregistering channels at runtime +// 3) Sentient robot to clean up the thousands of places using the old/legacy +// logging system. + +#ifndef VPC_TIER0_LOGGING_H_ +#define VPC_TIER0_LOGGING_H_ + +#include "color.h" +#include "icommandline.h" +#include + +// For XBX_** functions +#if defined(_X360) +#include "xbox/xbox_console.h" +#endif + +// Used by CColorizedLoggingListener +#if defined(_WIN32) || (defined(POSIX) && !defined(_GAMECONSOLE)) +#include "tier0/win32consoleio.h" +#endif + +// Constants, Types, Forward Declares +class CLoggingSystem; +class CThreadFastMutex; + +// Maximum length of a sprintf'ed logging message. +const int MAX_LOGGING_MESSAGE_LENGTH = 2048; + +// Maximum length of a channel or tag name. +const int MAX_LOGGING_IDENTIFIER_LENGTH = 32; + +// Maximum number of logging channels. Increase if needed. +const int MAX_LOGGING_CHANNEL_COUNT = 256; + +// Maximum number of logging tags across all channels. Increase if needed. +const int MAX_LOGGING_TAG_COUNT = 1024; + +// Maximum number of characters across all logging tags. Increase if needed. +const int MAX_LOGGING_TAG_CHARACTER_COUNT = 8192; + +// Maximum number of concurrent logging listeners in a given logging state. +const int MAX_LOGGING_LISTENER_COUNT = 16; + +// An invalid color set on a channel to imply that it should use +// a device-dependent default color where applicable. +const inline Color UNSPECIFIED_LOGGING_COLOR(0, 0, 0, 0); + +// An ID returned by the logging system to refer to a logging channel. +typedef int LoggingChannelID_t; + +// A sentinel value indicating an invalid logging channel ID. +const LoggingChannelID_t INVALID_LOGGING_CHANNEL_ID = -1; + +// The severity of a logging operation. +enum LoggingSeverity_t { + // An informative logging message. + LS_MESSAGE = 0, + + // A warning, typically non-fatal + LS_WARNING = 1, + + // A message caused by an Assert**() operation. + LS_ASSERT = 2, + + // An error, typically fatal/unrecoverable. + LS_ERROR = 3, + + // A placeholder level, higher than any legal value. + // Not a real severity value! + LS_HIGHEST_SEVERITY = 4, +}; + +// Action which should be taken by logging system as a result of +// a given logged message. +// +// The logging system invokes ILoggingResponsePolicy::OnLog() on +// the specified policy object, which returns a LoggingResponse_t. +enum LoggingResponse_t { + LR_CONTINUE, + LR_DEBUGGER, + LR_ABORT, +}; + +// Logging channel behavior flags, set on channel creation. +enum LoggingChannelFlags_t { + // Indicates that the spew is only relevant to interactive consoles. + LCF_CONSOLE_ONLY = 0x00000001, + + // Indicates that spew should not be echoed to any output devices. + // A suitable logging listener must be registered which respects this flag + // (e.g. a file logger). + LCF_DO_NOT_ECHO = 0x00000002, +}; + +// A callback function used to register tags on a logging channel +// during initialization. +typedef void (*RegisterTagsFunc)(); + +// A context structure passed to logging listeners and response policy classes. +struct LoggingContext_t { + // ID of the channel being logged to. + LoggingChannelID_t m_ChannelID; + // Flags associated with the channel. + LoggingChannelFlags_t m_Flags; + // Severity of the logging event. + LoggingSeverity_t m_Severity; + // Color of logging message if one was specified to Log_****() macro. + // If not specified, falls back to channel color. + // If channel color is not specified, this value is UNSPECIFIED_LOGGING_COLOR + // and indicates that a suitable default should be chosen. + Color m_Color; +}; + +// Interface for classes to handle logging output. +// +// The Log() function of this class is called synchronously and serially +// by the logging system on all registered instances of ILoggingListener +// in the current "logging state". +// +// Derived classes may do whatever they want with the message (write to disk, +// write to console, send over the network, drop on the floor, etc.). +// +// In general, derived classes should do one, simple thing with the output +// to allow callers to register multiple, orthogonal logging listener classes. +class ILoggingListener { + public: + virtual void Log(const LoggingContext_t *pContext, const tchar *pMessage) = 0; +}; + +// Interface for policy classes which determine how to behave when a +// message is logged. +// +// Can return: +// LR_CONTINUE (continue execution) +// LR_DEBUGGER (break into debugger if one is present, otherwise continue) +// LR_ABORT (terminate process immediately with a failure code of 1) +class ILoggingResponsePolicy { + public: + virtual LoggingResponse_t OnLog(const LoggingContext_t *pContext) = 0; +}; + +////////////////////////////////////////////////////////////////////////// +// Common Logging Listeners & Logging Response Policies +////////////////////////////////////////////////////////////////////////// + +// A basic logging listener which prints to stdout and the debug channel. +class CSimpleLoggingListener : public ILoggingListener { + public: + CSimpleLoggingListener(bool bQuietPrintf = false, bool bQuietDebugger = false) + : m_bQuietPrintf(bQuietPrintf), m_bQuietDebugger(bQuietDebugger) {} + + virtual void Log(const LoggingContext_t *, const tchar *pMessage) { +#ifdef _X360 + if (!m_bQuietDebugger && XBX_IsConsoleConnected()) { + // send to console + XBX_DebugString(XMAKECOLOR(0, 0, 0), pMessage); + } else +#endif + { +#if !defined(_CERT) && !defined(DBGFLAG_STRINGS_STRIP) + if (!m_bQuietPrintf) { + _tprintf(_T("%s"), pMessage); + } +#endif + +#ifdef _WIN32 + if (!m_bQuietDebugger && Plat_IsInDebugSession()) { + Plat_DebugString(pMessage); + } +#endif + } + } + + // If set to true, does not print anything to stdout. + bool m_bQuietPrintf; + // If set to true, does not print anything to debugger. + bool m_bQuietDebugger; +}; + +// A basic logging listener for GUI applications +class CSimpleWindowsLoggingListener : public ILoggingListener { + public: + virtual void Log(const LoggingContext_t *pContext, const tchar *pMessage) { + if (Plat_IsInDebugSession()) { + Plat_DebugString(pMessage); + } + if (pContext->m_Severity == LS_ERROR) { + if (Plat_IsInDebugSession()) DebuggerBreak(); + + Plat_MessageBox("Error", pMessage); + } + } +}; + +// ** NOTE FOR INTEGRATION ** +// This was copied over from source 2 rather than integrated because +// source 2 has more significantly refactored tier0 logging. +// +// A logging listener with Win32 console API color support which which prints +// to stdout and the debug channel. +#if !defined(_GAMECONSOLE) +class CColorizedLoggingListener : public CSimpleLoggingListener { + public: + CColorizedLoggingListener(bool bQuietPrintf = false, + bool bQuietDebugger = false) + : CSimpleLoggingListener(bQuietPrintf, bQuietDebugger) { + InitWin32ConsoleColorContext(&m_ColorContext); + } + + virtual void Log(const LoggingContext_t *pContext, const tchar *pMessage) { + if (!m_bQuietPrintf) { + uint16 nPrevColor = UINT16_MAX; + + if (pContext->m_Color != UNSPECIFIED_LOGGING_COLOR) { + nPrevColor = SetWin32ConsoleColor( + &m_ColorContext, pContext->m_Color.r(), pContext->m_Color.g(), + pContext->m_Color.b(), + MAX(MAX(pContext->m_Color.r(), pContext->m_Color.g()), + pContext->m_Color.b()) > 128); + } + + _tprintf(_T("%s"), pMessage); + + if (pContext->m_Color != UNSPECIFIED_LOGGING_COLOR) { + RestoreWin32ConsoleColor(&m_ColorContext, nPrevColor); + } + } + +#ifdef _WIN32 + if (!m_bQuietDebugger && Plat_IsInDebugSession()) { + Plat_DebugString(pMessage); + } +#endif + } + + Win32ConsoleColorContext_t m_ColorContext; +}; +#endif // !_GAMECONSOLE + +// Default logging response policy used when one is not specified. +class CDefaultLoggingResponsePolicy : public ILoggingResponsePolicy { + public: + virtual LoggingResponse_t OnLog(const LoggingContext_t *pContext) { + if (pContext->m_Severity == LS_ASSERT && + !CommandLine()->FindParm("-noassert")) { + return LR_DEBUGGER; + } else if (pContext->m_Severity == LS_ERROR) { + return LR_ABORT; + } else { + return LR_CONTINUE; + } + } +}; + +// A logging response policy which never terminates the process, even on error. +class CNonFatalLoggingResponsePolicy : public ILoggingResponsePolicy { + public: + virtual LoggingResponse_t OnLog(const LoggingContext_t *pContext) { + if ((pContext->m_Severity == LS_ASSERT && + !CommandLine()->FindParm("-noassert")) || + pContext->m_Severity == LS_ERROR) { + return LR_DEBUGGER; + } else { + return LR_CONTINUE; + } + } +}; + +////////////////////////////////////////////////////////////////////////// +// Central Logging System +////////////////////////////////////////////////////////////////////////// + +// The central logging system. +// +// Multiple instances can exist, though all exported tier0 functionality +// specifically works with a single global instance +// (via GetGlobalLoggingSystem()). +class CLoggingSystem { + public: + struct LoggingChannel_t; + + CLoggingSystem(); + ~CLoggingSystem(); + + // Register a logging channel with the logging system. + // The same channel can be registered multiple times, but the parameters + // in each call to RegisterLoggingChannel must either match across all calls + // or be set to defaults on any given call + // + // This function is not thread-safe and should generally only be called + // by a single thread. Using the logging channel definition macros ensures + // that this is called on the static initialization thread. + LoggingChannelID_t RegisterLoggingChannel( + const char *pChannelName, RegisterTagsFunc registerTagsFunc, + int flags = 0, LoggingSeverity_t minimumSeverity = LS_MESSAGE, + Color spewColor = UNSPECIFIED_LOGGING_COLOR); + + // Gets a channel ID from a string name. + // Performs a simple linear search; cache the value whenever possible + // or re-register the logging channel to get a global ID. + LoggingChannelID_t FindChannel(const char *pChannelName) const; + + int GetChannelCount() const { return m_nChannelCount; } + + // Gets a pointer to the logging channel description. + LoggingChannel_t *GetChannel(LoggingChannelID_t channelID); + const LoggingChannel_t *GetChannel(LoggingChannelID_t channelID) const; + + // Returns true if the given channel has the specified tag. + bool HasTag(LoggingChannelID_t channelID, const char *pTag) const { + return GetChannel(channelID)->HasTag(pTag); + } + + // Returns true if the given channel has been initialized. + // The main purpose is catching m_nChannelCount being zero because no channels + // have been registered. + bool IsValidChannelID(LoggingChannelID_t channelID) const { + return (channelID >= 0) && (channelID < m_nChannelCount); + } + + // Returns true if the given channel will spew at the given severity level. + bool IsChannelEnabled(LoggingChannelID_t channelID, + LoggingSeverity_t severity) const { + return IsValidChannelID(channelID) && + GetChannel(channelID)->IsEnabled(severity); + } + + // Functions to set the spew level of a channel either directly by ID or + // string name, or for all channels with a given tag. + // + // These functions are not technically thread-safe but calling them across + // multiple threads should cause no significant problems + // (the underlying data types being changed are 32-bit/atomic). + void SetChannelSpewLevel(LoggingChannelID_t channelID, + LoggingSeverity_t minimumSeverity); + void SetChannelSpewLevelByName(const char *pName, + LoggingSeverity_t minimumSeverity); + void SetChannelSpewLevelByTag(const char *pTag, + LoggingSeverity_t minimumSeverity); + + // Gets or sets the color of a logging channel. + // (The functions are not thread-safe, but the consequences are not + // significant.) + Color GetChannelColor(LoggingChannelID_t channelID) const { + return GetChannel(channelID)->m_SpewColor; + } + void SetChannelColor(LoggingChannelID_t channelID, Color color) { + GetChannel(channelID)->m_SpewColor = color; + } + + // Gets or sets the flags on a logging channel. + // (The functions are not thread-safe, but the consequences are not + // significant.) + LoggingChannelFlags_t GetChannelFlags(LoggingChannelID_t channelID) const { + return GetChannel(channelID)->m_Flags; + } + void SetChannelFlags(LoggingChannelID_t channelID, + LoggingChannelFlags_t flags) { + GetChannel(channelID)->m_Flags = flags; + } + + // Adds a string tag to a channel. + // This is not thread-safe and should only be called by a RegisterTagsFunc + // callback passed in to RegisterLoggingChannel (via the + // channel definition macros). + void AddTagToCurrentChannel(const char *pTagName); + + // Functions to save/restore the current logging state. + // Set bThreadLocal to true on a matching Push/Pop call if the intent + // is to override the logging listeners on the current thread only. + // + // Pushing the current logging state onto the state stack results + // in the current state being cleared by default (no listeners, default + // logging response policy). Set bClearState to false to copy the existing + // listener pointers to the new state. + // + // These functions which mutate logging state ARE thread-safe and are + // guarded by m_StateMutex. + void PushLoggingState(bool bThreadLocal = false, bool bClearState = true); + void PopLoggingState(bool bThreadLocal = false); + + // Registers a logging listener (a class which handles logged messages). + void RegisterLoggingListener(ILoggingListener *pListener); + + // Removes a logging listener from the registered list + void UnregisterLoggingListener(ILoggingListener *pListener); + + // Returns whether the specified logging listener is registered. + bool IsListenerRegistered(ILoggingListener *pListener); + + // Clears out all of the current logging state (removes all listeners, + // sets the response policy to the default). + void ResetCurrentLoggingState(); + + // Sets a policy class to decide what should happen when messages of a + // particular severity are logged + // (e.g. exit on error, break into debugger). + // If pLoggingResponse is NULL, uses the default response policy class. + void SetLoggingResponsePolicy(ILoggingResponsePolicy *pLoggingResponse); + + // Logs a message to the specified channel using a given severity and + // spew color. Passing in UNSPECIFIED_LOGGING_COLOR for 'color' allows + // the logging listeners to provide a default. + // NOTE: test 'IsChannelEnabled(channelID,severity)' before calling this! + LoggingResponse_t LogDirect(LoggingChannelID_t channelID, + LoggingSeverity_t severity, Color color, + const tchar *pMessage); + + // Internal data to represent a logging tag + struct LoggingTag_t { + const char *m_pTagName; + LoggingTag_t *m_pNextTag; + }; + + // Internal data to represent a logging channel. + struct LoggingChannel_t { + bool HasTag(const char *pTag) const { + LoggingTag_t *pCurrentTag = m_pFirstTag; + while (pCurrentTag != NULL) { + if (_stricmp(pCurrentTag->m_pTagName, pTag) == 0) { + return true; + } + pCurrentTag = pCurrentTag->m_pNextTag; + } + return false; + } + bool IsEnabled(LoggingSeverity_t severity) const { + return severity >= m_MinimumSeverity; + } + void SetSpewLevel(LoggingSeverity_t severity) { + m_MinimumSeverity = severity; + } + + LoggingChannelID_t m_ID; + LoggingChannelFlags_t + m_Flags; // an OR'd combination of LoggingChannelFlags_t + LoggingSeverity_t m_MinimumSeverity; // The minimum severity level required + // to activate this channel. + Color m_SpewColor; + char m_Name[MAX_LOGGING_IDENTIFIER_LENGTH]; + LoggingTag_t *m_pFirstTag; + }; + + private: + // Represents the current state of the logger (registered listeners, response + // policy class, etc.) and can vary from thread-to-thread. It can also be + // pushed/popped to save/restore listener/response state. + struct LoggingState_t { + // Index of the previous entry on the listener set stack. + int m_nPreviousStackEntry; + + // Number of active listeners in this set. Cannot exceed + // MAX_LOGGING_LISTENER_COUNT. If set to -1, implies that this state + // structure is not in use. + int m_nListenerCount; + // Array of registered logging listener objects. + ILoggingListener *m_RegisteredListeners[MAX_LOGGING_LISTENER_COUNT]; + + // Specific policy class to determine behavior of logging system under + // specific message types. + ILoggingResponsePolicy *m_pLoggingResponse; + }; + + // These state functions to assume the caller has already grabbed the mutex. + LoggingState_t *GetCurrentState(); + const LoggingState_t *GetCurrentState() const; + + int FindUnusedStateIndex(); + LoggingTag_t *AllocTag(const char *pTagName); + + int m_nChannelCount; + LoggingChannel_t m_RegisteredChannels[MAX_LOGGING_CHANNEL_COUNT]; + + int m_nChannelTagCount; + LoggingTag_t m_ChannelTags[MAX_LOGGING_TAG_COUNT]; + + // Index to first free character in name pool. + int m_nTagNamePoolIndex; + // Pool of character data used for tag names. + char m_TagNamePool[MAX_LOGGING_TAG_CHARACTER_COUNT]; + + // Protects all data in this class except the registered channels + // (which are supposed to be registered using the macros at static/global init + // time). It is assumed that this mutex is reentrant safe on all platforms. + CThreadFastMutex *m_pStateMutex; + + // The index of the current "global" state of the logging system. By default, + // all threads use this state for logging unless a given thread has pushed the + // logging state with bThreadLocal == true. If a thread-local state has been + // pushed, g_nThreadLocalStateIndex (a global thread-local integer) will be + // non-zero. By default, g_nThreadLocalStateIndex is 0 for all threads. + int m_nGlobalStateIndex; + + // A pool of logging states used to store a stack (potentially per-thread). + static const int MAX_LOGGING_STATE_COUNT = 16; + LoggingState_t m_LoggingStates[MAX_LOGGING_STATE_COUNT]; + + // Default policy class which determines behavior. + CDefaultLoggingResponsePolicy m_DefaultLoggingResponse; + + // Default spew function. + CSimpleLoggingListener m_DefaultLoggingListener; +}; + +////////////////////////////////////////////////////////////////////////// +// Logging Macros +////////////////////////////////////////////////////////////////////////// + +// This macro will resolve to the most appropriate overload of +// LoggingSystem_Log() depending on the number of parameters passed in. +#ifdef DBGFLAG_STRINGS_STRIP +#define InternalMsg(Channel, Severity, /* [Color], Message, */...) \ + do { \ + if (Severity == LS_ERROR && \ + LoggingSystem_IsChannelEnabled(Channel, Severity)) \ + LoggingSystem_Log(Channel, Severity, \ + /* [Color], Message, */##__VA_ARGS__); \ + } while (0) +#else +#define InternalMsg(Channel, Severity, /* [Color], Message, */...) \ + do { \ + if (LoggingSystem_IsChannelEnabled(Channel, Severity)) \ + LoggingSystem_Log(Channel, Severity, \ + /* [Color], Message, */##__VA_ARGS__); \ + } while (0) +#endif + +// New macros, use these! +// +// The macros take an optional Color parameter followed by the message +// and the message formatting. +// We rely on the variadic macro (__VA_ARGS__) operator to paste in the +// extra parameters and resolve to the appropriate overload. +#define Log_Msg(Channel, /* [Color], Message, */...) \ + InternalMsg(Channel, LS_MESSAGE, /* [Color], Message, */##__VA_ARGS__) +#define Log_Warning(Channel, /* [Color], Message, */...) \ + InternalMsg(Channel, LS_WARNING, /* [Color], Message, */##__VA_ARGS__) +#define Log_Error(Channel, /* [Color], Message, */...) \ + InternalMsg(Channel, LS_ERROR, /* [Color], Message, */##__VA_ARGS__) +#ifdef DBGFLAG_STRINGS_STRIP +#define Log_Assert(...) LR_CONTINUE +#else +#define Log_Assert(Message, ...) LoggingSystem_LogAssert(Message, ##__VA_ARGS__) +#endif + +#define DECLARE_LOGGING_CHANNEL(Channel) extern LoggingChannelID_t Channel + +#define DEFINE_LOGGING_CHANNEL_NO_TAGS(Channel, ChannelName, \ + /* [Flags], [Severity], [Color] */...) \ + LoggingChannelID_t Channel = \ + LoggingSystem_RegisterLoggingChannel(ChannelName, NULL, ##__VA_ARGS__) + +#define BEGIN_DEFINE_LOGGING_CHANNEL(Channel, ChannelName, \ + /* [Flags], [Severity], [Color] */...) \ + static void Register_##Channel##_Tags(); \ + LoggingChannelID_t Channel = LoggingSystem_RegisterLoggingChannel( \ + ChannelName, Register_##Channel##_Tags, ##__VA_ARGS__); \ + void Register_##Channel##_Tags() { +#define ADD_LOGGING_CHANNEL_TAG(Tag) LoggingSystem_AddTagToCurrentChannel(Tag) + +#define END_DEFINE_LOGGING_CHANNEL() } + +////////////////////////////////////////////////////////////////////////// +// DLL Exports +////////////////////////////////////////////////////////////////////////// + +// For documentation on these functions, please look at the corresponding +// function in CLoggingSystem (unless otherwise specified). +PLATFORM_INTERFACE LoggingChannelID_t LoggingSystem_RegisterLoggingChannel( + const char *pName, RegisterTagsFunc registerTagsFunc, int flags = 0, + LoggingSeverity_t severity = LS_MESSAGE, + Color color = UNSPECIFIED_LOGGING_COLOR); + +PLATFORM_INTERFACE void LoggingSystem_RegisterLoggingListener( + ILoggingListener *pListener); +PLATFORM_INTERFACE void LoggingSystem_UnregisterLoggingListener( + ILoggingListener *pListener); +PLATFORM_INTERFACE void LoggingSystem_ResetCurrentLoggingState(); +PLATFORM_INTERFACE void LoggingSystem_SetLoggingResponsePolicy( + ILoggingResponsePolicy *pResponsePolicy); +// NOTE: PushLoggingState() saves the current logging state on a stack and +// results in a new clear state (no listeners, default logging response policy). +PLATFORM_INTERFACE void LoggingSystem_PushLoggingState( + bool bThreadLocal = false, bool bClearState = true); +PLATFORM_INTERFACE void LoggingSystem_PopLoggingState( + bool bThreadLocal = false); + +PLATFORM_INTERFACE void LoggingSystem_AddTagToCurrentChannel( + const char *pTagName); + +// Returns INVALID_LOGGING_CHANNEL_ID if not found +PLATFORM_INTERFACE LoggingChannelID_t +LoggingSystem_FindChannel(const char *pChannelName); +PLATFORM_INTERFACE int LoggingSystem_GetChannelCount(); +PLATFORM_INTERFACE LoggingChannelID_t LoggingSystem_GetFirstChannelID(); +// Returns INVALID_LOGGING_CHANNEL_ID when there are no channels remaining. +PLATFORM_INTERFACE LoggingChannelID_t +LoggingSystem_GetNextChannelID(LoggingChannelID_t channelID); +PLATFORM_INTERFACE const CLoggingSystem::LoggingChannel_t * +LoggingSystem_GetChannel(LoggingChannelID_t channelID); + +PLATFORM_INTERFACE bool LoggingSystem_HasTag(LoggingChannelID_t channelID, + const char *pTag); + +PLATFORM_INTERFACE bool LoggingSystem_IsChannelEnabled( + LoggingChannelID_t channelID, LoggingSeverity_t severity); +PLATFORM_INTERFACE void LoggingSystem_SetChannelSpewLevel( + LoggingChannelID_t channelID, LoggingSeverity_t minimumSeverity); +PLATFORM_INTERFACE void LoggingSystem_SetChannelSpewLevelByName( + const char *pName, LoggingSeverity_t minimumSeverity); +PLATFORM_INTERFACE void LoggingSystem_SetChannelSpewLevelByTag( + const char *pTag, LoggingSeverity_t minimumSeverity); + +// Color is represented as an int32 due to C-linkage restrictions +PLATFORM_INTERFACE int32 +LoggingSystem_GetChannelColor(LoggingChannelID_t channelID); +PLATFORM_INTERFACE void LoggingSystem_SetChannelColor( + LoggingChannelID_t channelID, int color); + +PLATFORM_INTERFACE LoggingChannelFlags_t +LoggingSystem_GetChannelFlags(LoggingChannelID_t channelID); +PLATFORM_INTERFACE void LoggingSystem_SetChannelFlags( + LoggingChannelID_t channelID, LoggingChannelFlags_t flags); + +// Logs a variable-argument to a given channel with the specified severity. +// NOTE: if adding overloads to this function, remember that the Log_*** +// macros simply pass their variadic parameters through to LoggingSystem_Log(). +// Therefore, you need to ensure that the parameters are in the same general +// order and that there are no ambiguities with the overload. +PLATFORM_INTERFACE LoggingResponse_t +LoggingSystem_Log(LoggingChannelID_t channelID, LoggingSeverity_t severity, + const char *pMessageFormat, ...) FMTFUNCTION(3, 4); +PLATFORM_OVERLOAD LoggingResponse_t LoggingSystem_Log( + LoggingChannelID_t channelID, LoggingSeverity_t severity, Color spewColor, + const char *pMessageFormat, ...) FMTFUNCTION(4, 5); + +PLATFORM_INTERFACE LoggingResponse_t LoggingSystem_LogDirect( + LoggingChannelID_t channelID, LoggingSeverity_t severity, Color spewColor, + const char *pMessage); +PLATFORM_INTERFACE LoggingResponse_t +LoggingSystem_LogAssert(const char *pMessageFormat, ...) FMTFUNCTION(1, 2); + +#endif // VPC_TIER0_LOGGING_H_ \ No newline at end of file diff --git a/public/tier0/mem.h b/public/tier0/mem.h new file mode 100644 index 0000000..5302b08 --- /dev/null +++ b/public/tier0/mem.h @@ -0,0 +1,35 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Memory allocation! + +#ifndef VPC_TIER0_MEM_H_ +#define VPC_TIER0_MEM_H_ + +#include + +#include "tier0/platform.h" + +#if !defined(STATIC_TIER0) && !defined(_STATIC_LINKED) +#ifdef TIER0_DLL_EXPORT +#define MEM_INTERFACE DLL_EXPORT +#else +#define MEM_INTERFACE DLL_IMPORT +#endif +#else // BUILD_AS_DLL +#define MEM_INTERFACE extern +#endif // BUILD_AS_DLL + +// DLL-exported methods for particular kinds of memory +MEM_INTERFACE void *MemAllocScratch(int nMemSize); +MEM_INTERFACE void MemFreeScratch(); + +#if defined(POSIX) +MEM_INTERFACE void ZeroMemory(void *mem, size_t length); +#endif + +// Only works with USE_MEM_DEBUG and memory allocation call stack tracking +// enabled. +MEM_INTERFACE int GetAllocationCallStack(void *mem, void **pCallStackOut, + int iMaxEntriesOut); + +#endif // VPC_TIER0_MEM_H_ diff --git a/public/tier0/memalloc.h b/public/tier0/memalloc.h new file mode 100644 index 0000000..0cabe45 --- /dev/null +++ b/public/tier0/memalloc.h @@ -0,0 +1,684 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: This header should never be used directly from leaf code!!! +// Instead, just add the file memoverride.cpp into your project and all this +// will automagically be used + +#ifndef VPC_TIER0_MEMALLOC_H_ +#define VPC_TIER0_MEMALLOC_H_ + +// These memory debugging switches aren't relevant under Linux builds since +// memoverride.cpp isn't built into Linux projects +#ifndef LINUX +// Define this in release to get memory tracking even in release builds +//#define USE_MEM_DEBUG 1 + +// Define this in release to get light memory debugging +//#define USE_LIGHT_MEM_DEBUG + +// Define this to require -uselmd to turn light memory debugging on +//#define LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH +#endif + +#if defined(_MEMTEST) +#if defined(_WIN32) || defined(_PS3) +#define USE_MEM_DEBUG 1 +#endif +#endif + +#if defined(_PS3) +// Define STEAM_SHARES_GAME_ALLOCATOR to make Steam use the game's tier0 memory +// allocator. This adds some memory to the game's Small Block Heap and Medium +// Block Heap, to compensate. This configuration was disabled for Portal 2, as +// we could not sufficiently test it before ship. +//#define STEAM_SHARES_GAME_ALLOCATOR +#endif + +#if defined(STEAM_SHARES_GAME_ALLOCATOR) +#define MBYTES_STEAM_SBH_USAGE 2 +#define MBYTES_STEAM_MBH_USAGE 4 +#else // STEAM_SHARES_GAME_ALLOCATOR +#define MBYTES_STEAM_SBH_USAGE 0 +#define MBYTES_STEAM_MBH_USAGE 0 +#endif // STEAM_SHARES_GAME_ALLOCATOR + +// Undefine this if using a compiler lacking threadsafe RTTI (like vc6) +#define MEM_DEBUG_CLASSNAME 1 + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +#include +#ifdef LINUX +#undef offsetof +#define offsetof(s, m) (size_t) & (((s *)0)->m) +#endif + +#ifdef _PS3 +#define MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS 1 +#endif + +#include "tier0/mem.h" + +struct _CrtMemState; + +#define MEMALLOC_VERSION 1 + +typedef size_t (*MemAllocFailHandler_t)(size_t); + +struct GenericMemoryStat_t { + const char *name; + int value; +}; + +// Virtual memory interface +#include "tier0/memvirt.h" + +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +abstract_class IMemAlloc { + public: + // Release versions + virtual void *Alloc(size_t nSize) = 0; + + public: + virtual void *Realloc(void *pMem, size_t nSize) = 0; + + virtual void Free(void *pMem) = 0; + virtual void *Expand_NoLongerSupported(void *pMem, size_t nSize) = 0; + + // Debug versions + virtual void *Alloc(size_t nSize, const char *pFileName, int nLine) = 0; + + public: + virtual void *Realloc(void *pMem, size_t nSize, const char *pFileName, + int nLine) = 0; + virtual void Free(void *pMem, const char *pFileName, int nLine) = 0; + virtual void *Expand_NoLongerSupported(void *pMem, size_t nSize, + const char *pFileName, int nLine) = 0; + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + virtual void *AllocAlign(size_t nSize, size_t align) = 0; + virtual void *AllocAlign(size_t nSize, size_t align, const char *pFileName, + int nLine) = 0; + virtual void *ReallocAlign(void *pMem, size_t nSize, size_t align) = 0; +#endif + + inline void *IndirectAlloc(size_t nSize) { return Alloc(nSize); } + inline void *IndirectAlloc(size_t nSize, const char *pFileName, int nLine) { + return Alloc(nSize, pFileName, nLine); + } + + // Returns the size of a particular allocation (NOTE: may be larger than the + // size requested!) + virtual size_t GetSize(void *pMem) = 0; + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo(const char *pFileName, int nLine) = 0; + virtual void PopAllocDbgInfo() = 0; + + // FIXME: Remove when we have our own allocator + // these methods of the Crt debug code is used in our codebase currently + virtual int32 CrtSetBreakAlloc(int32 lNewBreakAlloc) = 0; + virtual int CrtSetReportMode(int nReportType, int nReportMode) = 0; + virtual int CrtIsValidHeapPointer(const void *pMem) = 0; + virtual int CrtIsValidPointer(const void *pMem, unsigned int size, + int access) = 0; + virtual int CrtCheckMemory(void) = 0; + virtual int CrtSetDbgFlag(int nNewFlag) = 0; + virtual void CrtMemCheckpoint(_CrtMemState * pState) = 0; + + // FIXME: Make a better stats interface + virtual void DumpStats() = 0; + virtual void DumpStatsFileBase(char const *pchFileBase) = 0; + virtual size_t ComputeMemoryUsedBy(char const *pchSubStr) = 0; + + // FIXME: Remove when we have our own allocator + virtual void *CrtSetReportFile(int nRptType, void *hFile) = 0; + virtual void *CrtSetReportHook(void *pfnNewHook) = 0; + virtual int CrtDbgReport(int nRptType, const char *szFile, int nLine, + const char *szModule, const char *pMsg) = 0; + + virtual int heapchk() = 0; + + virtual bool IsDebugHeap() = 0; + + virtual void GetActualDbgInfo(const char *&pFileName, int &nLine) = 0; + virtual void RegisterAllocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) = 0; + virtual void RegisterDeallocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) = 0; + + virtual int GetVersion() = 0; + + virtual void CompactHeap() = 0; + + // Function called when malloc fails or memory limits hit to attempt to free + // up memory (can come in any thread) + virtual MemAllocFailHandler_t SetAllocFailHandler( + MemAllocFailHandler_t pfnMemAllocFailHandler) = 0; + + virtual void DumpBlockStats(void *) = 0; + + virtual void SetStatsExtraInfo(const char *pMapName, + const char *pComment) = 0; + + // Returns 0 if no failure, otherwise the size_t of the last requested chunk + virtual size_t MemoryAllocFailed() = 0; + + virtual void CompactIncremental() = 0; + + virtual void OutOfMemory(size_t nBytesAttempted = 0) = 0; + + // Region-based allocations + virtual void *RegionAlloc(int region, size_t nSize) = 0; + virtual void *RegionAlloc(int region, size_t nSize, const char *pFileName, + int nLine) = 0; + + // Replacement for ::GlobalMemoryStatus which accounts for unused memory in + // our system + virtual void GlobalMemoryStatus(size_t * pUsedMemory, + size_t * pFreeMemory) = 0; + + // Obtain virtual memory manager interface + virtual IVirtualMemorySection *AllocateVirtualMemorySection( + size_t numMaxBytes) = 0; + + // Request 'generic' memory stats (returns a list of N named values; caller + // should assume this list will change over time) + virtual int GetGenericMemoryStats(GenericMemoryStat_t * *ppMemoryStats) = 0; + + virtual ~IMemAlloc(){}; + + // handles storing allocation info for coroutines + virtual uint32 GetDebugInfoSize() = 0; + virtual void SaveDebugInfo(void *pvDebugInfo) = 0; + virtual void RestoreDebugInfo(const void *pvDebugInfo) = 0; + virtual void InitDebugInfo(void *pvDebugInfo, const char *pchRootFileName, + int nLine) = 0; +}; + +//----------------------------------------------------------------------------- +// Singleton interface +//----------------------------------------------------------------------------- +#ifdef _PS3 + +PLATFORM_INTERFACE IMemAlloc *g_pMemAllocInternalPS3; +#ifndef PLATFORM_INTERFACE_MEM_ALLOC_INTERNAL_PS3_OVERRIDE +#define g_pMemAlloc g_pMemAllocInternalPS3 +#else +#define g_pMemAlloc PLATFORM_INTERFACE_MEM_ALLOC_INTERNAL_PS3_OVERRIDE +#endif + +#else // !_PS3 + +MEM_INTERFACE IMemAlloc *g_pMemAlloc; + +#endif + +//----------------------------------------------------------------------------- + +#ifdef MEMALLOC_REGIONS +#ifndef MEMALLOC_REGION +#define MEMALLOC_REGION 0 +#endif +inline void *MemAlloc_Alloc(size_t nSize) { + return g_pMemAlloc->RegionAlloc(MEMALLOC_REGION, nSize); +} + +inline void *MemAlloc_Alloc(size_t nSize, const char *pFileName, int nLine) { + return g_pMemAlloc->RegionAlloc(MEMALLOC_REGION, nSize, pFileName, nLine); +} +#else +#undef MEMALLOC_REGION +inline void *MemAlloc_Alloc(size_t nSize) { + return g_pMemAlloc->IndirectAlloc(nSize); +} + +inline void *MemAlloc_Alloc(size_t nSize, const char *pFileName, int nLine) { + return g_pMemAlloc->IndirectAlloc(nSize, pFileName, nLine); +} +#endif + +//----------------------------------------------------------------------------- + +#ifdef MEMALLOC_REGIONS +#else +#endif + +// don't clash with mathlib definition +inline bool ValueIsPowerOfTwo(size_t value) { + return (value & (value - 1)) == 0; +} + +inline void *MemAlloc_AllocAlignedUnattributed(size_t size, size_t align) { +#ifndef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + unsigned char *pAlloc, *pResult; + +#endif + + if (!ValueIsPowerOfTwo(align)) return NULL; + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + return g_pMemAlloc->AllocAlign(size, align); + +#else + + align = (align > sizeof(void *) ? align : sizeof(void *)) - 1; + + if ((pAlloc = (unsigned char *)MemAlloc_Alloc(sizeof(void *) + align + + size)) == (unsigned char *)NULL) + return NULL; + + pResult = + (unsigned char *)((size_t)(pAlloc + sizeof(void *) + align) & ~align); + ((unsigned char **)(pResult))[-1] = pAlloc; + + return (void *)pResult; + +#endif +} + +inline void *MemAlloc_AllocAlignedFileLine(size_t size, size_t align, + const char *pszFile, int nLine) { +#ifndef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + unsigned char *pAlloc, *pResult; + +#endif + + if (!ValueIsPowerOfTwo(align)) return NULL; + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + return g_pMemAlloc->AllocAlign(size, align, pszFile, nLine); + +#else + + align = (align > sizeof(void *) ? align : sizeof(void *)) - 1; + + if ((pAlloc = (unsigned char *)MemAlloc_Alloc(sizeof(void *) + align + size, + pszFile, nLine)) == + (unsigned char *)NULL) + return NULL; + + pResult = + (unsigned char *)((size_t)(pAlloc + sizeof(void *) + align) & ~align); + ((unsigned char **)(pResult))[-1] = pAlloc; + + return (void *)pResult; + +#endif +} + +#ifdef USE_MEM_DEBUG +#define MemAlloc_AllocAligned(s, a) \ + MemAlloc_AllocAlignedFileLine(s, a, __FILE__, __LINE__) +#elif defined(USE_LIGHT_MEM_DEBUG) +extern const char *g_pszModule; +#define MemAlloc_AllocAligned(s, a) \ + MemAlloc_AllocAlignedFileLine(s, a, g_pszModule, 0) +#else +#define MemAlloc_AllocAligned(s, a) MemAlloc_AllocAlignedUnattributed(s, a) +#endif + +inline void *MemAlloc_ReallocAligned(void *ptr, size_t size, size_t align) { + if (!ValueIsPowerOfTwo(align)) return NULL; + + // Don't change alignment between allocation + reallocation. + if (((size_t)ptr & (align - 1)) != 0) return NULL; + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + return g_pMemAlloc->ReallocAlign(ptr, size, align); + +#else + + if (!ptr) return MemAlloc_AllocAligned(size, align); + + // Figure out the actual allocation point + void *pAlloc = ptr; + pAlloc = (void *)(((size_t)pAlloc & ~(sizeof(void *) - 1)) - sizeof(void *)); + pAlloc = *((void **)pAlloc); + + // See if we have enough space + size_t nOffset = (size_t)ptr - (size_t)pAlloc; + size_t nOldSize = g_pMemAlloc->GetSize(pAlloc); + if (nOldSize >= size + nOffset) return ptr; + + void *pResult = MemAlloc_AllocAligned(size, align); + if (pResult) memcpy(pResult, ptr, nOldSize - nOffset); + + g_pMemAlloc->Free(pAlloc); + return pResult; + +#endif +} + +inline void MemAlloc_FreeAligned(void *pMemBlock) { +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + g_pMemAlloc->Free(pMemBlock); + +#else + + void *pAlloc; + + if (pMemBlock == NULL) return; + + pAlloc = pMemBlock; + + // pAlloc points to the pointer to starting of the memory block + pAlloc = (void *)(((size_t)pAlloc & ~(sizeof(void *) - 1)) - sizeof(void *)); + + // pAlloc is the pointer to the start of memory block + pAlloc = *((void **)pAlloc); + + g_pMemAlloc->Free(pAlloc); + +#endif +} + +inline void MemAlloc_FreeAligned(void *pMemBlock, const char *pszFile, + int nLine) { +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + g_pMemAlloc->Free(pMemBlock, pszFile, nLine); + +#else + + void *pAlloc; + + if (pMemBlock == NULL) return; + + pAlloc = pMemBlock; + + // pAlloc points to the pointer to starting of the memory block + pAlloc = (void *)(((size_t)pAlloc & ~(sizeof(void *) - 1)) - sizeof(void *)); + + // pAlloc is the pointer to the start of memory block + pAlloc = *((void **)pAlloc); + g_pMemAlloc->Free(pAlloc, pszFile, nLine); + +#endif +} + +inline size_t MemAlloc_GetSizeAligned(void *pMemBlock) { +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + + return g_pMemAlloc->GetSize(pMemBlock); + +#else + + void *pAlloc; + + if (pMemBlock == NULL) return 0; + + pAlloc = pMemBlock; + + // pAlloc points to the pointer to starting of the memory block + pAlloc = (void *)(((size_t)pAlloc & ~(sizeof(void *) - 1)) - sizeof(void *)); + + // pAlloc is the pointer to the start of memory block + pAlloc = *((void **)pAlloc); + return g_pMemAlloc->GetSize(pAlloc) - + ((::byte *)pMemBlock - (::byte *)pAlloc); + +#endif +} + +struct aligned_tmp_t { + // empty base class +}; + +// template here to allow adding alignment at levels of hierarchy that aren't +// the base +template +class CAlignedNewDelete : public T { + public: + void *operator new(size_t nSize) { + return MemAlloc_AllocAligned(nSize, bytesAlignment); + } + + void *operator new(size_t nSize, int nBlockUse, const char *pFileName, + int nLine) { + return MemAlloc_AllocAlignedFileLine(nSize, bytesAlignment, pFileName, + nLine); + } + + void operator delete(void *pData) { + if (pData) { + MemAlloc_FreeAligned(pData); + } + } + + void operator delete(void *pData, int nBlockUse, const char *pFileName, + int nLine) { + if (pData) { + MemAlloc_FreeAligned(pData, pFileName, nLine); + } + } +}; + +//----------------------------------------------------------------------------- + +#if (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +#define MEM_ALLOC_CREDIT_(tag) \ + CMemAllocAttributeAlloction memAllocAttributeAlloction(tag, __LINE__) +#define MemAlloc_PushAllocDbgInfo(pszFile, line) \ + g_pMemAlloc->PushAllocDbgInfo(pszFile, line) +#define MemAlloc_PopAllocDbgInfo() g_pMemAlloc->PopAllocDbgInfo() +#define MemAlloc_RegisterAllocation(pFileName, nLine, nLogicalSize, \ + nActualSize, nTime) \ + g_pMemAlloc->RegisterAllocation(pFileName, nLine, nLogicalSize, nActualSize, \ + nTime) +#define MemAlloc_RegisterDeallocation(pFileName, nLine, nLogicalSize, \ + nActualSize, nTime) \ + g_pMemAlloc->RegisterDeallocation(pFileName, nLine, nLogicalSize, \ + nActualSize, nTime) +#else +#define MEM_ALLOC_CREDIT_(tag) ((void)0) +#define MemAlloc_PushAllocDbgInfo(pszFile, line) ((void)0) +#define MemAlloc_PopAllocDbgInfo() ((void)0) +#define MemAlloc_RegisterAllocation(pFileName, nLine, nLogicalSize, \ + nActualSize, nTime) \ + ((void)0) +#define MemAlloc_RegisterDeallocation(pFileName, nLine, nLogicalSize, \ + nActualSize, nTime) \ + ((void)0) +#endif + +//----------------------------------------------------------------------------- + +class CMemAllocAttributeAlloction { + public: + CMemAllocAttributeAlloction(const char *pszFile, int line) { + MemAlloc_PushAllocDbgInfo(pszFile, line); + } + + ~CMemAllocAttributeAlloction() { MemAlloc_PopAllocDbgInfo(); } +}; + +#define MEM_ALLOC_CREDIT() MEM_ALLOC_CREDIT_(__FILE__) + +//----------------------------------------------------------------------------- + +#if defined(MSVC) && (defined(_DEBUG) || defined(USE_MEM_DEBUG)) + +#include + +// MEM_DEBUG_CLASSNAME is opt-in. +// Note: typeid().name() is not threadsafe, so if the project needs to access it +// in multiple threads simultaneously, it'll need a mutex. +#if defined(_CPPRTTI) && defined(MEM_DEBUG_CLASSNAME) + +template +const char *MemAllocClassName(T *p) { + static const char *pszName = + typeid(*p).name(); // @TODO: support having debug heap ignore certain + // allocations, and ignore memory allocated here + // [5/7/2009 tom] + return pszName; +} + +#define MEM_ALLOC_CREDIT_CLASS() MEM_ALLOC_CREDIT_(MemAllocClassName(this)) +#define MEM_ALLOC_CLASSNAME(type) (typeid((type *)(0)).name()) +#else +#define MEM_ALLOC_CREDIT_CLASS() MEM_ALLOC_CREDIT_(__FILE__) +#define MEM_ALLOC_CLASSNAME(type) (__FILE__) +#endif + +// MEM_ALLOC_CREDIT_FUNCTION is used when no this pointer is available ( inside +// 'new' overloads, for example ) +#ifdef _MSC_VER +#define MEM_ALLOC_CREDIT_FUNCTION() MEM_ALLOC_CREDIT_(__FUNCTION__) +#else +#define MEM_ALLOC_CREDIT_FUNCTION() (__FILE__) +#endif + +#else +#define MEM_ALLOC_CREDIT_CLASS() +#define MEM_ALLOC_CLASSNAME(type) NULL +#define MEM_ALLOC_CREDIT_FUNCTION() +#endif + +//----------------------------------------------------------------------------- + +#if (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +struct MemAllocFileLine_t { + const char *pszFile; + int line; +}; + +#define MEMALLOC_DEFINE_EXTERNAL_TRACKING(tag) \ + static CUtlMap s_##tag##Allocs( \ + DefLessFunc(void *)); \ + CUtlMap *g_p##tag##Allocs = \ + &s_##tag##Allocs; \ + static CThreadFastMutex s_##tag##AllocsMutex; \ + CThreadFastMutex *g_p##tag##AllocsMutex = &s_##tag##AllocsMutex; \ + const char *g_psz##tag##Alloc = strcpy( \ + (char *)MemAlloc_Alloc(strlen(#tag "Alloc") + 1, "intentional leak", 0), \ + #tag "Alloc"); + +#define MEMALLOC_DECLARE_EXTERNAL_TRACKING(tag) \ + extern CUtlMap *g_p##tag##Allocs; \ + extern CThreadFastMutex *g_p##tag##AllocsMutex; \ + extern const char *g_psz##tag##Alloc; + +#define MemAlloc_RegisterExternalAllocation(tag, p, size) \ + if (!p) \ + ; \ + else { \ + AUTO_LOCK_FM(*g_p##tag##AllocsMutex); \ + MemAllocFileLine_t fileLine = {g_psz##tag##Alloc, 0}; \ + g_pMemAlloc->GetActualDbgInfo(fileLine.pszFile, fileLine.line); \ + if (fileLine.pszFile != g_psz##tag##Alloc) { \ + g_p##tag##Allocs->Insert(p, fileLine); \ + } \ + \ + MemAlloc_RegisterAllocation(fileLine.pszFile, fileLine.line, size, size, \ + 0); \ + } + +#define MemAlloc_RegisterExternalDeallocation(tag, p, size) \ + if (!p) \ + ; \ + else { \ + AUTO_LOCK_FM(*g_p##tag##AllocsMutex); \ + MemAllocFileLine_t fileLine = {g_psz##tag##Alloc, 0}; \ + CUtlMap::IndexType_t iRecordedFileLine = \ + g_p##tag##Allocs->Find(p); \ + if (iRecordedFileLine != g_p##tag##Allocs->InvalidIndex()) { \ + fileLine = (*g_p##tag##Allocs)[iRecordedFileLine]; \ + g_p##tag##Allocs->RemoveAt(iRecordedFileLine); \ + } \ + \ + MemAlloc_RegisterDeallocation(fileLine.pszFile, fileLine.line, size, size, \ + 0); \ + } + +#else + +#define MEMALLOC_DEFINE_EXTERNAL_TRACKING(tag) +#define MEMALLOC_DECLARE_EXTERNAL_TRACKING(tag) +#define MemAlloc_RegisterExternalAllocation(tag, p, size) ((void)0) +#define MemAlloc_RegisterExternalDeallocation(tag, p, size) ((void)0) + +#endif + +//----------------------------------------------------------------------------- + +#endif // !STEAM && !NO_MALLOC_OVERRIDE + +//----------------------------------------------------------------------------- + +#if !defined(STEAM) && defined(NO_MALLOC_OVERRIDE) +#include + +#define MEM_ALLOC_CREDIT_(tag) ((void)0) +#define MEM_ALLOC_CREDIT() MEM_ALLOC_CREDIT_(__FILE__) +#define MEM_ALLOC_CREDIT_CLASS() +#define MEM_ALLOC_CLASSNAME(type) NULL + +#define MemAlloc_PushAllocDbgInfo(pszFile, line) +#define MemAlloc_PopAllocDbgInfo() + +#define MemAlloc_RegisterAllocation(a, b, c, d, e) ((void)0) +#define MemAlloc_RegisterDeallocation(a, b, c, d, e) ((void)0) + +#define MEMALLOC_DEFINE_EXTERNAL_TRACKING(tag) +#define MemAlloc_RegisterExternalAllocation(tag, p, size) ((void)0) +#define MemAlloc_RegisterExternalDeallocation(tag, p, size) ((void)0) + +inline void *MemAlloc_AllocAligned(size_t size, size_t align) { + return (void *)_aligned_malloc(size, align); +} +inline void *MemAlloc_AllocAligned(size_t size, size_t align, + const char *pszFile, int nLine) { + pszFile = pszFile; + nLine = nLine; + return (void *)_aligned_malloc(size, align); +} + +inline void MemAlloc_FreeAligned(void *pMemBlock) { _aligned_free(pMemBlock); } +inline void MemAlloc_FreeAligned(void *pMemBlock, const char *pszFile, + int nLine) { + pszFile = pszFile; + nLine = nLine; + _aligned_free(pMemBlock); +} + +#endif // !STEAM && NO_MALLOC_OVERRIDE + +//----------------------------------------------------------------------------- + +// linux memory tracking via hooks. +#if defined(POSIX) && !defined(_PS3) +PLATFORM_INTERFACE void MemoryLogMessage( + char const *s); // throw a message into the memory log +PLATFORM_INTERFACE void EnableMemoryLogging(bool bOnOff); +PLATFORM_INTERFACE void DumpMemoryLog(int nThresh); +PLATFORM_INTERFACE void DumpMemorySummary(void); +PLATFORM_INTERFACE void SetMemoryMark(void); +PLATFORM_INTERFACE void DumpChangedMemory(int nThresh); + +// ApproximateProcessMemoryUsage returns the approximate memory footprint of +// this process. +PLATFORM_INTERFACE size_t ApproximateProcessMemoryUsage(void); +#else +inline void MemoryLogMessage(char const *) {} +inline void EnableMemoryLogging(bool) {} +inline void DumpMemoryLog(int) {} +inline void DumpMemorySummary(void) {} +inline void SetMemoryMark(void) {} +inline void DumpChangedMemory(int) {} +inline size_t ApproximateProcessMemoryUsage(void) { return 0; } +#endif + +#endif // VPC_TIER0_MEMALLOC_H_ diff --git a/public/tier0/memdbgoff.h b/public/tier0/memdbgoff.h new file mode 100644 index 0000000..eb4ecff --- /dev/null +++ b/public/tier0/memdbgoff.h @@ -0,0 +1,22 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: This header, which must be the final line of a .h file, +// causes all crt methods to stop using debugging versions of the memory allocators. +// NOTE: Use memdbgon.h to re-enable memory debugging. + +#ifdef MEM_OVERRIDE_ON + +#undef malloc +#undef realloc +#undef calloc +#undef free +#undef _expand +#undef _msize +#undef new +#undef _aligned_malloc +#undef _aligned_free +#undef _malloc_dbg + +#undef MEM_OVERRIDE_ON + +#endif diff --git a/public/tier0/memdbgon.h b/public/tier0/memdbgon.h new file mode 100644 index 0000000..8925602 --- /dev/null +++ b/public/tier0/memdbgon.h @@ -0,0 +1,279 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: This header, which must be the final include in a .cpp (or .h) file, +// causes all crt methods to use debugging versions of the memory allocators. +// NOTE: Use memdbgoff.h to disable memory debugging. + +// SPECIAL NOTE! This file must *not* use include guards; we need to be able +// to include this potentially multiple times (since we can deactivate debugging +// by including memdbgoff.h) + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +// SPECIAL NOTE #2: This must be the final include in a .cpp or .h file!!! + +#if defined(_DEBUG) && !defined(USE_MEM_DEBUG) && !defined( _PS3 ) +#define USE_MEM_DEBUG 1 +#endif + +// If debug build or ndebug and not already included MS custom alloc files, or already included this file +#if (defined(_DEBUG) || !defined(_INC_CRTDBG)) || defined(MEMDBGON_H) + +#include "tier0/basetypes.h" + +#include "tier0/valve_off.h" + #ifdef COMPILER_MSVC + #include + #else + #include + #endif + #include + #ifndef _PS3 + #include + #endif +#include "tier0/valve_on.h" + +#include "commonmacros.h" +#include "memalloc.h" + +#ifdef _WIN32 +#ifndef MEMALLOC_REGION +#define MEMALLOC_REGION 0 +#endif +#else +#undef MEMALLOC_REGION +#endif + +#if defined(USE_MEM_DEBUG) + #if defined( POSIX ) || defined( _PS3 ) + #define _NORMAL_BLOCK 1 + + #include "tier0/valve_off.h" + #include + #include + #include + #if !defined( DID_THE_OPERATOR_NEW ) + #define DID_THE_OPERATOR_NEW + // posix doesn't have a new of this form, so we impl our own + void* operator new( size_t nSize, int blah, const char *pFileName, int nLine ); + void* operator new[]( size_t nSize, int blah, const char *pFileName, int nLine ); + #endif + + #else // defined(POSIX) + + // Include crtdbg.h and make sure _DEBUG is set to 1. + #if !defined(_DEBUG) + #define _DEBUG 1 + #include + #undef _DEBUG + #else + #include + #endif // !defined(_DEBUG) + + #endif // defined(POSIX) +#endif + +#include "tier0/memdbgoff.h" + +// -------------------------------------------------------- +// Debug/non-debug agnostic elements + +#define MEM_OVERRIDE_ON 1 + +#undef malloc +#undef realloc +#undef calloc +#undef _expand +#undef free +#undef _msize +#undef _aligned_malloc +#undef _aligned_free + +#ifndef MEMDBGON_H +inline void *MemAlloc_InlineCallocMemset( void *pMem, size_t nCount, size_t nElementSize) +{ + memset(pMem, 0, nElementSize * nCount); + return pMem; +} +#endif + +#define calloc(c, s) MemAlloc_InlineCallocMemset(malloc((c)*(s)), c, s) + +#ifndef USE_LIGHT_MEM_DEBUG +#define free(p) g_pMemAlloc->Free( p ) +#define _aligned_free( p ) MemAlloc_FreeAligned( p ) +#else +extern const char *g_pszModule; +#define free(p) g_pMemAlloc->Free( p, g_pszModule, 0 ) +#define _aligned_free( p ) MemAlloc_FreeAligned( p, g_pszModule, 0 ) +#endif +#define _msize(p) g_pMemAlloc->GetSize( p ) +#define _expand(p, s) _expand_NoLongerSupported(p, s) + +// -------------------------------------------------------- +// Debug path +#if defined(USE_MEM_DEBUG) + +#define malloc(s) MemAlloc_Alloc( s, __FILE__, __LINE__) +#define realloc(p, s) g_pMemAlloc->Realloc( p, s, __FILE__, __LINE__ ) +#define _aligned_malloc( s, a ) MemAlloc_AllocAlignedFileLine( s, a, __FILE__, __LINE__ ) + +#define _malloc_dbg(s, t, f, l) WHYCALLINGTHISDIRECTLY(s) + +#undef new + +#if defined( _PS3 ) + #ifndef PS3_OPERATOR_NEW_WRAPPER_DEFINED + #define PS3_OPERATOR_NEW_WRAPPER_DEFINED + inline void* operator new( size_t nSize, int blah, const char *pFileName, int nLine ) { return g_pMemAlloc->IndirectAlloc( nSize, pFileName, nLine ); } + inline void* operator new[]( size_t nSize, int blah, const char *pFileName, int nLine ) { return g_pMemAlloc->IndirectAlloc( nSize, pFileName, nLine ); } + #endif + #define new new( 1, __FILE__, __LINE__ ) +#elif !defined( GNUC ) + #if defined(__AFX_H__) && defined(DEBUG_NEW) + #define new DEBUG_NEW + #else + #define MEMALL_DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) + #define new MEMALL_DEBUG_NEW + #endif +#endif + +#undef _strdup +#undef strdup +#undef _wcsdup +#undef wcsdup + +#define _strdup(s) MemAlloc_StrDup(s, __FILE__, __LINE__) +#define strdup(s) MemAlloc_StrDup(s, __FILE__, __LINE__) +#define _wcsdup(s) MemAlloc_WcStrDup(s, __FILE__, __LINE__) +#define wcsdup(s) MemAlloc_WcStrDup(s, __FILE__, __LINE__) + +// Make sure we don't define strdup twice +#if !defined(MEMDBGON_H) + +inline char *MemAlloc_StrDup(const char *pString, const char *pFileName, unsigned nLine) +{ + char *pMemory; + + if (!pString) + return NULL; + + size_t len = strlen(pString) + 1; + if ((pMemory = (char *)MemAlloc_Alloc(len, pFileName, nLine)) != NULL) + { + return strcpy( pMemory, pString ); + } + + return NULL; +} + +inline wchar_t *MemAlloc_WcStrDup(const wchar_t *pString, const char *pFileName, unsigned nLine) +{ + wchar_t *pMemory; + + if (!pString) + return NULL; + + size_t len = (wcslen(pString) + 1); + if ((pMemory = (wchar_t *)MemAlloc_Alloc(len * sizeof(wchar_t), pFileName, nLine)) != NULL) + { + return wcscpy( pMemory, pString ); + } + + return NULL; +} + +#endif // DBMEM_DEFINED_STRDUP + +#else +// -------------------------------------------------------- +// Release path + +#ifndef USE_LIGHT_MEM_DEBUG +#define malloc(s) MemAlloc_Alloc( s ) +#define realloc(p, s) g_pMemAlloc->Realloc( p, s ) +#define _aligned_malloc( s, a ) MemAlloc_AllocAligned( s, a ) +#else +#define malloc(s) MemAlloc_Alloc( s, g_pszModule, 0 ) +#define realloc(p, s) g_pMemAlloc->Realloc( p, s, g_pszModule, 0 ) +#define _aligned_malloc( s, a ) MemAlloc_AllocAlignedFileLine( s, a, g_pszModule, 0 ) +#endif + +#ifndef _malloc_dbg +#define _malloc_dbg(s, t, f, l) WHYCALLINGTHISDIRECTLY(s) +#endif + +#undef new + +#if defined( _PS3 ) && !defined( _CERT ) + #ifndef PS3_OPERATOR_NEW_WRAPPER_DEFINED + #define PS3_OPERATOR_NEW_WRAPPER_DEFINED + inline void* operator new( size_t nSize, int blah, const char *pFileName, int nLine ) { return g_pMemAlloc->IndirectAlloc( nSize, pFileName, nLine ); } + inline void* operator new[]( size_t nSize, int blah, const char *pFileName, int nLine ) { return g_pMemAlloc->IndirectAlloc( nSize, pFileName, nLine ); } + #endif + #define new new( 1, __FILE__, __LINE__ ) +#endif + +#undef _strdup +#undef strdup +#undef _wcsdup +#undef wcsdup + +#define _strdup(s) MemAlloc_StrDup(s) +#define strdup(s) MemAlloc_StrDup(s) +#define _wcsdup(s) MemAlloc_WcStrDup(s) +#define wcsdup(s) MemAlloc_WcStrDup(s) + +// Make sure we don't define strdup twice +#if !defined(MEMDBGON_H) + +inline char *MemAlloc_StrDup(const char *pString) +{ + char *pMemory; + + if (!pString) + return NULL; + + size_t len = strlen(pString) + 1; + if ((pMemory = (char *)malloc(len)) != NULL) + { + return strcpy( pMemory, pString ); + } + + return NULL; +} + +inline wchar_t *MemAlloc_WcStrDup(const wchar_t *pString) +{ + wchar_t *pMemory; + + if (!pString) + return NULL; + + size_t len = (wcslen(pString) + 1); + if ((pMemory = (wchar_t *)malloc(len * sizeof(wchar_t))) != NULL) + { + return wcscpy( pMemory, pString ); + } + + return NULL; +} + +#endif // DBMEM_DEFINED_STRDUP + +#endif // USE_MEM_DEBUG + +#define MEMDBGON_H // Defined here so can be used above + +#else + +#if defined(USE_MEM_DEBUG) +#ifndef _STATIC_LINKED +#pragma message ("Note: file includes crtdbg.h directly, therefore will cannot use memdbgon.h in non-debug build") +#else +#error "Error: file includes crtdbg.h directly, therefore will cannot use memdbgon.h in non-debug build. Not recoverable in static build" +#endif +#endif +#endif // _INC_CRTDBG + +#endif // !STEAM && !NO_MALLOC_OVERRIDE diff --git a/public/tier0/memvirt.h b/public/tier0/memvirt.h new file mode 100644 index 0000000..6ffeabc --- /dev/null +++ b/public/tier0/memvirt.h @@ -0,0 +1,37 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: CVirtualMemoryManager interface + +#ifndef VPC_TIER0_MEMVIRT_H_ +#define VPC_TIER0_MEMVIRT_H_ + +#define VMM_KB (1024) +#define VMM_MB (1024 * VMM_KB) + +#ifdef _PS3 +// Total virtual address space reserved by CVirtualMemoryManager on startup: +#define VMM_VIRTUAL_SIZE (512 * VMM_MB) +#define VMM_PAGE_SIZE (64 * VMM_KB) +#endif + +// Allocate virtual sections via IMemAlloc::AllocateVirtualMemorySection +abstract_class IVirtualMemorySection { + public: + // Information about memory section + virtual void *GetBaseAddress() = 0; + virtual size_t GetPageSize() = 0; + virtual size_t GetTotalSize() = 0; + + // Functions to manage physical memory mapped to virtual memory + virtual bool CommitPages(void *pvBase, size_t numBytes) = 0; + virtual void DecommitPages(void *pvBase, size_t numBytes) = 0; + + // Release the physical memory and associated virtual address space + virtual void Release() = 0; +}; + +// Get the IVirtualMemorySection associated with a given memory address (if +// any): +extern IVirtualMemorySection *GetMemorySectionForAddress(void *pAddress); + +#endif // VPC_TIER0_MEMVIRT_H_ diff --git a/public/tier0/minidump.h b/public/tier0/minidump.h new file mode 100644 index 0000000..bccd44b --- /dev/null +++ b/public/tier0/minidump.h @@ -0,0 +1,74 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_MINIDUMP_H_ +#define VPC_TIER0_MINIDUMP_H_ + +#include "tier0/platform.h" + +// writes out a minidump of the current stack trace with a unique filename +PLATFORM_INTERFACE void WriteMiniDump(); + +using FnWMain = void (*)(int, tchar *[]); + +#ifdef IS_WINDOWS_PC + +// calls the passed in function pointer and catches any exceptions/crashes +// thrown by it, and writes a minidump use from wmain() to protect the whole +// program + +PLATFORM_INTERFACE void CatchAndWriteMiniDump(FnWMain pfn, int argc, + tchar *argv[]); + +// The ExceptionInfo_t struct is a typeless data struct +// which is OS-dependent and should never be used by external code. +// Just pass it back into MinidumpSetUnhandledExceptionFunction +struct ExceptionInfo_t; + +// Replaces the current function pointer with the one passed in. +// Returns the previously-set function. +// The function is called internally by WriteMiniDump() and +// CatchAndWriteMiniDump() The default is the built-in function that uses +// DbgHlp.dll's MiniDumpWriteDump function +using FnMiniDump = void (*)(unsigned int uStructuredExceptionCode, + ExceptionInfo_t *pExceptionInfo); +PLATFORM_INTERFACE FnMiniDump SetMiniDumpFunction(FnMiniDump pfn); + +// Use this to write a minidump explicitly. +// Some of the tools choose to catch the minidump themselves instead of using +// CatchAndWriteMinidump so they can show their own dialog. +// +// ptchMinidumpFileNameBuffer if not-NULL should be a writable tchar buffer of +// length at least _MAX_PATH and on return will contain the name of the minidump +// file written. If ptchMinidumpFileNameBuffer is NULL the name of the minidump +// file written will not be available after the function returns. +// + +// NOTE: Matches windows.h +enum MinidumpType_t { + MINIDUMP_Normal = 0x00000000, + MINIDUMP_WithDataSegs = 0x00000001, + MINIDUMP_WithFullMemory = 0x00000002, + MINIDUMP_WithHandleData = 0x00000004, + MINIDUMP_FilterMemory = 0x00000008, + MINIDUMP_ScanMemory = 0x00000010, + MINIDUMP_WithUnloadedModules = 0x00000020, + MINIDUMP_WithIndirectlyReferencedMemory = 0x00000040, + MINIDUMP_FilterModulePaths = 0x00000080, + MINIDUMP_WithProcessThreadData = 0x00000100, + MINIDUMP_WithPrivateReadWriteMemory = 0x00000200, + MINIDUMP_WithoutOptionalData = 0x00000400, + MINIDUMP_WithFullMemoryInfo = 0x00000800, + MINIDUMP_WithThreadInfo = 0x00001000, + MINIDUMP_WithCodeSegs = 0x00002000 +}; + +PLATFORM_INTERFACE bool WriteMiniDumpUsingExceptionInfo( + unsigned int uStructuredExceptionCode, ExceptionInfo_t *pExceptionInfo, + uint32 nMinidumpTypeFlags, // OR-ed together MinidumpType_t flags + tchar *ptchMinidumpFileNameBuffer = NULL); + +PLATFORM_INTERFACE void MinidumpSetUnhandledExceptionFunction(FnMiniDump pfn); + +#endif + +#endif // VPC_TIER0_MINIDUMP_H_ diff --git a/public/tier0/p4performancecounters.h b/public/tier0/p4performancecounters.h new file mode 100644 index 0000000..3754c43 --- /dev/null +++ b/public/tier0/p4performancecounters.h @@ -0,0 +1,290 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_P4PERFORMANCECOUNTERS_H_ +#define VPC_TIER0_P4PERFORMANCECOUNTERS_H_ + +#pragma once +// Pentium 4 support + +/* + https://www.intel.com/content/www/us/en/documentation-resources/developer.html + + IA-32 Intel Architecture Software Developer's Manual Volume 1: Basic + Architecture + + IA-32 Intel Architecture Software Developer's Manual Volume 2A: Instruction + Set Reference, A-M + + IA-32 Intel Architecture Software Developer's Manual Volume 2B: Instruction + Set Reference, N-Z + + IA-32 Intel Architecture Software Developer's Manual Volume 3: System + Programming Guide + + + From Mikael Pettersson's perfctr: + + http://user.it.uu.se/~mikpe/linux/perfctr/ + + * Known quirks: + - OVF_PMI+FORCE_OVF counters must have an ireset value of -1. + This allows the regular overflow check to also handle FORCE_OVF + counters. Not having this restriction would lead to MAJOR + complications in the driver's "detect overflow counters" code. + There is no loss of functionality since the ireset value doesn't + affect the counter's PMI rate for FORCE_OVF counters. + + - In experiments with FORCE_OVF counters, and regular OVF_PMI + counters with small ireset values between -8 and -1, it appears + that the faulting instruction is subjected to a new PMI before + it can complete, ad infinitum. This occurs even though the driver + clears the CCCR (and in testing also the ESCR) and invokes a + user-space signal handler before restoring the CCCR and resuming + the instruction. +*/ + +#define NCOUNTERS 18 + +// The 18 counters +enum Counters { + MSR_BPU_COUNTER0, + MSR_BPU_COUNTER1, + MSR_BPU_COUNTER2, + MSR_BPU_COUNTER3, + MSR_MS_COUNTER0, + MSR_MS_COUNTER1, + MSR_MS_COUNTER2, + MSR_MS_COUNTER3, + MSR_FLAME_COUNTER0, + MSR_FLAME_COUNTER1, + MSR_FLAME_COUNTER2, + MSR_FLAME_COUNTER3, + MSR_IQ_COUNTER0, + MSR_IQ_COUNTER1, + MSR_IQ_COUNTER2, + MSR_IQ_COUNTER3, + MSR_IQ_COUNTER4, + MSR_IQ_COUNTER5 +}; + +// register base for counters +#define MSR_COUNTER_BASE 0x300 + +// register base for CCCR register +#define MSR_CCCR_BASE 0x360 + +#pragma pack(push, 1) +// access to these bits is through the methods +typedef union ESCR { + struct { + uint64 Reserved0_1 : 2; // + uint64 USR : 1; // + uint64 OS : 1; // + uint64 TagEnable : 1; // + uint64 TagValue : 4; // + uint64 EventMask : 16; // from event select + uint64 ESCREventSelect : 6; // 31:25 class of event + uint64 Reserved31 : 1; // + + uint64 Reserved32_63 : 32; // + }; + uint64 flat; + +} ESCR; + +typedef union CCCR { + struct { + uint64 Reserved0_11 : 12; // 0 -11 + uint64 Enable : 1; // 12 + uint64 CCCRSelect : 3; // 13-15 + uint64 Reserved16_17 : 2; // 16 17 + + uint64 Compare : 1; // 18 + uint64 Complement : 1; // 19 + uint64 Threshold : 4; // 20-23 + uint64 Edge : 1; // 24 + uint64 FORCE_OVF : 1; // 25 + uint64 OVF_PMI : 1; // 26 + uint64 Reserved27_29 : 3; // 27-29 + uint64 Cascade : 1; // 30 + uint64 OVF : 1; // 31 + + uint64 Reserved32_63 : 32; // + }; + uint64 flat; + +} CCCR; + +#pragma pack(pop) + +extern const unsigned short cccr_escr_map[NCOUNTERS][8]; + +enum P4TagState { + TagDisable, // + TagEnable, // +}; + +enum P4ForceOverflow { + ForceOverflowDisable, + ForceOverflowEnable, +}; + +enum P4OverflowInterrupt { + OverflowInterruptDisable, + OverflowInterruptEnable, +}; + +// Turn off the no return value warning in ReadCounter. +class P4BaseEvent { + int m_counter; + + protected: + void SetCounter(int counter) { + m_counter = counter; + cccrPort = MSR_CCCR_BASE + m_counter; + counterPort = MSR_COUNTER_BASE + m_counter; + escrPort = cccr_escr_map[m_counter][cccr.CCCRSelect]; + } + + public: + unsigned short m_eventMask; + const tchar *description; + PME *pme; + ESCR escr; + CCCR cccr; + int counterPort; + int cccrPort; + int escrPort; + + P4BaseEvent() { + pme = PME::Instance(); + m_eventMask = 0; + description = _T(""); + escr.flat = 0; + cccr.flat = 0; + cccr.Reserved16_17 = 3; // must be set + counterPort = 0; + cccrPort = 0; + escrPort = 0; + m_counter = -1; + } + + void StartCounter() { + cccr.Enable = 1; + pme->WriteMSR(cccrPort, cccr.flat); + } + + void StopCounter() { + cccr.Enable = 0; + pme->WriteMSR(cccrPort, cccr.flat); + } + + void ClearCounter() { + pme->WriteMSR(counterPort, 0ui64); // clear + } + + void WriteCounter(int64 value) { + pme->WriteMSR(counterPort, value); // clear + } + + int64 ReadCounter() { +#if PME_DEBUG + if (escr.USR == 0 && escr.OS == 0) + return -1; // no area to collect, use SetCaptureMode + + if (escr.EventMask == 0) return -2; // no event mask set + + if (m_counter == -1) return -3; // counter not legal +#endif + + // ReadMSR should work here too, but RDPMC should be faster + int64 value = 0; + pme->ReadMSR(counterPort, &value); + return value; +#if 0 + // we need to copy this into a temp for some reason + int temp = m_counter; + _asm + { + mov ecx, temp + RDPMC + } +#endif + } + + void SetCaptureMode(PrivilegeCapture priv) { + switch (priv) { + case OS_Only: { + escr.USR = 0; + escr.OS = 1; + break; + } + case USR_Only: { + escr.USR = 1; + escr.OS = 0; + break; + } + case OS_and_USR: { + escr.USR = 1; + escr.OS = 1; + break; + } + } + + escr.EventMask = m_eventMask; + pme->WriteMSR(escrPort, escr.flat); + } + + void SetTagging(P4TagState tagEnable, uint8 tagValue) { + escr.TagEnable = tagEnable; + escr.TagValue = tagValue; + pme->WriteMSR(escrPort, escr.flat); + } + + void SetFiltering(CompareState compareEnable, CompareMethod compareMethod, + uint8 threshold, EdgeState edgeEnable) { + cccr.Compare = compareEnable; + cccr.Complement = compareMethod; + cccr.Threshold = threshold; + cccr.Edge = edgeEnable; + pme->WriteMSR(cccrPort, cccr.flat); + } + + void SetOverflowEnables(P4ForceOverflow overflowEnable, + P4OverflowInterrupt overflowInterruptEnable) { + cccr.FORCE_OVF = overflowEnable; + cccr.OVF_PMI = overflowInterruptEnable; + pme->WriteMSR(cccrPort, cccr.flat); + } + + void SetOverflow() { + cccr.OVF = 1; + pme->WriteMSR(cccrPort, cccr.flat); + } + + void ClearOverflow() { + cccr.OVF = 0; + pme->WriteMSR(cccrPort, cccr.flat); + } + + bool isOverflow() { + CCCR cccr_temp; + pme->ReadMSR(cccrPort, &cccr_temp.flat); + return cccr_temp.OVF; + } + + void SetCascade() { + cccr.Cascade = 1; + pme->WriteMSR(cccrPort, cccr.flat); + } + + void ClearCascade() { + cccr.Cascade = 0; + pme->WriteMSR(cccrPort, cccr.flat); + } +}; + +#include "eventmasks.h" +#include "eventmodes.h" + +#endif // VPC_TIER0_P4PERFORMANCECOUNTERS_H_ diff --git a/public/tier0/p5p6performancecounters.h b/public/tier0/p5p6performancecounters.h new file mode 100644 index 0000000..0c9bac7 --- /dev/null +++ b/public/tier0/p5p6performancecounters.h @@ -0,0 +1,213 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_P5P6PERFORMANCECOUNTERS_H_ +#define VPC_TIER0_P5P6PERFORMANCECOUNTERS_H_ + +// defined for < Pentium 4 + +//--------------------------------------------------------------------------- +// Format of the performance event IDs within this header file in case you +// wish to add any additional events that may not be present here. +// +// BITS 0-8 Unit Mask, Unsed on P5 processors +// BIT 9 Set if event can be set on counter 0 +// BIT 10 Set if event can be set on counter 1 +// BITS 11-15 Unused Set to zero +// BITS 16-23 Event Select ID, Only bits 16-21 used on P5 Family +// BITS 24-27 Unused set to zero +// BITS 28-32 Process family that the event belong to, as returned by +// the CPUID instruction. +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// PENTIUM PERFORMANCE COUNTERS. +//--------------------------------------------------------------------------- +#define P5_DTCRD 0x50000300 // Data Cache Reads +#define P5_DWRIT 0x50010300 // Data Cache Writes +#define P5_DTTLB 0x50020300 // Data TLB Miss +#define P5_DTRMS 0x50030300 // Data Read Misses +#define P5_DWRMS 0x50040300 // Data Write Miss +#define P5_WHLCL 0x50050300 // Write (Hit) to M- or E-state line +#define P5_DCLWB 0x50060300 // Data Cache Lines Written Back +#define P5_DCSNP 0x50070300 // External Snoops +#define P5_DCSHT 0x50080300 // Data Cache Snoop Hits +#define P5_MAIBP 0x50090300 // Memory Access in Both Pipes +#define P5_BANKS 0x500A0300 // Bank Conflicts +#define P5_MISAL 0x500B0300 // Misaligned Data Memory Reference +#define P5_COCRD 0x500C0300 // Code Cache Reads +#define P5_COTLB 0x500D0300 // Code TLB Misses +#define P5_COCMS 0x500E0300 // Code Cache Misses +#define P5_ANYSG 0x500F0300 // Any Segment Register Loaded +#define P5_BRANC 0x50120300 // Branches +#define P5_BTBHT 0x50130300 // BTB Hits +#define P5_TBRAN 0x50140300 // Taken Branch or BTB hit +#define P5_PFLSH 0x50150300 // Pipeline flushes +#define P5_INSTR 0x50160300 // Instructions Executed +#define P5_INSTV 0x50170300 // Instructions Executed in the V-Pipe (Pairing) +#define P5_CLOCL 0x50180300 // Bus active +#define P5_PSDWR 0x50190300 // Full write buffers +#define P5_PSWDR 0x501A0300 // Waiting for Data Memory Read +#define P5_NCLSW 0x501B0300 // Clocks stalled writing an E or M state line +#define P5_IORWC 0x501D0300 // I/O Read or Write Cycle +#define P5_NOCMR 0x501E0300 // Non-Cacheable Memory Reads +#define P5_PSLDA 0x501F0300 // Clocks stalled due to AGI +#define P5_FLOPS 0x50220300 // Floating Point Operations +#define P5_DBGR0 0x50230300 // Breakpoint match on DR0 +#define P5_DBGR1 0x50240300 // Breakpoint match on DR1 +#define P5_DBGR2 0x50250300 // Breakpoint match on DR2 +#define P5_DBGR3 0x50260300 // Breakpoint match on DR3 +#define P5_HWINT 0x50270300 // Hardware interrupts +#define P5_DTRWR 0x50280300 // Data reads or writes +#define P5_DTRWM 0x50290300 // Data read or write miss +#define P5_BOLAT 0x502A0100 // Bus ownership latency +#define P5_BOTFR 0x502A0200 // Bus ownership transfer +#define P5_MMXA1 0x502B0100 // MMX Instruction Executed in U-pipe +#define P5_MMXA2 0x502B0200 // MMX Instruction Executed in V-pipe +#define P5_MMXMS 0x502C0100 // Cache M state line sharing +#define P5_MMSLS 0x502C0200 // Cache line sharing +#define P5_MMXB1 0x502D0100 // EMMS Instructions Executed +#define P5_MMXB2 0x502D0200 // Transition from MMX to FP instructions +#define P5_NOCMW 0x502E0200 // Non-Cacheable Memory Writes +#define P5_MMXC1 0x502F0100 // Saturated MMX Instructions Executed +#define P5_MMXC2 0x502F0200 // Saturations Performed +#define P5_MMXHS 0x50300100 // Cycles Not in HALT State +#define P5_MMXD2 0x50310100 // MMX Data Read +#define P5_MMXFP 0x50320100 // Floating Point Stalls +#define P5_MMXTB 0x50320200 // Taken Branches +#define P5_MMXD0 0x50330100 // D1 Starvation and FIFO Empty +#define P5_MMXD1 0x50330200 // D1 Starvation and one instruction in FIFO +#define P5_MMXE1 0x50340100 // MMX Data Writes +#define P5_MMXE2 0x50340200 // MMX Data Write Misses +#define P5_MMXWB 0x50350100 // Pipeline flushes, wrong branch prediction +#define P5_MMXWJ 0x50350200 // Pipeline flushes, branch prediction WB-stage +#define P5_MMXF1 0x50360100 // Misaligned MMX Data Memory Reference +#define P5_MMXF2 0x50360200 // Pipeline Stalled Waiting for MMX data read +#define P5_MMXRI 0x50370100 // Returns Predicted Incorrectly +#define P5_MMXRP 0x50370200 // Returns Predicted +#define P5_MMXG1 0x50380100 // MMX Multiply Unit Interlock +#define P5_MMXG2 0x50380200 // MOVD/MOVQ store stall, previous operation +#define P5_MMXRT 0x50390100 // Returns +#define P5_MMXRO 0x50390200 // RSB Overflows +#define P5_MMXBF 0x503A0100 // BTB False entries +#define P5_MMXBM 0x503A0200 // BTB misprediction on a Not-Taken Branch +#define P5_PXDWR 0x503B0100 // stalled due MMX Full write buffers +#define P5_PXZWR 0x503B0200 // stalled on MMX write to E or M state line + +#define P5_CLOCK 0x503F0300 // Special value to count clocks on P5 + +//--------------------------------------------------------------------------- +// PENTIUM PRO / PENTIUM II PERFORMANCE COUNTERS. +//--------------------------------------------------------------------------- +#define P6_STRBB 0x60030300 // Store Buffer Block +#define P6_STBDC 0x60040300 // Store Buffer Drain Cycles +#define P6_MISMM 0x60050300 // Misaligned Data Memory Reference +#define P6_SEGLD 0x60060300 // Segment register loads +#define P6_FPOPE 0x60100100 // FP Computational Op. (COUNTER 0 ONLY) +#define P6_FPEOA 0x60110200 // FP Microcode Exceptions (COUNTER 1 ONLY) +#define P6_FMULT 0x60120200 // Multiplies (COUNTER 1 ONLY) +#define P6_FPDIV 0x60130200 // Divides (COUNTER 1 ONLY) +#define P6_DBUSY 0x60140200 // Cycles Divider Busy (COUNTER 1 ONLY) +#define P6_L2STR 0x60210300 // L2 address strobes => address bus utilization +#define P6_L2BBS 0x60220300 // Cycles L2 Bus Busy +#define P6_L2BBT 0x60230300 // Cycles L2 Bus Busy transferring data to CPU +#define P6_L2ALO 0x60240300 // L2 Lines Allocated +#define P6_L2MAL 0x60250300 // L2 M-state Lines Allocated +#define P6_L2CEV 0x60260300 // L2 Lines Evicted +#define P6_L2MEV 0x60270300 // L2 M-state Lines Evicted +#define P6_L2MCF 0x60280301 // L2 Cache Instruction Fetch Misses +#define P6_L2FET 0x6028030F // L2 Cache Instruction Fetches +#define P6_L2DRM 0x60290301 // L2 Cache Read Misses +#define P6_L2DMR 0x6029030F // L2 Cache Reads +#define P6_L2DWM 0x602A0301 // L2 Cache Write Misses +#define P6_L2DMW 0x602A030F // L2 Cache Writes +#define P6_L2CMS 0x602E0301 // L2 Cache Request Misses +#define P6_L2DCR 0x602E030F // L2 Cache Requests +#define P6_DMREF 0x60430300 // Data Memory References +#define P6_DCALO 0x6045030F // L1 Lines Allocated +#define P6_DCMAL 0x60460300 // L1 M-state Data Cache Lines Allocated +#define P6_DCMEV 0x60470300 // L1 M-state Data Cache Lines Evicted +#define P6_DCOUT 0x60480300 // L1 Misses outstanding +#define P6_TSMCD 0x60520300 // Time Self-Modifiying Code Detected +#define P6_BRWRA 0x60600300 // External Bus Cycles While Receive Active +#define P6_BRDCD 0x60600300 // External Bus Request Outstanding +#define P6_BRBNR 0x60610300 // External Bus Cycles While BNR Asserted +#define P6_BUSBS 0x60620300 // External Bus Cycles-DRDY Asserted (busy) +#define P6_BLOCK 0x60630300 // External Bus Cycles-LOCK signal asserted +#define P6_BBRCV 0x60640300 // External Bus Cycles-Processor receiving data +#define P6_BURST 0x60650300 // External Bus Burst Read Operations +#define P6_BRINV 0x60660300 // External Bus Read for Ownership Transaction +#define P6_BMLEV 0x60670300 // External Bus Writeback M-state Evicted +#define P6_BBIFT 0x60680300 // External Bus Burst Instruction Fetches +#define P6_BINVL 0x60690300 // External Bus Invalidate Transactions +#define P6_BPRBT 0x606A0300 // External Bus Partial Read Transactions +#define P6_BPTMO 0x606B0300 // External Bus Partial Memory Transactions +#define P6_BUSIO 0x606C0300 // External Bus I/O Bus Transactions +#define P6_BUSDF 0x606D0300 // External Bus Deferred Transactions +#define P6_BUSTB 0x606E0300 // External Bus Burst Transactions +#define P6_BMALL 0x606F0300 // External Bus Memory Transactions +#define P6_BSALL 0x60700300 // External Bus Transactions +#define P6_CLOCK 0x60790300 // Clockticks +#define P6_BRHIT 0x607A0300 // External Bus Cycles While HIT Asserted +#define P6_BRHTM 0x607B0300 // External Bus Cycles While HITM Asserted +#define P6_BRSST 0x607E0300 // External Bus Cycles While Snoop Stalled +#define P6_CMREF 0x60800300 // Total Instruction Fetches +#define P6_TOIFM 0x60810300 // Total Instruction Fetch Misses +#define P6_INTLB 0x60850300 // Instructions TLB Misses +#define P6_CSFET 0x60860300 // Cycles Instruction Fetch Stalled +#define P6_FTSTL 0x60870300 // Cycles Instruction Fetch stalled +#define P6_RSTAL 0x60A20300 // Resource Related Stalls +#define P6_MMXIE 0x60B00300 // MMX Instructions Executed +#define P6_SAISE 0x60B10300 // Saturated Arithmetic Instructions Executed +#define P6_PORT0 0x60B20301 // MMX micro-ops executed on Port 0 +#define P6_PORT1 0x60B20302 // MMX micro-ops executed on Port 1 +#define P6_PORT2 0x60B20304 // MMX micro-ops executed on Port 2 +#define P6_PORT3 0x60B20308 // MMX micro-ops executed on Port 3 +#define P6_MMXPA 0x60B30300 // MMX Packed Arithmetic +#define P6_MMXPM 0x60B30301 // MMX Packed Multiply +#define P6_MMXPS 0x60B30302 // MMX Packed Shift +#define P6_MMXPO 0x60B30304 // MMX Packed Operations +#define P6_MMXUO 0x60B30308 // MMX Unpacked Operations +#define P6_MMXPL 0x60B30310 // MMX Packed Logical +#define P6_INSTR 0x60C00300 // Instructions Retired +#define P6_FPOPS 0x60C10100 // FP operations retired (COUNTER 0 ONLY) +#define P6_UOPSR 0x60C20300 // Micro-Ops Retired +#define P6_BRRET 0x60C40300 // Branch Instructions Retired +#define P6_BRMSR 0x60C50300 // Branch Mispredictions Retired +#define P6_MASKD 0x60C60300 // Clocks while interrupts masked +#define P6_MSKPN 0x60C70300 // Clocks while interrupt is pending +#define P6_HWINT 0x60C80300 // Hardware Interrupts Received +#define P6_BTAKR 0x60C90300 // Taken Branch Retired +#define P6_BTAKM 0x60CA0300 // Taken Branch Mispredictions +#define P6_FPMMX 0x60CC0301 // Transitions from Floating Point to MMX +#define P6_MMXFP 0x60CC0300 // Transitions from MMX to Floating Point +#define P6_SIMDA 0x60CD0300 // SIMD Assists (EMMS Instructions Executed) +#define P6_MMXIR 0x60CE0300 // MMX Instructions Retired +#define P6_SAISR 0x60CF0300 // Saturated Arithmetic Instructions Retired +#define P6_INSTD 0x60D00300 // Instructions Decoded +#define P6_NPRTL 0x60D20300 // Renaming Stalls +#define P6_SRSES 0x60D40301 // Segment Rename Stalls - ES +#define P6_SRSDS 0x60D40302 // Segment Rename Stalls - DS +#define P6_SRSFS 0x60D40304 // Segment Rename Stalls - FS +#define P6_SRSGS 0x60D40308 // Segment Rename Stalls - GS +#define P6_SRSXS 0x60D4030F // Segment Rename Stalls - ES DS FS GS +#define P6_SRNES 0x60D50301 // Segment Renames - ES +#define P6_SRNDS 0x60D50302 // Segment Renames - DS +#define P6_SRNFS 0x60D50304 // Segment Renames - FS +#define P6_SRNGS 0x60D50308 // Segment Renames - GS +#define P6_SRNXS 0x60D5030F // Segment Renames - ES DS FS GS +#define P6_BRDEC 0x60E00300 // Branch Instructions Decoded +#define P6_BTBMS 0x60E20301 // BTB Misses +#define P6_RETDC 0x60E40300 // Bogus Branches +#define P6_BACLR 0x60E60300 // BACLEARS Asserted (Testing) + +// INTEL +#define PENTIUM_FAMILY 5 // define for pentium +#define PENTIUMPRO_FAMILY 6 // define for pentium pro +#define PENTIUM4_FAMILY 15 // define for pentium 4 + +// AMD +#define K6_FAMILY 5 +#define K8_FAMILY 6 +#define EXTENDED_FAMILY 15 // AMD 64 and AMD Opteron + +#endif // VPC_TIER0_P5P6PERFORMANCECOUNTERS_H_ diff --git a/public/tier0/platform.h b/public/tier0/platform.h new file mode 100644 index 0000000..6d457b8 --- /dev/null +++ b/public/tier0/platform.h @@ -0,0 +1,1932 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_PLATFORM_H_ +#define VPC_TIER0_PLATFORM_H_ + +#ifdef SN_TARGET_PS3 + +#define _PS3 1 +#define COMPILER_PS3 1 +#define PLATFORM_PS3 1 + +// There are 2 compilers for the PS3: GCC and the SN Systems compiler. +// They are mostly similar, but in a few places we need to distinguish between +// the two. +#if defined(__SNC__) +#define COMPILER_SNC 1 +#elif defined(__GCC__) +#define COMPILER_GCC 1 +#else +#error "Unrecognized PS3 compiler; either __SNC__ or __GCC__ must be defined" +#endif + +#endif // SN_TARGET_PS3 + +#ifdef __GCC__ +#define COMPILER_GCC 1 +#endif + +#if defined(_X360) || defined(_PS3) +#define PLATFORM_PPC 1 +#endif + +#ifdef COMPILER_MSVC +#pragma once +#endif + +#if defined(_PS3) +#include + +// We want to force the assert to be redefined, because the STD assert might +// have been included and redefined. ps3_assert.h will do a check for assert +// being redefined. #include "ps3/ps3_assert.h" +#ifndef COMPILER_PS3 +#error \ + "for PS3, VPC must define COMPILER_PS3 macro just like it does for COMPILER_MSVCX360 macro" +#endif +#if !defined(COMPILER_SNC) && !defined(COMPILER_GCC) +#error \ + "for PS3, VPC must define COMPILER_SNC or COMPILER_GCC macro, depending on the target compiler, just like it does for COMPILER_MSVCX360 macro" +#endif + +#elif defined(_X360) +#define NO_STEAM +#define NO_VOICE +// for the 360, the ppc platform and the rtos are tightly coupled +// setup the 360 environment here !once! for much less leaf module include +// wackiness these are critical order and purposely appear *before* anything +// else +#define _XBOX +#include +#include +#include +#include +#include +#include +#undef _XBOX + +#endif + +#include "wchartypes.h" +#include "tier0/valve_off.h" + +#ifdef _PS3 + +#include "ps3/ps3_platform.h" + +#define NO_STEAM_GAMECOORDINATOR + +#else + +#include +#include +#include +#include +#include +#include +#ifdef OSX +#include +#endif + +#endif + +// This macro +#if defined(_PS3) && defined(COMPILER_SNC) + +// There are known bugs in the PS3 optimizer. The following macros allow us to +// lower optimization for a subset of a file If you run into build problems with +// optimization on, try turning off optimization for the selected file. If that +// fixes the problem, use process of elimination and the below macros to find +// the bare minimum that needs to be unoptimized and report the compiler issue +// to Sony as well. +// +// The correlation between optimization levels and numbers passed to the _Pragma +// xopt and postopt calls is as follows: See: Control-group reference tables / +// -Xshow +// .... xopt +// -O1 0 +// -O2 5 +// -O3 5 +// +// These macros MUST be used in pairs - Otherwise, the compiler will barf 'At +// end of source: error 67: expected a "}"' + +// xopt disables some of the miscellaneous optimizations +#if __option(xopt) +#define SN_OPT_DISABLE \ + extern "C++" { \ + _Pragma("control %push xopt=0") +#define SN_OPT_ENABLE \ + _Pragma("control %pop xopt") \ + } +#else // !__option(xopt) +#define SN_OPT_DISABLE +#define SN_OPT_ENABLE +#endif // !__option(xopt) + +// postopt disables the main optimizer +#if __option(postopt) > 0 +#define SN_MAIN_OPT_DISABLE \ + extern "C++" { \ + _Pragma("control %push postopt=0") +#define SN_MAIN_OPT_ENABLE \ + _Pragma("control %pop postopt") \ + } +#else // !__option(postopt) > 0 +#define SN_MAIN_OPT_DISABLE +#define SN_MAIN_OPT_ENABLE +#endif // !__option(postopt) > 0 + +#else // ! ( _PS3 && COMPILER_SNC ) +#define SN_OPT_DISABLE +#define SN_OPT_ENABLE +#define SN_MAIN_OPT_DISABLE +#define SN_MAIN_OPT_ENABLE +#endif // ! ( _PS3 && COMPILER_SNC ) + +#ifdef __cplusplus +#if defined(COMPILER_GCC) || defined(COMPILER_PS3) +#include +#else +#include +#endif +#endif + +//----------------------------------------------------------------------------- +// Old-school defines we don't want to use moving forward +//----------------------------------------------------------------------------- +#if CROSS_PLATFORM_VERSION < 1 + +// feature enables +#define NEW_SOFTWARE_LIGHTING +#if !defined(_X360) +#define SUPPORT_PACKED_STORE +#endif + +#if defined(BINK_VIDEO) && (defined(_X360) || defined(_PS3)) +#define BINK_ENABLED_FOR_CONSOLE +#endif + +#if !defined(PORTAL2) +//#define PORTAL2 +#endif + +// C functions for external declarations that call the appropriate C++ methods +#ifndef EXPORT +#ifdef _WIN32 +#define EXPORT _declspec(dllexport) +#else +#define EXPORT /* */ +#endif +#endif + +#endif // CROSS_PLATFORM_VERSION < 1 + +#if defined(_STATIC_LINKED) +#include "staticlink/system.h" +#endif + +//----------------------------------------------------------------------------- +// NOTE: All compiler defines are set up in the base VPC scripts +// COMPILER_MSVC, COMPILER_MSVC32, COMPILER_MSVC64, COMPILER_MSVCX360 +// COMPILER_GCC +// The rationale for this is that we need COMPILER_MSVC for the pragma blocks +// #pragma once that occur at the top of all header files, therefore we can't +// place the defines for these in here. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Set up platform defines. +//----------------------------------------------------------------------------- +#ifdef _WIN32 +#define IsPlatformLinux() 0 +#define IsPlatformPosix() 0 +#define IsPlatformOSX() 0 +#define IsOSXOpenGL() 0 +#define IsPlatformPS3() 0 +#define IsPlatformPS3_PPU() 0 +#define IsPlatformPS3_SPU() 0 +#define PLATFORM_WINDOWS 1 +#define PLATFORM_OPENGL 0 + +#ifndef _X360 +#define IsPlatformX360() 0 +#define IsPlatformWindowsPC() 1 +#define PLATFORM_WINDOWS_PC 1 + +#ifdef _WIN64 +#define IsPlatformWindowsPC64() 1 +#define IsPlatformWindowsPC32() 0 +#define PLATFORM_WINDOWS_PC64 1 +#else +#define IsPlatformWindowsPC64() 0 +#define IsPlatformWindowsPC32() 1 +#define PLATFORM_WINDOWS_PC32 1 +#endif + +#else // _X360 + +#define IsPlatformWindowsPC() 0 +#define IsPlatformWindowsPC64() 0 +#define IsPlatformWindowsPC32() 0 +#define IsPlatformX360() 1 +#define PLATFORM_X360 1 + +#endif // _X360 +#elif defined(_PS3) + +#define IsPlatformX360() 0 +#define IsPlatformPS3() 1 +#ifdef SPU +#define IsPlatformPS3_PPU() 0 +#define IsPlatformPS3_SPU() 1 +#else +#define IsPlatformPS3_PPU() 1 +#define IsPlatformPS3_SPU() 0 +#endif +#define IsPlatformWindowsPC() 0 +#define IsPlatformWindowsPC64() 0 +#define IsPlatformWindowsPC32() 0 +#define IsPlatformPosix() 1 +#define PLATFORM_POSIX 1 +#define PLATFORM_OPENGL 0 + +#define IsPlatformLinux() 0 +#define IsPlatformOSX() 0 +#define IsOSXOpenGL() 0 + +#elif defined(POSIX) +#define IsPlatformX360() 0 +#define IsPlatformPS3() 0 +#define IsPlatformPS3_PPU() 0 +#define IsPlatformPS3_SPU() 0 +#define IsPlatformWindowsPC() 0 +#define IsPlatformWindowsPC64() 0 +#define IsPlatformWindowsPC32() 0 +#define IsPlatformPosix() 1 +#define PLATFORM_POSIX 1 + +#if defined(LINUX) && \ + !defined(OSX) // for havok we define both symbols, so don't let the osx + // build wander down here +#define IsPlatformLinux() 1 +#define IsPlatformOSX() 0 +#define IsOSXOpenGL() 0 +#define PLATFORM_OPENGL 0 +#define PLATFORM_LINUX 1 +#elif defined(OSX) +#define IsPlatformLinux() 0 +#define IsPlatformOSX() 1 +#define IsOSXOpenGL() 1 +#define PLATFORM_OSX 1 +#define PLATFORM_OPENGL 1 +#else +#define IsPlatformLinux() 0 +#define IsPlatformOSX() 0 +#define IsOSXOpenGL() 0 +#define PLATFORM_OPENGL 0 +#endif + +#else +#error +#endif + +// IsXXXX platform pseudo-functions +#if (defined(PLATFORM_WINDOWS) && (PLATFORM_WINDOWS)) +#define IsPlatformWindows() 1 +#else +#define IsPlatformWindows() 0 +#endif + +#if (defined(PLATFORM_OPENGL) && PLATFORM_OPENGL) +#define IsOpenGL() 1 +#else +#define IsOpenGL() 0 +#endif + +#ifndef _PS3 +//#include +//#include +#else +#include // For malloc() +#include // for alloca() +#define _alloca alloca +#ifdef __cplusplus +#include +#endif +#endif + +//----------------------------------------------------------------------------- +// Old-school defines we're going to support since much code uses them +//----------------------------------------------------------------------------- +#if CROSS_PLATFORM_VERSION < 2 + +#define IsLinux() IsPlatformLinux() +#define IsOSX() IsPlatformOSX() +#define IsPosix() IsPlatformPosix() +#define IsX360() IsPlatformX360() +#define IsPS3() IsPlatformPS3() + +// Setup platform defines. +#ifdef COMPILER_MSVC +#define MSVC 1 +#endif + +#ifdef COMPILER_GCC +#define GNUC 1 +#endif + +#if defined(_WIN32) +#define _WINDOWS 1 +#endif + +#ifdef PLATFORM_WINDOWS_PC +#define IS_WINDOWS_PC 1 +#endif + +#endif // CROSS_PLATFORM_VERSION < 2 + +// VXConsole is enabled for... +#if defined(_X360) || defined(_PS3) +#define USE_VXCONSOLE 1 +#define HasVxConsole() 1 +#else +#define HasVxConsole() 0 +#endif + +//----------------------------------------------------------------------------- +// Set up platform type defines. +//----------------------------------------------------------------------------- +#if defined(PLATFORM_X360) || defined(_PS3) +#ifndef _GAMECONSOLE +#define _GAMECONSOLE +#endif +#define IsPC() 0 +#define IsGameConsole() 1 +#else +#define IsPC() 1 +#define IsGameConsole() 0 +#endif + +//----------------------------------------------------------------------------- +// Set up build configuration defines. +//----------------------------------------------------------------------------- +#ifdef _CERT +#define IsCert() 1 +#else +#define IsCert() 0 +#endif + +#ifdef _DEBUG +#define IsRelease() 0 +#define IsDebug() 1 +#else +#define IsRelease() 1 +#define IsDebug() 0 +#endif + +#ifdef _RETAIL +#define IsRetail() 1 +#else +#define IsRetail() 0 +#endif + +//----------------------------------------------------------------------------- +// Portable data types +//----------------------------------------------------------------------------- +typedef unsigned char uint8; +typedef signed char int8; + +#if defined(COMPILER_MSVC) + +typedef __int16 int16; +typedef unsigned __int16 uint16; +typedef __int32 int32; +typedef unsigned __int32 uint32; +typedef __int64 int64; +typedef unsigned __int64 uint64; + +// intp is an integer that can accomodate a pointer +// (ie, sizeof(intp) >= sizeof(int) && sizeof(intp) >= sizeof(void *) +typedef intptr_t intp; +typedef uintptr_t uintp; + +#if defined(COMPILER_MSVCX360) +#ifdef __m128 +#undef __m128 +#endif +#define __m128 __vector4 +#endif + +#else // !COMPILER_MSVC + +typedef short int16; +typedef unsigned short uint16; +typedef int int32; +typedef unsigned int uint32; +typedef long long int64; +typedef unsigned long long uint64; +#ifdef PLATFORM_64BITS +typedef long long intp; +typedef unsigned long long uintp; +#else +typedef int intp; +typedef unsigned int uintp; +#endif +typedef void *HWND; +#endif // else COMPILER_MSVC + +#if defined(_PS3) && !defined(NO_SIMD) +typedef union __attribute__((aligned(16))) { + float m128_f32[4]; +} l_m128; + +typedef __vector float __vector4; +typedef __vector4 __m128; + +const __m128 VMX_ZERO = (vector float)(0.0f); +const __m128 VMX_ONE_HALF = (vector float)(0.5f); +const __m128 VMX_ONE = (vector float)(1.0f); + +// Syntaxic sugar for multiply +inline __attribute__((always_inline)) __m128 __vec_mul(const __m128 a, + const __m128 b) { + return vec_madd(a, b, VMX_ZERO); +} + +// Refined reciprocal function +inline __attribute__((always_inline)) __m128 __vec_rec(const __m128 a) { + // Get the reciprocal estimate + vector float estimate = vec_re(a); + + // One round of Newton-Raphson refinement + return vec_madd(vec_nmsub(estimate, a, VMX_ONE), estimate, estimate); +} + +// refined reciprocal square root +inline __attribute__((always_inline)) __m128 __vec_rsqrt(const __m128 a) { + // Get the square root reciprocal estimate + __m128 estimate = vec_rsqrte(a); + + // One round of Newton-Raphson refinement + __m128 estimateSquared = __vec_mul(estimate, estimate); + __m128 halfEstimate = __vec_mul(estimate, VMX_ONE_HALF); + return vec_madd(vec_nmsub(a, estimateSquared, VMX_ONE), halfEstimate, + estimate); +} + +// refined square root +inline __attribute__((always_inline)) __m128 __vec_sqrt(const __m128 a) { + return __vec_mul(a, __vec_rsqrt(a)); +} + +// estimate square root +inline __attribute__((always_inline)) __m128 __vec_sqrtest(const __m128 a) { + return __vec_mul(a, vec_rsqrte(a)); +} + +// Syntaxic sugar for multiply +inline __attribute__((always_inline)) __m128 __vec_div(const __m128 a, + const __m128 b) { + return __vec_mul(a, __vec_rec(b)); +} + +// load an unaligned array of float in a vector of floats +inline __attribute__((always_inline)) __m128 __vec_ld_unaligned( + const float *in) { + return vec_perm(vec_ld(0, in), vec_ld(sizeof(__m128), in), vec_lvsl(0, in)); +} + +// load an unaligned array of 3 floats in a vector of floats, last member being +// 0. +inline __attribute__((always_inline)) __m128 __vec_ld_unaligned3( + const float *in) { + return vec_and( + __vec_ld_unaligned(in), + (__m128)(vector unsigned int)(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); +} + +// stores a vector of floats in an unaligned array of float +inline __attribute__((always_inline)) void __vec_st_unaligned(__m128 in, + float *out) { + __m128 temp0 = vec_ld(0, out); + __m128 temp1 = vec_ld(16, out); + vector unsigned char align = vec_lvsr(0, out); + vector unsigned char mask = + vec_perm((vector unsigned char)(0), (vector unsigned char)(0xFF), align); + + in = vec_perm(in, in, align); + temp0 = vec_sel(temp0, in, (vector bool)mask); + temp1 = vec_sel(in, temp1, (vector bool)mask); + vec_st(temp0, 0, out); + vec_st(temp1, 16, out); +} + +// stores x,y,z from a vector of floats in an unaligned array of 3 floats +inline __attribute__((always_inline)) void __vec_st_unaligned3(__m128 in, + float *out) { + __m128 temp0 = vec_ld(0, out); + __m128 temp1 = vec_ld(16, out); + vector unsigned char align = vec_lvsr(0, out); + vector unsigned char mask = + vec_perm((vector unsigned char)(0), + (vector unsigned char)(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0), + align); + + in = vec_perm(in, in, align); + temp0 = vec_sel(temp0, in, (vector bool)mask); + temp1 = vec_sel(in, temp1, (vector bool)mask); + vec_st(temp0, 0, out); + vec_st(temp1, 16, out); +} + +#endif // defined(NO_SIMD) + +typedef float float32; +typedef double float64; + +// for when we don't care about how many bits we use +typedef unsigned int uint; + +#ifdef PLATFORM_POSIX +#ifndef _PS3 +typedef unsigned int DWORD; +typedef unsigned int *LPDWORD; +#endif +typedef unsigned short WORD; +typedef void *HINSTANCE; +#define _MAX_PATH PATH_MAX +#endif + +// MSVC CRT uses 0x7fff while gcc uses MAX_INT, leading to mismatches between +// platforms As a result, we pick the least common denominator here. This +// should be used anywhere you might typically want to use RAND_MAX +#define VALVE_RAND_MAX 0x7fff +// Maximum and minimum representable values +#ifndef PLATFORM_OSX + +// Prevent tons of warnings about limits redefinition. +#include + +#endif // PLATFORM_OSX + +#ifndef UINT_MIN +#define UINT_MIN UINT32_MIN +#endif + +#define FLOAT32_MAX FLT_MAX +#define FLOAT64_MAX DBL_MAX + +#ifdef GNUC +#undef offsetof +// Note: can't use builtin offsetof because many use cases (esp. in templates) +// wouldn't compile due to restrictions on the builtin offsetof +//#define offsetof( type, var ) __builtin_offsetof( type, var ) +#define offsetof(s, m) ((size_t) & (((s *)0x1000000)->m) - 0x1000000u) +#else +#include +#undef offsetof +#define offsetof(s, m) (size_t) & (((s *)0)->m) +#endif + +#define FLOAT32_MIN FLT_MIN +#define FLOAT64_MIN DBL_MIN + +//----------------------------------------------------------------------------- +// Long is evil because it's treated differently by different compilers +// Preventing its use is nasty however. This #define, which should be +// turned on in individual VPC files, causes you to include tier0/valve_off.h +// before standard C + windows headers, and include tier0/valve_on.h after +// standard C + windows headers. So, there's some painful overhead to disabling +// long +//----------------------------------------------------------------------------- +#ifdef DISALLOW_USE_OF_LONG +#define long long_is_the_devil_stop_using_it_use_int32_or_int64 +#endif + +//----------------------------------------------------------------------------- +// Various compiler-specific keywords +//----------------------------------------------------------------------------- +#ifdef COMPILER_MSVC + +#ifdef FORCEINLINE +#undef FORCEINLINE +#endif +#define STDCALL __stdcall +#ifndef FASTCALL +#define FASTCALL __fastcall +#endif +#define FORCEINLINE __forceinline +#define FORCEINLINE_TEMPLATE __forceinline +#define NULLTERMINATED __nullterminated + +// This can be used to ensure the size of pointers to members when declaring +// a pointer type for a class that has only been forward declared +#define SINGLE_INHERITANCE __single_inheritance +#define MULTIPLE_INHERITANCE __multiple_inheritance +#define EXPLICIT explicit +#define NO_VTABLE __declspec(novtable) + +// gcc doesn't allow storage specifiers on explicit template instatiation, but +// visual studio needs them to avoid link errors. +#define TEMPLATE_STATIC static + +// Used for dll exporting and importing +#define DLL_EXPORT extern "C" __declspec(dllexport) +#define DLL_IMPORT extern "C" __declspec(dllimport) + +// Can't use extern "C" when DLL exporting a class +#define DLL_CLASS_EXPORT __declspec(dllexport) +#define DLL_CLASS_IMPORT __declspec(dllimport) + +// Can't use extern "C" when DLL exporting a global +#define DLL_GLOBAL_EXPORT extern __declspec(dllexport) +#define DLL_GLOBAL_IMPORT extern __declspec(dllimport) + +// Pass hints to the compiler to prevent it from generating unnessecary / stupid +// code in certain situations. Several compilers other than MSVC also have an +// equivilent construct. +// +// Essentially the 'Hint' is that the condition specified is assumed to be true +// at that point in the compilation. If '0' is passed, then the compiler +// assumes that any subsequent code in the same 'basic block' is unreachable, +// and thus usually removed. +#define HINT(THE_HINT) __assume((THE_HINT)) + +// decls for aligning data +#define DECL_ALIGN(x) __declspec(align(x)) + +// GCC had a few areas where it didn't construct objects in the same order +// that Windows does. So when CVProfile::CVProfile() would access g_pMemAlloc, +// it would crash because the allocator wasn't initalized yet. +#define CONSTRUCT_EARLY + +#define SELECTANY __declspec(selectany) + +#define RESTRICT __restrict +#define RESTRICT_FUNC __declspec(restrict) +#define FMTFUNCTION(a, b) +#define NOINLINE + +#if !defined(NO_THREAD_LOCAL) +#define DECL_THREAD_LOCAL __declspec(thread) +#endif + +#define DISABLE_VC_WARNING(x) __pragma(warning(disable : 4310)) +#define DEFAULT_VC_WARNING(x) __pragma(warning(default : 4310)) + +#elif defined(COMPILER_GCC) || defined(COMPILER_SNC) + +#if defined(COMPILER_SNC) +#define STDCALL +#define __stdcall +#elif (CROSS_PLATFORM_VERSION >= 1) && !defined(PLATFORM_64BITS) && \ + !defined(COMPILER_PS3) +#define STDCALL __attribute__((__stdcall__)) +#else +#define STDCALL +#define __stdcall __attribute__((__stdcall__)) +#endif + +#define FASTCALL +#ifdef _LINUX_DEBUGGABLE +#define FORCEINLINE +#else +#ifdef _PS3 +// [IESTYN 7/29/2010] As of SDK 3.4.0, this causes bad code generation in +// NET_Tick::ReadFromBuffer in netmessages.cpp, +// which caused (seeming) random network packet corruption. +// It probably causes other bugs too. +#define FORCEINLINE inline /* __attribute__ ((always_inline)) */ +#else +#define FORCEINLINE inline __attribute__((always_inline)) +#endif +#endif + +// GCC 3.4.1 has a bug in supporting forced inline of templated functions +// this macro lets us not force inlining in that case +#define FORCEINLINE_TEMPLATE inline +#define SINGLE_INHERITANCE +#define MULTIPLE_INHERITANCE +#define EXPLICIT +#define NO_VTABLE + +#define NULLTERMINATED + +#if defined(COMPILER_SNC) +#define TEMPLATE_STATIC static +#else +#define TEMPLATE_STATIC +#endif + +// Used for dll exporting and importing +#ifdef COMPILER_SNC +#define DLL_DECLARATION_DEFAULT_VISIBILITY +#else +#define DLL_DECLARATION_DEFAULT_VISIBILITY \ + __attribute__((visibility("default"))) +#endif +#define DLL_EXPORT extern "C" DLL_DECLARATION_DEFAULT_VISIBILITY +#define DLL_IMPORT extern "C" + +// Can't use extern "C" when DLL exporting a class +#ifndef _PS3 +#define __stdcall __attribute__((__stdcall__)) +#endif +#define DLL_CLASS_EXPORT DLL_DECLARATION_DEFAULT_VISIBILITY +#define DLL_CLASS_IMPORT + +// Can't use extern "C" when DLL exporting a global +#define DLL_GLOBAL_EXPORT DLL_DECLARATION_DEFAULT_VISIBILITY +#define DLL_GLOBAL_IMPORT extern + +#define HINT(THE_HINT) __builtin_expect(THE_HINT, 1) +#define DECL_ALIGN(x) __attribute__((aligned(x))) +#define CONSTRUCT_EARLY __attribute__((init_priority(101))) +#define SELECTANY __attribute__((weak)) +#define RESTRICT __restrict__ +#define RESTRICT_FUNC RESTRICT_FUNC_NOT_YET_DEFINED_FOR_THIS_COMPILER +#define FMTFUNCTION(fmtargnumber, firstvarargnumber) \ + __attribute__((format(printf, fmtargnumber, firstvarargnumber))) +#define NOINLINE __attribute__((noinline)) + +#if !defined(NO_THREAD_LOCAL) +#define DECL_THREAD_LOCAL __thread +#endif + +#define DISABLE_VC_WARNING(x) +#define DEFAULT_VC_WARNING(x) + +#else + +#define DECL_ALIGN(x) /* */ +#define SELECTANY static + +#endif + +#if defined(GNUC) && !defined(COMPILER_PS3) // use pre-align on PS3 +// gnuc has the align decoration at the end +#define ALIGN4 +#define ALIGN8 +#define ALIGN16 +#define ALIGN32 +#define ALIGN128 + +#undef ALIGN16_POST +#define ALIGN4_POST DECL_ALIGN(4) +#define ALIGN8_POST DECL_ALIGN(8) +#define ALIGN16_POST DECL_ALIGN(16) +#define ALIGN32_POST DECL_ALIGN(32) +#define ALIGN128_POST DECL_ALIGN(128) +#else +// MSVC has the align at the start of the struct +// PS3 SNC supports both +#define ALIGN4 DECL_ALIGN(4) +#define ALIGN8 DECL_ALIGN(8) +#define ALIGN16 DECL_ALIGN(16) +#define ALIGN32 DECL_ALIGN(32) +#define ALIGN128 DECL_ALIGN(128) + +#define ALIGN4_POST +#define ALIGN8_POST +#define ALIGN16_POST +#define ALIGN32_POST +#define ALIGN128_POST +#endif + +// This can be used to declare an abstract (interface only) class. +// Classes marked abstract should not be instantiated. If they are, and access +// violation will occur. +// +// Example of use: +// +// abstract_class CFoo +// { +// ... +// } +// +// MSDN __declspec(novtable) documentation: +// https://docs.microsoft.com/en-us/cpp/cpp/novtable +// +// Note: NJS: This is not enabled for regular PC, due to not knowing the +// implications of exporting a class with no no vtable. +// It's probable that this shouldn't be an issue, but an experiment should +// be done to verify this. +// +#ifndef COMPILER_MSVCX360 +#define abstract_class class +#else +#define abstract_class class NO_VTABLE +#endif + +//----------------------------------------------------------------------------- +// Why do we need this? It would be nice to make it die die die +//----------------------------------------------------------------------------- +// Alloca defined for this platform +#if defined(COMPILER_MSVC) && !defined(WINDED) +#if defined(_M_IX86) +#define __i386__ 1 +#endif +#endif + +#if defined __i386__ && !defined __linux__ +#define id386 1 +#else +#define id386 0 +#endif // __i386__ + +//----------------------------------------------------------------------------- +// Disable annoying unhelpful warnings +//----------------------------------------------------------------------------- +#if defined(COMPILER_SNC) +#pragma diag_suppress = 1700 // warning 1700: class "%s" has virtual functions + // but non-virtual destructor +// Uncomment the following line if you want to investigate a specific compiler +// remark without all the noise: #pragma diag_suppress=1700, 83, 162, 182, 192, +// 194, 229, 238, 262, 341, 382, 401, 402, 403, 481, 817, 828, 833, 1363, 1771, +// 1774, 1779, 1780, 1783, 1785, 1786, 1788 +#endif + +// Pull in the /analyze code annotations. +#include "annotations.h" + +//----------------------------------------------------------------------------- +// Convert int<-->pointer, avoiding 32/64-bit compiler warnings: +//----------------------------------------------------------------------------- +#define INT_TO_POINTER(i) (void *)((i) + (char *)NULL) +#define POINTER_TO_INT(p) ((int)(uint64)(p)) + +//----------------------------------------------------------------------------- +// Stack-based allocation related helpers +//----------------------------------------------------------------------------- +#if defined(COMPILER_GCC) || defined(COMPILER_SNC) + +#define stackalloc(_size) alloca(ALIGN_VALUE(_size, 16)) + +#ifdef PLATFORM_OSX +#define mallocsize(_p) (malloc_size(_p)) +#else +#define mallocsize(_p) (malloc_usable_size(_p)) +#endif + +#elif defined(COMPILER_MSVC) + +#define stackalloc(_size) _alloca(ALIGN_VALUE(_size, 16)) +#define mallocsize(_p) (_msize(_p)) + +#endif + +#define stackfree(_p) 0 + +//----------------------------------------------------------------------------- +// Used to break into the debugger +//----------------------------------------------------------------------------- +#ifdef COMPILER_MSVC64 +#define DebuggerBreak() __debugbreak() +#elif COMPILER_MSVC32 +#define DebuggerBreak() __asm { int 3} +#elif COMPILER_MSVCX360 +#define DebuggerBreak() DebugBreak() +#elif COMPILER_GCC +#if defined(_PS3) +#define DebuggerBreak() \ + { __asm volatile("tw 31,1,1"); } +#elif defined(OSX) +#define DebuggerBreak() \ + if (Plat_IsInDebugSession()) { \ + __asm__ __volatile__("int $3"); \ + } else { \ + raise(SIGTRAP); \ + } +#elif defined(PLATFORM_CYGWIN) || defined(PLATFORM_POSIX) +#define DebuggerBreak() __asm__("int $0x3;") +#else +#define DebuggerBreak() asm("int3") +#endif +#elif defined(COMPILER_SNC) && defined(COMPILER_PS3) +static bool sPS3_SuppressAssertsInThisFile = + false; // you can throw this in the debugger to temporarily disable asserts + // inside any particular .cpp module. +#define DebuggerBreak() \ + if (!sPS3_SuppressAssertsInThisFile) \ + __builtin_snpause(); // from SNC Migration Guide, tw 31,1,1 +#else +#error DebuggerBreak() is not defined for this platform! +#endif + +#if defined(_X360) || defined(_PS3) +#if defined(fsel) +#error +#endif +#else + +FORCEINLINE float fsel(float fComparand, float fValGE, float fLT) { + return fComparand >= 0 ? fValGE : fLT; +} +FORCEINLINE double fsel(double fComparand, double fValGE, double fLT) { + return fComparand >= 0 ? fValGE : fLT; +} + +#endif + +//----------------------------------------------------------------------------- +// DLL export for platform utilities +//----------------------------------------------------------------------------- +#ifndef STATIC_TIER0 + +#ifdef TIER0_DLL_EXPORT +#define PLATFORM_INTERFACE DLL_EXPORT +#define PLATFORM_OVERLOAD DLL_GLOBAL_EXPORT +#define PLATFORM_CLASS DLL_CLASS_EXPORT +#else +#define PLATFORM_INTERFACE DLL_IMPORT +#define PLATFORM_OVERLOAD DLL_GLOBAL_IMPORT +#define PLATFORM_CLASS DLL_CLASS_IMPORT +#endif + +#else // BUILD_AS_DLL + +#define PLATFORM_INTERFACE extern "C" +#define PLATFORM_OVERLOAD +#define PLATFORM_CLASS + +#endif // BUILD_AS_DLL + +//----------------------------------------------------------------------------- +// Returns true if debugger attached, false otherwise +//----------------------------------------------------------------------------- +#if defined(PLATFORM_WINDOWS) || defined(_PS3) +PLATFORM_INTERFACE void Plat_DebugString(const tchar *); +#else +#define Plat_DebugString(s) ((void)0) +#endif + +PLATFORM_INTERFACE bool Plat_IsInDebugSession(); + +#define DebuggerBreakIfDebugging() \ + if (!Plat_IsInDebugSession()) \ + ; \ + else \ + DebuggerBreak() + +//----------------------------------------------------------------------------- +// Message Box +//----------------------------------------------------------------------------- +#if defined(PLATFORM_WINDOWS_PC) +PLATFORM_INTERFACE void Plat_MessageBox(const char *pTitle, + const tchar *pMessage); +#else +#define Plat_MessageBox(t, m) ((void)0) +#endif + +//----------------------------------------------------------------------------- +// Posix platform helpers +//----------------------------------------------------------------------------- +#ifdef PLATFORM_POSIX + +// Visual Studio likes to put an underscore in front of anything that looks like +// a portable function. +#define _strupr strupr +#define _getcwd getcwd +#define _open open +#define _lseek lseek +#define _read read +#define _close close +#define _vsnprintf vsnprintf +#define _stat stat +#define _O_RDONLY O_RDONLY +#define _stricmp strcasecmp +#define _finite finite +#define _unlink unlink +#define _putenv putenv +#define _chdir chdir +#define _access access + +#define strcmpi stricmp +#define stricmp strcasecmp +#define _alloca alloca +#define GetProcAddress dlsym +#define _chdir chdir +#ifndef _PS3 +#define _strnicmp strnicmp +#endif +#define strnicmp strncasecmp +#define _snwprintf swprintf +#define swprintf_s swprintf +#define wcsicmp _wcsicmp +#define _wcsicmp wcscmp +#define _tempnam tempnam +#define strtok_s strtok_r +#define _mkdir(dir) mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO) +#define _wtoi(arg) wcstol(arg, NULL, 10) +#define _wtoi64(arg) wcstoll(arg, NULL, 10) + +#ifndef _PS3 +typedef uint32 HMODULE; +#endif +typedef void *HANDLE; +#define __cdecl + +#if !defined(_snprintf) // some vpc's define this on the command line +#define _snprintf snprintf +#endif + +#include +#include // get unlink +#include + +#endif // PLATFORM_POSIX + +#ifdef PLATFORM_WINDOWS +#ifndef SOCKLEN_T +#define SOCKLEN_T +typedef int socklen_t; +#endif +#endif + +//----------------------------------------------------------------------------- +// Generally useful platform-independent macros (move to another file?) +//----------------------------------------------------------------------------- + +// need macro for constant expression +#define ALIGN_VALUE(val, alignment) (((val) + (alignment)-1) & ~((alignment)-1)) + +// Force a function call site -not- to inlined. (useful for profiling) +#define DONT_INLINE(a) (((int)(a) + 1) ? (a) : (a)) + +// Marks the codepath from here until the next branch entry point as +// unreachable, and asserts if any attempt is made to execute it. +#define UNREACHABLE() \ + { \ + Assert(0); \ + HINT(0); \ + } + +// In cases where no default is present or appropriate, this causes MSVC to +// generate as little code as possible, and throw an assertion in debug. +#define NO_DEFAULT \ + default: \ + UNREACHABLE(); + +// Defines MAX_PATH +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +//----------------------------------------------------------------------------- +// FP exception handling +//----------------------------------------------------------------------------- +//#define CHECK_FLOAT_EXCEPTIONS 1 +//#define CHECK_FPU_CONTROL_WORD_SET 1 // x360 only + +#if defined(COMPILER_MSVC64) + +inline void SetupFPUControlWord() {} + +#elif defined(COMPILER_MSVC32) + +inline void SetupFPUControlWordForceExceptions() { + // use local to get and store control word + uint16 tmpCtrlW; + __asm + { + fnclex /* clear all current exceptions */ + fnstcw word ptr [tmpCtrlW] /* get current control word */ + and [tmpCtrlW], 0FCC0h /* Keep infinity control + rounding control */ + or [tmpCtrlW], 0230h /* set to 53-bit, mask only inexact, underflow */ + fldcw word ptr [tmpCtrlW] /* put new control word in FPU */ + } +} + +#ifdef CHECK_FLOAT_EXCEPTIONS + +inline void SetupFPUControlWord() { SetupFPUControlWordForceExceptions(); } + +#else + +inline void SetupFPUControlWord() { + // use local to get and store control word + uint16 tmpCtrlW; + __asm + { + fnstcw word ptr [tmpCtrlW] /* get current control word */ + and [tmpCtrlW], 0FCC0h /* Keep infinity control + rounding control */ + or [tmpCtrlW], 023Fh /* set to 53-bit, mask only inexact, underflow */ + fldcw word ptr [tmpCtrlW] /* put new control word in FPU */ + } +} + +#endif + +#elif defined(COMPILER_GCC) + +// Works for PS3 +inline void SetupFPUControlWord() { +#ifdef _PS3 +// TODO: PS3 compiler spits out the following errors: +// C:/tmp/ccIN0aaa.s: Assembler messages: +// C:/tmp/ccIN0aaa.s(80): Error: Unrecognized opcode: `fnstcw' +// C:/tmp/ccIN0aaa.s(93): Error: Unrecognized opcode: `fldcw' +#else + __volatile unsigned short int __cw; + __asm __volatile("fnstcw %0" : "=m"(__cw)); + __cw = __cw & 0x0FCC0; // keep infinity control, keep rounding mode + __cw = __cw | 0x023F; // set 53-bit, no exceptions + __asm __volatile("fldcw %0" : : "m"(__cw)); +#endif +} + +#elif defined(COMPILER_SNC) + +// Works for PS3 +inline void SetupFPUControlWord() { +#ifdef _PS3 +// TODO: PS3 compiler spits out the following errors: +// C:/tmp/ccIN0aaa.s: Assembler messages: +// C:/tmp/ccIN0aaa.s(80): Error: Unrecognized opcode: `fnstcw' +// C:/tmp/ccIN0aaa.s(93): Error: Unrecognized opcode: `fldcw' +#else + __volatile unsigned short int __cw; + __asm __volatile("fnstcw %0" : "=m"(__cw)); + __cw = __cw & 0x0FCC0; // keep infinity control, keep rounding mode + __cw = __cw | 0x023F; // set 53-bit, no exceptions + __asm __volatile("fldcw %0" : : "m"(__cw)); +#endif +} + +#elif defined(COMPILER_MSVCX360) + +#ifdef CHECK_FPU_CONTROL_WORD_SET +FORCEINLINE bool IsFPUControlWordSet() { + float f = 0.996f; + union { + double flResult; + int pResult[2]; + }; + flResult = __fctiw(f); + return (pResult[1] == 1); +} +#else +#define IsFPUControlWordSet() 1 +#endif + +inline void SetupFPUControlWord() { + // Set round-to-nearest in FPSCR + // (cannot assemble, must use op-code form) + __emit(0xFF80010C); // mtfsfi 7,0 + + // Favour compatibility over speed (make sure the VPU set to Java-compliant + // mode) NOTE: the VPU *always* uses round-to-nearest + __vector4 a = {0.0f, 0.0f, 0.0f, 0.0f}; + a; // Avoid compiler warning + __asm + { + mtvscr a; // Clear the Vector Status & Control Register to zero + } +} + +#endif // COMPILER_MSVCX360 + +//----------------------------------------------------------------------------- +// Purpose: Standard functions for handling endian-ness +//----------------------------------------------------------------------------- + +//------------------------------------- +// Basic swaps +//------------------------------------- + +template +inline T WordSwapC(T w) { + uint16 temp; + + temp = ((*((uint16 *)&w) & 0xff00) >> 8); + temp |= ((*((uint16 *)&w) & 0x00ff) << 8); + + return *((T *)&temp); +} + +template +inline T DWordSwapC(T dw) { + uint32 temp; + + temp = *((uint32 *)&dw) >> 24; + temp |= ((*((uint32 *)&dw) & 0x00FF0000) >> 8); + temp |= ((*((uint32 *)&dw) & 0x0000FF00) << 8); + temp |= ((*((uint32 *)&dw) & 0x000000FF) << 24); + + return *((T *)&temp); +} + +//------------------------------------- +// Fast swaps +//------------------------------------- + +#if defined(COMPILER_MSVCX360) + +#define WordSwap WordSwap360Intr +#define DWordSwap DWordSwap360Intr + +template +inline T WordSwap360Intr(T w) { + T output; + __storeshortbytereverse(w, 0, &output); + return output; +} + +template +inline T DWordSwap360Intr(T dw) { + T output; + __storewordbytereverse(dw, 0, &output); + return output; +} + +#elif defined(COMPILER_MSVC32) + +#define WordSwap WordSwapAsm +#define DWordSwap DWordSwapAsm + +template +inline T WordSwapAsm(T w) { + __asm + { + mov ax, w + xchg al, ah + } +} + +template +inline T DWordSwapAsm(T dw) { + __asm + { + mov eax, dw + bswap eax + } +} + +#else + +#define WordSwap WordSwapC +#define DWordSwap DWordSwapC + +#endif + +//------------------------------------- +// The typically used methods. +//------------------------------------- + +#if defined(_SGI_SOURCE) || defined(PLATFORM_X360) || defined(_PS3) +#define PLAT_BIG_ENDIAN 1 +#else +#define PLAT_LITTLE_ENDIAN 1 +#endif + +// If a swapped float passes through the fpu, the bytes may get changed. +// Prevent this by swapping floats as DWORDs. +#define SafeSwapFloat(pOut, pIn) (*((uint *)pOut) = DWordSwap(*((uint *)pIn))) + +#if defined(PLAT_LITTLE_ENDIAN) +#define BigShort(val) WordSwap(val) +#define BigWord(val) WordSwap(val) +#define BigLong(val) DWordSwap(val) +#define BigDWord(val) DWordSwap(val) +#define LittleShort(val) (val) +#define LittleWord(val) (val) +#define LittleLong(val) (val) +#define LittleDWord(val) (val) +#define SwapShort(val) BigShort(val) +#define SwapWord(val) BigWord(val) +#define SwapLong(val) BigLong(val) +#define SwapDWord(val) BigDWord(val) + +// Pass floats by pointer for swapping to avoid truncation in the fpu +#define BigFloat(pOut, pIn) SafeSwapFloat(pOut, pIn) +#define LittleFloat(pOut, pIn) (*pOut = *pIn) +#define SwapFloat(pOut, pIn) BigFloat(pOut, pIn) + +#elif defined(PLAT_BIG_ENDIAN) + +#define BigShort(val) (val) +#define BigWord(val) (val) +#define BigLong(val) (val) +#define BigDWord(val) (val) +#define LittleShort(val) WordSwap(val) +#define LittleWord(val) WordSwap(val) +#define LittleLong(val) DWordSwap(val) +#define LittleDWord(val) DWordSwap(val) +#define SwapShort(val) LittleShort(val) +#define SwapWord(val) LittleWord(val) +#define SwapLong(val) LittleLong(val) +#define SwapDWord(val) LittleDWord(val) + +// Pass floats by pointer for swapping to avoid truncation in the fpu +#define BigFloat(pOut, pIn) (*pOut = *pIn) +#define LittleFloat(pOut, pIn) SafeSwapFloat(pOut, pIn) +#define SwapFloat(pOut, pIn) LittleFloat(pOut, pIn) + +#else + +// @Note (toml 05-02-02): this technique expects the compiler to +// optimize the expression and eliminate the other path. On any new +// platform/compiler this should be tested. +inline short BigShort(short val) { + int test = 1; + return (*(char *)&test == 1) ? WordSwap(val) : val; +} +inline uint16 BigWord(uint16 val) { + int test = 1; + return (*(char *)&test == 1) ? WordSwap(val) : val; +} +inline long BigLong(long val) { + int test = 1; + return (*(char *)&test == 1) ? DWordSwap(val) : val; +} +inline uint32 BigDWord(uint32 val) { + int test = 1; + return (*(char *)&test == 1) ? DWordSwap(val) : val; +} +inline short LittleShort(short val) { + int test = 1; + return (*(char *)&test == 1) ? val : WordSwap(val); +} +inline uint16 LittleWord(uint16 val) { + int test = 1; + return (*(char *)&test == 1) ? val : WordSwap(val); +} +inline long LittleLong(long val) { + int test = 1; + return (*(char *)&test == 1) ? val : DWordSwap(val); +} +inline uint32 LittleDWord(uint32 val) { + int test = 1; + return (*(char *)&test == 1) ? val : DWordSwap(val); +} +inline short SwapShort(short val) { return WordSwap(val); } +inline uint16 SwapWord(uint16 val) { return WordSwap(val); } +inline long SwapLong(long val) { return DWordSwap(val); } +inline uint32 SwapDWord(uint32 val) { return DWordSwap(val); } + +// Pass floats by pointer for swapping to avoid truncation in the fpu +inline void BigFloat(float *pOut, const float *pIn) { + int test = 1; + (*(char *)&test == 1) ? SafeSwapFloat(pOut, pIn) : (*pOut = *pIn); +} +inline void LittleFloat(float *pOut, const float *pIn) { + int test = 1; + (*(char *)&test == 1) ? (*pOut = *pIn) : SafeSwapFloat(pOut, pIn); +} +inline void SwapFloat(float *pOut, const float *pIn) { + SafeSwapFloat(pOut, pIn); +} + +#endif + +#if PLAT_BIG_ENDIAN +#if defined(_PS3) +inline uint32 LoadLittleDWord(uint32 *base, unsigned int dwordIndex) { + return __lwbrx(base + dwordIndex); +} + +inline void StoreLittleDWord(uint32 *base, unsigned int dwordIndex, + uint32 dword) { + __stwbrx(base + dwordIndex, dword); +} +inline uint64 LoadLittleInt64(uint64 *base, unsigned int nWordIndex) { + return __ldbrx(base + nWordIndex); +} + +inline void StoreLittleInt64(uint64 *base, unsigned int nWordIndex, + uint64 nWord) { + __stdbrx(base + nWordIndex, nWord); +} +#else +inline uint32 LoadLittleDWord(uint32 *base, unsigned int dwordIndex) { + return __loadwordbytereverse(dwordIndex << 2, base); +} + +inline void StoreLittleDWord(uint32 *base, unsigned int dwordIndex, + uint32 dword) { + __storewordbytereverse(dword, dwordIndex << 2, base); +} +inline uint64 LoadLittleInt64(uint64 *base, unsigned int nWordIndex) { + return __loaddoublewordbytereverse(nWordIndex << 2, base); +} + +inline void StoreLittleInt64(uint64 *base, unsigned int nWordIndex, + uint64 nWord) { + __storedoublewordbytereverse(nWord, nWordIndex << 2, base); +} +#endif +#else +inline uint32 LoadLittleDWord(uint32 *base, unsigned int dwordIndex) { + return LittleDWord(base[dwordIndex]); +} + +inline void StoreLittleDWord(uint32 *base, unsigned int dwordIndex, + uint32 dword) { + base[dwordIndex] = LittleDWord(dword); +} +#endif + +// When in benchmark mode, the timer returns a simple incremented value each +// time you call it. +// +// It should not be changed after startup unless you really know what you're +// doing. The only place that should do this is the benchmark code itself so it +// can output a legit duration. +PLATFORM_INTERFACE void Plat_SetBenchmarkMode(bool bBenchmarkMode); +PLATFORM_INTERFACE bool Plat_IsInBenchmarkMode(); + +PLATFORM_INTERFACE double +Plat_FloatTime(); // Returns time in seconds since the module was loaded. +PLATFORM_INTERFACE uint32 Plat_MSTime(); // Time in milliseconds. +PLATFORM_INTERFACE uint64 +Plat_GetClockStart(); // Snapshot of the clock when app started. +PLATFORM_INTERFACE uint64 Timer_GetTimeUS(); + +// Get the local calendar time. +// Same as time() followed by localtime(), but non-crash-prone and threadsafe. +PLATFORM_INTERFACE void Plat_GetLocalTime(struct tm *pNow); + +// Convert a time_t (specified in nTime - seconds since Jan 1, 1970 UTC) to a +// local calendar time in a threadsafe and non-crash-prone way. +PLATFORM_INTERFACE void Plat_ConvertToLocalTime(uint64 nTime, struct tm *pNow); + +// Get a time string (same as ascstring, but threadsafe). +PLATFORM_INTERFACE void Plat_GetTimeString(struct tm *pTime, char *pOut, + int nMaxBytes); + +// converts a time_t to a struct tm without the local time conversion of +// ConvertToLocalTime +PLATFORM_INTERFACE void Plat_gmtime(uint64 nTime, struct tm *pTime); +PLATFORM_INTERFACE time_t Plat_timegm(struct tm *timeptr); + +// Get the process' executable filename. +PLATFORM_INTERFACE void Plat_GetModuleFilename(char *pOut, int nMaxBytes); + +PLATFORM_INTERFACE void Plat_ExitProcess(int nCode); + +// b/w compatibility +#define Sys_FloatTime Plat_FloatTime + +// Protect against bad auto operator= +#define DISALLOW_OPERATOR_EQUAL(_classname) \ + private: \ + _classname &operator=(const _classname &); \ + \ + public: + +// Define a reasonable operator= +#define IMPLEMENT_OPERATOR_EQUAL(_classname) \ + public: \ + _classname &operator=(const _classname &src) { \ + memcpy(this, &src, sizeof(_classname)); \ + return *this; \ + } + +// Processor Information: +struct CPUInformation { + int m_Size; // Size of this structure, for forward compatability. + + bool m_bRDTSC : 1, // Is RDTSC supported? + m_bCMOV : 1, // Is CMOV supported? + m_bFCMOV : 1, // Is FCMOV supported? + m_bSSE : 1, // Is SSE supported? + m_bSSE2 : 1, // Is SSE2 Supported? + m_b3DNow : 1, // Is 3DNow! Supported? + m_bMMX : 1, // Is MMX supported? + m_bHT : 1; // Is HyperThreading supported? + + uint8 m_nLogicalProcessors; // Number op logical processors. + uint8 m_nPhysicalProcessors; // Number of physical processors + + bool m_bSSE3 : 1, m_bSSSE3 : 1, m_bSSE4a : 1, m_bSSE41 : 1, m_bSSE42 : 1; + + int64 m_Speed; // In cycles per second. + + tchar *m_szProcessorID; // Processor vendor Identification. + + CPUInformation() : m_Size(0) {} +}; + +PLATFORM_INTERFACE const CPUInformation &GetCPUInformation(); + +PLATFORM_INTERFACE void GetCurrentDate(int *pDay, int *pMonth, int *pYear); +PLATFORM_INTERFACE void GetCurrentDayOfTheWeek(int *pDay); // 0 = Sunday +PLATFORM_INTERFACE void GetCurrentDayOfTheYear(int *pDay); // 0 = Jan 1 + +// ---------------------------------------------------------------------------------- +// // Performance Monitoring Events - L2 stats etc... +// ---------------------------------------------------------------------------------- +// // +PLATFORM_INTERFACE void InitPME(); +PLATFORM_INTERFACE void ShutdownPME(); + +//----------------------------------------------------------------------------- +// Security related functions +//----------------------------------------------------------------------------- +// Ensure that the hardware key's drivers have been installed. +PLATFORM_INTERFACE bool Plat_VerifyHardwareKeyDriver(); + +// Ok, so this isn't a very secure way to verify the hardware key for now. It +// is primarially depending on the fact that all the binaries have been wrapped +// with the secure wrapper provided by the hardware keys vendor. +PLATFORM_INTERFACE bool Plat_VerifyHardwareKey(); + +// The same as above, but notifies user with a message box when the key isn't in +// and gives him an opportunity to correct the situation. +PLATFORM_INTERFACE bool Plat_VerifyHardwareKeyPrompt(); + +// Can be called in real time, doesn't perform the verify every frame. Mainly +// just here to allow the game to drop out quickly when the key is removed, +// rather than allowing the wrapper to pop up it's own blocking dialog, which +// the engine doesn't like much. +PLATFORM_INTERFACE bool Plat_FastVerifyHardwareKey(); + +//----------------------------------------------------------------------------- +// Just logs file and line to simple.log +//----------------------------------------------------------------------------- +PLATFORM_INTERFACE void *Plat_SimpleLog(const tchar *file, int line); + +#if defined(_X360) +#define Plat_FastMemset XMemSet +#define Plat_FastMemcpy XMemCpy +#else +#define Plat_FastMemset memset +#define Plat_FastMemcpy memcpy +#endif + +//----------------------------------------------------------------------------- +// XBOX Components valid in PC compilation space +//----------------------------------------------------------------------------- + +#define XBOX_DVD_SECTORSIZE 2048 +#define XBOX_DVD_ECC_SIZE 32768 // driver reads in quantum ECC blocks +#define XBOX_HDD_SECTORSIZE 512 + +// Custom windows messages for Xbox input +#define WM_XREMOTECOMMAND (WM_USER + 100) +#define WM_XCONTROLLER_KEY (WM_USER + 101) +#define WM_SYS_UI (WM_USER + 102) +#define WM_SYS_SIGNINCHANGED (WM_USER + 103) +#define WM_SYS_STORAGEDEVICESCHANGED (WM_USER + 104) +#define WM_SYS_PROFILESETTINGCHANGED (WM_USER + 105) +#define WM_SYS_MUTELISTCHANGED (WM_USER + 106) +#define WM_SYS_INPUTDEVICESCHANGED (WM_USER + 107) +#define WM_SYS_INPUTDEVICECONFIGCHANGED (WM_USER + 108) +#define WM_LIVE_CONNECTIONCHANGED (WM_USER + 109) +#define WM_LIVE_INVITE_ACCEPTED (WM_USER + 110) +#define WM_LIVE_LINK_STATE_CHANGED (WM_USER + 111) +#define WM_LIVE_CONTENT_INSTALLED (WM_USER + 112) +#define WM_LIVE_MEMBERSHIP_PURCHASED (WM_USER + 113) +#define WM_LIVE_VOICECHAT_AWAY (WM_USER + 114) +#define WM_LIVE_PRESENCE_CHANGED (WM_USER + 115) +#define WM_FRIENDS_PRESENCE_CHANGED (WM_USER + 116) +#define WM_FRIENDS_FRIEND_ADDED (WM_USER + 117) +#define WM_FRIENDS_FRIEND_REMOVED (WM_USER + 118) +#define WM_CUSTOM_GAMEBANNERPRESSED (WM_USER + 119) +#define WM_CUSTOM_ACTIONPRESSED (WM_USER + 120) +#define WM_XMP_STATECHANGED (WM_USER + 121) +#define WM_XMP_PLAYBACKBEHAVIORCHANGED (WM_USER + 122) +#define WM_XMP_PLAYBACKCONTROLLERCHANGED (WM_USER + 123) +#define WM_SYS_SHUTDOWNREQUEST (WM_USER + 124) + +#if defined(_PS3) +#define PLATFORM_EXT ".ps3" +#elif defined(PLATFORM_X360) +#define PLATFORM_EXT ".360" +#else +#define PLATFORM_EXT "" +#endif + +inline const char *GetPlatformExt(void) { return PLATFORM_EXT; } + +// flat view, 6 hw threads +#define XBOX_PROCESSOR_0 (1 << 0) +#define XBOX_PROCESSOR_1 (1 << 1) +#define XBOX_PROCESSOR_2 (1 << 2) +#define XBOX_PROCESSOR_3 (1 << 3) +#define XBOX_PROCESSOR_4 (1 << 4) +#define XBOX_PROCESSOR_5 (1 << 5) + +// core view, 3 cores with 2 hw threads each +#define XBOX_CORE_0_HWTHREAD_0 XBOX_PROCESSOR_0 +#define XBOX_CORE_0_HWTHREAD_1 XBOX_PROCESSOR_1 +#define XBOX_CORE_1_HWTHREAD_0 XBOX_PROCESSOR_2 +#define XBOX_CORE_1_HWTHREAD_1 XBOX_PROCESSOR_3 +#define XBOX_CORE_2_HWTHREAD_0 XBOX_PROCESSOR_4 +#define XBOX_CORE_2_HWTHREAD_1 XBOX_PROCESSOR_5 + +//----------------------------------------------------------------------------- +// Include additional dependant header components. +//----------------------------------------------------------------------------- +#if defined(PLATFORM_X360) +#include "xbox/xbox_core.h" +#elif defined(PLATFORM_PS3) +#include "ps3/ps3_core.h" +#endif + +//----------------------------------------------------------------------------- +// Methods to invoke the constructor, copy constructor, and destructor +//----------------------------------------------------------------------------- + +template +inline T *Construct(T *pMemory) { + return ::new (pMemory) T; +} + +template +inline T *Construct(T *pMemory, ARG1 a1) { + return ::new (pMemory) T(a1); +} + +template +inline T *Construct(T *pMemory, ARG1 a1, ARG2 a2) { + return ::new (pMemory) T(a1, a2); +} + +template +inline T *Construct(T *pMemory, ARG1 a1, ARG2 a2, ARG3 a3) { + return ::new (pMemory) T(a1, a2, a3); +} + +template +inline T *Construct(T *pMemory, ARG1 a1, ARG2 a2, ARG3 a3, ARG4 a4) { + return ::new (pMemory) T(a1, a2, a3, a4); +} + +template +inline T *Construct(T *pMemory, ARG1 a1, ARG2 a2, ARG3 a3, ARG4 a4, ARG5 a5) { + return ::new (pMemory) T(a1, a2, a3, a4, a5); +} + +template +inline T *CopyConstruct(T *pMemory, T const &src) { + return ::new (pMemory) T(src); +} + +template +inline void Destruct(T *pMemory) { + pMemory->~T(); + +#ifdef _DEBUG + memset(pMemory, 0xDD, sizeof(T)); +#endif +} + +// +// GET_OUTER() +// +// A platform-independent way for a contained class to get a pointer to its +// owner. If you know a class is exclusively used in the context of some +// "outer" class, this is a much more space efficient way to get at the outer +// class than having the inner class store a pointer to it. +// +// class COuter +// { +// class CInner // Note: this does not need to be a nested class to +// work +// { +// void PrintAddressOfOuter() +// { +// printf( "Outer is at 0x%x\n", GET_OUTER( COuter, +// m_Inner ) ); +// } +// }; +// +// CInner m_Inner; +// friend class CInner; +// }; + +#define GET_OUTER(OuterType, OuterMember) \ + ((OuterType *)((uint8 *)this - offsetof(OuterType, OuterMember))) + +/* TEMPLATE_FUNCTION_TABLE() + + (Note added to platform.h so platforms that correctly support templated + functions can handle portions as templated functions rather than + wrapped functions) + + Helps automate the process of creating an array of function + templates that are all specialized by a single integer. + This sort of thing is often useful in optimization work. + + For example, using TEMPLATE_FUNCTION_TABLE, this: + + TEMPLATE_FUNCTION_TABLE(int, Function, ( int blah, int blah ), 10) + { + return argument * argument; + } + + is equivilent to the following: + + (NOTE: the function has to be wrapped in a class due to code + generation bugs involved with directly specializing a function + based on a constant.) + + template + class FunctionWrapper + { + public: + int Function( int blah, int blah ) + { + return argument*argument; + } + } + + typedef int (*FunctionType)( int blah, int blah ); + + class FunctionName + { + public: + enum { count = 10 }; + FunctionType functions[10]; + }; + + FunctionType FunctionName::functions[] = + { + FunctionWrapper<0>::Function, + FunctionWrapper<1>::Function, + FunctionWrapper<2>::Function, + FunctionWrapper<3>::Function, + FunctionWrapper<4>::Function, + FunctionWrapper<5>::Function, + FunctionWrapper<6>::Function, + FunctionWrapper<7>::Function, + FunctionWrapper<8>::Function, + FunctionWrapper<9>::Function + }; +*/ + +PLATFORM_INTERFACE bool vtune(bool resume); + +#define TEMPLATE_FUNCTION_TABLE(RETURN_TYPE, NAME, ARGS, COUNT) \ + \ + typedef RETURN_TYPE(FASTCALL *__Type_##NAME) ARGS; \ + \ + template \ + struct __Function_##NAME { \ + static RETURN_TYPE FASTCALL Run ARGS; \ + }; \ + \ + template \ + struct __MetaLooper_##NAME : __MetaLooper_##NAME { \ + __Type_##NAME func; \ + inline __MetaLooper_##NAME() { func = __Function_##NAME::Run; } \ + }; \ + \ + template <> \ + struct __MetaLooper_##NAME<0> { \ + __Type_##NAME func; \ + inline __MetaLooper_##NAME() { func = __Function_##NAME<0>::Run; } \ + }; \ + \ + class NAME { \ + private: \ + static const __MetaLooper_##NAME m; \ + \ + public: \ + enum { count = COUNT }; \ + static const __Type_##NAME *functions; \ + }; \ + const __MetaLooper_##NAME NAME::m; \ + const __Type_##NAME *NAME::functions = (__Type_##NAME *)&m; \ + template \ + RETURN_TYPE FASTCALL __Function_##NAME::Run ARGS + +#define LOOP_INTERCHANGE(BOOLEAN, CODE) \ + if ((BOOLEAN)) { \ + CODE; \ + } else { \ + CODE; \ + } + +//----------------------------------------------------------------------------- +// Dynamic libs support +//----------------------------------------------------------------------------- +#if defined(PLATFORM_WINDOWS) + +PLATFORM_INTERFACE void *Plat_GetProcAddress(const char *pszModule, + const char *pszName); + +template +class CDynamicFunction { + public: + CDynamicFunction(const char *pszModule, const char *pszName, + FUNCPTR_TYPE pfnFallback = NULL) { + m_pfn = pfnFallback; + void *pAddr = Plat_GetProcAddress(pszModule, pszName); + if (pAddr) { + m_pfn = (FUNCPTR_TYPE)pAddr; + } + } + + operator bool() { return m_pfn != NULL; } + bool operator!() { return !m_pfn; } + operator FUNCPTR_TYPE() { return m_pfn; } + + private: + FUNCPTR_TYPE m_pfn; +}; +#endif + +//----------------------------------------------------------------------------- +// What OS version are we? +//----------------------------------------------------------------------------- +enum PlatOSVersion_t { + PLAT_OS_VERSION_UNKNOWN = -1, + + // X360-specific versions + PLAT_OS_VERSION_XBOX360 = 0, + + // PC-specific OS versions + PLAT_OS_VERSION_XP = 5, + // Vista, 7, 8, 8.1. See + // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa + PLAT_OS_VERSION_VISTA = 6, + PLAT_OS_VERSION_TEN = 10, +}; + +// Watchdog timer support. Call BeginWatchdogTimer( nn ) to kick the timer off. +// if you don't call EndWatchdogTimer within nn seconds, the program will kick +// off an exception. This is for making sure that hung dedicated servers abort +// (and restart) instead of staying hung. Calling EndWatchdogTimer more than +// once or when there is no active watchdog is fine. Only does anything under +// linux right now. It should be possible to implement this functionality in +// windows via a thread, if desired. + +#if defined(POSIX) && !defined(_PS3) + +PLATFORM_INTERFACE void BeginWatchdogTimer(int nSecs); +PLATFORM_INTERFACE void EndWatchdogTimer(void); +PLATFORM_INTERFACE void ResetBaseTime( + void); // reset plat_floattime to 0 for a subprocess +#else +FORCEINLINE void BeginWatchdogTimer(int) {} + +FORCEINLINE void EndWatchdogTimer() {} +// reset plat_floattime to 0 for a subprocess +FORCEINLINE void ResetBaseTime() {} + +#endif + +#ifdef COMPILER_MSVC +FORCEINLINE uint32 RotateBitsLeft32(uint32 nValue, int nRotateBits) { + return _rotl(nValue, nRotateBits); +} +FORCEINLINE uint64 RotateBitsLeft64(uint64 nValue, int nRotateBits) { + return _rotl64(nValue, nRotateBits); +} +FORCEINLINE uint32 RotateBitsRight32(uint32 nValue, int nRotateBits) { + return _rotr(nValue, nRotateBits); +} +FORCEINLINE uint64 RotateBitsRight64(uint64 nValue, int nRotateBits) { + return _rotr64(nValue, nRotateBits); +} +#else +FORCEINLINE uint32 RotateBitsLeft32(uint32 nValue, int nRotateBits) { + return (nValue << nRotateBits) | (nValue >> ((-nRotateBits) & 31)); +} +FORCEINLINE uint64 RotateBitsLeft64(uint64 nValue, int nRotateBits) { + return (nValue << nRotateBits) | (nValue >> ((-nRotateBits) & 63)); +} +FORCEINLINE uint32 RotateBitsRight32(uint32 nValue, int nRotateBits) { + return (nValue >> nRotateBits) | (nValue << ((-nRotateBits) & 31)); +} +FORCEINLINE uint64 RotateBitsRight64(uint64 nValue, int nRotateBits) { + return (nValue >> nRotateBits) | (nValue << ((-nRotateBits) & 63)); +} +#endif +PLATFORM_INTERFACE const char *GetPlatformSpecificFileName( + const char *FileName); + +#include "tier0/valve_on.h" + +#if defined(TIER0_DLL_EXPORT) +extern "C" int V_tier0_stricmp(const char *s1, const char *s2); +#undef stricmp +#undef strcmpi +#define stricmp(s1, s2) V_tier0_stricmp(s1, s2) +#define strcmpi(s1, s2) V_tier0_stricmp(s1, s2) +#else +int _V_stricmp(const char *s1, const char *s2); +int V_strncasecmp(const char *s1, const char *s2, int n); + +// A special high-performance case-insensitive compare function that in +// a single call distinguishes between exactly matching strings, +// strings equal in case-insensitive way, and not equal strings: +// returns 0 if strings match exactly +// returns >0 if strings match in a case-insensitive way, but do not match +// exactly returns <0 if strings do not match even in a case-insensitive way +int _V_stricmp_NegativeForUnequal(const char *s1, const char *s2); + +#undef stricmp +#undef strcmpi +#define stricmp(s1, s2) _V_stricmp(s1, s2) +#define strcmpi(s1, s2) _V_stricmp(s1, s2) +#undef strnicmp +#define strnicmp V_strncasecmp +#endif + +#endif // VPC_TIER0_PLATFORM_H_ diff --git a/public/tier0/pmelib.h b/public/tier0/pmelib.h new file mode 100644 index 0000000..1e531ed --- /dev/null +++ b/public/tier0/pmelib.h @@ -0,0 +1,169 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_PMELIB_H_ +#define VPC_TIER0_PMELIB_H_ + +#include "tier0/platform.h" + +#define VERSION "1.0.2" + +// uncomment this list to add some runtime checks +//#define PME_DEBUG + +#include "tier0/valve_off.h" +#include +#include "tier0/valve_on.h" + +using namespace std; + +// RDTSC Instruction macro +#ifdef COMPILER_MSVC64 +#define RDTSC(var) (var = __rdtsc()) +#else +#define RDTSC(var) \ + _asm RDTSC _asm mov DWORD PTR var, eax _asm mov DWORD PTR var + 4, edx +#endif + +// RDPMC Instruction macro +#ifdef COMPILER_MSVC64 +#define RDPMC(counter, var) (var = __readpmc(counter)) +#else +#define RDPMC(counter, var) \ + _asm mov ecx, counter _asm RDPMC _asm mov DWORD PTR var, \ + eax _asm mov DWORD PTR var + 4, edx +#endif + +// RDPMC Instruction macro, for performance counter 1 (ecx = 1) +#ifdef COMPILER_MSVC64 +#define RDPMC0(var) (var = __readpmc(0)) +#else +#define RDPMC0(var) \ + _asm mov ecx, 0 _asm RDPMC _asm mov DWORD PTR var, \ + eax _asm mov DWORD PTR var + 4, edx +#endif + +#ifdef COMPILER_MSVC64 +#define RDPMC1(var) (var = __readpmc(1)) +#else +#define RDPMC1(var) \ + _asm mov ecx, 1 _asm RDPMC _asm mov DWORD PTR var, \ + eax _asm mov DWORD PTR var + 4, edx +#endif + +#define EVENT_TYPE(mode) EventType##mode +#define EVENT_MASK(mode) EventMask##mode + +#include "ia32detect.h" + +enum ProcessPriority { + ProcessPriorityNormal, + ProcessPriorityHigh, +}; + +enum PrivilegeCapture { + OS_Only, // ring 0, kernel level + USR_Only, // app level + OS_and_USR, // all levels +}; + +enum CompareMethod { + CompareGreater, // + CompareLessEqual, // +}; + +enum EdgeState { + RisingEdgeDisabled, // + RisingEdgeEnabled, // +}; + +enum CompareState { + CompareDisable, // + CompareEnable, // +}; + +// Singletion Class +class PME : public ia32detect { + public: + // private: + + static PME* _singleton; + + HANDLE hFile; + bool bDriverOpen; + double m_CPUClockSpeed; + + // ia32detect detect; + HRESULT Init(); + HRESULT Close(); + + protected: + PME() { + hFile = NULL; + bDriverOpen = FALSE; + m_CPUClockSpeed = 0; + Init(); + } + + public: + static PME* Instance(); // gives back a real object + + ~PME() { Close(); } + + double GetCPUClockSpeedSlow(void); + double GetCPUClockSpeedFast(void); + + HRESULT SelectP5P6PerformanceEvent(uint32 dw_event, uint32 dw_counter, + bool b_user, bool b_kernel); + + HRESULT ReadMSR(uint32 dw_reg, int64* pi64_value); + HRESULT ReadMSR(uint32 dw_reg, uint64* pi64_value); + + HRESULT WriteMSR(uint32 dw_reg, const int64& i64_value); + HRESULT WriteMSR(uint32 dw_reg, const uint64& i64_value); + + void SetProcessPriority(ProcessPriority priority) { + switch (priority) { + case ProcessPriorityNormal: { + SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); + break; + } + case ProcessPriorityHigh: { + SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + break; + } + } + } + + //--------------------------------------------------------------------------- + // Return the family of the processor + //--------------------------------------------------------------------------- + CPUVendor GetVendor(void) { return vendor; } + + int GetProcessorFamily(void) { return version.Family; } + +#ifdef DBGFLAG_VALIDATE + void Validate(CValidator& validator, + tchar* pchName); // Validate our internal structures +#endif // DBGFLAG_VALIDATE +}; + +#include "p5p6performancecounters.h" +#include "p4performancecounters.h" +#include "k8performancecounters.h" + +enum PerfErrors { + E_UNKNOWN_CPU_VENDOR = -1, + E_BAD_COUNTER = -2, + E_UNKNOWN_CPU = -3, + E_CANT_OPEN_DRIVER = -4, + E_DRIVER_ALREADY_OPEN = -5, + E_DRIVER_NOT_OPEN = -6, + E_DISABLED = -7, + E_BAD_DATA = -8, + E_CANT_CLOSE = -9, + E_ILLEGAL_OPERATION = -10, +}; + +#endif // VPC_TIER0_PMELIB_H_ \ No newline at end of file diff --git a/public/tier0/stackstats.h b/public/tier0/stackstats.h new file mode 100644 index 0000000..d3c7870 --- /dev/null +++ b/public/tier0/stackstats.h @@ -0,0 +1,1141 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Tools for grabbing/dumping the stack at runtime + +#ifndef VPC_TIER0_STACKSTATS_H_ +#define VPC_TIER0_STACKSTATS_H_ + +#include "tier0/stacktools.h" +#include "tier0/threadtools.h" + +#if defined ENABLE_RUNTIME_STACK_TRANSLATION +#define ENABLE_STACK_STATS_GATHERING // uncomment to enable the gathering class +#endif + +#if defined ENABLE_STACK_STATS_GATHERING +#include "tier0/valve_off.h" + +#include //needed for CCallStackStatsGatherer +#include + +#include "tier0/valve_on.h" + +#define CDefaultStatsGathererAllocator std::allocator +#else +template +class CNullStatsGathererAllocator { + CNullStatsGathererAllocator(void) {} +}; +#define CDefaultStatsGathererAllocator CNullStatsGathererAllocator +#endif + +typedef size_t (*FN_DescribeStruct)(uint8 *, size_t); + +class CCallStackStatsGatherer_Standardized_t; +struct CCallStackStatsGatherer_FunctionTable_t { + void (*pfn_GetDumpInfo)(void *, const char *&, size_t &, size_t &, void *&, + size_t &, CCallStackStatsGatherer_Standardized_t *&, + size_t &); + void (*pfn_PushSubTree)(void *, + const CCallStackStatsGatherer_Standardized_t &, + const CCallStackStorage &); + void (*pfn_PopSubTree)(void *); + size_t (*pfn_DescribeCallStackStatStruct)(uint8 *, size_t); + void (*pfn_SyncMutexes)(void *, bool); + void *(*pfn_GetEntry)(void *, uint32); + void (*pfn_ApplyTreeAccessLock)(void *, bool); + void (*pfn_LockEntry)(void *, uint32, bool); +}; + +// templatized classes are fun, can't really use a base pointer effectively, so +// we'll have a translator struct and store pointers to instances of the +// translator function +class CCallStackStatsGatherer_Standardized_t { + public: + CCallStackStatsGatherer_Standardized_t(void){}; + CCallStackStatsGatherer_Standardized_t( + void *pThis, const CCallStackStatsGatherer_FunctionTable_t &FnTable) + : pGatherer(pThis), pFunctionTable(&FnTable){}; + + // go ahead and create some helper functions that are likely to be used by + // helper code + void PushSubTree( + const CCallStackStatsGatherer_Standardized_t &SubTree, + const CCallStackStorage &PushStack = CCallStackStorage()) const { + pFunctionTable->pfn_PushSubTree(pGatherer, SubTree, PushStack); + } + + inline void PopSubTree(void) const { + pFunctionTable->pfn_PopSubTree(pGatherer); + } + + void *pGatherer; + const CCallStackStatsGatherer_FunctionTable_t *pFunctionTable; +}; + +// Designed to be called by an instance of CCallStackStatsGatherer to dump it's +// data +PLATFORM_INTERFACE bool _CCallStackStatsGatherer_Internal_DumpStatsToFile( + const char *szFileName, + const CCallStackStatsGatherer_Standardized_t &StatsGatherer, + bool bAllowMemoryAllocations); + +template +class CCallStackStatsGatherer_StructAccessor_Base { + public: + CCallStackStatsGatherer_StructAccessor_Base( + const CCallStackStatsGatherer_Standardized_t &Gatherer, int iEntryIndex) + : m_Gatherer(Gatherer), m_iEntryIndex(iEntryIndex){}; + + protected: + uint32 + m_iEntryIndex; // index of the stat entry we want in the vector. Stored + // as index as the vector base address can change. + CCallStackStatsGatherer_Standardized_t + m_Gatherer; // so we can lock the vector memory in place while + // manipulating the values +}; + +class CCallStackStatsGatherer_StatMutexBase { + public: + void LockEntry(uint32 iEntryIndex, bool bLock) { + } // true to increase lock refcount, false to decrease +}; + +template // must be a power of 2 +class CCallStackStatsGatherer_StatMutexPool { + public: + void LockEntry(uint32 iEntryIndex, bool bLock) { +#if defined(ENABLE_STACK_STATS_GATHERING) + static_assert((SHAREDENTRYMUTEXES & (SHAREDENTRYMUTEXES - 1)) == + 0); // must be a power of 2 + + if (bLock) { + m_IndividualEntryMutexes[iEntryIndex & (SHAREDENTRYMUTEXES - 1)].Lock(); + } else { + m_IndividualEntryMutexes[iEntryIndex & (SHAREDENTRYMUTEXES - 1)].Unlock(); + } +#endif + } + + protected: + CThreadFastMutex m_IndividualEntryMutexes[SHAREDENTRYMUTEXES]; +}; + +// STATSTRUCT - The structure you'll use to track whatever it is you're +// tracking. CAPTUREDCALLSTACKLENGTH - The maximum length of your stack trace +// that we'll use to distinguish entries STACKACQUISITIONFUNCTION - The function +// to use to gather the current call stack. GetCallStack() is safe, +// GetCallStack_Fast() is faster, but has special requirements STATMUTEXHANDLER - +// If you want automatic mutex management for your individual entries, supply a +// handler here. You'll need to not only call GetEntry(), but lock/unlock the +//entry while accessing it. TEMPLATIZEDMEMORYALLOCATOR - We'll need to allocate +// memory, supply an allocator if you want to manage that +template , + template class TEMPLATIZEDMEMORYALLOCATOR = + CDefaultStatsGathererAllocator> +class CCallStackStatsGatherer : public STATMUTEXHANDLER { + public: +#if !defined(ENABLE_STACK_STATS_GATHERING) + CCallStackStatsGatherer(void) { + for (size_t i = 0; i != CAPTUREDCALLSTACKLENGTH; ++i) + m_SingleCallStack[i] = NULL; + } +#endif + + CCallStackStatsGatherer_StructAccessor_Base GetEntry( + void *const + CallStack[CAPTUREDCALLSTACKLENGTH]); // get the entry using some + // callstack grabbed a while ago. + // Assumes ALL invalid entries + // have been nullified + CCallStackStatsGatherer_StructAccessor_Base GetEntry( + void *const CallStack[CAPTUREDCALLSTACKLENGTH], + uint32 iValidEntries); // same as above, but does the work of nullifying + // invalid entries + CCallStackStatsGatherer_StructAccessor_Base GetEntry( + const CCallStackStorage &PushStack = + CCallStackStorage(STACKACQUISITIONFUNCTION)); + CCallStackStatsGatherer_StructAccessor_Base GetEntry( + uint32 iEntryIndex); + + // get the entry index for the caller's current call stack. Pre-nullified + // entries count as valid entries (and save re-nullification work) + uint32 GetEntryIndex( + void *const CallStack[CAPTUREDCALLSTACKLENGTH], + uint32 iValidEntries); // index is unchanging, safe to keep and use later + // (designed for exactly that purpose) + uint32 GetEntryIndex(const CCallStackStorage &PushStack = + CCallStackStorage(STACKACQUISITIONFUNCTION)); + + typedef void *(&StackReference)[CAPTUREDCALLSTACKLENGTH]; + StackReference GetCallStackForIndex(uint32 iEntryIndex) { +#if defined(ENABLE_STACK_STATS_GATHERING) + return m_StatEntries[iEntryIndex].m_CallStack; +#else + return m_SingleCallStack; +#endif + } + + void GetCallStackForIndex(uint32 iEntryIndex, + void *CallStackOut[CAPTUREDCALLSTACKLENGTH]); + + size_t NumEntries(void) const; + + bool DumpToFile(const char *szFileName, bool bAllowMemoryAllocations = true); + + static const CCallStackStatsGatherer_FunctionTable_t &GetFunctionTable(void); + + static void GetDumpInfo(void *pThis, const char *&szStructName, + size_t &iCapturedStackLength, + size_t &iEntrySizeWithStack, void *&pEntries, + size_t &iEntryCount, + CCallStackStatsGatherer_Standardized_t *&pSubTrees, + size_t &iSubTreeCount); + static void PushSubTree(void *pParent, + const CCallStackStatsGatherer_Standardized_t &SubTree, + const CCallStackStorage &PushStack); + + void PushSubTree(CCallStackStatsGatherer_Standardized_t &Parent, + const CCallStackStorage &PushStack = + CCallStackStorage(STACKACQUISITIONFUNCTION)); + + static void PopSubTree(void *pParent); + static void SyncMutexes(void *pParent, + bool bLock); // true for lock, false for unlock + static void *GetEntry(void *pParent, uint32 iEntryIndex); + static void ApplyTreeAccessLock(void *pParent, bool bLock); + static void LockEntry(void *pParent, uint32 iEntryIndex, bool bLock); + + void Reset(void); + + CCallStackStatsGatherer_Standardized_t Standardized(void); + operator CCallStackStatsGatherer_Standardized_t(void); + + const static FN_GetCallStack + StackFunction; // publish the requested acquisition function so you can + // easily key all your stack gathering to the class + // instance + const static size_t CapturedCallStackLength; + + private: +#pragma pack(push) +#pragma pack(1) + struct StackAndStats_t { + void *m_CallStack[CAPTUREDCALLSTACKLENGTH]; + STATSTRUCT m_Stats; + }; +#pragma pack(pop) + + typedef CCallStackStatsGatherer + ThisCast; + +#if defined(ENABLE_STACK_STATS_GATHERING) + + struct IndexMapKey_t { + IndexMapKey_t(void *const CallStack[CAPTUREDCALLSTACKLENGTH]) { + m_Hash = 0; + for (int i = 0; i < CAPTUREDCALLSTACKLENGTH; ++i) { + m_CallStack[i] = CallStack[i]; + m_Hash += (uintp)CallStack[i]; + } + } + bool operator<(const IndexMapKey_t &key) const { + if (m_Hash != key.m_Hash) return m_Hash < key.m_Hash; + + // only here if there's a hash match. Do a full check. Extremely likely to + // be the exact same call stack. But not 100% guaranteed. + for (int i = 0; i < CAPTUREDCALLSTACKLENGTH; ++i) { + if (m_CallStack[i] == key.m_CallStack[i]) { + continue; + } + + return m_CallStack[i] < key.m_CallStack[i]; + } + + return false; // exact same call stack + } + + uintp m_Hash; + void *m_CallStack[CAPTUREDCALLSTACKLENGTH]; + }; + + void KeepSubTree(CCallStackStatsGatherer_Standardized_t &SubTree) { + AUTO_LOCK_FM(m_SubTreeMutex); + for (typename StoredSubTreeVector_t::iterator treeIter = + m_StoredSubTrees.begin(); + treeIter != m_StoredSubTrees.end(); ++treeIter) { + if (SubTree.pGatherer == treeIter->pGatherer) return; + } + + // Warning( "Storing subtree\n" ); + + m_StoredSubTrees.push_back(SubTree); + } + + uint32 PatchInSubTrees(void *const CallStackIn[CAPTUREDCALLSTACKLENGTH], + void *CallStackOut[CAPTUREDCALLSTACKLENGTH], + uint32 iValidEntries) { + if (iValidEntries > CAPTUREDCALLSTACKLENGTH) { + iValidEntries = CAPTUREDCALLSTACKLENGTH; + } + + AUTO_LOCK_FM(m_SubTreeMutex); + if (m_PushedSubTrees.size() == 0) { + memcpy(CallStackOut, CallStackIn, sizeof(void *) * iValidEntries); + return iValidEntries; + } + + unsigned long iThreadID = ThreadGetCurrentId(); + typename PushedSubTreeVector_t::reverse_iterator treeIter; + for (treeIter = m_PushedSubTrees.rbegin(); + treeIter != m_PushedSubTrees.rend(); ++treeIter) { + if (treeIter->iThreadID == iThreadID) { + break; + } + } + + if (treeIter == m_PushedSubTrees.rend()) { + memcpy(CallStackOut, CallStackIn, sizeof(void *) * iValidEntries); + return iValidEntries; + } + + // char szTemp[4096]; + // TranslateStackInfo( CallStackIn, CAPTUREDCALLSTACKLENGTH, szTemp, sizeof( + // szTemp ), "\n\t" ); + + // Warning( "Attempting to link + // trees:\n=======================ONE=======================\n\t%s\n", szTemp + // ); TranslateStackInfo( treeIter->Stack, CAPTUREDCALLSTACKLENGTH, szTemp, + // sizeof( szTemp ), "\n\t" ); Warning( + // "=======================TWO=======================\n\t%s\n", szTemp ); + + void *pMatchAddress = + treeIter->Stack[1]; // while the first entry is where the actual push + // was made. The second entry is the first that is + // matchable in most cases + + uint32 i; + for (i = 0; i < iValidEntries; ++i) { + if (CallStackIn[i] == pMatchAddress) { + // TranslateStackInfo( CallStackIn, i, szTemp, sizeof( szTemp ), "\n\t" + // ); Warning( + // "======================MATCH======================\n\t%s\n", szTemp ); + + CallStackOut[i] = + treeIter->tree + .pGatherer; // tag this entry as leading into the sub-tree + KeepSubTree(treeIter->tree); // store the sub-tree forever + return i + 1; + } + CallStackOut[i] = CallStackIn[i]; + } + + return iValidEntries; + + // Warning( "=======================END=======================\n" ); + } + + struct StatIndex_t { + StatIndex_t(void) : m_Index((unsigned int)-1){}; + unsigned int m_Index; + }; + + typedef std::vector> + StatVector_t; + typedef std::map< + IndexMapKey_t, StatIndex_t, std::less, + TEMPLATIZEDMEMORYALLOCATOR>> + IndexMap_t; + typedef typename IndexMap_t::iterator IndexMapIter_t; + typedef typename IndexMap_t::value_type IndexMapEntry_t; + + StatVector_t m_StatEntries; + IndexMap_t m_IndexMap; + + struct PushedSubTree_t { + unsigned long iThreadID; + CCallStackStatsGatherer_Standardized_t tree; + void *Stack[CAPTUREDCALLSTACKLENGTH]; + }; + + typedef std::vector> + PushedSubTreeVector_t; + PushedSubTreeVector_t m_PushedSubTrees; + + typedef std::vector< + CCallStackStatsGatherer_Standardized_t, + TEMPLATIZEDMEMORYALLOCATOR> + StoredSubTreeVector_t; + StoredSubTreeVector_t m_StoredSubTrees; + + CThreadFastMutex m_IndexMapMutex; + CThreadFastMutex m_SubTreeMutex; + + // only for locking the memory in place, locked for write when the entry + // addresses might change. Locked for read when you've claimed you're + // manipulating a value. You're on your own for making sure two threads don't + // access the same index simultaneously + CThreadRWLock m_StatEntryLock; + +#else //#if defined( ENABLE_STACK_STATS_GATHERING ) + + STATSTRUCT m_SingleEntry; // the class is disabled, we'll always return this + // same struct + void *m_SingleCallStack[CAPTUREDCALLSTACKLENGTH]; + + static size_t NULL_DescribeCallStackStatStruct(uint8 *pDescribeWriteBuffer, + size_t iDescribeMaxLength) { + return 0; + } + +#endif //#if defined( ENABLE_STACK_STATS_GATHERING ) +}; + +template class TEMPLATIZEDMEMORYALLOCATOR> +const FN_GetCallStack CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::StackFunction = + STACKACQUISITIONFUNCTION; + +template class TEMPLATIZEDMEMORYALLOCATOR> +const size_t CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::CapturedCallStackLength = + CAPTUREDCALLSTACKLENGTH; + +#if defined(ENABLE_STACK_STATS_GATHERING) + +class CallStackStatStructDescFuncs; +PLATFORM_INTERFACE size_t _CCallStackStatsGatherer_Write_FieldDescriptions( + CallStackStatStructDescFuncs *pFieldDescriptions, uint8 *pWriteBuffer, + size_t iWriteBufferSize); +// PLATFORM_INTERFACE size_t _CCallStackStatsGatherer_Write_FieldMergeScript( +// CallStackStatStructDescFuncs *pFieldDescriptions, +// CallStackStatStructDescFuncs::MergeScript_Language scriptMergeLanguage, uint8 +// *pWriteBuffer, size_t iWriteBufferSize ); + +#define DECLARE_CALLSTACKSTATSTRUCT() \ + static const char *STATSTRUCTSTRINGNAME; \ + static size_t DescribeCallStackStatStruct(uint8 *pDescribeWriteBuffer, \ + size_t iDescribeMaxLength); + +#define BEGIN_STATSTRUCTDESCRIPTION(className) \ + const char *className::STATSTRUCTSTRINGNAME = #className; \ + size_t className::DescribeCallStackStatStruct(uint8 *pDescribeWriteBuffer, \ + size_t iDescribeMaxLength) { \ + size_t iWroteBytes = 0; +#define END_STATSTRUCTDESCRIPTION() \ + return iWroteBytes; \ + } + +#define DECLARE_CALLSTACKSTATSTRUCT_FIELDDESCRIPTION() \ + static CallStackStatStructDescFuncs *GetStatStructFieldDescriptions(void); + +#define BEGIN_STATSTRUCTFIELDDESCRIPTION(className) \ + CallStackStatStructDescFuncs *className::GetStatStructFieldDescriptions( \ + void) { \ + typedef className ThisStruct; \ + CallStackStatStructDescFuncs *_pHeadLinkage = NULL; \ + CallStackStatStructDescFuncs **_pLinkageHelperVar = &_pHeadLinkage; + +#define _DEFINE_STATSTRUCTFIELD_VARNAME(varName, fieldName, fieldStruct, \ + fieldParmsInParentheses) \ + static fieldStruct varName##_desc##fieldParmsInParentheses; \ + varName##_desc.m_szFieldName = #fieldName; \ + varName##_desc.m_iFieldOffset = (size_t)(&((ThisStruct *)NULL)->fieldName); \ + varName##_desc.m_pNext = NULL; \ + *_pLinkageHelperVar = &varName##_desc; \ + _pLinkageHelperVar = &varName##_desc.m_pNext; + +#define DEFINE_STATSTRUCTFIELD(fieldName, fieldStruct, \ + fieldParmsInParentheses) \ + _DEFINE_STATSTRUCTFIELD_VARNAME(fieldName, fieldName, fieldStruct, \ + fieldParmsInParentheses) +#define DEFINE_STATSTRUCTFIELD_ARRAYENTRY(arrayName, arrayIndex, fieldStruct, \ + fieldParmsInParentheses) \ + _DEFINE_STATSTRUCTFIELD_VARNAME(arrayName##_##arrayIndex, \ + arrayName##[##arrayIndex##], fieldStruct, \ + fieldParmsInParentheses) + +#define END_STATSTRUCTFIELDDESCRIPTION() \ + static CallStackStatStructDescFuncs *s_pHeadStruct = _pHeadLinkage; \ + return s_pHeadStruct; \ + } + +#define WRITE_STATSTRUCT_FIELDDESCRIPTION() \ + iWroteBytes += _CCallStackStatsGatherer_Write_FieldDescriptions( \ + GetStatStructFieldDescriptions(), pDescribeWriteBuffer + iWroteBytes, \ + iDescribeMaxLength - iWroteBytes); +//#define WRITE_STATSTRUCT_FIELDMERGESCRIPT( scriptMergeLanguage ) iWroteBytes +//+= _CCallStackStatsGatherer_Write_FieldMergeScript( +//GetStatStructFieldDescriptions(), +//CallStackStatStructDescFuncs::scriptMergeLanguage, pDescribeWriteBuffer + +//iWroteBytes, iDescribeMaxLength - iWroteBytes ); + +#else //#if defined( ENABLE_STACK_STATS_GATHERING ) + +#define DECLARE_CALLSTACKSTATSTRUCT() +#define BEGIN_STATSTRUCTDESCRIPTION(className) +#define END_STATSTRUCTDESCRIPTION() + +#define DECLARE_CALLSTACKSTATSTRUCT_FIELDDESCRIPTION() +#define BEGIN_STATSTRUCTFIELDDESCRIPTION(className) +#define DEFINE_STATSTRUCTFIELD(fieldName, fieldStruct, fieldParmsInParentheses) +#define END_STATSTRUCTFIELDDESCRIPTION() + +#define WRITE_STATSTRUCT_FIELDDESCRIPTION() +//#define WRITE_STATSTRUCT_FIELDMERGESCRIPT( scriptMergeLanguage ) + +#endif //#if defined( ENABLE_STACK_STATS_GATHERING ) + +template class TEMPLATIZEDMEMORYALLOCATOR> +CCallStackStatsGatherer_StructAccessor_Base CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>:: + GetEntry( + void *const + CallStack[CAPTUREDCALLSTACKLENGTH]) // get the entry using some + // callstack grabbed a while + // ago. Assumes ALL invalid + // entries have been nullified +{ +#if defined(ENABLE_STACK_STATS_GATHERING) + return GetEntry(GetEntryIndex(CallStack)); +#else + return CCallStackStatsGatherer_StructAccessor_Base(Standardized(), + 0); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +CCallStackStatsGatherer_StructAccessor_Base CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>:: + GetEntry(void *const CallStack[CAPTUREDCALLSTACKLENGTH], + uint32 iValidEntries) // same as above, but does the work of + // nullifying invalid entries +{ +#if defined(ENABLE_STACK_STATS_GATHERING) + void *CleanedCallStack[CAPTUREDCALLSTACKLENGTH]; + size_t i; + for (i = 0; i < CAPTUREDCALLSTACKLENGTH; ++i) { + CleanedCallStack[i] = CallStack[i]; + } + + for (; i < CAPTUREDCALLSTACKLENGTH; ++i) { + CleanedCallStack[i] = NULL; + } + return GetEntry(GetEntryIndex(CleanedCallStack)); +#else + return CCallStackStatsGatherer_StructAccessor_Base(Standardized(), + 0); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +CCallStackStatsGatherer_StructAccessor_Base CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, + TEMPLATIZEDMEMORYALLOCATOR>::GetEntry(const CCallStackStorage &PushStack) { +#if defined(ENABLE_STACK_STATS_GATHERING) + static_assert(CAPTUREDCALLSTACKLENGTH <= std::size(PushStack.pStack)); + return GetEntry(GetEntryIndex(PushStack.pStack, PushStack.iValidEntries)); +#else + return CCallStackStatsGatherer_StructAccessor_Base(Standardized(), + 0); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +uint32 CCallStackStatsGatherer:: + GetEntryIndex(const CCallStackStorage &PushStack) { +#if defined(ENABLE_STACK_STATS_GATHERING) + static_assert(CAPTUREDCALLSTACKLENGTH <= std::size(PushStack.pStack)); + return GetEntryIndex(PushStack.pStack, PushStack.iValidEntries); +#else + return 0; +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +CCallStackStatsGatherer_StructAccessor_Base CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::GetEntry(uint32 + iEntryIndex) { +#if defined(ENABLE_STACK_STATS_GATHERING) + return CCallStackStatsGatherer_StructAccessor_Base(Standardized(), + iEntryIndex); +#else + return CCallStackStatsGatherer_StructAccessor_Base(Standardized(), + 0); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +uint32 CCallStackStatsGatherer:: + GetEntryIndex( + void *const CallStack[CAPTUREDCALLSTACKLENGTH], + uint32 iValidEntries) // index is unchanging, safe to keep and use + // later (designed for exactly that purpose) +{ +#if defined(ENABLE_STACK_STATS_GATHERING) + AUTO_LOCK_FM(m_IndexMapMutex); + std::pair indexMapIter; + void *PatchedStack[CAPTUREDCALLSTACKLENGTH]; + + // if we have a sub-tree. We'll be splicing it into the original call stack as + // if it were a return address. Then patching that when we interpret the + // results later A stack with a sub-tree along the line is treated as + // distinctly different than one without a sub-tree + iValidEntries = PatchInSubTrees(CallStack, PatchedStack, iValidEntries); + + Assert(iValidEntries <= CAPTUREDCALLSTACKLENGTH); + + for (int i = iValidEntries; i < CAPTUREDCALLSTACKLENGTH; ++i) { + PatchedStack[i] = NULL; + } + + indexMapIter = m_IndexMap.insert( + IndexMapEntry_t(IndexMapKey_t(PatchedStack), StatIndex_t())); + + if (indexMapIter.first->second.m_Index == -1) { + m_StatEntryLock.LockForWrite(); + indexMapIter.first->second.m_Index = (unsigned int)m_StatEntries.size(); + + m_StatEntries.push_back(StackAndStats_t()); + memcpy(m_StatEntries[indexMapIter.first->second.m_Index].m_CallStack, + PatchedStack, sizeof(void *) * CAPTUREDCALLSTACKLENGTH); + m_StatEntryLock.UnlockWrite(); + } + + return indexMapIter.first->second.m_Index; +#else + return 0; +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer:: + GetCallStackForIndex(uint32 iEntryIndex, + void *CallStackOut[CAPTUREDCALLSTACKLENGTH]) { +#if defined(ENABLE_STACK_STATS_GATHERING) + m_StatEntryLock.LockForRead(); + for (size_t i = 0; i != CAPTUREDCALLSTACKLENGTH; ++i) { + CallStackOut[i] = m_StatEntries[iEntryIndex].m_CallStack[i]; + } + m_StatEntryLock.UnlockRead(); +#else + for (size_t i = 0; i != CAPTUREDCALLSTACKLENGTH; ++i) CallStackOut[i] = NULL; +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +size_t CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::NumEntries(void) const { +#if defined(ENABLE_STACK_STATS_GATHERING) + return m_StatEntries.size(); +#else + return 0; +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +bool CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, + TEMPLATIZEDMEMORYALLOCATOR>::DumpToFile(const char *szFileName, + bool bAllowMemoryAllocations) { +#if defined(ENABLE_STACK_STATS_GATHERING) + CCallStackStatsGatherer_Standardized_t StandardThis = Standardized(); + SyncMutexes(this, true); + bool bRetVal = _CCallStackStatsGatherer_Internal_DumpStatsToFile( + szFileName, StandardThis, bAllowMemoryAllocations); + SyncMutexes(this, false); + return bRetVal; +#else + return false; +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +const CCallStackStatsGatherer_FunctionTable_t &CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::GetFunctionTable(void) { + static CCallStackStatsGatherer_FunctionTable_t retVal = { + GetDumpInfo, + PushSubTree, + PopSubTree, +#if defined(ENABLE_STACK_STATS_GATHERING) + STATSTRUCT::DescribeCallStackStatStruct, +#else + NULL_DescribeCallStackStatStruct, +#endif + SyncMutexes, + GetEntry, + ApplyTreeAccessLock, + LockEntry + }; + + return retVal; +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer:: + GetDumpInfo(void *pThis, const char *&szStructName, + size_t &iCapturedStackLength, size_t &iEntrySizeWithStack, + void *&pEntries, size_t &iEntryCount, + CCallStackStatsGatherer_Standardized_t *&pSubTrees, + size_t &iSubTreeCount) { + ThisCast *pThisCast = (ThisCast *)pThis; + iCapturedStackLength = CAPTUREDCALLSTACKLENGTH; + iEntrySizeWithStack = sizeof( + CCallStackStatsGatherer::StackAndStats_t); + +#if defined(ENABLE_STACK_STATS_GATHERING) + szStructName = STATSTRUCT::STATSTRUCTSTRINGNAME; + iEntryCount = pThisCast->m_StatEntries.size(); + pEntries = iEntryCount > 0 ? &pThisCast->m_StatEntries[0] : NULL; + iSubTreeCount = pThisCast->m_StoredSubTrees.size(); + pSubTrees = iSubTreeCount > 0 ? &pThisCast->m_StoredSubTrees[0] : NULL; +#else + szStructName = ""; + iEntryCount = 0; + pEntries = NULL; + iSubTreeCount = 0; + pSubTrees = NULL; +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer:: + PushSubTree(void *pParent, + const CCallStackStatsGatherer_Standardized_t &SubTree, + const CCallStackStorage &PushStack) { +#if defined(ENABLE_STACK_STATS_GATHERING) + ThisCast *pParentCast = (ThisCast *)pParent; + PushedSubTree_t pushVal; + pushVal.iThreadID = ThreadGetCurrentId(); + pushVal.tree = SubTree; + + memcpy( + pushVal.Stack, PushStack.pStack, + MIN(CAPTUREDCALLSTACKLENGTH, PushStack.iValidEntries) * sizeof(void *)); + + for (int i = PushStack.iValidEntries; i < CAPTUREDCALLSTACKLENGTH; ++i) { + pushVal.Stack[i] = NULL; + } + + pParentCast->m_SubTreeMutex.Lock(); + pParentCast->m_PushedSubTrees.push_back(pushVal); + pParentCast->m_SubTreeMutex.Unlock(); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer:: + PushSubTree(CCallStackStatsGatherer_Standardized_t &Parent, + const CCallStackStorage &PushStack) { +#if defined(ENABLE_STACK_STATS_GATHERING) + CCallStackStatsGatherer_Standardized_t StandardThis = Standardized(); + Parent.PushSubTree(StandardThis, PushStack); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::PopSubTree(void *pParent) { +#if defined(ENABLE_STACK_STATS_GATHERING) + ThisCast *pParentCast = (ThisCast *)pParent; + pParentCast->m_SubTreeMutex.Lock(); + unsigned long iThreadID = ThreadGetCurrentId(); + + for (typename PushedSubTreeVector_t::reverse_iterator treeIter = + pParentCast->m_PushedSubTrees.rbegin(); + treeIter != pParentCast->m_PushedSubTrees.rend(); ++treeIter) { + if (treeIter->iThreadID == iThreadID) { + ++treeIter; //[24.4.1/1] &*(reverse_iterator(i)) == &*(i - 1) + typename PushedSubTreeVector_t::iterator eraseIter = treeIter.base(); + pParentCast->m_PushedSubTrees.erase(eraseIter); + break; + } + } + + pParentCast->m_SubTreeMutex.Unlock(); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer:: + SyncMutexes(void *pParent, bool bLock) // true for lock, false for unlock +{ +#if defined(ENABLE_STACK_STATS_GATHERING) + ThisCast *pParentCast = (ThisCast *)pParent; + if (bLock) { + pParentCast->m_IndexMapMutex.Lock(); + pParentCast->m_SubTreeMutex.Lock(); + + for (typename StoredSubTreeVector_t::iterator treeIter = + pParentCast->m_StoredSubTrees.begin(); + treeIter != pParentCast->m_StoredSubTrees.end(); ++treeIter) { + treeIter->pFunctionTable->pfn_SyncMutexes(treeIter->pGatherer, true); + } + } else { + for (typename StoredSubTreeVector_t::iterator treeIter = + pParentCast->m_StoredSubTrees.begin(); + treeIter != pParentCast->m_StoredSubTrees.end(); ++treeIter) { + treeIter->pFunctionTable->pfn_SyncMutexes(treeIter->pGatherer, false); + } + + pParentCast->m_IndexMapMutex.Unlock(); + pParentCast->m_SubTreeMutex.Unlock(); + } +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void *CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, + TEMPLATIZEDMEMORYALLOCATOR>::GetEntry(void *pParent, uint32 iEntryIndex) { + ThisCast *pParentCast = (ThisCast *)pParent; +#if defined(ENABLE_STACK_STATS_GATHERING) + return &pParentCast->m_StatEntries[iEntryIndex].m_Stats; +#else + return &pParentCast->m_SingleEntry; +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, + TEMPLATIZEDMEMORYALLOCATOR>::ApplyTreeAccessLock(void *pParent, + bool bLock) { +#if defined(ENABLE_STACK_STATS_GATHERING) + ThisCast *pParentCast = (ThisCast *)pParent; + if (bLock) { + pParentCast->m_StatEntryLock.LockForRead(); + } else { + pParentCast->m_StatEntryLock.UnlockRead(); + } +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::LockEntry(void *pParent, + uint32 iEntryIndex, + bool bLock) { +#if defined(ENABLE_STACK_STATS_GATHERING) + ThisCast *pParentCast = (ThisCast *)pParent; + pParentCast->STATMUTEXHANDLER::LockEntry(iEntryIndex, bLock); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +void CCallStackStatsGatherer::Reset(void) { +#if defined(ENABLE_STACK_STATS_GATHERING) + m_StatEntryLock.LockForWrite(); + m_IndexMapMutex.Lock(); + m_SubTreeMutex.Lock(); + + m_StatEntries.clear(); + m_IndexMap.clear(); + m_StoredSubTrees.clear(); + + m_SubTreeMutex.Unlock(); + m_IndexMapMutex.Unlock(); + m_StatEntryLock.UnlockWrite(); +#endif +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +CCallStackStatsGatherer:: +operator CCallStackStatsGatherer_Standardized_t(void) { + return CCallStackStatsGatherer_Standardized_t(this, GetFunctionTable()); +} + +template class TEMPLATIZEDMEMORYALLOCATOR> +CCallStackStatsGatherer_Standardized_t CCallStackStatsGatherer< + STATSTRUCT, CAPTUREDCALLSTACKLENGTH, STACKACQUISITIONFUNCTION, + STATMUTEXHANDLER, TEMPLATIZEDMEMORYALLOCATOR>::Standardized(void) { + return CCallStackStatsGatherer_Standardized_t(this, GetFunctionTable()); +} + +class PLATFORM_CLASS CallStackStatStructDescFuncs { + public: + // description file format + // 1 byte version per entry. Assuming that the field will get more descriptive + // over time, reserving this now. + virtual size_t DescribeField(uint8 *pDescribeWriteBuffer, + size_t iDescribeMaxLength) = 0; + + enum MergeScript_Language { + SSMSL_Squirrel, // Only support squirrel for now, theoretically expandable + // to any vscript supported language + }; + +#if 0 // embedded script handling not ready yet + // this is expected to write a piece of script code into the body of a + // function that will merge two values of this type The function will have + // two parameters, "mergeTo", and "mergeFrom". Both are tables with your + // field defined by name So, an example to merge an integer count defined + // as "foo" in the stack struct would look like this "mergeTo.foo += + // mergeFrom.foo\n" + virtual size_t DescribeMergeOperation( MergeScript_Language scriptLanguage, uint8 *pDescribeWriteBuffer, size_t iDescribeMaxLength ) = 0; +#endif + + // reserve your description field versions here to avoid stomping others + enum DescribeFieldVersions_t { + DFV_BasicStatStructFieldTypes_t, // Format: 1 byte + // BasicStatStructFieldTypes_t type, 4 byte + // field offset, null terminated string + // field name + }; + + const char *m_szFieldName; + size_t m_iFieldOffset; + CallStackStatStructDescFuncs + *m_pNext; // needed for how the description macros are laid out. Couldn't + // figure out a way to store the static struct instances as a + // static array +}; + +enum StatStructDescription_LumpID { + SSDLID_UNKNOWN, + SSDLID_STATICINTERPRETER, + SSDLID_FIELDDESC, + SSDLID_EMBEDDEDSCRIPT, + + SSDLID_COUNT, + SSDLID_FORCE_UINT32 = 0xFFFFFFFF, +}; + +enum BasicStatStructFieldTypes_t { + BSSFT_UNKNOWN, + BSSFT_BOOL, + BSSFT_INT8, + BSSFT_UINT8, + BSSFT_INT16, + BSSFT_UINT16, + BSSFT_INT32, + BSSFT_UINT32, + BSSFT_INT64, + BSSFT_UINT64, + BSSFT_FLOAT, + BSSFT_DOUBLE, + BSSFT_VECTOR2D, + BSSFT_VECTOR3D, + BSSFT_VECTOR4D, + + BSSFT_COUNT, +}; + +#define BSSFT_INT (sizeof(int) == sizeof(int32) ? BSSFT_INT32 : BSSFT_INT64) +#define BSSFT_UINT \ + (sizeof(unsigned int) == sizeof(uint32) ? BSSFT_UINT32 : BSSFT_UINT64) +#define BSSFT_SIZE_T \ + (sizeof(size_t) == sizeof(uint32) ? BSSFT_UINT32 : BSSFT_UINT64) + +enum BasicStatStructFieldCombineMethods_t { + BSSFCM_UNKNOWN, + BSSFCM_CUSTOM, // rely on some outside handler + BSSFCM_ADD, // add the values + // BSSFCM_SUBTRACT, //what would subtract even mean? which one from which? + BSSFCM_MAX, // keep max value + BSSFCM_MIN, // keep min value + BSSFCM_AND, // &= , Non-integer behavior undefined + BSSFCM_OR, // |= , Non-integer behavior undefined + BSSFCM_XOR, // ^= , Non-integer behavior undefined + /*BSSFCM_LIST, //keep a list of each value (probably complicated)*/ + + BSSFCM_COUNT, +}; + +class PLATFORM_CLASS BasicStatStructFieldDesc + : public CallStackStatStructDescFuncs { + public: + BasicStatStructFieldDesc(BasicStatStructFieldTypes_t type, + BasicStatStructFieldCombineMethods_t combineMethod) + : m_Type(type), m_Combine(combineMethod){}; + size_t DescribeField(uint8 *pDescribeWriteBuffer, size_t iDescribeMaxLength); +#if 0 // embedded script handling not ready yet + size_t DescribeMergeOperation( MergeScript_Language scriptLanguage, uint8 *pDescribeWriteBuffer, size_t iDescribeMaxLength ); +#endif + + BasicStatStructFieldTypes_t m_Type; + BasicStatStructFieldCombineMethods_t m_Combine; +}; + +// struct is locked in place while you're holding onto one of these. Get a base, +// then create one of these from it +template +class CCallStackStatsGatherer_StructAccessor_AutoLock + : CCallStackStatsGatherer_StructAccessor_Base { + public: + CCallStackStatsGatherer_StructAccessor_AutoLock( + CCallStackStatsGatherer_StructAccessor_Base ©From) + : CCallStackStatsGatherer_StructAccessor_Base(copyFrom) { + this->m_Gatherer.pFunctionTable->pfn_ApplyTreeAccessLock( + this->m_Gatherer.pGatherer, true); + this->m_Gatherer.pFunctionTable->pfn_LockEntry(this->m_Gatherer.pGatherer, + this->m_iEntryIndex, true); + this->m_pStruct = + (STATSTRUCT *)this->m_Gatherer.pFunctionTable->pfn_GetEntry( + this->m_Gatherer.pGatherer, this->m_iEntryIndex); + } + + ~CCallStackStatsGatherer_StructAccessor_AutoLock(void) { + this->m_Gatherer.pFunctionTable->pfn_LockEntry(this->m_Gatherer.pGatherer, + this->m_iEntryIndex, false); + this->m_Gatherer.pFunctionTable->pfn_ApplyTreeAccessLock( + this->m_Gatherer.pGatherer, false); + } + + STATSTRUCT *operator->() { return this->m_pStruct; } + + STATSTRUCT *GetStruct( + void) // do not hold this pointer outside the lock period + { + return this->m_pStruct; + } + + protected: + STATSTRUCT *m_pStruct; +}; + +// struct is locked in place only between Lock() and paired Unlock() calls. Get +// a base, then create one of these from it. It's safe to hold onto this for an +// extended period of time. The entry index is unchanging in the gatherer tree. +template +class CCallStackStatsGatherer_StructAccessor_Manual + : CCallStackStatsGatherer_StructAccessor_Base { + public: + CCallStackStatsGatherer_StructAccessor_Manual( + CCallStackStatsGatherer_StructAccessor_Base ©From) + : CCallStackStatsGatherer_StructAccessor_Base(copyFrom), + m_pStruct(NULL) {} + + STATSTRUCT *operator->() { + return this->m_pStruct; // NULL while entry is not locked. + } + + STATSTRUCT *GetStruct( + void) // do not hold this pointer outside the lock period + { + return this->m_pStruct; // NULL while entry is not locked. + } + + void Lock(void) { + this->m_Gatherer.pFunctionTable->pfn_ApplyTreeAccessLock( + this->m_Gatherer.pGatherer, true); + this->m_Gatherer.pFunctionTable->pfn_LockEntry(this->m_Gatherer.pGatherer, + this->m_iEntryIndex, true); + this->m_pStruct = + (STATSTRUCT *)this->m_Gatherer.pFunctionTable->pfn_GetEntry( + this->m_Gatherer.pGatherer, this->m_iEntryIndex); + } + + void Unlock(void) { + this->m_pStruct = NULL; + this->m_Gatherer.pFunctionTable->pfn_LockEntry(this->m_Gatherer.pGatherer, + this->m_iEntryIndex, false); + this->m_Gatherer.pFunctionTable->pfn_ApplyTreeAccessLock( + this->m_Gatherer.pGatherer, false); + } + + protected: + STATSTRUCT *m_pStruct; +}; + +class CCallStackStats_PushSubTree_AutoPop { + public: + CCallStackStats_PushSubTree_AutoPop( + const CCallStackStatsGatherer_Standardized_t &Parent, + const CCallStackStatsGatherer_Standardized_t &Child, + const CCallStackStorage &PushStack = CCallStackStorage()) + : m_PopFrom(Parent) { + Parent.pFunctionTable->pfn_PushSubTree(Parent.pGatherer, Child, PushStack); + } + ~CCallStackStats_PushSubTree_AutoPop(void) { m_PopFrom.PopSubTree(); } + + CCallStackStatsGatherer_Standardized_t m_PopFrom; +}; + +#endif // VPC_TIER0_STACKSTATS_H_ diff --git a/public/tier0/stacktools.h b/public/tier0/stacktools.h new file mode 100644 index 0000000..424c778 --- /dev/null +++ b/public/tier0/stacktools.h @@ -0,0 +1,191 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Tools for grabbing/dumping the stack at runtime + +#ifndef VPC_TIER0_STACKTOOLS_H_ +#define VPC_TIER0_STACKTOOLS_H_ + +#include "tier0/platform.h" + +#if (defined(PLATFORM_WINDOWS) || defined(PLATFORM_X360)) && \ + !defined(STEAM) && !defined(_CERT) && \ + defined(TCHAR_IS_CHAR) // designed for windows/x360, not built/tested with + // wide characters, not intended for release builds + // (but probably wouldn't damage anything) +#define ENABLE_RUNTIME_STACK_TRANSLATION // uncomment to enable runtime stack + // translation tools. All of which use + // on-demand loading of necessary + // dll's and pdb's +#endif + +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) +//#define ENABLE_THREAD_PARENT_STACK_TRACING 1 //uncomment to actually enable +// tracking stack traces from threads and jobs to their parent thread. Must also +// define THREAD_PARENT_STACK_TRACE_SUPPORTED in threadtools.h +#if defined(ENABLE_THREAD_PARENT_STACK_TRACING) +#define THREAD_PARENT_STACK_TRACE_LENGTH 32 +#endif +#endif + +PLATFORM_INTERFACE int GetCallStack(void **pReturnAddressesOut, int iArrayCount, + int iSkipCount); + +// ONLY WORKS IF THE CRAWLED PORTION OF THE STACK DISABLES FRAME POINTER +// OMISSION (/Oy-) "vpc /nofpo" +PLATFORM_INTERFACE int GetCallStack_Fast(void **pReturnAddressesOut, + int iArrayCount, int iSkipCount); + +using FN_GetCallStack = int (*)(void **pReturnAddressesOut, int iArrayCount, + int iSkipCount); + +// where we'll find our PDB's for win32. +PLATFORM_INTERFACE void SetStackTranslationSymbolSearchPath( + const char *szSemicolonSeparatedList = NULL); +PLATFORM_INTERFACE void StackToolsNotify_LoadedLibrary(const char *szLibName); + +// maximum output sample "tier0.dll!TranslateStackInfo - +// u:\Dev\L4D\src\tier0\stacktools.cpp(162) + 4 bytes" +enum TranslateStackInfo_StyleFlags_t { + TSISTYLEFLAG_NONE = 0, + TSISTYLEFLAG_MODULENAME = + (1 << 0), // start with module Sample: "tier0.dll!" + TSISTYLEFLAG_SYMBOLNAME = (1 << 1), // include the symbol name + // Sample: "TranslateStackInfo" + TSISTYLEFLAG_FULLPATH = + (1 << 2), // include full path Sample: + // "u:\Dev\L4D\src\tier0\stacktools.cpp" + TSISTYLEFLAG_SHORTPATH = (1 << 3), // only include 2 directories Sample: + // "\src\tier0\stacktools.cpp" + TSISTYLEFLAG_LINE = + (1 << 4), // file line number Sample: + // "(162)" + TSISTYLEFLAG_LINEANDOFFSET = + (1 << 5), // file line + offset Sample: "(162) + 4 bytes" + TSISTYLEFLAG_LAST = TSISTYLEFLAG_LINEANDOFFSET, + TSISTYLEFLAG_DEFAULT = (TSISTYLEFLAG_MODULENAME | TSISTYLEFLAG_SYMBOLNAME | + TSISTYLEFLAG_FULLPATH | + TSISTYLEFLAG_LINEANDOFFSET), // produces sample above +}; + +// Generates a formatted list of function information, returns number of +// translated entries On 360 this generates a string that can be decoded by +// VXConsole in print functions. Optimal path for translation because it's one +// way. Other paths require multiple transactions. +PLATFORM_INTERFACE int TranslateStackInfo( + const void *const *pCallStack, int iCallStackCount, tchar *szOutput, + int iOutBufferSize, const tchar *szEntrySeparator, + TranslateStackInfo_StyleFlags_t style = TSISTYLEFLAG_DEFAULT); + +PLATFORM_INTERFACE void PreloadStackInformation( + void *const *pAddresses, + int iAddressCount); // caches data and reduces communication with VXConsole + // to speed up 360 decoding when using any of the + // Get***FromAddress() functions. Nop on PC. +PLATFORM_INTERFACE bool GetFileAndLineFromAddress( + const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, + uint32 &iLineNumberOut, uint32 *pDisplacementOut = NULL); +PLATFORM_INTERFACE bool GetSymbolNameFromAddress( + const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, + uint64 *pDisplacementOut = NULL); +PLATFORM_INTERFACE bool GetModuleNameFromAddress(const void *pAddress, + tchar *pModuleNameOut, + int iMaxModuleNameLength); + +class PLATFORM_CLASS + CCallStackStorage // a helper class to grab a stack trace as close to the + // leaf code surface as possible, then pass it on to + // deeper functions intact with less unpredictable + // inlining pollution +{ + public: + CCallStackStorage(FN_GetCallStack GetStackFunction = GetCallStack, + uint32 iSkipCalls = 0); + CCallStackStorage(const CCallStackStorage ©From) { + iValidEntries = copyFrom.iValidEntries; + memcpy(pStack, copyFrom.pStack, sizeof(void *) * copyFrom.iValidEntries); + } + + void *pStack[128]; // probably too big, possibly too small for some + // applications. Don't want to spend the time figuring out + // how to generalize this without templatizing pollution + // or mallocs + uint32 iValidEntries; +}; + +// Hold onto one of these to denote the top of a functional stack trace. Also +// allows us to string together threads to their parents +class PLATFORM_CLASS CStackTop_Base { + protected: +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) + CStackTop_Base *m_pPrevTop; + void *m_pStackBase; + void *m_pReplaceAddress; + + void *const *m_pParentStackTrace; + int m_iParentStackTraceLength; +#endif +}; + +// makes a copy of the parent stack +class PLATFORM_CLASS CStackTop_CopyParentStack : public CStackTop_Base { + public: + CStackTop_CopyParentStack(void *const *pParentStackTrace, + int iParentStackTraceLength); + ~CStackTop_CopyParentStack(void); +}; + +// just references the parent stack. Assuming that you'll keep that memory +// around as long as you're keeping this Stack Top marker. +class PLATFORM_CLASS CStackTop_ReferenceParentStack : public CStackTop_Base { + public: + CStackTop_ReferenceParentStack(void *const *pParentStackTrace = NULL, + int iParentStackTraceLength = 0); + ~CStackTop_ReferenceParentStack(void); + void ReleaseParentStackReferences( + void); // in case you need to delete the parent stack trace before this + // class goes out of scope +}; + +// Encodes data so that every byte's most significant bit is a 1. Ensuring no +// null terminators. This puts the encoded data in the 128-255 value range. +// Leaving all standard ascii characters for control. Returns string length (not +// including the written null terminator as is standard). Or if the buffer is +// too small. Returns negative of necessary buffer size (including room needed +// for null terminator) +PLATFORM_INTERFACE int EncodeBinaryToString(const void *pToEncode, + int iDataLength, char *pEncodeOut, + int iEncodeBufferSize); + +// Decodes a string produced by EncodeBinaryToString(). Safe to decode in place +// if you don't mind trashing your string, binary byte count always less than +// string byte count. Returns: +// >= 0 is the decoded data size +// INT_MIN (most negative value possible) indicates an improperly formatted +// string (not our data) all other negative values are the negative of how +// much dest buffer size is necessary. +PLATFORM_INTERFACE int DecodeBinaryFromString(const char *pString, + void *pDestBuffer, + int iDestBufferSize, + char **ppParseFinishOut = NULL); + +// 360<->VXConsole specific communication definitions +#define XBX_CALLSTACKDECODEPREFIX ":CSDECODE[" + +enum StackTranslation_BinaryHandler_Command_t { + ST_BHC_LOADEDLIBARY, + ST_BHC_GETTRANSLATIONINFO, +}; + +#pragma pack(push) +#pragma pack(1) +struct FullStackInfo_t { + const void *pAddress; + char szModuleName[24]; + char szFileName[MAX_PATH / 2]; + char szSymbol[64]; + uint32 iLine; + uint32 iSymbolOffset; +}; +#pragma pack(pop) + +#endif // VPC_TIER0_STACKTOOLS_H_ diff --git a/public/tier0/threadtools.h b/public/tier0/threadtools.h new file mode 100644 index 0000000..c482ad6 --- /dev/null +++ b/public/tier0/threadtools.h @@ -0,0 +1,2576 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A collection of utility classes to simplify thread handling, and as +// much as possible contain portability problems. Here avoiding including +// windows.h. + +#ifndef VPC_TIER0_THREADTOOLS_H_ +#define VPC_TIER0_THREADTOOLS_H_ + +#include + +#include "tier0/platform.h" +#include "tier0/dbg.h" + +#if defined(POSIX) && !defined(_PS3) && !defined(_X360) +#include +#include +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 0x00000102 +#define WAIT_FAILED -1 +#define THREAD_PRIORITY_HIGHEST 2 +#endif + +#if defined(_PS3) +#include +#include +#include +#include +#endif + +#ifdef OSX +// Add some missing defines +#define PTHREAD_MUTEX_TIMED_NP PTHREAD_MUTEX_NORMAL +#define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE +#define PTHREAD_MUTEX_ERRORCHECK_NP PTHREAD_MUTEX_ERRORCHECK +#define PTHREAD_MUTEX_ADAPTIVE_NP 3 +#endif + +#ifdef _PS3 +#define PS3_SYS_PPU_THREAD_COMMON_STACK_SIZE (256 * 1024) +#endif + +#ifdef COMPILER_MSVC64 +#include +#endif + +// #define THREAD_PROFILER 1 + +#define THREAD_MUTEX_TRACING_SUPPORTED +#if defined(_WIN32) && defined(_DEBUG) +#define THREAD_MUTEX_TRACING_ENABLED +#endif + +#ifdef _WIN32 +typedef void *HANDLE; +#endif + +// maximum number of threads that can wait on one object +#define CTHREADEVENT_MAX_WAITING_THREADS 4 + +#if (defined(PLATFORM_WINDOWS_PC) || defined(PLATFORM_X360)) && \ + !defined(STEAM) && !defined(_CERT) +// Thread parent stack trace linkage requires ALL executing binaries to disable +// frame pointer omission to operate speedily/successfully. (/Oy-) "vpc /nofpo" +#define THREAD_PARENT_STACK_TRACE_SUPPORTED \ + 1 // uncomment to support joining the root of a thread's stack trace to its + // parent's at time of invocation. Must also set + // ENABLE_THREAD_PARENT_STACK_TRACING in stacktools.h +#endif + +#if defined(THREAD_PARENT_STACK_TRACE_SUPPORTED) +#include "tier0/stacktools.h" +#if defined(ENABLE_THREAD_PARENT_STACK_TRACING) // stacktools.h opted in +#define THREAD_PARENT_STACK_TRACE_ENABLED \ + 1 // both threadtools.h and stacktools.h have opted into the feature, enable + // it +#endif +#endif + +extern bool gbCheckNotMultithreaded; + +#ifdef _PS3 + +#define USE_INTRINSIC_INTERLOCKED + +#define CHECK_NOT_MULTITHREADED() \ + { \ + static int init = 0; \ + static sys_ppu_thread_t threadIDPrev; \ + \ + if (!init) { \ + sys_ppu_thread_get_id(&threadIDPrev); \ + init = 1; \ + } else if (gbCheckNotMultithreaded) { \ + sys_ppu_thread_t threadID; \ + sys_ppu_thread_get_id(&threadID); \ + if (threadID != threadIDPrev) { \ + fprintf( \ + stderr, \ + "CHECK_NOT_MULTITHREADED: prev thread = %x, cur thread = %x\n", \ + (uint)threadIDPrev, (uint)threadID); \ + *(int *)0 = 0; \ + } \ + } \ + } + +#else +#define CHECK_NOT_MULTITHREADED() +#endif + +#if defined(_X360) || defined(_PS3) +#define MAX_THREADS_SUPPORTED 16 +#else +#define MAX_THREADS_SUPPORTED 32 +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +const unsigned TT_INFINITE = 0xffffffff; + +// Win64 uses unsigned long aka 32 bit thread id. +#if defined(PLATFORM_64BITS) && !defined(_WIN64) +typedef uint64 ThreadId_t; +#else +typedef uint32 ThreadId_t; +#endif + +//----------------------------------------------------------------------------- +// +// Simple thread creation. Differs from VCR mode/CreateThread/_beginthreadex +// in that it accepts a standard C function rather than compiler specific one. +// +//----------------------------------------------------------------------------- +#ifdef COMPILER_SNC +typedef uint64 ThreadHandle_t; +#else // COMPILER_SNC +FORWARD_DECLARE_HANDLE(ThreadHandle_t); +#endif // !COMPILER_SNC +typedef uint (*ThreadFunc_t)(void *pParam); + +#if defined(_PS3) +PLATFORM_OVERLOAD ThreadHandle_t +CreateSimpleThread(ThreadFunc_t, void *pParam, ThreadId_t *pID, + unsigned stackSize = 0x10000 /*64*/); +PLATFORM_INTERFACE ThreadHandle_t CreateSimpleThread( + ThreadFunc_t, void *pParam, unsigned stackSize = 0x10000 /*64*/); +#else //_PS3 +PLATFORM_OVERLOAD ThreadHandle_t CreateSimpleThread(ThreadFunc_t, void *pParam, + ThreadId_t *pID, + unsigned stackSize = 0); +PLATFORM_INTERFACE ThreadHandle_t CreateSimpleThread(ThreadFunc_t, void *pParam, + unsigned stackSize = 0); +#endif //_PS3 +PLATFORM_INTERFACE bool ReleaseThreadHandle(ThreadHandle_t); + +//----------------------------------------------------------------------------- + +PLATFORM_INTERFACE void ThreadSleep(unsigned duration = 0); +PLATFORM_INTERFACE ThreadId_t ThreadGetCurrentId(); +PLATFORM_INTERFACE ThreadHandle_t ThreadGetCurrentHandle(); +PLATFORM_INTERFACE int ThreadGetPriority(ThreadHandle_t hThread = NULL); +PLATFORM_INTERFACE bool ThreadSetPriority(ThreadHandle_t hThread, int priority); +inline bool ThreadSetPriority(int priority) { + return ThreadSetPriority(NULL, priority); +} +#ifndef _X360 +PLATFORM_INTERFACE bool ThreadInMainThread(); +PLATFORM_INTERFACE void DeclareCurrentThreadIsMainThread(); +#else +PLATFORM_INTERFACE byte *g_pBaseMainStack; +PLATFORM_INTERFACE byte *g_pLimitMainStack; +inline bool ThreadInMainThread() { + byte b; + byte *p = &b; + return (p < g_pBaseMainStack && p >= g_pLimitMainStack); +} +#endif + +// NOTE: ThreadedLoadLibraryFunc_t needs to return the sleep time in +// milliseconds or TT_INFINITE +typedef int (*ThreadedLoadLibraryFunc_t)(); +PLATFORM_INTERFACE void SetThreadedLoadLibraryFunc( + ThreadedLoadLibraryFunc_t func); +PLATFORM_INTERFACE ThreadedLoadLibraryFunc_t GetThreadedLoadLibraryFunc(); + +#if defined(PLATFORM_WINDOWS_PC32) +DLL_IMPORT unsigned long STDCALL GetCurrentThreadId(); +#define ThreadGetCurrentId GetCurrentThreadId +#endif + +inline void ThreadPause() { +#if defined(COMPILER_PS3) + __db16cyc(); +#elif defined(COMPILER_GCC) + __asm __volatile("pause"); +#elif defined(COMPILER_MSVC64) + _mm_pause(); +#elif defined(COMPILER_MSVC32) + __asm pause; +#elif defined(COMPILER_MSVCX360) + YieldProcessor(); + __asm { or r0,r0,r0} + YieldProcessor(); + __asm { or r1,r1,r1 } +#else +#error "implement me" +#endif +} + +PLATFORM_INTERFACE bool ThreadJoin(ThreadHandle_t, + unsigned timeout = TT_INFINITE); + +PLATFORM_INTERFACE void ThreadSetDebugName(ThreadHandle_t hThread, + const char *pszName); +inline void ThreadSetDebugName(const char *pszName) { + ThreadSetDebugName(NULL, pszName); +} + +PLATFORM_INTERFACE void ThreadSetAffinity(ThreadHandle_t hThread, + int nAffinityMask); + +//----------------------------------------------------------------------------- +// +// Interlock methods. These perform very fast atomic thread +// safe operations. These are especially relevant in a multi-core setting. +// +//----------------------------------------------------------------------------- + +#ifdef _WIN32 +#define NOINLINE +#elif defined(_PS3) +#define NOINLINE __attribute__((noinline)) +#elif defined(POSIX) +#define NOINLINE __attribute__((noinline)) +#endif + +#if defined(_X360) || defined(_PS3) +#define ThreadMemoryBarrier() __lwsync() +#else +#define ThreadMemoryBarrier() ((void)0) +#endif + +#if defined(_LINUX) || defined(_OSX) +#define USE_INTRINSIC_INTERLOCKED +// linux implementation +inline int32 ThreadInterlockedIncrement(int32 volatile *p) { + Assert((size_t)p % 4 == 0); + return __sync_fetch_and_add(p, 1) + 1; +} + +inline int32 ThreadInterlockedDecrement(int32 volatile *p) { + Assert((size_t)p % 4 == 0); + return __sync_fetch_and_add(p, -1) - 1; +} + +inline int32 ThreadInterlockedExchange(int32 volatile *p, int32 value) { + Assert((size_t)p % 4 == 0); + int32 nRet; + + // Note: The LOCK instruction prefix is assumed on the XCHG instruction and + // GCC gets very confused on the Mac when we use it. + __asm __volatile("xchgl %2,(%1)" + : "=r"(nRet) + : "r"(p), "0"(value) + : "memory"); + return nRet; +} + +inline int32 ThreadInterlockedExchangeAdd(int32 volatile *p, int32 value) { + Assert((size_t)p % 4 == 0); + return __sync_fetch_and_add(p, value); +} +inline int32 ThreadInterlockedCompareExchange(int32 volatile *p, int32 value, + int32 comperand) { + Assert((size_t)p % 4 == 0); + return __sync_val_compare_and_swap(p, comperand, value); +} + +inline bool ThreadInterlockedAssignIf(int32 volatile *p, int32 value, + int32 comperand) { + Assert((size_t)p % 4 == 0); + return __sync_bool_compare_and_swap(p, comperand, value); +} + +#elif (defined(COMPILER_MSVC32) && (_MSC_VER >= 1310)) +// windows 32 implemnetation using compiler intrinsics +#define USE_INTRINSIC_INTERLOCKED + +extern "C" { +long __cdecl _InterlockedIncrement(volatile long *); +long __cdecl _InterlockedDecrement(volatile long *); +long __cdecl _InterlockedExchange(volatile long *, long); +long __cdecl _InterlockedExchangeAdd(volatile long *, long); +long __cdecl _InterlockedCompareExchange(volatile long *, long, long); +} + +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedDecrement) +#pragma intrinsic(_InterlockedExchange) +#pragma intrinsic(_InterlockedExchangeAdd) +#pragma intrinsic(_InterlockedIncrement) + +inline int32 ThreadInterlockedIncrement(int32 volatile *p) { + Assert((size_t)p % 4 == 0); + return _InterlockedIncrement((volatile long *)p); +} +inline int32 ThreadInterlockedDecrement(int32 volatile *p) { + Assert((size_t)p % 4 == 0); + return _InterlockedDecrement((volatile long *)p); +} +inline int32 ThreadInterlockedExchange(int32 volatile *p, int32 value) { + Assert((size_t)p % 4 == 0); + return _InterlockedExchange((volatile long *)p, value); +} +inline int32 ThreadInterlockedExchangeAdd(int32 volatile *p, int32 value) { + Assert((size_t)p % 4 == 0); + return _InterlockedExchangeAdd((volatile long *)p, value); +} +inline int32 ThreadInterlockedCompareExchange(int32 volatile *p, int32 value, + int32 comperand) { + Assert((size_t)p % 4 == 0); + return _InterlockedCompareExchange((volatile long *)p, value, comperand); +} +inline bool ThreadInterlockedAssignIf(int32 volatile *p, int32 value, + int32 comperand) { + Assert((size_t)p % 4 == 0); + return (_InterlockedCompareExchange((volatile long *)p, value, comperand) == + comperand); +} +#elif defined(_PS3) +PLATFORM_INTERFACE inline int32 ThreadInterlockedIncrement(int32 volatile *ea) { + return cellAtomicIncr32((uint32_t *)ea) + 1; +} +PLATFORM_INTERFACE inline int32 ThreadInterlockedDecrement(int32 volatile *ea) { + return cellAtomicDecr32((uint32_t *)ea) - 1; +} +PLATFORM_INTERFACE inline int32 ThreadInterlockedExchange(int32 volatile *ea, + int32 value) { + return cellAtomicStore32((uint32_t *)ea, value); +} +PLATFORM_INTERFACE inline int32 ThreadInterlockedExchangeAdd(int32 volatile *ea, + int32 value) { + return cellAtomicAdd32((uint32_t *)ea, value); +} +PLATFORM_INTERFACE inline int32 ThreadInterlockedCompareExchange( + int32 volatile *ea, int32 value, int32 comperand) { + return cellAtomicCompareAndSwap32((uint32_t *)ea, comperand, value); +} +PLATFORM_INTERFACE inline bool ThreadInterlockedAssignIf(int32 volatile *ea, + int32 value, + int32 comperand) { + return (cellAtomicCompareAndSwap32((uint32_t *)ea, comperand, value) == + (uint32_t)comperand); +} + +PLATFORM_INTERFACE inline int64 ThreadInterlockedCompareExchange64( + int64 volatile *pDest, int64 value, int64 comperand) { + return cellAtomicCompareAndSwap64((uint64_t *)pDest, comperand, value); +} +PLATFORM_INTERFACE inline bool ThreadInterlockedAssignIf64( + volatile int64 *pDest, int64 value, int64 comperand) { + return (cellAtomicCompareAndSwap64((uint64_t *)pDest, comperand, value) == + (uint64_t)comperand); +} + +#elif defined(_X360) +#define TO_INTERLOCK_PARAM(p) ((volatile long *)p) +#define TO_INTERLOCK_PTR_PARAM(p) ((void **)p) +FORCEINLINE int32 ThreadInterlockedIncrement(int32 volatile *pDest) { + Assert((size_t)pDest % 4 == 0); + return InterlockedIncrement(TO_INTERLOCK_PARAM(pDest)); +} +FORCEINLINE int32 ThreadInterlockedDecrement(int32 volatile *pDest) { + Assert((size_t)pDest % 4 == 0); + return InterlockedDecrement(TO_INTERLOCK_PARAM(pDest)); +} +FORCEINLINE int32 ThreadInterlockedExchange(int32 volatile *pDest, + int32 value) { + Assert((size_t)pDest % 4 == 0); + return InterlockedExchange(TO_INTERLOCK_PARAM(pDest), value); +} +FORCEINLINE int32 ThreadInterlockedExchangeAdd(int32 volatile *pDest, + int32 value) { + Assert((size_t)pDest % 4 == 0); + return InterlockedExchangeAdd(TO_INTERLOCK_PARAM(pDest), value); +} +FORCEINLINE int32 ThreadInterlockedCompareExchange(int32 volatile *pDest, + int32 value, + int32 comperand) { + Assert((size_t)pDest % 4 == 0); + return InterlockedCompareExchange(TO_INTERLOCK_PARAM(pDest), value, + comperand); +} +FORCEINLINE bool ThreadInterlockedAssignIf(int32 volatile *pDest, int32 value, + int32 comperand) { + Assert((size_t)pDest % 4 == 0); + return (InterlockedCompareExchange(TO_INTERLOCK_PARAM(pDest), value, + comperand) == comperand); +} +#else +// non 32-bit windows and 360 implementation +PLATFORM_INTERFACE int32 ThreadInterlockedIncrement(int32 volatile *) NOINLINE; +PLATFORM_INTERFACE int32 ThreadInterlockedDecrement(int32 volatile *) NOINLINE; +PLATFORM_INTERFACE int32 ThreadInterlockedExchange(int32 volatile *, + int32 value) NOINLINE; +PLATFORM_INTERFACE int32 ThreadInterlockedExchangeAdd(int32 volatile *, + int32 value) NOINLINE; +PLATFORM_INTERFACE int32 ThreadInterlockedCompareExchange( + int32 volatile *, int32 value, int32 comperand) NOINLINE; +PLATFORM_INTERFACE bool ThreadInterlockedAssignIf(int32 volatile *, int32 value, + int32 comperand) NOINLINE; +#endif + +#if defined(USE_INTRINSIC_INTERLOCKED) && !defined(PLATFORM_64BITS) +#define TIPTR() +inline void *ThreadInterlockedExchangePointer(void *volatile *p, void *value) { + return (void *)((intp)ThreadInterlockedExchange( + reinterpret_cast(p), reinterpret_cast(value))); +} +inline void *ThreadInterlockedCompareExchangePointer(void *volatile *p, + void *value, + void *comperand) { + return (void *)((intp)ThreadInterlockedCompareExchange( + reinterpret_cast(p), reinterpret_cast(value), + reinterpret_cast(comperand))); +} +inline bool ThreadInterlockedAssignPointerIf(void *volatile *p, void *value, + void *comperand) { + return (ThreadInterlockedCompareExchange(reinterpret_cast(p), + reinterpret_cast(value), + reinterpret_cast(comperand)) == + reinterpret_cast(comperand)); +} +#else +PLATFORM_INTERFACE void *ThreadInterlockedExchangePointer(void *volatile *, + void *value) NOINLINE; +PLATFORM_INTERFACE void *ThreadInterlockedCompareExchangePointer( + void *volatile *, void *value, void *comperand) NOINLINE; +PLATFORM_INTERFACE bool ThreadInterlockedAssignPointerIf( + void *volatile *, void *value, void *comperand) NOINLINE; +#endif + +inline unsigned ThreadInterlockedExchangeSubtract(int32 volatile *p, + int32 value) { + return ThreadInterlockedExchangeAdd((int32 volatile *)p, -value); +} + +inline void const *ThreadInterlockedExchangePointerToConst( + void const *volatile *p, void const *value) { + return ThreadInterlockedExchangePointer(const_cast(p), + const_cast(value)); +} +inline void const *ThreadInterlockedCompareExchangePointerToConst( + void const *volatile *p, void const *value, void const *comperand) { + return ThreadInterlockedCompareExchangePointer( + const_cast(p), const_cast(value), + const_cast(comperand)); +} +inline bool ThreadInterlockedAssignPointerToConstIf(void const *volatile *p, + void const *value, + void const *comperand) { + return ThreadInterlockedAssignPointerIf(const_cast(p), + const_cast(value), + const_cast(comperand)); +} + +#ifndef _PS3 +PLATFORM_INTERFACE int64 ThreadInterlockedCompareExchange64( + int64 volatile *, int64 value, int64 comperand) NOINLINE; +PLATFORM_INTERFACE bool ThreadInterlockedAssignIf64(volatile int64 *pDest, + int64 value, + int64 comperand) NOINLINE; +#endif + +PLATFORM_INTERFACE int64 ThreadInterlockedExchange64(int64 volatile *, + int64 value) NOINLINE; +PLATFORM_INTERFACE int64 +ThreadInterlockedIncrement64(int64 volatile *) NOINLINE; +PLATFORM_INTERFACE int64 +ThreadInterlockedDecrement64(int64 volatile *) NOINLINE; +PLATFORM_INTERFACE int64 ThreadInterlockedExchangeAdd64(int64 volatile *, + int64 value) NOINLINE; + +inline unsigned ThreadInterlockedExchangeSubtract(uint32 volatile *p, + uint32 value) { + return ThreadInterlockedExchangeAdd((int32 volatile *)p, value); +} + +inline unsigned ThreadInterlockedIncrement(uint32 volatile *p) { + return ThreadInterlockedIncrement((int32 volatile *)p); +} +inline unsigned ThreadInterlockedDecrement(uint32 volatile *p) { + return ThreadInterlockedDecrement((int32 volatile *)p); +} +inline unsigned ThreadInterlockedExchange(uint32 volatile *p, uint32 value) { + return ThreadInterlockedExchange((int32 volatile *)p, value); +} +inline unsigned ThreadInterlockedExchangeAdd(uint32 volatile *p, uint32 value) { + return ThreadInterlockedExchangeAdd((int32 volatile *)p, value); +} +inline unsigned ThreadInterlockedCompareExchange(uint32 volatile *p, + uint32 value, + uint32 comperand) { + return ThreadInterlockedCompareExchange((int32 volatile *)p, value, + comperand); +} +inline bool ThreadInterlockedAssignIf(uint32 volatile *p, uint32 value, + uint32 comperand) { + return ThreadInterlockedAssignIf((int32 volatile *)p, value, comperand); +} + +// inline int ThreadInterlockedExchangeSubtract( int volatile *p, int value ) +// { return ThreadInterlockedExchangeAdd( (int32 volatile *)p, value ); } inline +// int ThreadInterlockedIncrement( int volatile *p ) { return +// ThreadInterlockedIncrement( (int32 volatile *)p ); } inline int +// ThreadInterlockedDecrement( int volatile *p ) { return +// ThreadInterlockedDecrement( (int32 volatile *)p ); } inline int +// ThreadInterlockedExchange( int volatile *p, int value ) { return +// ThreadInterlockedExchange( (int32 volatile *)p, value ); } inline int +// ThreadInterlockedExchangeAdd( int volatile *p, int value ) { return +// ThreadInterlockedExchangeAdd( (int32 volatile *)p, value ); } inline int +// ThreadInterlockedCompareExchange( int volatile *p, int value, int comperand ) +// { return ThreadInterlockedCompareExchange( (int32 volatile *)p, value, +// comperand ); } inline bool ThreadInterlockedAssignIf( int volatile *p, int +// value, int comperand ) { return ThreadInterlockedAssignIf( (int32 +// volatile *)p, value, comperand ); } + +//----------------------------------------------------------------------------- +// Access to VTune thread profiling +//----------------------------------------------------------------------------- +#if defined(_WIN32) && defined(THREAD_PROFILER) +PLATFORM_INTERFACE void ThreadNotifySyncPrepare(void *p); +PLATFORM_INTERFACE void ThreadNotifySyncCancel(void *p); +PLATFORM_INTERFACE void ThreadNotifySyncAcquired(void *p); +PLATFORM_INTERFACE void ThreadNotifySyncReleasing(void *p); +#else +#define ThreadNotifySyncPrepare(p) ((void)0) +#define ThreadNotifySyncCancel(p) ((void)0) +#define ThreadNotifySyncAcquired(p) ((void)0) +#define ThreadNotifySyncReleasing(p) ((void)0) +#endif + +//----------------------------------------------------------------------------- +// Encapsulation of a thread local datum (needed because THREAD_LOCAL doesn't +// work in a DLL loaded with LoadLibrary() +//----------------------------------------------------------------------------- + +#ifndef NO_THREAD_LOCAL + +#if defined(_PS3) +// linux totally supports compiler thread locals, even across dll's. +#define PLAT_COMPILER_SUPPORTED_THREADLOCALS 1 +#define CTHREADLOCALUSEERROR \ + PS3_ELF_EXPORTED_THREADLOCAL PS3_ELF_EXPORTED_THREADLOCAL \ + PS3_ELF_EXPORTED_THREADLOCAL +#define CTHREADLOCALINTEGER(typ) CTHREADLOCALUSEERROR int +#define CTHREADLOCALINT CTHREADLOCALUSEERROR int +#define CTHREADLOCALPTR(typ) CTHREADLOCALUSEERROR typ * +#define CTHREADLOCAL(typ) CTHREADLOCALUSEERROR typ +#define GETLOCAL(x) (x) +#endif + +#if defined(_LINUX) && !defined(OSX) +// linux totally supports compiler thread locals, even across dll's. +#define PLAT_COMPILER_SUPPORTED_THREADLOCALS 1 +#define CTHREADLOCALINTEGER(typ) __thread int +#define CTHREADLOCALINT __thread int +#define CTHREADLOCALPTR(typ) __thread typ * +#define CTHREADLOCAL(typ) __thread typ +#define GETLOCAL(x) (x) +#ifndef TIER0_DLL_EXPORT +DLL_IMPORT __thread int g_nThreadID; +#endif +#endif + +#if defined(_WIN32) || defined(OSX) +#ifndef __AFXTLS_H__ // not compatible with some Windows headers + +#define CTHREADLOCALINT GenericThreadLocals::CThreadLocalInt +#define CTHREADLOCALINTEGER(typ) GenericThreadLocals::CThreadLocalInt +#define CTHREADLOCALPTR(typ) GenericThreadLocals::CThreadLocalPtr +#define CTHREADLOCAL(typ) GenericThreadLocals::CThreadLocal +#define GETLOCAL(x) (x.Get()) + +namespace GenericThreadLocals { +// a (not so efficient) implementation of thread locals for compilers without +// full support (i.e. visual c). don't use this explicity - instead, use the +// CTHREADxxx macros above. + +class PLATFORM_CLASS CThreadLocalBase { + public: + CThreadLocalBase(); + ~CThreadLocalBase(); + + void *Get() const; + void Set(void *); + + private: +#if defined(POSIX) && !defined(_GAMECONSOLE) + pthread_key_t m_index; +#else + uint32 m_index; +#endif +}; + +//--------------------------------------------------------- + +template +class CThreadLocal : public CThreadLocalBase { + public: + CThreadLocal() { +#ifdef PLATFORM_64BITS + COMPILE_TIME_ASSERT(sizeof(T) <= sizeof(void *)); +#else + COMPILE_TIME_ASSERT(sizeof(T) == sizeof(void *)); +#endif + } + + void operator=(T i) { Set(i); } + + T Get() const { +#ifdef PLATFORM_64BITS + void *pData = CThreadLocalBase::Get(); + return *reinterpret_cast(&pData); +#else + return reinterpret_cast(CThreadLocalBase::Get()); +#endif + } + + void Set(T val) { +#ifdef PLATFORM_64BITS + void *pData = 0; + *reinterpret_cast(&pData) = val; + CThreadLocalBase::Set(pData); +#else + CThreadLocalBase::Set(reinterpret_cast(val)); +#endif + } +}; + +//--------------------------------------------------------- + +template +class CThreadLocalInt : public CThreadLocal { + public: + operator const T() const { return this->Get(); } + int operator=(T i) { + this->Set(i); + return i; + } + + T operator++() { + T i = this->Get(); + this->Set(++i); + return i; + } + T operator++(int) { + T i = this->Get(); + this->Set(i + 1); + return i; + } + + T operator--() { + T i = this->Get(); + this->Set(--i); + return i; + } + T operator--(int) { + T i = this->Get(); + this->Set(i - 1); + return i; + } + + inline CThreadLocalInt() {} + inline CThreadLocalInt(const T &initialvalue) { this->Set(initialvalue); } +}; + +//--------------------------------------------------------- + +template +class CThreadLocalPtr : private CThreadLocalBase { + public: + CThreadLocalPtr() {} + + operator const void *() const { return (const T *)Get(); } + operator void *() { return (T *)Get(); } + + operator const T *() const { return (const T *)Get(); } + operator const T *() { return (const T *)Get(); } + operator T *() { return (T *)Get(); } + + T *operator=(T *p) { + Set(p); + return p; + } + + bool operator!() const { return (!Get()); } + bool operator!=(int i) const { + AssertMsg(i == 0, "Only NULL allowed on integer compare"); + return (Get() != NULL); + } + bool operator==(int i) const { + AssertMsg(i == 0, "Only NULL allowed on integer compare"); + return (Get() == NULL); + } + bool operator==(const void *p) const { return (Get() == p); } + bool operator!=(const void *p) const { return (Get() != p); } + bool operator==(const T *p) const { return operator==((const void *)p); } + bool operator!=(const T *p) const { return operator!=((const void *)p); } + + T *operator->() { return (T *)Get(); } + T &operator*() { return *((T *)Get()); } + + const T *operator->() const { return (const T *)Get(); } + const T &operator*() const { return *((const T *)Get()); } + + const T &operator[](int i) const { return *((const T *)Get() + i); } + T &operator[](int i) { return *((T *)Get() + i); } + + private: + // Disallowed operations + CThreadLocalPtr(T *pFrom); + CThreadLocalPtr(const CThreadLocalPtr &from); + T **operator&(); + T *const *operator&() const; + void operator=(const CThreadLocalPtr &from); + bool operator==(const CThreadLocalPtr &p) const; + bool operator!=(const CThreadLocalPtr &p) const; +}; +} // namespace GenericThreadLocals + +#ifdef _OSX +PLATFORM_INTERFACE GenericThreadLocals::CThreadLocalInt g_nThreadID; +#else // _OSX +#ifndef TIER0_DLL_EXPORT + +#ifndef _PS3 +extern GenericThreadLocals::CThreadLocalInt g_nThreadID; +#endif // !_PS3 + +#endif // TIER0_DLL_EXPORT +#endif // _OSX + +#endif /// afx32 +#endif //__win32 + +#endif // NO_THREAD_LOCAL + +//----------------------------------------------------------------------------- +// +// A super-fast thread-safe integer A simple class encapsulating the notion of +// an atomic integer used across threads that uses the built in and faster +// "interlocked" functionality rather than a full-blown mutex. Useful for simple +// things like reference counts, etc. +// +//----------------------------------------------------------------------------- + +template +class CInterlockedIntT { + public: + CInterlockedIntT() : m_value(0) { + COMPILE_TIME_ASSERT(sizeof(T) == sizeof(int32)); + } + CInterlockedIntT(T value) : m_value(value) {} + + T operator()(void) const { return m_value; } + operator T() const { return m_value; } + + bool operator!() const { return (m_value == 0); } + bool operator==(T rhs) const { return (m_value == rhs); } + bool operator!=(T rhs) const { return (m_value != rhs); } + + T operator++() { return (T)ThreadInterlockedIncrement((int32 *)&m_value); } + T operator++(int) { return operator++() - 1; } + + T operator--() { return (T)ThreadInterlockedDecrement((int32 *)&m_value); } + T operator--(int) { return operator--() + 1; } + + bool AssignIf(T conditionValue, T newValue) { + return ThreadInterlockedAssignIf((int32 *)&m_value, (int32)newValue, + (int32)conditionValue); + } + + T operator=(T newValue) { + ThreadInterlockedExchange((int32 *)&m_value, newValue); + return m_value; + } + + // Atomic add is like += except it returns the previous value as its return + // value + T AtomicAdd(T add) { + return (T)ThreadInterlockedExchangeAdd((int32 *)&m_value, (int32)add); + } + + void operator+=(T add) { + ThreadInterlockedExchangeAdd((int32 *)&m_value, (int32)add); + } + void operator-=(T subtract) { operator+=(-subtract); } + void operator*=(T multiplier) { + T original, result; + do { + original = m_value; + result = original * multiplier; + } while (!AssignIf(original, result)); + } + void operator/=(T divisor) { + T original, result; + do { + original = m_value; + result = original / divisor; + } while (!AssignIf(original, result)); + } + + T operator+(T rhs) const { return m_value + rhs; } + T operator-(T rhs) const { return m_value - rhs; } + + private: + volatile T m_value; +}; + +typedef CInterlockedIntT CInterlockedInt; +typedef CInterlockedIntT CInterlockedUInt; + +//----------------------------------------------------------------------------- + +template +class CInterlockedPtr { + public: + CInterlockedPtr() : m_value(0) { + COMPILE_TIME_ASSERT( + sizeof(T *) == + sizeof(int32)); /* Will need to rework operator+= for 64 bit */ + } + CInterlockedPtr(T *value) : m_value(value) {} + + operator T *() const { return m_value; } + + bool operator!() const { return (m_value == 0); } + bool operator==(T *rhs) const { return (m_value == rhs); } + bool operator!=(T *rhs) const { return (m_value != rhs); } + + T *operator++() { + return ((T *)ThreadInterlockedExchangeAdd((int32 *)&m_value, sizeof(T))) + + 1; + } + T *operator++(int) { + return (T *)ThreadInterlockedExchangeAdd((int32 *)&m_value, sizeof(T)); + } + + T *operator--() { + return ((T *)ThreadInterlockedExchangeAdd((int32 *)&m_value, -sizeof(T))) - + 1; + } + T *operator--(int) { + return (T *)ThreadInterlockedExchangeAdd((int32 *)&m_value, -sizeof(T)); + } + + bool AssignIf(T *conditionValue, T *newValue) { + return ThreadInterlockedAssignPointerToConstIf( + (void const **)&m_value, (void const *)newValue, + (void const *)conditionValue); + } + + T *operator=(T *newValue) { + ThreadInterlockedExchangePointerToConst((void const **)&m_value, + (void const *)newValue); + return newValue; + } + + void operator+=(int add) { + ThreadInterlockedExchangeAdd((int32 *)&m_value, add * sizeof(T)); + } + void operator-=(int subtract) { operator+=(-subtract); } + + // Atomic add is like += except it returns the previous value as its return + // value + T *AtomicAdd(int add) { + return (T *)ThreadInterlockedExchangeAdd((int32 *)&m_value, + add * sizeof(T)); + } + + T *operator+(int rhs) const { return m_value + rhs; } + T *operator-(int rhs) const { return m_value - rhs; } + T *operator+(unsigned rhs) const { return m_value + rhs; } + T *operator-(unsigned rhs) const { return m_value - rhs; } + size_t operator-(T *p) const { return m_value - p; } + size_t operator-(const CInterlockedPtr &p) const { + return m_value - p.m_value; + } + + private: + T *volatile m_value; +}; + +//----------------------------------------------------------------------------- +// +// Platform independent for critical sections management +// +//----------------------------------------------------------------------------- + +class PLATFORM_CLASS CThreadMutex { + public: + CThreadMutex(); + ~CThreadMutex(); + + //------------------------------------------------------ + // Mutex acquisition/release. Const intentionally defeated. + //------------------------------------------------------ + void Lock(); + void Lock() const { (const_cast(this))->Lock(); } + void Unlock(); + void Unlock() const { (const_cast(this))->Unlock(); } + + bool TryLock(); + bool TryLock() const { return (const_cast(this))->TryLock(); } + + void LockSilent(); // A Lock() operation which never spews. Required by the + // logging system to prevent badness. + void UnlockSilent(); // An Unlock() operation which never spews. Required by + // the logging system to prevent badness. + + //------------------------------------------------------ + // Use this to make deadlocks easier to track by asserting + // when it is expected that the current thread owns the mutex + //------------------------------------------------------ + bool AssertOwnedByCurrentThread(); + + //------------------------------------------------------ + // Enable tracing to track deadlock problems + //------------------------------------------------------ + void SetTrace(bool); + + private: + // Disallow copying + CThreadMutex(const CThreadMutex &); + CThreadMutex &operator=(const CThreadMutex &); + +#if defined(_WIN32) + // Efficient solution to breaking the windows.h dependency, invariant is + // tested. +#ifdef _WIN64 +#define TT_SIZEOF_CRITICALSECTION 40 +#else +#ifndef _X360 +#define TT_SIZEOF_CRITICALSECTION 24 +#else +#define TT_SIZEOF_CRITICALSECTION 28 +#endif // !_X360 +#endif // _WIN64 + byte m_CriticalSection[TT_SIZEOF_CRITICALSECTION]; +#elif defined(_PS3) + sys_mutex_t m_Mutex; +#elif defined(POSIX) + pthread_mutex_t m_Mutex; + pthread_mutexattr_t m_Attr; +#else +#error +#endif + +#ifdef THREAD_MUTEX_TRACING_SUPPORTED + // Debugging (always herge to allow mixed debug/release builds w/o changing + // size) + uint m_currentOwnerID; + uint16 m_lockCount; + bool m_bTrace; +#endif +}; + +//----------------------------------------------------------------------------- +// +// An alternative mutex that is useful for cases when thread contention is +// rare, but a mutex is required. Instances should be declared volatile. +// Sleep of 0 may not be sufficient to keep high priority threads from starving +// lesser threads. This class is not a suitable replacement for a critical +// section if the resource contention is high. +// +//----------------------------------------------------------------------------- + +#if !defined(THREAD_PROFILER) + +class CThreadFastMutex { + public: + CThreadFastMutex() : m_ownerID(0), m_depth(0) {} + + private: + FORCEINLINE bool TryLockInline(const uint32 threadId) volatile { + if (threadId != m_ownerID && + !ThreadInterlockedAssignIf((volatile int32 *)&m_ownerID, + (int32)threadId, 0)) + return false; + + ThreadMemoryBarrier(); + ++m_depth; + return true; + } + + bool TryLock(const uint32 threadId) volatile { + return TryLockInline(threadId); + } + + PLATFORM_CLASS void Lock(const uint32 threadId, + unsigned nSpinSleepTime) volatile; + + public: + bool TryLock() volatile { +#ifdef _DEBUG + if (m_depth == INT_MAX) DebuggerBreak(); + + if (m_depth < 0) DebuggerBreak(); +#endif + return TryLockInline(ThreadGetCurrentId()); + } + +#ifndef _DEBUG + FORCEINLINE +#endif + void Lock(unsigned int nSpinSleepTime = 0) volatile { + const uint32 threadId = ThreadGetCurrentId(); + + if (!TryLockInline(threadId)) { + ThreadPause(); + Lock(threadId, nSpinSleepTime); + } +#ifdef _DEBUG + if (m_ownerID != ThreadGetCurrentId()) DebuggerBreak(); + + if (m_depth == INT_MAX) DebuggerBreak(); + + if (m_depth < 0) DebuggerBreak(); +#endif + } + +#ifndef _DEBUG + FORCEINLINE +#endif + void Unlock() volatile { +#ifdef _DEBUG + if (m_ownerID != ThreadGetCurrentId()) DebuggerBreak(); + + if (m_depth <= 0) DebuggerBreak(); +#endif + + --m_depth; + if (!m_depth) { + ThreadMemoryBarrier(); + ThreadInterlockedExchange(&m_ownerID, 0); + } + } + + bool TryLock() const volatile { + return (const_cast(this))->TryLock(); + } + void Lock(unsigned nSpinSleepTime = 0) const volatile { + (const_cast(this))->Lock(nSpinSleepTime); + } + void Unlock() const volatile { + (const_cast(this))->Unlock(); + } + + // To match regular CThreadMutex: + bool AssertOwnedByCurrentThread() { return true; } + void SetTrace(bool) {} + + uint32 GetOwnerId() const { return m_ownerID; } + int GetDepth() const { return m_depth; } + + private: + volatile uint32 m_ownerID; + int m_depth; +}; + +class ALIGN128 CAlignedThreadFastMutex : public CThreadFastMutex { + public: + CAlignedThreadFastMutex() { + Assert((size_t)this % 128 == 0 && sizeof(*this) == 128); + } + + private: + uint8 pad[128 - sizeof(CThreadFastMutex)]; //-V730_NOINIT +}; + +#else +#ifdef _PS3 + +class CThreadFastMutex { + public: + CThreadFastMutex(); + ~CThreadFastMutex(); + + //------------------------------------------------------ + // Mutex acquisition/release. Const intentionally defeated. + //------------------------------------------------------ + void Lock(); + void Lock() const { (const_cast(this))->Lock(); } + void Unlock(); + void Unlock() const { (const_cast(this))->Unlock(); } + + bool TryLock(); + bool TryLock() const { + return (const_cast(this))->TryLock(); + } + + //------------------------------------------------------ + // Use this to make deadlocks easier to track by asserting + // when it is expected that the current thread owns the mutex + //------------------------------------------------------ + bool AssertOwnedByCurrentThread(); + + //------------------------------------------------------ + // Enable tracing to track deadlock problems + //------------------------------------------------------ + void SetTrace(bool); + + private: + // Disallow copying + CThreadFastMutex(const CThreadFastMutex &); + // CThreadFastMutex &operator=( const CThreadFastMutex & ); + sys_lwmutex_t m_Mutex; + sys_mutex_t m_SlowMutex; +}; + +#else + +typedef CThreadMutex CThreadFastMutex; + +#endif + +class ALIGN128 CAlignedThreadFastMutex : public CThreadFastMutex { + public: + CAlignedThreadFastMutex() { + Assert((size_t)this % 128 == 0 && sizeof(*this) == 128); + } + + private: + uint8 pad[128 - sizeof(CThreadFastMutex)]; +}; + +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +class CThreadNullMutex { + public: + static void Lock() {} + static void Unlock() {} + + static bool TryLock() { return true; } + static bool AssertOwnedByCurrentThread() { return true; } + static void SetTrace(bool) {} + + static uint32 GetOwnerId() { return 0; } + static int GetDepth() { return 0; } +}; + +//----------------------------------------------------------------------------- +// +// A mutex decorator class used to control the use of a mutex, to make it +// less expensive when not multithreading +// +//----------------------------------------------------------------------------- + +template +class CThreadConditionalMutex : public BaseClass { + public: + void Lock() { + if (*pCondition) BaseClass::Lock(); + } + void Lock() const { + if (*pCondition) BaseClass::Lock(); + } + void Unlock() { + if (*pCondition) BaseClass::Unlock(); + } + void Unlock() const { + if (*pCondition) BaseClass::Unlock(); + } + + bool TryLock() { + if (*pCondition) + return BaseClass::TryLock(); + else + return true; + } + bool TryLock() const { + if (*pCondition) + return BaseClass::TryLock(); + else + return true; + } + bool AssertOwnedByCurrentThread() { + if (*pCondition) + return BaseClass::AssertOwnedByCurrentThread(); + else + return true; + } + void SetTrace(bool b) { + if (*pCondition) BaseClass::SetTrace(b); + } +}; + +//----------------------------------------------------------------------------- +// Mutex decorator that blows up if another thread enters +//----------------------------------------------------------------------------- + +template +class CThreadTerminalMutex : public BaseClass { + public: + bool TryLock() { + if (!BaseClass::TryLock()) { + DebuggerBreak(); + return false; + } + return true; + } + bool TryLock() const { + if (!BaseClass::TryLock()) { + DebuggerBreak(); + return false; + } + return true; + } + void Lock() { + if (!TryLock()) BaseClass::Lock(); + } + void Lock() const { + if (!TryLock()) BaseClass::Lock(); + } +}; + +//----------------------------------------------------------------------------- +// +// Class to Lock a critical section, and unlock it automatically +// when the lock goes out of scope +// +//----------------------------------------------------------------------------- + +template +class CAutoLockT { + public: + FORCEINLINE CAutoLockT(MUTEX_TYPE &lock) : m_lock(lock) { m_lock.Lock(); } + + FORCEINLINE CAutoLockT(const MUTEX_TYPE &lock) + : m_lock(const_cast(lock)) { + m_lock.Lock(); + } + + FORCEINLINE ~CAutoLockT() { m_lock.Unlock(); } + + private: + MUTEX_TYPE &m_lock; + + // Disallow copying + CAutoLockT(const CAutoLockT &); + CAutoLockT &operator=(const CAutoLockT &); +}; + +typedef CAutoLockT CAutoLock; + +//--------------------------------------------------------- + +template +struct CAutoLockTypeDeducer {}; +template <> +struct CAutoLockTypeDeducer { + typedef CThreadMutex Type_t; +}; +template <> +struct CAutoLockTypeDeducer { + typedef CThreadNullMutex Type_t; +}; +#if !defined(THREAD_PROFILER) +template <> +struct CAutoLockTypeDeducer { + typedef CThreadFastMutex Type_t; +}; +template <> +struct CAutoLockTypeDeducer { + typedef CAlignedThreadFastMutex Type_t; +}; +#else +template <> +struct CAutoLockTypeDeducer { + typedef CAlignedThreadFastMutex Type_t; +}; +#endif + +#ifdef MSVC +#define AUTO_LOCK_(type, mutex) \ + CAutoLockT UNIQUE_ID(static_cast(mutex)) +#else +// clang requires +#define AUTO_LOCK_(type, mutex) \ + CAutoLockT UNIQUE_ID(static_cast(mutex)) +#endif + +#define AUTO_LOCK(mutex) \ + AUTO_LOCK_(CAutoLockTypeDeducer::Type_t, mutex) + +#define AUTO_LOCK_FM(mutex) \ + AUTO_LOCK_(CAutoLockTypeDeducer::Type_t, mutex) + +#define LOCAL_THREAD_LOCK_(tag) \ + ; \ + static CThreadFastMutex autoMutex_##tag; \ + AUTO_LOCK(autoMutex_##tag) + +#define LOCAL_THREAD_LOCK() LOCAL_THREAD_LOCK_(_) + +//----------------------------------------------------------------------------- +// +// Base class for event, semaphore and mutex objects. +// +//----------------------------------------------------------------------------- + +// TW_TIMEOUT must match WAIT_TIMEOUT definition +#define TW_TIMEOUT 0x00000102 +// TW_FAILED must match WAIT_FAILED definition +#define TW_FAILED 0xFFFFFFFF + +class PLATFORM_CLASS CThreadSyncObject { + public: + ~CThreadSyncObject(); + + //----------------------------------------------------- + // Query if object is useful + //----------------------------------------------------- + bool operator!() const; + + //----------------------------------------------------- + // Access handle + //----------------------------------------------------- +#ifdef _WIN32 + operator HANDLE() { return GetHandle(); } + const HANDLE GetHandle() const { return m_hSyncObject; } +#endif + //----------------------------------------------------- + // Wait for a signal from the object + //----------------------------------------------------- + bool Wait(uint32 dwTimeout = TT_INFINITE); + + //----------------------------------------------------- + // Wait for a signal from any of the specified objects. + // + // Returns the index of the object that signaled the event + // or THREADSYNC_TIMEOUT if the timeout was hit before the wait condition was + // met. + // + // Returns TW_FAILED if an incoming object is invalid. + // + // If bWaitAll=true, then it'll return 0 if all the objects were set. + //----------------------------------------------------- + static uint32 WaitForMultiple(int nObjects, CThreadSyncObject **ppObjects, + bool bWaitAll, uint32 dwTimeout = TT_INFINITE); + + // This builds a list of pointers and calls straight through to the other + // WaitForMultiple. + static uint32 WaitForMultiple(int nObjects, CThreadSyncObject *ppObjects, + bool bWaitAll, uint32 dwTimeout = TT_INFINITE); + + protected: + CThreadSyncObject(); + void AssertUseable(); + +#ifdef _WIN32 + HANDLE m_hSyncObject; + bool m_bCreatedHandle; +#elif defined(_PS3) + static sys_lwmutex_t m_staticMutex; + static uint32_t m_bstaticMutexInitialized; + static uint32_t m_bstaticMutexInitializing; +#elif defined(POSIX) + pthread_mutex_t m_Mutex; + pthread_cond_t m_Condition; + bool m_bInitalized; + int m_cSet; + bool m_bManualReset; + bool m_bWakeForEvent; +#else +#error "Implement me" +#endif + + private: + CThreadSyncObject(const CThreadSyncObject &); + CThreadSyncObject &operator=(const CThreadSyncObject &); +}; + +//----------------------------------------------------------------------------- +// +// Wrapper for unnamed event objects +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// +// CThreadSemaphore +// +//----------------------------------------------------------------------------- + +class PLATFORM_CLASS CThreadSemaphore : public CThreadSyncObject { + public: + CThreadSemaphore(int32 initialValue, int32 maxValue); + + //----------------------------------------------------- + // Increases the count of the semaphore object by a specified + // amount. Wait() decreases the count by one on return. + //----------------------------------------------------- + bool Release(int32 releaseCount = 1, int32 *pPreviousCount = NULL); + bool Wait(uint32 dwTimeout = TT_INFINITE); + + private: + CThreadSemaphore(const CThreadSemaphore &); + CThreadSemaphore &operator=(const CThreadSemaphore &); +#ifdef _PS3 + bool AddWaitingThread(); + void RemoveWaitingThread(); + sys_semaphore_t m_Semaphore; + sys_semaphore_value_t m_sema_max_val; + uint32_t m_numWaitingThread; + uint32_t m_bInitalized; + uint32_t m_semaCount; +#endif +}; + +#if defined(_WIN32) + +//----------------------------------------------------------------------------- +// +// A mutex suitable for out-of-process, multi-processor usage +// +//----------------------------------------------------------------------------- + +class PLATFORM_CLASS CThreadFullMutex : public CThreadSyncObject { + public: + CThreadFullMutex(bool bEstablishInitialOwnership = false, + const char *pszName = NULL); + + //----------------------------------------------------- + // Release ownership of the mutex + //----------------------------------------------------- + bool Release(); + + // To match regular CThreadMutex: + void Lock() { Wait(); } + void Lock(unsigned timeout) { Wait(timeout); } + void Unlock() { Release(); } + bool AssertOwnedByCurrentThread() { return true; } + void SetTrace(bool) {} + + private: + CThreadFullMutex(const CThreadFullMutex &); + CThreadFullMutex &operator=(const CThreadFullMutex &); +}; +#endif + +enum NamedEventResult_t { + TT_EventDoesntExist = 0, + TT_EventNotSignaled, + TT_EventSignaled +}; +#if defined(_PS3) +//--------------------------------------------------------------------------- +// CThreadEventWaitObject - the purpose of this class is to help implement +// WaitForMultipleObejcts on PS3. +// +// Each event maintains a linked list of CThreadEventWaitObjects. When a +// thread wants to wait on an event it passes the event a semaphore that +// ptr to see the index of the event that triggered it +// +// The thread-specific mutex is to ensure that setting the index and setting the +// semaphore are atomic +//--------------------------------------------------------------------------- + +class CThreadEventWaitObject { + public: + CThreadEventWaitObject *m_pPrev, *m_pNext; + sys_semaphore_t *m_pSemaphore; + int m_index; + int *m_pFlag; + + CThreadEventWaitObject() {} + + void Init(sys_semaphore_t *pSem, int index, int *pFlag) { + m_pSemaphore = pSem; + m_index = index; + m_pFlag = pFlag; + } + + void Set(); +}; +#endif //_PS3 + +class PLATFORM_CLASS CThreadEvent : public CThreadSyncObject { + public: + CThreadEvent(bool fManualReset = false); +#ifdef PLATFORM_WINDOWS + CThreadEvent(const char *name, bool initialState = false, + bool bManualReset = false); + static NamedEventResult_t CheckNamedEvent(const char *name, + uint32 dwTimeout = 0); + + CThreadEvent(HANDLE hHandle); +#endif + //----------------------------------------------------- + // Set the state to signaled + //----------------------------------------------------- + bool Set(); + + //----------------------------------------------------- + // Set the state to nonsignaled + //----------------------------------------------------- + bool Reset(); + + //----------------------------------------------------- + // Check if the event is signaled + //----------------------------------------------------- + bool Check(); // Please, use for debugging only! + + bool Wait(uint32 dwTimeout = TT_INFINITE); + + // See CThreadSyncObject for definitions of these functions. + static uint32 WaitForMultiple(int nObjects, CThreadEvent **ppObjects, + bool bWaitAll, uint32 dwTimeout = TT_INFINITE); + // To implement these, I need to check that casts are safe + static uint32 WaitForMultiple(int nObjects, CThreadEvent *ppObjects, + bool bWaitAll, uint32 dwTimeout = TT_INFINITE); + +#ifdef _PS3 + void RegisterWaitingThread(sys_semaphore_t *pSemaphore, int index, int *flag); + void UnregisterWaitingThread(sys_semaphore_t *pSemaphore); +#endif + + protected: +#ifdef _PS3 + // These virtual functions need to be inline in order for the class to be + // exported from tier0.prx + virtual bool AddWaitingThread() { + // This checks if the event is already signaled and if not creates a + // semaphore which will be signaled when the event is finally signaled. + bool result; + + sys_lwmutex_lock(&m_staticMutex, 0); + + if (m_bSet) + result = false; + else { + result = true; + + m_numWaitingThread++; + + if (m_numWaitingThread == 1) { + sys_semaphore_attribute_t semAttr; + sys_semaphore_attribute_initialize(semAttr); + int err = sys_semaphore_create(&m_Semaphore, &semAttr, 0, 256); + Assert(err == CELL_OK); + m_bInitalized = true; + } + } + + sys_lwmutex_unlock(&m_staticMutex); + return result; + } + + virtual void RemoveWaitingThread() { + sys_lwmutex_lock(&m_staticMutex, 0); + + m_numWaitingThread--; + + if (m_numWaitingThread == 0) { + int err = sys_semaphore_destroy(m_Semaphore); + Assert(err == CELL_OK); + m_bInitalized = false; + } + + sys_lwmutex_unlock(&m_staticMutex); + } +#endif + private: + CThreadEvent(const CThreadEvent &); + CThreadEvent &operator=(const CThreadEvent &); +#if defined(_PS3) + uint32_t m_bSet; + bool m_bManualReset; + + sys_semaphore_t m_Semaphore; + uint32_t m_numWaitingThread; + uint32_t m_bInitalized; + + CThreadEventWaitObject m_waitObjects[CTHREADEVENT_MAX_WAITING_THREADS + 2]; + CThreadEventWaitObject *m_pWaitObjectsPool; + CThreadEventWaitObject *m_pWaitObjectsList; + + CThreadEventWaitObject *LLUnlinkNode(CThreadEventWaitObject *node); + CThreadEventWaitObject *LLLinkNode(CThreadEventWaitObject *list, + CThreadEventWaitObject *node); + +#endif +}; + +// Hard-wired manual event for use in array declarations +class CThreadManualEvent : public CThreadEvent { + public: + CThreadManualEvent() : CThreadEvent(true) {} +}; + +PLATFORM_INTERFACE int ThreadWaitForObjects(int nEvents, const HANDLE *pHandles, + bool bWaitAll = true, + unsigned timeout = TT_INFINITE); +inline int ThreadWaitForEvents(int nEvents, const CThreadEvent *pEvents, + bool bWaitAll = true, + unsigned timeout = TT_INFINITE) { + return ThreadWaitForObjects(nEvents, (const HANDLE *)pEvents, bWaitAll, + timeout); +} + +//----------------------------------------------------------------------------- +// +// CThreadRWLock +// +//----------------------------------------------------------------------------- + +class PLATFORM_CLASS CThreadRWLock { + public: + CThreadRWLock(); + + void LockForRead(); + void UnlockRead(); + void LockForWrite(); + void UnlockWrite(); + + void LockForRead() const { const_cast(this)->LockForRead(); } + void UnlockRead() const { const_cast(this)->UnlockRead(); } + void LockForWrite() const { + const_cast(this)->LockForWrite(); + } + void UnlockWrite() const { const_cast(this)->UnlockWrite(); } + + private: + void WaitForRead(); + +#ifdef WIN32 + CThreadFastMutex m_mutex; +#else + CThreadMutex m_mutex; +#endif + CThreadEvent m_CanWrite; + CThreadEvent m_CanRead; + + int m_nWriters; + int m_nActiveReaders; + int m_nPendingReaders; +}; + +//----------------------------------------------------------------------------- +// +// CThreadSpinRWLock +// +//----------------------------------------------------------------------------- + +#ifndef OLD_SPINRWLOCK +class ALIGN8 PLATFORM_CLASS CThreadSpinRWLock { + public: + CThreadSpinRWLock() { + m_lockInfo.m_i32 = 0; + m_writerId = 0; +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + m_iWriteDepth = 0; +#endif + } + + bool IsLockedForWrite(); + bool IsLockedForRead(); + + FORCEINLINE bool TryLockForWrite(); + bool TryLockForWrite_UnforcedInline(); + + void LockForWrite(); + void SpinLockForWrite(); + + FORCEINLINE bool TryLockForRead(); + bool TryLockForRead_UnforcedInline(); + + void LockForRead(); + void SpinLockForRead(); + + void UnlockWrite(); + void UnlockRead(); + + bool TryLockForWrite() const { + return const_cast(this)->TryLockForWrite(); + } + bool TryLockForRead() const { + return const_cast(this)->TryLockForRead(); + } + void LockForRead() const { + const_cast(this)->LockForRead(); + } + void UnlockRead() const { + const_cast(this)->UnlockRead(); + } + void LockForWrite() const { + const_cast(this)->LockForWrite(); + } + void UnlockWrite() const { + const_cast(this)->UnlockWrite(); + } + + private: + enum { THREAD_SPIN = (8 * 1024) }; + + union LockInfo_t { + struct { +#if PLAT_LITTLE_ENDIAN + uint16 m_nReaders; + uint16 m_fWriting; +#else + uint16 m_fWriting; + uint16 m_nReaders; +#endif + }; + uint32 m_i32; + }; + + LockInfo_t m_lockInfo; + uint32 m_writerId; +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + int m_iWriteDepth; + uint32 pad; +#endif +} ALIGN8_POST; + +#else + +/* (commented out to reduce distraction in colorized editor, remove entirely +when new implementation settles) class ALIGN8 PLATFORM_CLASS CThreadSpinRWLock +{ +public: + CThreadSpinRWLock() { COMPILE_TIME_ASSERT( sizeof( LockInfo_t ) == +sizeof( int64 ) ); Assert( (intp)this % 8 == 0 ); memset( this, 0, sizeof( *this +) ); } + + bool TryLockForWrite(); + bool TryLockForRead(); + + void LockForRead(); + void UnlockRead(); + void LockForWrite(); + void UnlockWrite(); + + bool TryLockForWrite() const { return const_cast(this)->TryLockForWrite(); } bool TryLockForRead() const { return +const_cast(this)->TryLockForRead(); } void LockForRead() +const { const_cast(this)->LockForRead(); } void +UnlockRead() const { const_cast(this)->UnlockRead(); } void +LockForWrite() const { const_cast(this)->LockForWrite(); } + void UnlockWrite() const { const_cast(this)->UnlockWrite(); } + +private: + // This structure is used as an atomic & exchangeable 64-bit value. It +would probably be better to just have one 64-bit value + // and accessor functions that make/break it, but at this late stage of +development, I'm just wrapping it into union + // Beware of endianness: on Xbox/PowerPC m_writerId is high-word of +m_i64; on PC, it's low-dword of m_i64 union LockInfo_t + { + struct + { + uint32 m_writerId; + int m_nReaders; + }; + int64 m_i64; + }; + + bool AssignIf( const LockInfo_t &newValue, const LockInfo_t &comperand +); bool TryLockForWrite( const uint32 threadId ); void SpinLockForWrite( const +uint32 threadId ); + + volatile LockInfo_t m_lockInfo; + CInterlockedInt m_nWriters; +} ALIGN8_POST; +*/ +#endif + +//----------------------------------------------------------------------------- +// +// A thread wrapper similar to a Java thread. +// +//----------------------------------------------------------------------------- +#ifdef _PS3 +// Everything must be inline for this to work across PRX boundaries + +class CThread; +PLATFORM_INTERFACE CThread *GetCurThreadPS3(); +PLATFORM_INTERFACE void SetCurThreadPS3(CThread *); +PLATFORM_INTERFACE void AllocateThreadID(void); +PLATFORM_INTERFACE void FreeThreadID(void); +#endif + +class PLATFORM_CLASS CThread { + public: + CThread(); + virtual ~CThread(); + + //----------------------------------------------------- + + const char *GetName(); + void SetName(const char *pszName); + + size_t CalcStackDepth(void *pStackVariable) { + return ((byte *)m_pStackBase - (byte *)pStackVariable); + } + + //----------------------------------------------------- + // Functions for the other threads + //----------------------------------------------------- + + // Start thread running - error if already running + enum ThreadPriorityEnum_t { +#ifdef _PS3 + PRIORITY_NORMAL = 1001, + PRIORITY_HIGH = 100, + PRIORITY_LOW = 2001, + PRIORITY_DEFAULT = 1001 +#else + PRIORITY_DEFAULT = 0, // THREAD_PRIORITY_NORMAL, + PRIORITY_NORMAL = 0, // THREAD_PRIORITY_NORMAL, + PRIORITY_HIGH = 1, // THREAD_PRIORITY_ABOVE_NORMAL, + PRIORITY_LOW = -1 // THREAD_PRIORITY_BELOW_NORMAL +#endif + }; + virtual bool Start(unsigned nBytesStack = 0, + ThreadPriorityEnum_t nPriority = PRIORITY_DEFAULT); + + // Returns true if thread has been created and hasn't yet exited + bool IsAlive(); + + // This method causes the current thread to wait until this thread + // is no longer alive. + bool Join(unsigned timeout = TT_INFINITE); + + // Access the thread handle directly + ThreadHandle_t GetThreadHandle(); + +#ifdef _WIN32 + uint GetThreadId(); +#endif + + //----------------------------------------------------- + + int GetResult(); + + //----------------------------------------------------- + // Functions for both this, and maybe, and other threads + //----------------------------------------------------- + + // Forcibly, abnormally, but relatively cleanly stop the thread + void Stop(int exitCode = 0); + + // Get the priority + int GetPriority() const; + + // Set the priority + bool SetPriority(int priority); + + // Suspend a thread, can only call from the thread itself + unsigned Suspend(); + + // Resume a suspended thread + unsigned Resume(); + + // Check if thread is suspended + bool IsSuspended() { return !m_NotSuspendedEvent.Check(); } + + // Force hard-termination of thread. Used for critical failures. + bool Terminate(int exitCode = 0); + + //----------------------------------------------------- + // Global methods + //----------------------------------------------------- + + // Get the Thread object that represents the current thread, if any. + // Can return NULL if the current thread was not created using + // CThread + static CThread *GetCurrentCThread(); + + // Offer a context switch. Under Win32, equivalent to Sleep(0) +#ifdef Yield +#undef Yield +#endif + static void Yield(); + + // This method causes the current thread to yield and not to be + // scheduled for further execution until a certain amount of real + // time has elapsed, more or less. Duration is in milliseconds + static void Sleep(unsigned duration); + + protected: + // Optional pre-run call, with ability to fail-create. Note Init() + // is forced synchronous with Start() + virtual bool Init(); + + // Thread will run this function on startup, must be supplied by + // derived class, performs the intended action of the thread. + virtual int Run() = 0; + + // Called when the thread exits + virtual void OnExit(); + + // Allow for custom start waiting + virtual bool WaitForCreateComplete(CThreadEvent *pEvent); + const ThreadId_t GetThreadID() const { return (ThreadId_t)m_threadId; } + +#ifdef PLATFORM_WINDOWS + const ThreadHandle_t GetThreadHandle() const { + return (ThreadHandle_t)m_hThread; + } + + static unsigned long __stdcall ThreadProc(void *pv); + typedef unsigned long(__stdcall *ThreadProc_t)(void *); +#else + static void *ThreadProc(void *pv); + typedef void *(*ThreadProc_t)(void *pv); +#endif + + virtual ThreadProc_t GetThreadProc(); + CThreadMutex m_Lock; + CThreadEvent m_ExitEvent; // Set right before the thread's function exits. + + private: + enum Flags { SUPPORT_STOP_PROTOCOL = 1 << 0 }; + + // Thread initially runs this. param is actually 'this'. function + // just gets this and calls ThreadProc + struct ThreadInit_t { + CThread *pThread; + CThreadEvent *pInitCompleteEvent; + bool *pfInitSuccess; +#if defined(THREAD_PARENT_STACK_TRACE_ENABLED) + void *ParentStackTrace[THREAD_PARENT_STACK_TRACE_LENGTH]; +#endif + }; + + // make copy constructor and assignment operator inaccessible + CThread(const CThread &); + CThread &operator=(const CThread &); + +#ifdef _WIN32 + HANDLE m_hThread; + ThreadId_t m_threadId; +#elif defined(_PS3) + sys_ppu_thread_t m_threadId; + volatile sys_ppu_thread_t m_threadZombieId; + + // Mutex and condition variable used by the Suspend / Resume logic + sys_mutex_t m_mutexSuspend; + sys_cond_t m_condSuspend; + + // EAPS3 Event to indicate that a thread has terminated. This helps with the + // replacing of WaitForMultipleObjects + // on the PS3, since it waits for a thread to finish. + CThreadEvent m_threadEnd; +#elif defined(_POSIX) + pthread_t m_threadId; + volatile pthread_t m_threadZombieId; +#endif + int m_result; + char m_szName[64]; + void *m_pStackBase; + unsigned m_flags; + CThreadManualEvent m_NotSuspendedEvent; +}; + +// The CThread implementation needs to be inlined for performance on the PS3 - +// It makes a difference of more than 1ms/frame Since the dependency checker +// isn't smart enough to take an #ifdef _PS3 into account, all platforms will +// inline it. +#ifdef _PS3 +#include "threadtools.inl" +#endif + +//----------------------------------------------------------------------------- +// Simple thread class encompasses the notion of a worker thread, handing +// synchronized communication. +//----------------------------------------------------------------------------- + +// These are internal reserved error results from a call attempt +enum WTCallResult_t { + WTCR_FAIL = -1, + WTCR_TIMEOUT = -2, + WTCR_THREAD_GONE = -3, +}; + +class PLATFORM_CLASS CWorkerThread : public CThread { + public: + CWorkerThread(); + + //----------------------------------------------------- + // + // Inter-thread communication + // + // Calls in either direction take place on the same "channel." + // Separate functions are specified to make identities obvious + // + //----------------------------------------------------- + + // Master: Signal the thread, and block for a response + int CallWorker(unsigned, unsigned timeout = TT_INFINITE, + bool fBoostWorkerPriorityToMaster = true); + + // Worker: Signal the thread, and block for a response + int CallMaster(unsigned, unsigned timeout = TT_INFINITE); + + // Wait for the next request + bool WaitForCall(unsigned dwTimeout, unsigned *pResult = NULL); + bool WaitForCall(unsigned *pResult = NULL); + + // Is there a request? + bool PeekCall(unsigned *pParam = NULL); + + // Reply to the request + void Reply(unsigned); + + // Wait for a reply in the case when CallWorker() with timeout != TT_INFINITE + int WaitForReply(unsigned timeout = TT_INFINITE); + + // If you want to do WaitForMultipleObjects you'll need to include + // this handle in your wait list or you won't be responsive + CThreadEvent &GetCallHandle(); // (returns m_EventSend) + + // Find out what the request was + unsigned GetCallParam() const; + + // Boost the worker thread to the master thread, if worker thread is lesser, + // return old priority + int BoostPriority(); + + protected: + typedef uint32 (*WaitFunc_t)(uint32 nHandles, CThreadEvent **ppHandles, + int bWaitAll, uint32 timeout); + int Call(unsigned, unsigned timeout, bool fBoost, WaitFunc_t = NULL); + int WaitForReply(unsigned timeout, WaitFunc_t); + + private: + CWorkerThread(const CWorkerThread &); + CWorkerThread &operator=(const CWorkerThread &); + + CThreadEvent m_EventSend; + CThreadEvent m_EventComplete; + + unsigned m_Param; + int m_ReturnVal; +}; + +// a unidirectional message queue. A queue of type T. Not especially high speed +// since each message is malloced/freed. Note that if your message class has +// destructors/constructors, they MUST be thread safe! +template +class CMessageQueue { + CThreadEvent SignalEvent; // signals presence of data + CThreadMutex QueueAccessMutex; + + // the parts protected by the mutex + struct MsgNode { + MsgNode *Next; + T Data; + }; + + MsgNode *Head; + MsgNode *Tail; + + public: + CMessageQueue(void) { Head = Tail = NULL; } + + // check for a message. not 100% reliable - someone could grab the message + // first + bool MessageWaiting(void) { return (Head != NULL); } + + void WaitMessage(T *pMsg) { + for (;;) { + while (!MessageWaiting()) SignalEvent.Wait(); + QueueAccessMutex.Lock(); + if (!Head) { + // multiple readers could make this null + QueueAccessMutex.Unlock(); + continue; + } + *(pMsg) = Head->Data; + MsgNode *remove_this = Head; + Head = Head->Next; + if (!Head) // if empty, fix tail ptr + Tail = NULL; + QueueAccessMutex.Unlock(); + delete remove_this; + break; + } + } + + void QueueMessage(T const &Msg) { + MsgNode *new1 = new MsgNode; + new1->Data = Msg; + new1->Next = NULL; + QueueAccessMutex.Lock(); + if (Tail) { + Tail->Next = new1; + Tail = new1; + } else { + Head = new1; + Tail = new1; + } + SignalEvent.Set(); + QueueAccessMutex.Unlock(); + } +}; + +//----------------------------------------------------------------------------- +// +// CThreadMutex. Inlining to reduce overhead and to allow client code +// to decide debug status (tracing) +// +//----------------------------------------------------------------------------- + +#ifdef MSVC +typedef struct _RTL_CRITICAL_SECTION RTL_CRITICAL_SECTION; +typedef RTL_CRITICAL_SECTION CRITICAL_SECTION; + +#ifndef _X360 +extern "C" { +void __declspec(dllimport) __stdcall InitializeCriticalSection( + _Out_ CRITICAL_SECTION *); +void __declspec(dllimport) __stdcall EnterCriticalSection( + _Inout_ CRITICAL_SECTION *); +void __declspec(dllimport) __stdcall LeaveCriticalSection( + _Inout_ CRITICAL_SECTION *); +void __declspec(dllimport) __stdcall DeleteCriticalSection( + _Inout_ CRITICAL_SECTION *); +}; +#endif +#endif + +//--------------------------------------------------------- +#if !defined(POSIX) || defined(_GAMECONSOLE) + +inline void CThreadMutex::Lock() { +#if defined(_PS3) +#ifndef NO_THREAD_SYNC + sys_mutex_lock(m_Mutex, 0); +#endif +#else +#if defined(THREAD_MUTEX_TRACING_ENABLED) + uint thisThreadID = ThreadGetCurrentId(); + if (m_bTrace && m_currentOwnerID && (m_currentOwnerID != thisThreadID)) + Msg(_T( "Thread %u about to wait for lock %x owned by %u\n" ), + ThreadGetCurrentId(), (CRITICAL_SECTION *)&m_CriticalSection, + m_currentOwnerID); +#endif + + LockSilent(); + +#ifdef THREAD_MUTEX_TRACING_ENABLED + if (m_lockCount == 0) { + // we now own it for the first time. Set owner information + m_currentOwnerID = thisThreadID; + if (m_bTrace) + Msg(_T( "Thread %u now owns lock 0x%x\n" ), m_currentOwnerID, + (CRITICAL_SECTION *)&m_CriticalSection); + } + m_lockCount++; +#endif +#endif +} + +//--------------------------------------------------------- + +inline void CThreadMutex::Unlock() { +#if defined(_PS3) + +#ifndef NO_THREAD_SYNC + sys_mutex_unlock(m_Mutex); +#endif + +#else +#ifdef THREAD_MUTEX_TRACING_ENABLED + AssertMsg(m_lockCount >= 1, "Invalid unlock of thread lock"); + m_lockCount--; + if (m_lockCount == 0) { + if (m_bTrace) + Msg(_T( "Thread %u releasing lock 0x%x\n" ), m_currentOwnerID, + (CRITICAL_SECTION *)&m_CriticalSection); + m_currentOwnerID = 0; + } +#endif + UnlockSilent(); +#endif +} + +//--------------------------------------------------------- + +inline void CThreadMutex::LockSilent() { +#ifdef MSVC + EnterCriticalSection((CRITICAL_SECTION *)&m_CriticalSection); +#else + DebuggerBreak(); // should not be called - not defined for this + // platform/compiler!!! +#endif +} + +//--------------------------------------------------------- + +inline void CThreadMutex::UnlockSilent() { +#ifdef MSVC + LeaveCriticalSection((CRITICAL_SECTION *)&m_CriticalSection); +#else + DebuggerBreak(); // should not be called - not defined for this + // platform/compiler!!! +#endif +} + +//--------------------------------------------------------- + +inline bool CThreadMutex::AssertOwnedByCurrentThread() { +#ifdef THREAD_MUTEX_TRACING_ENABLED +#ifdef _WIN32 + if (ThreadGetCurrentId() == m_currentOwnerID) return true; + AssertMsg3(0, "Expected thread %u as owner of lock 0x%x, but %u owns", + ThreadGetCurrentId(), (CRITICAL_SECTION *)&m_CriticalSection, + m_currentOwnerID); + return false; +#elif defined(_PS3) + return true; +#endif +#else + return true; +#endif +} + +//--------------------------------------------------------- + +inline void CThreadMutex::SetTrace(bool bTrace) { +#ifdef _WIN32 +#ifdef THREAD_MUTEX_TRACING_ENABLED + m_bTrace = bTrace; +#endif +#elif defined _PS3 + // EAPS3 +#endif +} + +//--------------------------------------------------------- + +#elif defined(POSIX) && !defined(_GAMECONSOLE) + +inline CThreadMutex::CThreadMutex() { + // enable recursive locks as we need them + pthread_mutexattr_init(&m_Attr); + pthread_mutexattr_settype(&m_Attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m_Mutex, &m_Attr); +} + +//--------------------------------------------------------- + +inline CThreadMutex::~CThreadMutex() { pthread_mutex_destroy(&m_Mutex); } + +//--------------------------------------------------------- + +inline void CThreadMutex::Lock() { pthread_mutex_lock(&m_Mutex); } + +//--------------------------------------------------------- + +inline void CThreadMutex::Unlock() { pthread_mutex_unlock(&m_Mutex); } + +//--------------------------------------------------------- + +inline void CThreadMutex::LockSilent() { pthread_mutex_lock(&m_Mutex); } + +//--------------------------------------------------------- + +inline void CThreadMutex::UnlockSilent() { pthread_mutex_unlock(&m_Mutex); } + +//--------------------------------------------------------- + +inline bool CThreadMutex::AssertOwnedByCurrentThread() { return true; } + +//--------------------------------------------------------- + +inline void CThreadMutex::SetTrace(bool fTrace) {} + +#else +#error +#endif // POSIX + +//----------------------------------------------------------------------------- +// +// CThreadRWLock inline functions +// +//----------------------------------------------------------------------------- + +inline CThreadRWLock::CThreadRWLock() + : m_CanRead(true), + m_nWriters(0), + m_nActiveReaders(0), + m_nPendingReaders(0) {} + +inline void CThreadRWLock::LockForRead() { + m_mutex.Lock(); + if (m_nWriters) { + WaitForRead(); + } + m_nActiveReaders++; + m_mutex.Unlock(); +} + +inline void CThreadRWLock::UnlockRead() { + m_mutex.Lock(); + m_nActiveReaders--; + if (m_nActiveReaders == 0 && m_nWriters != 0) { + m_CanWrite.Set(); + } + m_mutex.Unlock(); +} + +//----------------------------------------------------------------------------- +// +// CThreadSpinRWLock inline functions +// +//----------------------------------------------------------------------------- + +#ifndef OLD_SPINRWLOCK + +#if defined(TEST_THREAD_SPIN_RW_LOCK) +#define RWLAssert(exp) \ + if (exp) \ + ; \ + else \ + DebuggerBreak(); +#else +#define RWLAssert(exp) ((void)0) +#endif + +inline bool CThreadSpinRWLock::IsLockedForWrite() { + return (m_lockInfo.m_fWriting == 1); +} + +inline bool CThreadSpinRWLock::IsLockedForRead() { + return (m_lockInfo.m_nReaders > 0); +} + +FORCEINLINE bool CThreadSpinRWLock::TryLockForWrite() { + volatile LockInfo_t &curValue = m_lockInfo; + if (!(curValue.m_i32 & 0x00010000) && + ThreadInterlockedAssignIf(&curValue.m_i32, 0x00010000, 0)) { + ThreadMemoryBarrier(); + RWLAssert(m_iWriteDepth == 0 && m_writerId == 0); + m_writerId = ThreadGetCurrentId(); +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + m_iWriteDepth++; +#endif + return true; + } + + return false; +} + +inline bool CThreadSpinRWLock::TryLockForWrite_UnforcedInline() { + if (TryLockForWrite()) { + return true; + } + +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (m_writerId != ThreadGetCurrentId()) { + return false; + } + m_iWriteDepth++; + return true; +#else + return false; +#endif +} + +FORCEINLINE void CThreadSpinRWLock::LockForWrite() { + if (!TryLockForWrite()) { + SpinLockForWrite(); + } +} + +FORCEINLINE bool CThreadSpinRWLock::TryLockForRead() { + volatile LockInfo_t &curValue = m_lockInfo; + if (!(curValue.m_i32 & 0x00010000)) // !m_lockInfo.m_fWriting + { + LockInfo_t oldValue; + LockInfo_t newValue; + oldValue.m_i32 = (curValue.m_i32 & 0xffff); + newValue.m_i32 = oldValue.m_i32 + 1; + + if (ThreadInterlockedAssignIf(&m_lockInfo.m_i32, newValue.m_i32, + oldValue.m_i32)) { + ThreadMemoryBarrier(); + RWLAssert(m_lockInfo.m_fWriting == 0); + return true; + } + } + return false; +} + +inline bool CThreadSpinRWLock::TryLockForRead_UnforcedInline() { +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (m_lockInfo.m_i32 & 0x00010000) // m_lockInfo.m_fWriting + { + if (m_writerId == ThreadGetCurrentId()) { + m_lockInfo.m_nReaders++; + return true; + } + + return false; + } +#endif + return TryLockForRead(); +} + +FORCEINLINE void CThreadSpinRWLock::LockForRead() { + if (!TryLockForRead()) { + SpinLockForRead(); + } +} + +FORCEINLINE void CThreadSpinRWLock::UnlockWrite() { + RWLAssert(m_writerId == ThreadGetCurrentId()); +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (--m_iWriteDepth == 0) +#endif + { + m_writerId = 0; + ThreadMemoryBarrier(); + m_lockInfo.m_i32 = 0; + } +} + +#ifndef REENTRANT_THREAD_SPIN_RW_LOCK +FORCEINLINE +#else +inline +#endif +void CThreadSpinRWLock::UnlockRead() { + RWLAssert(m_writerId == 0 || + (m_writerId == ThreadGetCurrentId() && m_lockInfo.m_fWriting)); +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (!(m_lockInfo.m_i32 & 0x00010000)) // !m_lockInfo.m_fWriting +#endif + { + ThreadMemoryBarrier(); + ThreadInterlockedDecrement(&m_lockInfo.m_i32); + RWLAssert(m_writerId == 0 && !m_lockInfo.m_fWriting); + } +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + else if (m_writerId == ThreadGetCurrentId()) { + m_lockInfo.m_nReaders--; + } else { + RWLAssert(0); + } +#endif +} + +#else +/* (commented out to reduce distraction in colorized editor, remove entirely +when new implementation settles) inline bool CThreadSpinRWLock::AssignIf( const +LockInfo_t &newValue, const LockInfo_t &comperand ) +{ + // Note: using unions guarantees no aliasing bugs. Casting structures +through *(int64*)& + // may create hard-to-catch bugs because when you do that, +compiler doesn't know that the newly computed pointer + // is actually aliased with LockInfo_t structure. It's rarely a +problem in practice, but when it is, it's a royal pain to debug. return +ThreadInterlockedAssignIf64( &m_lockInfo.m_i64, newValue.m_i64, comperand.m_i64 +); +} + +FORCEINLINE bool CThreadSpinRWLock::TryLockForWrite( const uint32 threadId ) +{ + // In order to grab a write lock, there can be no readers and no owners +of the write lock if ( m_lockInfo.m_nReaders > 0 || ( m_lockInfo.m_writerId && +m_lockInfo.m_writerId != threadId ) ) + { + return false; + } + + static const LockInfo_t oldValue = { {0, 0} }; + LockInfo_t newValue = { { threadId, 0 } }; + if ( AssignIf( newValue, oldValue ) ) + { + ThreadMemoryBarrier(); + return true; + } + return false; +} + +inline bool CThreadSpinRWLock::TryLockForWrite() +{ + m_nWriters++; + if ( !TryLockForWrite( ThreadGetCurrentId() ) ) + { + m_nWriters--; + return false; + } + return true; +} + +FORCEINLINE bool CThreadSpinRWLock::TryLockForRead() +{ + if ( m_nWriters != 0 ) + { + return false; + } + // In order to grab a write lock, the number of readers must not change +and no thread can own the write LockInfo_t oldValue; LockInfo_t newValue; + + if( IsX360() || IsPS3() ) + { + // this is the code equivalent to original code (see below) that +doesn't cause LHS on Xbox360 + // WARNING: This code assumes BIG Endian CPU + oldValue.m_i64 = uint32( m_lockInfo.m_nReaders ); + newValue.m_i64 = oldValue.m_i64 + 1; // NOTE: when we have -1 +(or 0xFFFFFFFF) readers, this will result in non-equivalent code + } + else + { + // this is the original code that worked here for a while + oldValue.m_nReaders = m_lockInfo.m_nReaders; + oldValue.m_writerId = 0; + newValue.m_nReaders = oldValue.m_nReaders + 1; + newValue.m_writerId = 0; + } + + if ( AssignIf( newValue, oldValue ) ) + { + ThreadMemoryBarrier(); + return true; + } + return false; +} + +inline void CThreadSpinRWLock::LockForWrite() +{ + const uint32 threadId = ThreadGetCurrentId(); + + m_nWriters++; + + if ( !TryLockForWrite( threadId ) ) + { + ThreadPause(); + SpinLockForWrite( threadId ); + } +} +*/ +#endif + +// read data from a memory address +template +FORCEINLINE T ReadVolatileMemory(T const *pPtr) { + volatile const T *pVolatilePtr = (volatile const T *)pPtr; + return *pVolatilePtr; +} + +//----------------------------------------------------------------------------- + +#if defined(_PS3) +BOOL SetEvent(CThreadEvent *pEvent); +BOOL ResetEvent(CThreadEvent *pEvent); +DWORD WaitForMultipleObjects(DWORD nCount, CThreadEvent **lppHandles, + BOOL bWaitAll, DWORD dwMilliseconds); +#endif // _PS3 +#endif // VPC_TIER0_THREADTOOLS_H_ diff --git a/public/tier0/threadtools.inl b/public/tier0/threadtools.inl new file mode 100644 index 0000000..e0a09ab --- /dev/null +++ b/public/tier0/threadtools.inl @@ -0,0 +1,579 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_THREADTOOLS_INL_ +#define VPC_TIER0_THREADTOOLS_INL_ + +// This file is included in threadtools.h for PS3 and threadtools.cpp for all +// other platforms +// +// Do not #include other files here + +#ifndef _PS3 +// this is defined in the .cpp for the PS3 to avoid introducing a dependency for +// files including the header +CTHREADLOCALPTR(CThread) g_pCurThread; + +#define INLINE_ON_PS3 +#else +// Inlining these functions on PS3 (which are called across PRX boundaries) +// saves us over 1ms per frame +#define INLINE_ON_PS3 inline +#endif + +INLINE_ON_PS3 CThread::CThread() + : +#ifdef _WIN32 + m_hThread(NULL), + m_threadId(0), +#elif defined(_PS3) || defined(_POSIX) + m_threadId(0), + m_threadZombieId(0), +#endif + m_result(0), + m_pStackBase(NULL), + m_flags(0) { + m_szName[0] = 0; + m_NotSuspendedEvent.Set(); +} + +//--------------------------------------------------------- + +INLINE_ON_PS3 CThread::~CThread() { +#ifdef MSVC + if (m_hThread) +#elif defined(POSIX) && !defined(_PS3) + if (m_threadId) +#endif + { + if (IsAlive()) { + Msg("Illegal termination of worker thread! Threads must negotiate an end " + "to the thread before the CThread object is destroyed.\n"); +#ifdef _WIN32 + + (void)DoNewAssertDialog( + __FILE__, __LINE__, + "Illegal termination of worker thread! Threads must negotiate an end " + "to the thread before the CThread object is destroyed.\n"); +#endif + if (GetCurrentCThread() == this) { + Stop(); // BUGBUG: Alfred - this doesn't make sense, this destructor + // fires from the hosting thread not the thread itself!! + } + } + } +#if defined(POSIX) || defined(_PS3) + if (m_threadZombieId) { + // just clean up zombie threads immediately (the destructor is fired from + // the hosting thread) + Join(); + } +#endif +} + +//--------------------------------------------------------- + +INLINE_ON_PS3 const char *CThread::GetName() { + AUTO_LOCK(m_Lock); + if (!m_szName[0]) { +#if defined(_WIN32) + _snprintf(m_szName, sizeof(m_szName) - 1, "Thread(%p/%p)", this, m_hThread); +#elif defined(_PS3) + snprintf(m_szName, sizeof(m_szName) - 1, "Thread(%p)", this); +#elif defined(POSIX) + _snprintf(m_szName, sizeof(m_szName) - 1, "Thread(%p/0x%x)", this, + (uint)m_threadId); +#endif + m_szName[sizeof(m_szName) - 1] = 0; + } + return m_szName; +} + +//--------------------------------------------------------- + +INLINE_ON_PS3 void CThread::SetName(const char *pszName) { + AUTO_LOCK(m_Lock); + strncpy(m_szName, pszName, sizeof(m_szName) - 1); + m_szName[sizeof(m_szName) - 1] = 0; +} + +//----------------------------------------------------- +// Functions for the other threads +//----------------------------------------------------- + +// Start thread running - error if already running +INLINE_ON_PS3 bool CThread::Start(unsigned nBytesStack, + ThreadPriorityEnum_t nPriority) { + AUTO_LOCK(m_Lock); + + if (IsAlive()) { + AssertMsg(0, "Tried to create a thread that has already been created!"); + return false; + } + + bool bInitSuccess = false; + CThreadEvent createComplete; + ThreadInit_t init = {this, &createComplete, &bInitSuccess}; + +#if defined(THREAD_PARENT_STACK_TRACE_ENABLED) + { + int iValidEntries = GetCallStack_Fast(init.ParentStackTrace, + ARRAYSIZE(init.ParentStackTrace), 0); + for (auto &&e : init.ParentStackTrace) { + e = NULL; + } + } +#endif + +#ifdef PLATFORM_WINDOWS + m_hThread = (HANDLE)CreateThread( + NULL, nBytesStack, (LPTHREAD_START_ROUTINE)GetThreadProc(), + new ThreadInit_t(init), 0, (LPDWORD)&m_threadId); + + if (nPriority != PRIORITY_DEFAULT) { + SetThreadPriority(m_hThread, nPriority); + } + + if (!m_hThread) { + AssertMsg1(0, "Failed to create thread (error 0x%x)", GetLastError()); + return false; + } +#elif PLATFORM_PS3 + // On the PS3, a stack size of 0 doesn't imply a default stack size, so we + // need to force it to our + // own default size. + if (nBytesStack == 0) { + nBytesStack = PS3_SYS_PPU_THREAD_COMMON_STACK_SIZE; + } + + // The thread is about to begin + m_threadEnd.Reset(); + + // sony documentation: + // "If the PPU thread is not joined by sys_ppu_thread_join() after exit, + // it should always be created as non-joinable (not specifying + // SYS_PPU_THREAD_CREATE_JOINABLE). Otherwise, some resources are left + // allocated after termination of the PPU thread as if memory leaks." + const char *threadName = m_szName; + if (sys_ppu_thread_create(&m_threadId, (void (*)(uint64_t))GetThreadProc(), + (uint64_t)(new ThreadInit_t(init)), nPriority, + nBytesStack, SYS_PPU_THREAD_CREATE_JOINABLE, + threadName) != CELL_OK) { + AssertMsg1(0, "Failed to create thread (error 0x%x)", errno); + return false; + } + + bInitSuccess = true; +#elif PLATFORM_POSIX + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, MAX(nBytesStack, 1024u * 1024)); + if (pthread_create(&m_threadId, &attr, (void *(*)(void *))GetThreadProc(), + new ThreadInit_t(init)) != 0) { + AssertMsg1(0, "Failed to create thread (error 0x%x)", GetLastError()); + return false; + } + bInitSuccess = true; +#endif + + if (!WaitForCreateComplete(&createComplete)) { + Msg("Thread failed to initialize\n"); +#ifdef _WIN32 + CloseHandle(m_hThread); + m_hThread = NULL; +#elif defined(_PS3) + m_threadEnd.Set(); + m_threadId = NULL; + m_threadZombieId = 0; +#endif + + return false; + } + + if (!bInitSuccess) { + Msg("Thread failed to initialize\n"); +#ifdef _WIN32 + CloseHandle(m_hThread); + m_hThread = NULL; +#elif defined(POSIX) && !defined(_PS3) + m_threadId = 0; + m_threadZombieId = 0; +#endif + return false; + } + +#ifdef _WIN32 + if (!m_hThread) { + Msg("Thread exited immediately\n"); + } +#endif + +#ifdef _WIN32 + AddThreadHandleToIDMap(m_hThread, m_threadId); + return !!m_hThread; +#elif defined(POSIX) + return !!m_threadId; +#endif +} + +//--------------------------------------------------------- +// +// Return true if the thread has been created and hasn't yet exited +// + +INLINE_ON_PS3 bool CThread::IsAlive() { +#ifdef PLATFORM_WINDOWS + DWORD dwExitCode; + return (m_hThread && GetExitCodeThread(m_hThread, &dwExitCode) && + dwExitCode == STILL_ACTIVE); +#elif defined(POSIX) + return !!m_threadId; +#endif +} + +// This method causes the current thread to wait until this thread +// is no longer alive. +INLINE_ON_PS3 bool CThread::Join(unsigned timeout) { +#ifdef _WIN32 + if (m_hThread) +#elif defined(POSIX) + if (m_threadId || m_threadZombieId) +#endif + { + AssertMsg(GetCurrentCThread() != this, + _T("Thread cannot be joined with self")); + +#ifdef _WIN32 + return ThreadJoin((ThreadHandle_t)m_hThread, timeout); +#elif defined(POSIX) + bool ret = ThreadJoin( + (ThreadHandle_t)(m_threadId ? m_threadId : m_threadZombieId), timeout); + m_threadZombieId = 0; + return ret; +#endif + } + return true; +} + +//--------------------------------------------------------- + +INLINE_ON_PS3 ThreadHandle_t CThread::GetThreadHandle() { +#ifdef _WIN32 + return (ThreadHandle_t)m_hThread; +#else + return (ThreadHandle_t)m_threadId; +#endif +} + +//--------------------------------------------------------- + +INLINE_ON_PS3 int CThread::GetResult() { return m_result; } + +//----------------------------------------------------- +// Functions for both this, and maybe, and other threads +//----------------------------------------------------- + +// Forcibly, abnormally, but relatively cleanly stop the thread +// + +INLINE_ON_PS3 void CThread::Stop(int exitCode) { + if (!IsAlive()) return; + + if (GetCurrentCThread() == this) { +#if !defined(_PS3) + m_result = exitCode; + if (!(m_flags & SUPPORT_STOP_PROTOCOL)) { + OnExit(); + g_pCurThread = NULL; + +#ifdef _WIN32 + RemoveThreadHandleToIDMap(m_hThread); + CloseHandle(m_hThread); + m_hThread = NULL; +#else + m_threadId = 0; + m_threadZombieId = 0; +#endif + } else { + throw exitCode; + } +#else + AssertMsg(false, + "Called CThread::Stop() for a platform that doesn't have it!\n"); +#endif + } else + AssertMsg(0, "Only thread can stop self: Use a higher-level protocol"); +} + +//--------------------------------------------------------- + +// Get the priority +INLINE_ON_PS3 int CThread::GetPriority() const { +#ifdef _WIN32 + return GetThreadPriority(m_hThread); +#elif defined(_PS3) + return ThreadGetPriority((ThreadHandle_t)m_threadId); +#elif defined(POSIX) + struct sched_param thread_param; + int policy; + pthread_getschedparam(m_threadId, &policy, &thread_param); + return thread_param.sched_priority; +#endif +} + +//--------------------------------------------------------- + +// Set the priority +INLINE_ON_PS3 bool CThread::SetPriority(int priority) { +#ifdef WIN32 + return ThreadSetPriority((ThreadHandle_t)m_hThread, priority); +#else + return ThreadSetPriority((ThreadHandle_t)m_threadId, priority); +#endif +} + +//--------------------------------------------------------- + +// Suspend a thread +INLINE_ON_PS3 unsigned CThread::Suspend() { + AssertMsg(ThreadGetCurrentId() == (ThreadId_t)m_threadId, + "Cannot call CThread::Suspend from outside thread"); + + if (ThreadGetCurrentId() != (ThreadId_t)m_threadId) { + DebuggerBreakIfDebugging(); + } + + m_NotSuspendedEvent.Reset(); + m_NotSuspendedEvent.Wait(); + + return 0; +} + +//--------------------------------------------------------- + +INLINE_ON_PS3 unsigned CThread::Resume() { + if (m_NotSuspendedEvent.Check()) { + DevWarning("Called Resume() on a thread that is not suspended!\n"); + } + m_NotSuspendedEvent.Set(); + return 0; +} + +//--------------------------------------------------------- + +// Force hard-termination of thread. Used for critical failures. +INLINE_ON_PS3 bool CThread::Terminate(int exitCode) { +#if defined(_X360) + AssertMsg(0, "Cannot terminate a thread on the Xbox!"); + return false; +#elif defined(_WIN32) + // I hope you know what you're doing! + if (!TerminateThread(m_hThread, exitCode)) return false; + RemoveThreadHandleToIDMap(m_hThread); + CloseHandle(m_hThread); + m_hThread = NULL; +#elif defined(_PS3) + m_threadEnd.Set(); + m_threadId = NULL; +#elif defined(POSIX) + pthread_kill(m_threadId, SIGKILL); + m_threadId = 0; +#endif + return true; +} + +//----------------------------------------------------- +// Global methods +//----------------------------------------------------- + +// Get the Thread object that represents the current thread, if any. +// Can return NULL if the current thread was not created using +// CThread +// + +INLINE_ON_PS3 CThread *CThread::GetCurrentCThread() { +#ifdef _PS3 + return GetCurThreadPS3(); +#else + return g_pCurThread; +#endif +} + +//--------------------------------------------------------- +// +// Offer a context switch. Under Win32, equivalent to Sleep(0) +// + +#ifdef Yield +#undef Yield +#endif +INLINE_ON_PS3 void CThread::Yield() { +#ifdef _WIN32 + ::Sleep(0); +#elif defined(_PS3) + // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. + sys_timer_usleep(60); +#elif defined(POSIX) + pthread_yield(); +#endif +} + +//--------------------------------------------------------- +// +// This method causes the current thread to yield and not to be +// scheduled for further execution until a certain amount of real +// time has elapsed, more or less. Duration is in milliseconds + +INLINE_ON_PS3 void CThread::Sleep(unsigned duration) { +#ifdef _WIN32 + ::Sleep(duration); +#elif defined(_PS3) + sys_timer_usleep(duration * 1000); +#elif defined(POSIX) + usleep(duration * 1000); +#endif +} + +//--------------------------------------------------------- + +// Optional pre-run call, with ability to fail-create. Note Init() +// is forced synchronous with Start() +INLINE_ON_PS3 bool CThread::Init() { return true; } + +//--------------------------------------------------------- + +#if defined(_PS3) +INLINE_ON_PS3 int CThread::Run() { return -1; } +#endif // _PS3 + +// Called when the thread exits +INLINE_ON_PS3 void CThread::OnExit() {} + +// Allow for custom start waiting +INLINE_ON_PS3 bool CThread::WaitForCreateComplete(CThreadEvent *pEvent) { + // Force serialized thread creation... + if (!pEvent->Wait(60000)) { + AssertMsg(0, + "Probably deadlock or failure waiting for thread to initialize."); + return false; + } + return true; +} + +//--------------------------------------------------------- +INLINE_ON_PS3 CThread::ThreadProc_t CThread::GetThreadProc() { + return ThreadProc; +} + +#ifdef PLATFORM_WINDOWS +unsigned long STDCALL CThread::ThreadProc(LPVOID pv) +#else +INLINE_ON_PS3 void *CThread::ThreadProc(LPVOID pv) +#endif +{ +#if defined(POSIX) || defined(_PS3) + ThreadInit_t *pInit = reinterpret_cast(pv); +#else + std::unique_ptr pInit((ThreadInit_t *)pv); +#endif + +#ifdef _X360 + // Make sure all threads are consistent w.r.t floating-point math + SetupFPUControlWord(); +#endif + AllocateThreadID(); + + CThread *pThread = pInit->pThread; +#ifdef _PS3 + SetCurThreadPS3(pThread); +#else + g_pCurThread = pThread; +#endif + + pThread->m_pStackBase = AlignValue(&pThread, 4096); + + pInit->pThread->m_result = -1; + +#if defined(THREAD_PARENT_STACK_TRACE_ENABLED) + CStackTop_ReferenceParentStack stackTop(pInit->ParentStackTrace, + ARRAYSIZE(pInit->ParentStackTrace)); +#endif + + bool bInitSuccess = true; + if (pInit->pfInitSuccess) *(pInit->pfInitSuccess) = false; + +#ifdef _PS3 + *(pInit->pfInitSuccess) = pInit->pThread->Init(); +#else + try { + bInitSuccess = pInit->pThread->Init(); + } + + catch (...) { + pInit->pInitCompleteEvent->Set(); + throw; + } +#endif // _PS3 + + if (pInit->pfInitSuccess) *(pInit->pfInitSuccess) = bInitSuccess; + pInit->pInitCompleteEvent->Set(); + if (!bInitSuccess) return 0; + + if (!Plat_IsInDebugSession() && + (pInit->pThread->m_flags & SUPPORT_STOP_PROTOCOL)) { +#ifndef _PS3 + try +#endif + { + pInit->pThread->m_result = pInit->pThread->Run(); + } +#ifndef _PS3 + catch (...) { + } +#endif + } else { + pInit->pThread->m_result = pInit->pThread->Run(); + } + + pInit->pThread->OnExit(); +#ifdef _PS3 + SetCurThreadPS3(NULL); +#else + g_pCurThread = NULL; +#endif + FreeThreadID(); + + AUTO_LOCK(pThread->m_Lock); +#ifdef _WIN32 + RemoveThreadHandleToIDMap(pThread->m_hThread); + CloseHandle(pThread->m_hThread); + pThread->m_hThread = NULL; +#elif defined(_PS3) + pThread->m_threadZombieId = pThread->m_threadId; + pThread->m_threadEnd.Set(); + pThread->m_threadId = 0; +#elif defined(POSIX) + pThread->m_threadZombieId = pThread->m_threadId; + pThread->m_threadId = 0; +#else +#error +#endif + + pThread->m_ExitEvent.Set(); +#ifdef _PS3 + { + pThread->m_Lock.Unlock(); + sys_ppu_thread_exit(pInit->pThread->m_result); + // reacquire the lock in case thread exit didn't actually exit the thread, + // so that AUTO_LOCK won't double-unlock the lock (to keep it paired) + pThread->m_Lock.Lock(); + } +#endif + +#if defined(POSIX) || defined(_PS3) + return (void *)pInit->pThread->m_result; +#else + return pInit->pThread->m_result; +#endif +} + +#endif // VPC_TIER0_THREADTOOLS_INL_ diff --git a/public/tier0/tslist.h b/public/tier0/tslist.h new file mode 100644 index 0000000..a9fc6c8 --- /dev/null +++ b/public/tier0/tslist.h @@ -0,0 +1,729 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Based on "Lock-Free Techniques for Concurrent Access to Shared Objects" by +// Dominique Fober, Yann Orlarey, Stéphane Letz +// +// LIFO from disassembly of Windows API and +// https://hal.archives-ouvertes.fr/hal-02158796/file/fober-JIM2002.pdf +// +// FIFO from +// https://hal.archives-ouvertes.fr/hal-02158796/file/fober-JIM2002.pdf + +#ifndef VPC_TIER0_TSLIST_H_ +#define VPC_TIER0_TSLIST_H_ + +#if (defined(PLATFORM_X360) || defined(PLATFORM_WINDOWS_PC64)) +#define USE_NATIVE_SLIST +#endif + +#if defined(USE_NATIVE_SLIST) && !defined(_X360) && !defined(_PS3) +#include "winlite.h" +#endif + +#include "tier0/dbg.h" +#include "tier0/threadtools.h" + +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- + +#if defined(PLATFORM_WINDOWS_PC64) + +#define TSLIST_HEAD_ALIGNMENT 16 // MEMORY_ALLOCATION_ALIGNMENT +#define TSLIST_NODE_ALIGNMENT 16 // MEMORY_ALLOCATION_ALIGNMENT + +#define TSLIST_HEAD_ALIGN ALIGN16 +#define TSLIST_NODE_ALIGN ALIGN16 +#define TSLIST_HEAD_ALIGN_POST ALIGN16_POST +#define TSLIST_NODE_ALIGN_POST ALIGN16_POST + +#else + +#define TSLIST_HEAD_ALIGNMENT 8 +#define TSLIST_NODE_ALIGNMENT 8 + +#define TSLIST_HEAD_ALIGN ALIGN8 +#define TSLIST_NODE_ALIGN ALIGN8 +#define TSLIST_HEAD_ALIGN_POST ALIGN8_POST +#define TSLIST_NODE_ALIGN_POST ALIGN8_POST + +#endif + +//----------------------------------------------------------------------------- + +PLATFORM_INTERFACE bool RunTSQueueTests(int nListSize = 10000, int nTests = 1); +PLATFORM_INTERFACE bool RunTSListTests(int nListSize = 10000, int nTests = 1); + +//----------------------------------------------------------------------------- +// Lock free list. +//----------------------------------------------------------------------------- +//#define USE_NATIVE_SLIST + +#ifdef USE_NATIVE_SLIST +typedef SLIST_ENTRY TSLNodeBase_t; +typedef SLIST_HEADER TSLHead_t; +#else +struct TSLIST_NODE_ALIGN TSLNodeBase_t { + TSLNodeBase_t *Next; // name to match Windows +} TSLIST_NODE_ALIGN_POST; + +union TSLHead_t { + struct Value_t { + TSLNodeBase_t *Next; +// Depth must be in the least significant halfword when atomically +// loading into register, +// to avoid carrying digits from Sequence. Carrying digits from Depth +// to Sequence is ok, because Sequence can be pretty much random. We +// could operate on both of them separately, but it could perhaps (?) +// lead to problems with store forwarding. I don't know 'cause I didn't +// performance-test or design original code, I'm just making it work on +// PowerPC. +#ifdef PLAT_BIG_ENDIAN + int16 Sequence; + int16 Depth; +#else + int16 Depth; + int16 Sequence; +#endif + } value; + + struct Value32_t { + TSLNodeBase_t *Next_do_not_use_me; + int32 DepthAndSequence; + } value32; + + int64 value64; +}; +#endif + +//------------------------------------- + +class TSLIST_HEAD_ALIGN CTSListBase { + public: + CTSListBase() { + if (((size_t)&m_Head) % TSLIST_HEAD_ALIGNMENT != 0) { + Error(_T( "CTSListBase: Misaligned list\n" )); + DebuggerBreak(); + } + +#ifdef USE_NATIVE_SLIST + InitializeSListHead(&m_Head); +#else + m_Head.value64 = (int64)0; +#endif + } + + ~CTSListBase() { Detach(); } + + TSLNodeBase_t *Push(TSLNodeBase_t *pNode) { +#ifdef _DEBUG + if ((size_t)pNode % TSLIST_NODE_ALIGNMENT != 0) { + Error(_T( "CTSListBase: Misaligned node\n" )); + DebuggerBreak(); + } +#endif + +#ifdef USE_NATIVE_SLIST +#ifdef _X360 + // integrated write-release barrier + return (TSLNodeBase_t *)InterlockedPushEntrySListRelease(&m_Head, pNode); +#else + return (TSLNodeBase_t *)InterlockedPushEntrySList(&m_Head, pNode); +#endif +#else + TSLHead_t oldHead; + TSLHead_t newHead; + +#if defined(PLATFORM_PS3) || defined(PLATFORM_X360) + __lwsync(); // write-release barrier +#endif + + for (;;) { + oldHead.value64 = m_Head.value64; + pNode->Next = oldHead.value.Next; + newHead.value.Next = pNode; + + newHead.value32.DepthAndSequence = + oldHead.value32.DepthAndSequence + 0x10001; + + if (ThreadInterlockedAssignIf64(&m_Head.value64, newHead.value64, + oldHead.value64)) { + break; + } + ThreadPause(); + }; + + return (TSLNodeBase_t *)oldHead.value.Next; +#endif + } + + TSLNodeBase_t *Pop() { +#ifdef USE_NATIVE_SLIST +#ifdef _X360 + // integrated read-acquire barrier + TSLNodeBase_t *pNode = + (TSLNodeBase_t *)InterlockedPopEntrySListAcquire(&m_Head); +#else + TSLNodeBase_t *pNode = (TSLNodeBase_t *)InterlockedPopEntrySList(&m_Head); +#endif + return pNode; +#else + TSLHead_t oldHead; + TSLHead_t newHead; + + for (;;) { + oldHead.value64 = m_Head.value64; + if (!oldHead.value.Next) return NULL; + + newHead.value.Next = oldHead.value.Next->Next; + newHead.value32.DepthAndSequence = oldHead.value32.DepthAndSequence - 1; + + if (ThreadInterlockedAssignIf64(&m_Head.value64, newHead.value64, + oldHead.value64)) { +#if defined(PLATFORM_PS3) || defined(PLATFORM_X360) + __lwsync(); // read-acquire barrier +#endif + break; + } + ThreadPause(); + }; + + return (TSLNodeBase_t *)oldHead.value.Next; +#endif + } + + TSLNodeBase_t *Detach() { +#ifdef USE_NATIVE_SLIST + TSLNodeBase_t *pBase = (TSLNodeBase_t *)InterlockedFlushSList(&m_Head); +#if defined(_X360) || defined(_PS3) + __lwsync(); // read-acquire barrier +#endif + return pBase; +#else + TSLHead_t oldHead; + TSLHead_t newHead; + + do { + ThreadPause(); + + oldHead.value64 = m_Head.value64; + if (!oldHead.value.Next) return NULL; + + newHead.value.Next = NULL; + // the reason for AND'ing it instead of poking a short into + // memory + // is probably to avoid store forward issues, but I'm not sure + // because I didn't construct this code. In any case, leaving it + // as is on big-endian + newHead.value32.DepthAndSequence = + oldHead.value32.DepthAndSequence & 0xffff0000; + + } while (!ThreadInterlockedAssignIf64(&m_Head.value64, newHead.value64, + oldHead.value64)); + + return (TSLNodeBase_t *)oldHead.value.Next; +#endif + } + + TSLHead_t *AccessUnprotected() { return &m_Head; } + + int Count() const { +#ifdef USE_NATIVE_SLIST + return QueryDepthSList(const_cast(&m_Head)); +#else + return m_Head.value.Depth; +#endif + } + + private: + TSLHead_t m_Head; +} TSLIST_HEAD_ALIGN_POST; + +//------------------------------------- + +template +class TSLIST_HEAD_ALIGN CTSSimpleList : public CTSListBase { + public: + void Push(T *pNode) { + Assert(sizeof(T) >= sizeof(TSLNodeBase_t)); + CTSListBase::Push((TSLNodeBase_t *)pNode); + } + + T *Pop() { return (T *)CTSListBase::Pop(); } +} TSLIST_HEAD_ALIGN_POST; + +//------------------------------------- +// this is a replacement for CTSList<> and CObjectPool<> that does not +// have a per-item, per-alloc new/delete overhead +// similar to CTSSimpleList except that it allocates it's own pool objects +// and frees them on destruct. Also it does not overlay the TSNodeBase_t memory +// on T's memory +template +class TSLIST_HEAD_ALIGN CTSPool : public CTSListBase { + // packs the node and the item (T) into a single struct and pools those + struct TSLIST_NODE_ALIGN simpleTSPoolStruct_t : public TSLNodeBase_t { + T elem; + } TSLIST_NODE_ALIGN_POST; + + public: + ~CTSPool() { + simpleTSPoolStruct_t *pNode = NULL; + while (1) { + pNode = (simpleTSPoolStruct_t *)CTSListBase::Pop(); + if (!pNode) break; + delete pNode; + } + } + + void PutObject(T *pInfo) { + char *pElem = (char *)pInfo; + pElem -= offsetof(simpleTSPoolStruct_t, elem); + simpleTSPoolStruct_t *pNode = (simpleTSPoolStruct_t *)pElem; + + CTSListBase::Push(pNode); + } + + T *GetObject() { + simpleTSPoolStruct_t *pNode = (simpleTSPoolStruct_t *)CTSListBase::Pop(); + if (!pNode) { + pNode = new simpleTSPoolStruct_t; + } + return &pNode->elem; + } + + // omg windows sdk - why do you #define GetObject()? + FORCEINLINE T *Get() { return GetObject(); } + +} TSLIST_HEAD_ALIGN_POST; +//------------------------------------- + +template +class TSLIST_HEAD_ALIGN CTSList : public CTSListBase { + public: + struct TSLIST_NODE_ALIGN Node_t : public TSLNodeBase_t { + Node_t() = default; + Node_t(const T &init) : elem(init) {} + + T elem; + + } TSLIST_NODE_ALIGN_POST; + + ~CTSList() { Purge(); } + + void Purge() { + Node_t *pCurrent = Detach(); + Node_t *pNext; + while (pCurrent) { + pNext = (Node_t *)pCurrent->Next; + delete pCurrent; + pCurrent = pNext; + } + } + + void RemoveAll() { Purge(); } + + Node_t *Push(Node_t *pNode) { return (Node_t *)CTSListBase::Push(pNode); } + + Node_t *Pop() { return (Node_t *)CTSListBase::Pop(); } + + void PushItem(const T &init) { Push(new Node_t(init)); } + + bool PopItem(T *pResult) { + Node_t *pNode = Pop(); + if (!pNode) return false; + *pResult = pNode->elem; + delete pNode; + return true; + } + + Node_t *Detach() { return (Node_t *)CTSListBase::Detach(); } + +} TSLIST_HEAD_ALIGN_POST; + +//------------------------------------- + +template +class TSLIST_HEAD_ALIGN CTSListWithFreeList : public CTSListBase { + public: + struct TSLIST_NODE_ALIGN Node_t : public TSLNodeBase_t { + Node_t() = default; + Node_t(const T &init) : elem(init) {} + + T elem; + } TSLIST_NODE_ALIGN_POST; + + ~CTSListWithFreeList() { Purge(); } + + void Purge() { + Node_t *pCurrent = Detach(); + Node_t *pNext; + while (pCurrent) { + pNext = (Node_t *)pCurrent->Next; + delete pCurrent; + pCurrent = pNext; + } + pCurrent = (Node_t *)m_FreeList.Detach(); + while (pCurrent) { + pNext = (Node_t *)pCurrent->Next; + delete pCurrent; + pCurrent = pNext; + } + } + + void RemoveAll() { + Node_t *pCurrent = Detach(); + Node_t *pNext; + while (pCurrent) { + pNext = (Node_t *)pCurrent->Next; + m_FreeList.Push(pCurrent); + pCurrent = pNext; + } + } + + Node_t *Push(Node_t *pNode) { return (Node_t *)CTSListBase::Push(pNode); } + + Node_t *Pop() { return (Node_t *)CTSListBase::Pop(); } + + void PushItem(const T &init) { + Node_t *pNode = (Node_t *)m_FreeList.Pop(); + if (!pNode) { + pNode = new Node_t; + } + pNode->elem = init; + Push(pNode); + } + + bool PopItem(T *pResult) { + Node_t *pNode = Pop(); + if (!pNode) return false; + *pResult = pNode->elem; + m_FreeList.Push(pNode); + return true; + } + + Node_t *Detach() { return (Node_t *)CTSListBase::Detach(); } + + void FreeNode(Node_t *pNode) { m_FreeList.Push(pNode); } + + private: + CTSListBase m_FreeList; +} TSLIST_HEAD_ALIGN_POST; + +//----------------------------------------------------------------------------- +// Lock free queue +// +// A special consideration: the element type should be simple. This code +// actually dereferences freed nodes as part of pop, but later detects +// that. If the item in the queue is a complex type, only bad things can +// come of that. Also, therefore, if you're using Push/Pop instead of +// push item, be aware that the node memory cannot be freed until +// all threads that might have been popping have completed the pop. +// The PushItem()/PopItem() for handles this by keeping a persistent +// free list. Dont mix Push/PushItem. Note also nodes will be freed at the end, +// and are expected to have been allocated with operator new. +//----------------------------------------------------------------------------- + +template +class TSLIST_HEAD_ALIGN CTSQueue { + public: + struct TSLIST_NODE_ALIGN Node_t { + Node_t() : pNext(nullptr) {} + Node_t(const T &init) : elem(init), pNext(nullptr) {} + + Node_t *pNext; + T elem; + } TSLIST_NODE_ALIGN_POST; + + union TSLIST_HEAD_ALIGN NodeLink_t { + struct Value_t { + Node_t *pNode; + int32 sequence; + } value; + + int64 value64; + } TSLIST_HEAD_ALIGN_POST; + + CTSQueue() { + static_assert(sizeof(Node_t) >= sizeof(TSLNodeBase_t)); + if (((size_t)&m_Head) % TSLIST_HEAD_ALIGNMENT != 0) { + Error("CTSQueue: Misaligned queue\n"); + DebuggerBreak(); + } + if (((size_t)&m_Tail) % TSLIST_HEAD_ALIGNMENT != 0) { + Error("CTSQueue: Misaligned queue\n"); + DebuggerBreak(); + } + m_Count = 0; + m_Head.value.sequence = m_Tail.value.sequence = 0; + m_Head.value.pNode = m_Tail.value.pNode = + new Node_t; // list always contains a dummy node + m_Head.value.pNode->pNext = End(); + } + + ~CTSQueue() { + Purge(); + Assert(m_Count == 0); + Assert(m_Head.value.pNode == m_Tail.value.pNode); + Assert(m_Head.value.pNode->pNext == End()); + delete m_Head.value.pNode; + } + + // Note: Purge, RemoveAll, and Validate are *not* threadsafe + void Purge() { + if (IsDebug()) { + Validate(); + } + + Node_t *pNode; + while ((pNode = Pop()) != NULL) { + delete pNode; + } + + while (bFreeList && (pNode = (Node_t *)m_FreeNodes.Pop()) != NULL) { + delete pNode; + } + + Assert(m_Count == 0); + Assert(m_Head.value.pNode == m_Tail.value.pNode); + Assert(m_Head.value.pNode->pNext == End()); + + m_Head.value.sequence = m_Tail.value.sequence = 0; + } + + void RemoveAll() { + if (IsDebug()) { + Validate(); + } + + Node_t *pNode; + while (bFreeList && (pNode = Pop()) != NULL) { + m_FreeNodes.Push((TSLNodeBase_t *)pNode); + } + } + + bool Validate() { + bool bResult = true; + int nNodes = 0; + + if (m_Count == 0) { + if (m_Head.value.pNode != m_Tail.value.pNode) { + DebuggerBreakIfDebugging(); + bResult = false; + } + } + + Node_t *pNode = m_Head.value.pNode; + while (pNode != End()) { + nNodes++; + pNode = pNode->pNext; + } + + nNodes--; // skip dummy node + + if (nNodes != m_Count) { + DebuggerBreakIfDebugging(); + bResult = false; + } + + if (!bResult) { + Msg("Corrupt CTSQueueDetected"); + } + + return bResult; + } + + void FinishPush(Node_t *pNode, const NodeLink_t &oldTail) { + NodeLink_t newTail; + + newTail.value.pNode = pNode; + newTail.value.sequence = oldTail.value.sequence + 1; + + ThreadMemoryBarrier(); + + InterlockedCompareExchangeNodeLink(&m_Tail, newTail, oldTail); + } + + Node_t *Push(Node_t *pNode) { +#ifdef _DEBUG + if ((size_t)pNode % TSLIST_NODE_ALIGNMENT != 0) { + Error("CTSListBase: Misaligned node\n"); + DebuggerBreak(); + } +#endif + + NodeLink_t oldTail; + + pNode->pNext = End(); + + for (;;) { + oldTail.value.sequence = m_Tail.value.sequence; + oldTail.value.pNode = m_Tail.value.pNode; + if (InterlockedCompareExchangeNode(&(oldTail.value.pNode->pNext), pNode, + End()) == End()) { + break; + } else { + // Another thread is trying to push, help it along + FinishPush(oldTail.value.pNode->pNext, oldTail); + } + } + + FinishPush(pNode, oldTail); // This can fail if another thread pushed + // between the sequence and node grabs above. + // Later pushes or pops corrects + + ++m_Count; + + return oldTail.value.pNode; + } + + Node_t *Pop() { +#define TSQUEUE_BAD_NODE_LINK ((Node_t *)INT_TO_POINTER(0xdeadbeef)) + NodeLink_t *volatile pHead = &m_Head; + NodeLink_t *volatile pTail = &m_Tail; + Node_t *volatile *pHeadNode = &m_Head.value.pNode; + volatile int *volatile pHeadSequence = &m_Head.value.sequence; + Node_t *volatile *pTailNode = &pTail->value.pNode; + + NodeLink_t head; + NodeLink_t newHead; + Node_t *pNext; + int tailSequence; + T elem; + + for (;;) { + head.value.sequence = + *pHeadSequence; // must grab sequence first, which allows condition + // below to ensure pNext is valid + ThreadMemoryBarrier(); // need a barrier to prevent reordering of these + // assignments + head.value.pNode = *pHeadNode; + tailSequence = pTail->value.sequence; + pNext = head.value.pNode->pNext; + + if (pNext && + head.value.sequence == + *pHeadSequence) // Checking pNext only to force optimizer to not + // reorder the assignment to pNext and the + // compare of the sequence + { + if (bTestOptimizer) { + if (pNext == TSQUEUE_BAD_NODE_LINK) { + Msg("Bad node link detected\n"); + continue; + } + } + if (head.value.pNode == *pTailNode) { + if (pNext == End()) { + return NULL; + } + + // Another thread is trying to push, help it along + NodeLink_t &oldTail = + head; // just reuse local memory for head to build old tail + oldTail.value.sequence = tailSequence; // reuse head pNode + FinishPush(pNext, oldTail); + } else if (pNext != End()) { + elem = + pNext->elem; // NOTE: next could be a freed node here, by design + newHead.value.pNode = pNext; + newHead.value.sequence = head.value.sequence + 1; + if (InterlockedCompareExchangeNodeLink(pHead, newHead, head)) { + ThreadMemoryBarrier(); + + if (bTestOptimizer) { + head.value.pNode->pNext = TSQUEUE_BAD_NODE_LINK; + } + break; + } + } + } + } + + --m_Count; + head.value.pNode->elem = elem; + return head.value.pNode; + } + + void FreeNode(Node_t *pNode) { + if (bFreeList) { + m_FreeNodes.Push((TSLNodeBase_t *)pNode); + } else { + delete pNode; + } + } + + void PushItem(const T &init) { + Node_t *pNode; + if (bFreeList && (pNode = (Node_t *)m_FreeNodes.Pop()) != NULL) { + pNode->elem = init; + } else { + pNode = new Node_t(init); + } + Push(pNode); + } + + bool PopItem(T *pResult) { + Node_t *pNode = Pop(); + if (!pNode) return false; + *pResult = pNode->elem; + if (bFreeList) { + m_FreeNodes.Push((TSLNodeBase_t *)pNode); + } else { + delete pNode; + } + return true; + } + + int Count() { return m_Count; } + + private: + Node_t *End() { return (Node_t *)this; } // just need a unique signifier + +#ifndef _WIN64 + Node_t *InterlockedCompareExchangeNode(Node_t *volatile *ppNode, + Node_t *value, Node_t *comperand) { + return (Node_t *)::ThreadInterlockedCompareExchangePointer( + (void **)ppNode, value, comperand); + } + + bool InterlockedCompareExchangeNodeLink(NodeLink_t volatile *pLink, + const NodeLink_t &value, + const NodeLink_t &comperand) { + return ThreadInterlockedAssignIf64((int64 *)pLink, value.value64, + comperand.value64); + } + +#else + Node_t *InterlockedCompareExchangeNode(Node_t *volatile *ppNode, + Node_t *value, Node_t *comperand) { + AUTO_LOCK(m_ExchangeMutex); + Node_t *retVal = *ppNode; + if (*ppNode == comperand) *ppNode = value; + return retVal; + } + + bool InterlockedCompareExchangeNodeLink(NodeLink_t volatile *pLink, + const NodeLink_t &value, + const NodeLink_t &comperand) { + AUTO_LOCK(m_ExchangeMutex); + if (pLink->value64 == comperand.value64) { + pLink->value64 = value.value64; + return true; + } + return false; + } + + CThreadFastMutex m_ExchangeMutex; +#endif + + NodeLink_t m_Head; + NodeLink_t m_Tail; + + CInterlockedInt m_Count; + + CTSListBase m_FreeNodes; +} TSLIST_HEAD_ALIGN_POST; + +#include "tier0/memdbgoff.h" + +#endif // VPC_TIER0_TSLIST_H_ diff --git a/public/tier0/validator.h b/public/tier0/validator.h new file mode 100644 index 0000000..4718748 --- /dev/null +++ b/public/tier0/validator.h @@ -0,0 +1,61 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_VALIDATOR_H_ +#define VPC_TIER0_VALIDATOR_H_ + +#include "valobject.h" + +#ifdef DBGFLAG_VALIDATE + +class CValidator { + public: + // Constructors & destructors + CValidator(void); + ~CValidator(void); + + // Call this each time we enter a new Validate function + void Push(tchar *pchType, void *pvObj, tchar *pchName); + + // Call this each time we exit a Validate function + void Pop(void); + + // Claim ownership of a memory block + void ClaimMemory(void *pvMem); + + // Finish performing a check and perform necessary computations + void Finalize(void); + + // Render our results to the console + void RenderObjects(int cubThreshold); // Render all reported objects + void RenderLeaks(void); // Render all memory leaks + + // List manipulation functions: + CValObject *FindObject( + void *pvObj); // Returns CValObject containing pvObj, or NULL. + void DiffAgainst( + CValidator *pOtherValidator); // Removes any entries from this validator + // that are also present in the other. + + // Accessors + bool BMemLeaks(void) { return m_bMemLeaks; }; + CValObject *PValObjectFirst(void) { return m_pValObjectFirst; }; + + void Validate(CValidator &validator, + tchar *pchName); // Validate our internal structures + + private: + CValObject *m_pValObjectFirst; // Linked list of all ValObjects + CValObject *m_pValObjectLast; // Last ValObject on the linked list + + CValObject *m_pValObjectCur; // Object we're current processing + + int m_cpvOwned; // Total # of blocks owned + + int m_cpubLeaked; // # of leaked memory blocks + int m_cubLeaked; // Amount of leaked memory + bool m_bMemLeaks; // Has any memory leaked? +}; + +#endif // DBGFLAG_VALIDATE + +#endif // VPC_TIER0_VALIDATOR_H_ diff --git a/public/tier0/valobject.h b/public/tier0/valobject.h new file mode 100644 index 0000000..184fbd9 --- /dev/null +++ b/public/tier0/valobject.h @@ -0,0 +1,66 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: CValObject is used for tracking individual objects that report +// in to CValidator. Whenever a new object reports in (via CValidator::Push), +// we create a new CValObject to aggregate stats for it. + +#ifndef VPC_TIER0_VALOBJECT_H_ +#define VPC_TIER0_VALOBJECT_H_ + +#ifdef DBGFLAG_VALIDATE +class CValObject { + public: + // Constructors & destructors + CValObject(void){}; + ~CValObject(void); + + void Init(tchar *pchType, void *pvObj, tchar *pchName, + CValObject *pValObjectParent, CValObject *pValObjectPrev); + + // Our object has claimed ownership of a memory block + void ClaimMemoryBlock(void *pvMem); + + // A child of ours has claimed ownership of a memory block + void ClaimChildMemoryBlock(int cubUser); + + // Accessors + tchar *PchType(void) { return m_rgchType; }; + void *PvObj(void) { return m_pvObj; }; + tchar *PchName(void) { return m_rgchName; }; + CValObject *PValObjectParent(void) { return m_pValObjectParent; }; + int NLevel(void) { return m_nLevel; }; + CValObject *PValObjectNext(void) { return m_pValObjectNext; }; + int CpubMemSelf(void) { return m_cpubMemSelf; }; + int CubMemSelf(void) { return m_cubMemSelf; }; + int CpubMemTree(void) { return m_cpubMemTree; }; + int CubMemTree(void) { return m_cubMemTree; }; + int NUser(void) { return m_nUser; }; + void SetNUser(int nUser) { m_nUser = nUser; }; + void SetBNewSinceSnapshot(bool bNewSinceSnapshot) { + m_bNewSinceSnapshot = bNewSinceSnapshot; + } + bool BNewSinceSnapshot(void) { return m_bNewSinceSnapshot; } + + private: + bool m_bNewSinceSnapshot; // If this block is new since the snapshot. + tchar m_rgchType[64]; // Type of the object we represent + tchar m_rgchName[64]; // Name of this particular object + void *m_pvObj; // Pointer to the object we represent + + CValObject *m_pValObjectParent; // Our parent object in the tree. + int m_nLevel; // Our depth in the tree + + CValObject *m_pValObjectNext; // Next ValObject in the linked list + + int m_cpubMemSelf; // # of memory blocks we own directly + int m_cubMemSelf; // Total size of the memory blocks we own directly + + int m_cpubMemTree; // # of memory blocks owned by us and our children + int m_cubMemTree; // Total size of the memory blocks owned by us and our + // children + + int m_nUser; // Field provided for use by our users +}; +#endif // DBGFLAG_VALIDATE + +#endif // VPC_TIER0_VALOBJECT_H_ diff --git a/public/tier0/valve_off.h b/public/tier0/valve_off.h new file mode 100644 index 0000000..b365999 --- /dev/null +++ b/public/tier0/valve_off.h @@ -0,0 +1,22 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: This turns off all Valve-specific #defines. Because we +// sometimes call external include files from inside .cpp files, we need to wrap +// those includes like this: #include "tier0/valve_off.h" #include +// #include "tier0/valve_on.h" + +#ifdef STEAM + +// Unicode-related #defines (see wchartypes.h) +#undef char + +// Memory-related #defines +#undef malloc +#undef realloc +#undef _expand +#undef free + +#endif // STEAM + +// Allow long to be used in 3rd-party headers +#undef long \ No newline at end of file diff --git a/public/tier0/valve_on.h b/public/tier0/valve_on.h new file mode 100644 index 0000000..fe1dfeb --- /dev/null +++ b/public/tier0/valve_on.h @@ -0,0 +1,28 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: This turns on all Valve-specific #defines. Because we sometimes +// call external include files from inside .cpp files, we need to +// wrap those includes like this: +// #include "tier0/valve_off.h" +// #include +// #include "tier0/valve_on.h" + +#ifdef STEAM + +// Unicode-related #defines (see wchartypes.h) +#ifdef ENFORCE_WCHAR +#define char DontUseChar_SeeWcharOn.h +#endif + +// Memory-related #defines +#define malloc(cub) HEY_DONT_USE_MALLOC_USE_PVALLOC +#define realloc(pvOld, cub) HEY_DONT_USE_REALLOC_USE_PVREALLOC +#define _expand(pvOld, cub) HEY_DONT_USE_EXPAND_USE_PVEXPAND +#define free(pv) HEY_DONT_USE_FREE_USE_FREEPV + +#endif + +// Long is evil because it's treated differently by different compilers +#ifdef DISALLOW_USE_OF_LONG +#define long long_is_the_devil_stop_using_it_use_int32_or_int64 +#endif diff --git a/public/tier0/vprof.h b/public/tier0/vprof.h new file mode 100644 index 0000000..5503f3f --- /dev/null +++ b/public/tier0/vprof.h @@ -0,0 +1,1421 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Real-Time Hierarchical Profiling + +#ifndef VPC_TIER0_VPROF_H_ +#define VPC_TIER0_VPROF_H_ + +#include "tier0/dbg.h" +#include "tier0/fasttimer.h" +#include "tier0/l2cache.h" +#include "tier0/threadtools.h" +#include "tier0/vprof_sn.h" + +// VProf is enabled by default in all configurations -except- X360 Retail. +#if !(defined(_GAMECONSOLE) && defined(_CERT)) +#define VPROF_ENABLED +#endif + +#if defined(_X360) && defined(VPROF_ENABLED) + +// PIX is always enabled in PROFILE build on X360 +#ifdef PROFILE +#define VPROF_PIX 1 +#endif + +#include "tier0/pmc360.h" +#ifndef USE_PIX +#define VPROF_UNDO_PIX +#undef _PIX_H_ +#undef PIXBeginNamedEvent +#undef PIXEndNamedEvent +#undef PIXSetMarker +#undef PIXNameThread +#define USE_PIX +#include +#undef USE_PIX +#else +#include +#endif +#endif + +// enable this to get detailed nodes beneath budget +//#define VPROF_LEVEL 1 + +#if defined(_X360) || defined(_PS3) +#define VPROF_VXCONSOLE_EXISTS 1 +#endif + +#if defined(_X360) && defined(VPROF_PIX) +#pragma comment(lib, "Xapilibi") +#endif + +//----------------------------------------------------------------------------- +// +// Profiling instrumentation macros +// + +#define MAXCOUNTERS 256 + +#ifdef VPROF_ENABLED + +#define VPROF_VTUNE_GROUP + +#define VPROF(name) \ + VPROF_(name, 1, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, 0) +#define VPROF_ASSERT_ACCOUNTED(name) \ + VPROF_(name, 1, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, true, 0) +#define VPROF_(name, detail, group, bAssertAccounted, budgetFlags) \ + VPROF_##detail(name, group, bAssertAccounted, budgetFlags) + +#define VPROF_BUDGET(name, group) \ + VPROF_BUDGET_FLAGS(name, group, BUDGETFLAG_OTHER) +#define VPROF_BUDGET_FLAGS(name, group, flags) \ + VPROF_(name, 0, group, false, flags) + +#define VPROF_SCOPE_BEGIN(tag) \ + do { \ + VPROF(tag) +#define VPROF_SCOPE_END() \ + } \ + while (0) + +#define VPROF_ONLY(expression) (expression) + +#define VPROF_ENTER_SCOPE(name) \ + g_VProfCurrentProfile.EnterScope( \ + name, 1, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, 0) +#define VPROF_EXIT_SCOPE() g_VProfCurrentProfile.ExitScope() + +#define VPROF_BUDGET_GROUP_ID_UNACCOUNTED 0 + +// Budgetgroup flags. These are used with VPROF_BUDGET_FLAGS. +// These control which budget panels the groups show up in. +// If a budget group uses VPROF_BUDGET, it gets the default +// which is BUDGETFLAG_OTHER. +#define BUDGETFLAG_CLIENT (1 << 0) // Shows up in the client panel. +#define BUDGETFLAG_SERVER (1 << 1) // Shows up in the server panel. +#define BUDGETFLAG_OTHER \ + (1 << 2) // Unclassified (the client shows these but the dedicated server + // doesn't). +#define BUDGETFLAG_HIDDEN (1 << 15) +#define BUDGETFLAG_ALL 0xFFFF + +// NOTE: You can use strings instead of these defines. . they are defined here +// and added in vprof.cpp so that they are always in the same order. +#define VPROF_BUDGETGROUP_OTHER_UNACCOUNTED _T("Unaccounted") +#define VPROF_BUDGETGROUP_WORLD_RENDERING _T("World Rendering") +#define VPROF_BUDGETGROUP_DISPLACEMENT_RENDERING _T("Displacement_Rendering") +#define VPROF_BUDGETGROUP_GAME _T("Game") +#define VPROF_BUDGETGROUP_NPCS _T("NPCs") +#define VPROF_BUDGETGROUP_SERVER_ANIM _T("Server Animation") +#define VPROF_BUDGETGROUP_PHYSICS _T("Physics") +#define VPROF_BUDGETGROUP_STATICPROP_RENDERING _T("Static_Prop_Rendering") +#define VPROF_BUDGETGROUP_MODEL_RENDERING _T("Other_Model_Rendering") +#define VPROF_BUDGETGROUP_MODEL_FAST_PATH_RENDERING \ + _T("Fast Path Model Rendering") +#define VPROF_BUDGETGROUP_BRUSH_FAST_PATH_RENDERING \ + _T("Fast Path Brush Rendering") +#define VPROF_BUDGETGROUP_BRUSHMODEL_RENDERING _T("Brush_Model_Rendering") +#define VPROF_BUDGETGROUP_SHADOW_RENDERING _T("Shadow_Rendering") +#define VPROF_BUDGETGROUP_DETAILPROP_RENDERING _T("Detail_Prop_Rendering") +#define VPROF_BUDGETGROUP_PARTICLE_RENDERING _T("Particle/Effect_Rendering") +#define VPROF_BUDGETGROUP_ROPES _T("Ropes") +#define VPROF_BUDGETGROUP_DLIGHT_RENDERING _T("Dynamic_Light_Rendering") +#define VPROF_BUDGETGROUP_OTHER_NETWORKING _T("Networking") +#define VPROF_BUDGETGROUP_CLIENT_ANIMATION _T("Client_Animation") +#define VPROF_BUDGETGROUP_OTHER_SOUND _T("Sound") +#define VPROF_BUDGETGROUP_OTHER_VGUI _T("VGUI") +#define VPROF_BUDGETGROUP_OTHER_FILESYSTEM _T("FileSystem") +#define VPROF_BUDGETGROUP_PREDICTION _T("Prediction") +#define VPROF_BUDGETGROUP_INTERPOLATION _T("Interpolation") +#define VPROF_BUDGETGROUP_SWAP_BUFFERS _T("Swap_Buffers") +#define VPROF_BUDGETGROUP_PLAYER _T("Player") +#define VPROF_BUDGETGROUP_OCCLUSION _T("Occlusion") +#define VPROF_BUDGETGROUP_OVERLAYS _T("Overlays") +#define VPROF_BUDGETGROUP_TOOLS _T("Tools") +#define VPROF_BUDGETGROUP_LIGHTCACHE _T("Light_Cache") +#define VPROF_BUDGETGROUP_DISP_HULLTRACES _T("Displacement_Hull_Traces") +#define VPROF_BUDGETGROUP_TEXTURE_CACHE _T("Texture_Cache") +#define VPROF_BUDGETGROUP_REPLAY _T("Replay") +#define VPROF_BUDGETGROUP_PARTICLE_SIMULATION _T("Particle Simulation") +#define VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING _T("Flashlight Shadows") +#define VPROF_BUDGETGROUP_CLIENT_SIM \ + _T("Client Simulation") // think functions, tempents, etc. +#define VPROF_BUDGETGROUP_STEAM _T("Steam") +#define VPROF_BUDGETGROUP_CVAR_FIND _T("Cvar_Find") +#define VPROF_BUDGETGROUP_CLIENTLEAFSYSTEM _T("ClientLeafSystem") +#define VPROF_BUDGETGROUP_JOBS_COROUTINES _T("Jobs/Coroutines") + +#ifdef VPROF_VXCONSOLE_EXISTS +// update flags +#define VPROF_UPDATE_BUDGET 0x01 // send budget data every frame +#define VPROF_UPDATE_TEXTURE_GLOBAL \ + 0x02 // send global texture data every frame +#define VPROF_UPDATE_TEXTURE_PERFRAME \ + 0x04 // send perframe texture data every frame +#endif + +//------------------------------------- + +#ifndef VPROF_LEVEL +#define VPROF_LEVEL 0 +#endif + +#if !defined(VPROF_SN_LEVEL) && !defined(_CERT) +#define VPROF_SN_LEVEL 0 +#endif + +#define VPROF_0(name, group, assertAccounted, budgetFlags) \ + CVProfScope VProf_(name, 0, group, assertAccounted, budgetFlags); + +#if VPROF_LEVEL > 0 +#define VPROF_1(name, group, assertAccounted, budgetFlags) \ + CVProfScope VProf_(name, 1, group, assertAccounted, budgetFlags); +#else +#if VPROF_SN_LEVEL > 0 && defined(_PS3) +#define VPROF_1(name, group, assertAccounted, budgetFlags) \ + CVProfSnMarkerScope VProfSn_(name) +#else +#define VPROF_1(name, group, assertAccounted, budgetFlags) ((void)0) +#endif +#endif + +#if VPROF_LEVEL > 1 +#define VPROF_2(name, group, assertAccounted, budgetFlags) \ + CVProfScope VProf_(name, 2, group, assertAccounted, budgetFlags); +#else +#if VPROF_SN_LEVEL > 1 && defined(_PS3) +#define VPROF_2(name, group, assertAccounted, budgetFlags) \ + CVProfSnMarkerScope VProfSn_(name) +#else +#define VPROF_2(name, group, assertAccounted, budgetFlags) ((void)0) +#endif +#endif + +#if VPROF_LEVEL > 2 +#define VPROF_3(name, group, assertAccounted, budgetFlags) \ + CVProfScope VProf_(name, 3, group, assertAccounted, budgetFlags); +#else +#if VPROF_SN_LEVEL > 2 && defined(_PS3) +#define VPROF_3(name, group, assertAccounted, budgetFlags) \ + CVProfSnMarkerScope VProfSn_(name) +#else +#define VPROF_3(name, group, assertAccounted, budgetFlags) ((void)0) +#endif +#endif + +#if VPROF_LEVEL > 3 +#define VPROF_4(name, group, assertAccounted, budgetFlags) \ + CVProfScope VProf_(name, 4, group, assertAccounted, budgetFlags); +#else +#if VPROF_SN_LEVEL > 3 && defined(_PS3) +#define VPROF_4(name, group, assertAccounted, budgetFlags) \ + CVProfSnMarkerScope VProfSn_(name) +#else +#define VPROF_4(name, group, assertAccounted, budgetFlags) ((void)0) +#endif +#endif + +//------------------------------------- + +#ifdef _MSC_VER +#define VProfCode(code) \ + if (0) \ + ; \ + else { \ + VPROF(__FUNCTION__ ": " #code); \ + code; \ + } +#else +#define VProfCode(code) \ + if (0) \ + ; \ + else { \ + VPROF(#code); \ + code; \ + } +#endif + +//------------------------------------- + +#define VPROF_INCREMENT_COUNTER(name, amount) \ + do { \ + static CVProfCounter _counter(name); \ + _counter.Increment(amount); \ + } while (0) +#define VPROF_INCREMENT_GROUP_COUNTER(name, group, amount) \ + do { \ + static CVProfCounter _counter(name, group); \ + _counter.Increment(amount); \ + } while (0) +#define VPROF_SET_COUNTER(name, amount) \ + do { \ + static CVProfCounter _counter(name); \ + _counter.Set(amount); \ + } while (0) +#define VPROF_SET_GROUP_COUNTER(name, group, amount) \ + do { \ + static CVProfCounter _counter(name, group); \ + _counter.Set(amount); \ + } while (0) + +#else + +#if defined(VPROF_SN_LEVEL) && (VPROF_SN_LEVEL >= 0) +#define VPROF(name) CVProfSnMarkerScope VProfSn_(name) +#define VPROF_ASSERT_ACCOUNTED(name) VPROF(name) +#define VPROF_(name, detail, group, bAssertAccounted, budgetFlags) \ + VPROF_##detail(name, group, bAssertAccounted, budgetFlags) +#define VPROF_0(name, group, assertAccounted, budgetFlags) VPROF(name) +#define VPROF_BUDGET(name, group) VPROF(name) +#define VPROF_BUDGET_FLAGS(name, group, flags) VPROF(name) + +#define VPROF_SCOPE_BEGIN(tag) \ + do { \ + VPROF(tag) +#define VPROF_SCOPE_END() \ + } \ + while (0) + +#define VPROF_ONLY(expression) (expression) + +#define VPROF_ENTER_SCOPE(name) g_pfnPushMarker(name) +#define VPROF_EXIT_SCOPE() g_pfnPopMarker() +#else +#define VPROF(name) ((void)0) +#define VPROF_ASSERT_ACCOUNTED(name) ((void)0) +#define VPROF_(name, detail, group, bAssertAccounted, budgetFlags) ((void)0) +#define VPROF_0(name, group, assertAccounted, budgetFlags) ((void)0) +#define VPROF_BUDGET(name, group) ((void)0) +#define VPROF_BUDGET_FLAGS(name, group, flags) ((void)0) + +#define VPROF_SCOPE_BEGIN(tag) do { +#define VPROF_SCOPE_END() \ + } \ + while (0) + +#define VPROF_ONLY(expression) ((void)0) + +#define VPROF_ENTER_SCOPE(name) +#define VPROF_EXIT_SCOPE() +#endif + +#if defined(VPROF_SN_LEVEL) && (VPROF_SN_LEVEL >= 1) +#define VPROF_1(name, group, assertAccounted, budgetFlags) VPROF(name) +#else +#define VPROF_1(name, group, assertAccounted, budgetFlags) ((void)0) +#endif + +#if defined(VPROF_SN_LEVEL) && (VPROF_SN_LEVEL >= 2) +#define VPROF_2(name, group, assertAccounted, budgetFlags) VPROF(name) +#else +#define VPROF_2(name, group, assertAccounted, budgetFlags) ((void)0) +#endif + +#if defined(VPROF_SN_LEVEL) && (VPROF_SN_LEVEL >= 3) +#define VPROF_3(name, group, assertAccounted, budgetFlags) VPROF(name) +#else +#define VPROF_3(name, group, assertAccounted, budgetFlags) ((void)0) +#endif + +#if defined(VPROF_SN_LEVEL) && (VPROF_SN_LEVEL >= 4) +#define VPROF_4(name, group, assertAccounted, budgetFlags) VPROF(name) +#else +#define VPROF_4(name, group, assertAccounted, budgetFlags) ((void)0) +#endif + +#define VPROF_INCREMENT_COUNTER(name, amount) ((void)0) +#define VPROF_INCREMENT_GROUP_COUNTER(name, group, amount) ((void)0) +#define VPROF_SET_COUNTER(name, amount) ((void)0) +#define VPROF_SET_GROUP_COUNTER(name, group, amount) ((void)0) + +#define VPROF_TEST_SPIKE(msec) ((void)0) + +#define VProfCode(code) code + +#endif + +//----------------------------------------------------------------------------- + +#ifdef VPROF_ENABLED + +//----------------------------------------------------------------------------- +// +// A node in the call graph hierarchy +// + +class PLATFORM_CLASS CVProfNode { + friend class CVProfRecorder; + friend class CVProfile; + + public: + CVProfNode(const tchar *pszName, int detailLevel, CVProfNode *pParent, + const tchar *pBudgetGroupName, int budgetFlags); + ~CVProfNode(); + + CVProfNode *GetSubNode(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, int budgetFlags); + CVProfNode *GetSubNode(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName); + CVProfNode *GetParent(); + CVProfNode *GetSibling(); + CVProfNode *GetPrevSibling(); + CVProfNode *GetChild(); + + void MarkFrame(); + void ResetPeak(); + + void Pause(); + void Resume(); + void Reset(); + + void EnterScope(); + bool ExitScope(); + + const tchar *GetName(); + + int GetBudgetGroupID() { return m_BudgetGroupID; } + + // Only used by the record/playback stuff. + void SetBudgetGroupID(int id) { m_BudgetGroupID = id; } + + int GetCurCalls(); + double GetCurTime(); + int GetPrevCalls(); + double GetPrevTime(); + unsigned GetTotalCalls(); + double GetTotalTime(); + double GetPeakTime(); + + double GetCurTimeLessChildren(); + double GetPrevTimeLessChildren(); + double GetTotalTimeLessChildren(); + + int GetPrevL2CacheMissLessChildren(); + int GetPrevLoadHitStoreLessChildren(); + + void ClearPrevTime(); + + int GetL2CacheMisses(); + + // Not used in the common case... + void SetCurFrameTime(unsigned long milliseconds); + + void SetClientData(int iClientData) { m_iClientData = iClientData; } + int GetClientData() const { return m_iClientData; } + +#ifdef DBGFLAG_VALIDATE + void Validate(CValidator &validator, + tchar *pchName); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + + // Used by vprof record/playback. + private: + void SetUniqueNodeID(int id) { m_iUniqueNodeID = id; } + + int GetUniqueNodeID() const { return m_iUniqueNodeID; } + + static int s_iCurrentUniqueNodeID; + + private: + const tchar *m_pszName; + CFastTimer m_Timer; + + // L2 Cache data. + int m_iPrevL2CacheMiss; + int m_iCurL2CacheMiss; + int m_iTotalL2CacheMiss; + +#ifndef _X360 + // L2 Cache data. + CL2Cache m_L2Cache; +#else // 360: + + unsigned int m_iBitFlags; // see enum below for settings + CPMCData m_PMCData; + int m_iPrevLoadHitStores; + int m_iCurLoadHitStores; + int m_iTotalLoadHitStores; + + public: + enum FlagBits { + kRecordL2 = 0x01, + kCPUTrace = 0x02, ///< cause a PIX trace inside this node. + }; + // call w/ true to enable L2 and LHS recording; false to turn it off + inline void EnableL2andLHS(bool enable) { + if (enable) + m_iBitFlags |= kRecordL2; + else + m_iBitFlags &= (~kRecordL2); + } + + inline bool IsL2andLHSEnabled(void) { return (m_iBitFlags & kRecordL2) != 0; } + + int GetLoadHitStores(); + + private: + +#endif + + int m_nRecursions; + + unsigned m_nCurFrameCalls; + CCycleCount m_CurFrameTime; + + unsigned m_nPrevFrameCalls; + CCycleCount m_PrevFrameTime; + + unsigned m_nTotalCalls; + CCycleCount m_TotalTime; + + CCycleCount m_PeakTime; + + CVProfNode *m_pParent; + CVProfNode *m_pChild; + CVProfNode *m_pSibling; + + int m_BudgetGroupID; + + int m_iClientData; + int m_iUniqueNodeID; +}; + +//----------------------------------------------------------------------------- +// +// Coordinator and root node of the profile hierarchy tree +// + +enum VProfReportType_t { + VPRT_SUMMARY = (1 << 0), + VPRT_HIERARCHY = (1 << 1), + VPRT_HIERARCHY_TIME_PER_FRAME_AND_COUNT_ONLY = (1 << 2), + VPRT_LIST_BY_TIME = (1 << 3), + VPRT_LIST_BY_TIME_LESS_CHILDREN = (1 << 4), + VPRT_LIST_BY_AVG_TIME = (1 << 5), + VPRT_LIST_BY_AVG_TIME_LESS_CHILDREN = (1 << 6), + VPRT_LIST_BY_PEAK_TIME = (1 << 7), + VPRT_LIST_BY_PEAK_OVER_AVERAGE = (1 << 8), + VPRT_LIST_TOP_ITEMS_ONLY = (1 << 9), + + VPRT_FULL = (0xffffffff & ~(VPRT_HIERARCHY_TIME_PER_FRAME_AND_COUNT_ONLY | + VPRT_LIST_TOP_ITEMS_ONLY)), +}; + +enum CounterGroup_t { + COUNTER_GROUP_DEFAULT = 0, + COUNTER_GROUP_NO_RESET, // The engine doesn't reset these counters. Usually, + // they are used like global variables that can be + // accessed across modules. + COUNTER_GROUP_TEXTURE_GLOBAL, // Global texture usage counters (totals for + // what is currently in memory). + COUNTER_GROUP_TEXTURE_PER_FRAME, // Per-frame texture usage counters. + COUNTER_GROUP_GRAPHICS_PER_FRAME, // Misc graphics counters that are reset + // each frame +}; + +class PLATFORM_CLASS CVProfile { + public: + CVProfile(); + ~CVProfile(); + + void Term(); + + // + // Runtime operations + // + + void Start(); + void Stop(); + + void SetTargetThreadId(unsigned id) { m_TargetThreadId = id; } + unsigned GetTargetThreadId() { return m_TargetThreadId; } + bool InTargetThread() { return (m_TargetThreadId == ThreadGetCurrentId()); } + +#ifdef VPROF_VXCONSOLE_EXISTS + enum VXConsoleReportMode_t { + VXCONSOLE_REPORT_TIME = 0, + VXCONSOLE_REPORT_L2CACHE_MISSES, + VXCONSOLE_REPORT_LOAD_HIT_STORE, + VXCONSOLE_REPORT_COUNT, + }; + + void VXProfileStart(); + void VXProfileUpdate(); + void VXEnableUpdateMode(int event, bool bEnable); + void VXSendNodes(void); + + void PMCDisableAllNodes( + CVProfNode *pStartNode = + NULL); ///< turn off l2 and lhs recording for everywhere + bool PMCEnableL2Upon( + const tchar *pszNodeName, + bool bRecursive = + false); ///< enable l2 and lhs recording for one given node + bool PMCDisableL2Upon( + const tchar *pszNodeName, + bool bRecursive = + false); ///< enable l2 and lhs recording for one given node + + void DumpEnabledPMCNodes(void); + + void VXConsoleReportMode(VXConsoleReportMode_t mode); + void VXConsoleReportScale(VXConsoleReportMode_t mode, float flScale); +#endif + +#ifdef _X360 + + // the CPU trace mode is actually a small state machine; it can be off, primed + // for single capture, primed for everything-in-a-frame capture, or currently + // in everything-in-a-frame capture. + enum CPUTraceState { + kDisabled, + kFirstHitNode, // record from the first time we hit the node until that + // node ends + kAllNodesInFrame_WaitingForMark, // we're going to record all the times a + // node is hit in a frame, but are waiting + // for the frame to start + kAllNodesInFrame_Recording, // we're recording all hits on a node this + // frame. + + // Same as above, but going to record for > 1 frame + kAllNodesInFrame_WaitingForMarkMultiFrame, // we're going to record all the + // times a node is hit in a + // frame, but are waiting for + // the frame to start + kAllNodesInFrame_RecordingMultiFrame, + }; + + // Global switch to turn CPU tracing on or off at all. The idea is you set up + // a node first, then trigger tracing by throwing this to true. It'll reset + // back to false after the trace happens. + inline CPUTraceState GetCPUTraceMode(); + inline void SetCPUTraceEnabled(CPUTraceState enabled, + bool bTraceCompleteEvent = false, + int nNumFrames = -1); + inline void + IncrementMultiTraceIndex(); // tick up the counter that gets appended to the + // multi-per-frame traces + inline unsigned int GetMultiTraceIndex(); // return the counter + void CPUTraceDisableAllNodes( + CVProfNode *pStartNode = + NULL); // disable the cpu trace flag wherever it may be + CVProfNode *CPUTraceEnableForNode( + const tchar *pszNodeName); // enable cpu trace on this node only, + // disabling it wherever else it may be on. + CVProfNode *CPUTraceGetEnabledNode( + CVProfNode *pStartNode = + NULL); // return the node enabled for CPU tracing, or NULL. + const char * + GetCPUTraceFilename(); // get the filename the trace should write into. + const char *SetCPUTraceFilename( + const char *filename); // set the filename the trace should write into. + // (don't specify the extension; I'll do that.) + inline bool TraceCompleteEvent(void); + +#ifdef _X360 + void LatchMultiFrame(int64 cycles); + void SpewWorstMultiFrame(); +#endif + +#endif + + void EnterScope(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, bool bAssertAccounted); + void EnterScope(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, bool bAssertAccounted, + int budgetFlags); + void ExitScope(); + + void MarkFrame(); + void ResetPeaks(); + + void Pause(); + void Resume(); + void Reset(); + + bool IsEnabled() const; + int GetDetailLevel() const; + + bool AtRoot() const; + + // + // Queries + // + +#ifdef VPROF_VTUNE_GROUP +#define MAX_GROUP_STACK_DEPTH 1024 + + void EnableVTuneGroup(const tchar *pGroupName) { + m_nVTuneGroupID = BudgetGroupNameToBudgetGroupID(pGroupName); + m_bVTuneGroupEnabled = true; + } + void DisableVTuneGroup(void) { m_bVTuneGroupEnabled = false; } + + inline void PushGroup(int nGroupID); + inline void PopGroup(void); +#endif + + int NumFramesSampled() { return m_nFrames; } + double GetPeakFrameTime(); + double GetTotalTimeSampled(); + double GetTimeLastFrame(); + + CVProfNode *GetRoot(); + CVProfNode *FindNode(CVProfNode *pStartNode, const tchar *pszNode); + CVProfNode *GetCurrentNode(); + + void OutputReport(int type = VPRT_FULL, const tchar *pszStartNode = NULL, + int budgetGroupID = -1); + + const tchar *GetBudgetGroupName(int budgetGroupID); + int GetBudgetGroupFlags(int budgetGroupID) + const; // Returns a combination of BUDGETFLAG_ defines. + int GetNumBudgetGroups(void); + void GetBudgetGroupColor(int budgetGroupID, int &r, int &g, int &b, int &a); + int BudgetGroupNameToBudgetGroupID(const tchar *pBudgetGroupName); + int BudgetGroupNameToBudgetGroupID(const tchar *pBudgetGroupName, + int budgetFlagsToORIn); + void RegisterNumBudgetGroupsChangedCallBack(void (*pCallBack)(void)); + + int BudgetGroupNameToBudgetGroupIDNoCreate(const tchar *pBudgetGroupName) { + return FindBudgetGroupName(pBudgetGroupName); + } + + void HideBudgetGroup(int budgetGroupID, bool bHide = true); + void HideBudgetGroup(const tchar *pszName, bool bHide = true) { + HideBudgetGroup(BudgetGroupNameToBudgetGroupID(pszName), bHide); + } + + int *FindOrCreateCounter( + const tchar *pName, CounterGroup_t eCounterGroup = COUNTER_GROUP_DEFAULT); + void ResetCounters(CounterGroup_t eCounterGroup); + + int GetNumCounters(void) const; + + const tchar *GetCounterName(int index) const; + int GetCounterValue(int index) const; + const tchar *GetCounterNameAndValue(int index, int &val) const; + CounterGroup_t GetCounterGroup(int index) const; + + // Performance monitoring events. + void PMEInitialized(bool bInit) { m_bPMEInit = bInit; } + void PMEEnable(bool bEnable) { m_bPMEEnabled = bEnable; } + +#ifdef _X360 + bool UsePME(void) { return (CPMCData::IsInitialized() && m_bPMEEnabled); } +#elif defined(_PS3) + inline bool UsePME(void) { return false; } +#else + bool UsePME(void) { return (m_bPMEInit && m_bPMEEnabled); } +#endif + +#ifdef DBGFLAG_VALIDATE + void Validate(CValidator &validator, + tchar *pchName); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + + protected: + void FreeNodes_R(CVProfNode *pNode); + +#ifdef VPROF_VTUNE_GROUP + bool VTuneGroupEnabled() { return m_bVTuneGroupEnabled; } + int VTuneGroupID() { return m_nVTuneGroupID; } +#endif + + void SumTimes(const tchar *pszStartNode, int budgetGroupID); + void SumTimes(CVProfNode *pNode, int budgetGroupID); + void DumpNodes(CVProfNode *pNode, int indent, bool bAverageAndCountOnly); + int FindBudgetGroupName(const tchar *pBudgetGroupName); + int AddBudgetGroupName(const tchar *pBudgetGroupName, int budgetFlags); + +#ifdef VPROF_VTUNE_GROUP + bool m_bVTuneGroupEnabled; + int m_nVTuneGroupID; + int m_GroupIDStack[MAX_GROUP_STACK_DEPTH]; + int m_GroupIDStackDepth; +#endif + int m_enabled; + bool m_fAtRoot; // tracked for efficiency of the "not profiling" case + CVProfNode *m_pCurNode; + CVProfNode m_Root; + int m_nFrames; + int m_ProfileDetailLevel; + int m_pausedEnabledDepth; + + class CBudgetGroup { + public: + tchar *m_pName; + int m_BudgetFlags; + }; + + CBudgetGroup *m_pBudgetGroups; + int m_nBudgetGroupNamesAllocated; + int m_nBudgetGroupNames; + void (*m_pNumBudgetGroupsChangedCallBack)(void); + + // Performance monitoring events. + bool m_bPMEInit; + bool m_bPMEEnabled; + + int m_Counters[MAXCOUNTERS]; + char m_CounterGroups[MAXCOUNTERS]; // (These are CounterGroup_t's). + tchar *m_CounterNames[MAXCOUNTERS]; + int m_NumCounters; + +#ifdef VPROF_VXCONSOLE_EXISTS + int m_UpdateMode; + int m_nFramesRemaining; + int m_nFrameCount; + int64 m_WorstCycles; + char m_WorstTraceFilename[128]; + char m_CPUTraceFilename[128]; + unsigned int m_iSuccessiveTraceIndex; + VXConsoleReportMode_t m_ReportMode; + float m_pReportScale[VXCONSOLE_REPORT_COUNT]; + bool m_bTraceCompleteEvent; +#endif +#ifdef _X360 + CPUTraceState m_iCPUTraceEnabled; +#endif + + unsigned m_TargetThreadId; +}; + +//------------------------------------- + +PLATFORM_INTERFACE CVProfile g_VProfCurrentProfile; + +//----------------------------------------------------------------------------- + +PLATFORM_INTERFACE bool g_VProfSignalSpike; + +class CVProfSpikeDetector { + public: + CVProfSpikeDetector(double spike) : m_timeLast(GetTimeLast()) { + m_spike = spike; + m_Timer.Start(); + } + + ~CVProfSpikeDetector() { + m_Timer.End(); + if (Plat_FloatTime() - m_timeLast > 2.0) { + m_timeLast = Plat_FloatTime(); + if (m_Timer.GetDuration().GetMillisecondsF() > m_spike) { + g_VProfSignalSpike = true; + } + } + } + + private: + static double &GetTimeLast() { + static double timeLast = 0; + return timeLast; + } + CFastTimer m_Timer; + double m_spike; + double &m_timeLast; +}; + +// Macro to signal a local spike. Meant as temporary instrumentation, do not +// leave in code +#define VPROF_TEST_SPIKE(msec) CVProfSpikeDetector UNIQUE_ID(msec) + +//----------------------------------------------------------------------------- + +#ifdef VPROF_VTUNE_GROUP +inline void CVProfile::PushGroup(int nGroupID) { + // There is always at least one item on the stack since we force + // the first element to be VPROF_BUDGETGROUP_OTHER_UNACCOUNTED. + Assert(m_GroupIDStackDepth > 0); + Assert(m_GroupIDStackDepth < MAX_GROUP_STACK_DEPTH); + m_GroupIDStack[m_GroupIDStackDepth] = nGroupID; + m_GroupIDStackDepth++; + if (m_GroupIDStack[m_GroupIDStackDepth - 2] != nGroupID && + VTuneGroupEnabled() && nGroupID == VTuneGroupID()) { + vtune(true); + } +} +#endif // VPROF_VTUNE_GROUP + +#ifdef VPROF_VTUNE_GROUP +inline void CVProfile::PopGroup(void) { + m_GroupIDStackDepth--; + // There is always at least one item on the stack since we force + // the first element to be VPROF_BUDGETGROUP_OTHER_UNACCOUNTED. + Assert(m_GroupIDStackDepth > 0); + if (m_GroupIDStack[m_GroupIDStackDepth] != + m_GroupIDStack[m_GroupIDStackDepth + 1] && + VTuneGroupEnabled() && + m_GroupIDStack[m_GroupIDStackDepth + 1] == VTuneGroupID()) { + vtune(false); + } +} +#endif // VPROF_VTUNE_GROUP + +//----------------------------------------------------------------------------- + +class CVProfScope : public CVProfSnMarkerScope { + public: + CVProfScope(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, bool bAssertAccounted, + int budgetFlags); + ~CVProfScope(); + + private: + bool m_bEnabled; +}; + +//----------------------------------------------------------------------------- +// +// CVProfNode, inline methods +// + +inline CVProfNode::CVProfNode(const tchar *pszName, int detailLevel, + CVProfNode *pParent, + const tchar *pBudgetGroupName, int budgetFlags) + : m_pszName(pszName), + m_nCurFrameCalls(0), + m_nPrevFrameCalls(0), + m_nRecursions(0), + m_pParent(pParent), + m_pChild(NULL), + m_pSibling(NULL), + m_iClientData(-1) +#ifdef _X360 + , + m_iBitFlags(0) +#endif +{ + m_iUniqueNodeID = s_iCurrentUniqueNodeID++; + + if (m_iUniqueNodeID > 0) { + m_BudgetGroupID = g_VProfCurrentProfile.BudgetGroupNameToBudgetGroupID( + pBudgetGroupName, budgetFlags); + } else { + m_BudgetGroupID = 0; // "m_Root" can't call BudgetGroupNameToBudgetGroupID + // because g_VProfCurrentProfile not yet initialized + } + + Reset(); + + if (m_pParent && (m_BudgetGroupID == VPROF_BUDGET_GROUP_ID_UNACCOUNTED)) { + m_BudgetGroupID = m_pParent->GetBudgetGroupID(); + } +} + +//------------------------------------- + +inline CVProfNode *CVProfNode::GetParent() { + Assert(m_pParent); + return m_pParent; +} + +//------------------------------------- + +inline CVProfNode *CVProfNode::GetSibling() { return m_pSibling; } + +//------------------------------------- +// Hacky way to the previous sibling, only used from vprof panel at the moment, +// so it didn't seem like it was worth the memory waste to add the reverse +// link per node. + +inline CVProfNode *CVProfNode::GetPrevSibling() { + CVProfNode *p = GetParent(); + + if (!p) return NULL; + + CVProfNode *s; + for (s = p->GetChild(); s && (s->GetSibling() != this); s = s->GetSibling()) + ; + + return s; +} + +//------------------------------------- + +inline CVProfNode *CVProfNode::GetChild() { return m_pChild; } + +//------------------------------------- + +inline const tchar *CVProfNode::GetName() { return m_pszName; } + +//------------------------------------- + +inline unsigned CVProfNode::GetTotalCalls() { return m_nTotalCalls; } + +//------------------------------------- + +inline double CVProfNode::GetTotalTime() { + return m_TotalTime.GetMillisecondsF(); +} + +//------------------------------------- + +inline int CVProfNode::GetCurCalls() { return m_nCurFrameCalls; } + +//------------------------------------- + +inline double CVProfNode::GetCurTime() { + return m_CurFrameTime.GetMillisecondsF(); +} + +//------------------------------------- + +inline int CVProfNode::GetPrevCalls() { return m_nPrevFrameCalls; } + +//------------------------------------- + +inline double CVProfNode::GetPrevTime() { + return m_PrevFrameTime.GetMillisecondsF(); +} + +//------------------------------------- + +inline double CVProfNode::GetPeakTime() { + return m_PeakTime.GetMillisecondsF(); +} + +//------------------------------------- + +inline double CVProfNode::GetTotalTimeLessChildren() { + double result = GetTotalTime(); + CVProfNode *pChild = GetChild(); + while (pChild) { + result -= pChild->GetTotalTime(); + pChild = pChild->GetSibling(); + } + return result; +} + +//------------------------------------- + +inline double CVProfNode::GetCurTimeLessChildren() { + double result = GetCurTime(); + CVProfNode *pChild = GetChild(); + while (pChild) { + result -= pChild->GetCurTime(); + pChild = pChild->GetSibling(); + } + return result; +} + +inline double CVProfNode::GetPrevTimeLessChildren() { + double result = GetPrevTime(); + CVProfNode *pChild = GetChild(); + while (pChild) { + result -= pChild->GetPrevTime(); + pChild = pChild->GetSibling(); + } + return result; +} + +//----------------------------------------------------------------------------- +inline int CVProfNode::GetPrevL2CacheMissLessChildren() { + int result = m_iPrevL2CacheMiss; + CVProfNode *pChild = GetChild(); + while (pChild) { + result -= pChild->m_iPrevL2CacheMiss; + pChild = pChild->GetSibling(); + } + return result; +} + +//----------------------------------------------------------------------------- +inline int CVProfNode::GetPrevLoadHitStoreLessChildren() { +#ifndef _X360 + return 0; +#else + int result = m_iPrevLoadHitStores; + CVProfNode *pChild = GetChild(); + while (pChild) { + result -= pChild->m_iPrevLoadHitStores; + pChild = pChild->GetSibling(); + } + return result; +#endif +} + +//----------------------------------------------------------------------------- +inline void CVProfNode::ClearPrevTime() { m_PrevFrameTime.Init(); } + +//----------------------------------------------------------------------------- +inline int CVProfNode::GetL2CacheMisses(void) { +#ifndef _X360 + return m_L2Cache.GetL2CacheMisses(); +#else + return m_iTotalL2CacheMiss; +#endif +} + +#ifdef _X360 +inline int CVProfNode::GetLoadHitStores(void) { return m_iTotalLoadHitStores; } +#endif + +//----------------------------------------------------------------------------- +// +// CVProfile, inline methods +// + +//------------------------------------- + +inline bool CVProfile::IsEnabled() const { return (m_enabled != 0); } + +//------------------------------------- + +inline int CVProfile::GetDetailLevel() const { return m_ProfileDetailLevel; } + +//------------------------------------- + +inline bool CVProfile::AtRoot() const { return m_fAtRoot; } + +//------------------------------------- + +inline void CVProfile::Start() { + if (++m_enabled == 1) { + m_Root.EnterScope(); +#ifdef VPROF_VXCONSOLE_EXISTS + VXProfileStart(); +#endif +#ifdef _X360 + CPMCData::InitializeOnceProgramWide(); +#endif + } +} + +//------------------------------------- + +inline void CVProfile::Stop() { + if (--m_enabled == 0) m_Root.ExitScope(); +} + +//------------------------------------- + +inline void CVProfile::EnterScope(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, + bool bAssertAccounted, int budgetFlags) { + if ((m_enabled != 0 || !m_fAtRoot) && + InTargetThread()) // if became disabled, need to unwind back to root + // before stopping + { + // Only account for vprof stuff on the primary thread. + // if( !Plat_IsPrimaryThread() ) + // return; + + if (pszName != m_pCurNode->GetName()) { + m_pCurNode = m_pCurNode->GetSubNode(pszName, detailLevel, + pBudgetGroupName, budgetFlags); + } + m_pBudgetGroups[m_pCurNode->GetBudgetGroupID()].m_BudgetFlags |= + budgetFlags; + +#if defined(_DEBUG) && !defined(_X360) + // 360 doesn't want this to allow tier0 debug/release .def files to match + if (bAssertAccounted) { + // FIXME + AssertOnce(m_pCurNode->GetBudgetGroupID() != 0); + } +#endif + m_pCurNode->EnterScope(); + m_fAtRoot = false; + } +#if defined(_X360) && defined(VPROF_PIX) + if (m_pCurNode->GetBudgetGroupID() != VPROF_BUDGET_GROUP_ID_UNACCOUNTED) + PIXBeginNamedEvent(0, pszName); +#endif +} + +inline void CVProfile::EnterScope(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, + bool bAssertAccounted) { + EnterScope(pszName, detailLevel, pBudgetGroupName, bAssertAccounted, + BUDGETFLAG_OTHER); +} + +//------------------------------------- + +inline void CVProfile::ExitScope() { +#if defined(_X360) && defined(VPROF_PIX) + /* + #ifdef PIXBeginNamedEvent + #error + #endif + */ + if (m_pCurNode->GetBudgetGroupID() != VPROF_BUDGET_GROUP_ID_UNACCOUNTED) + PIXEndNamedEvent(); +#endif + if ((!m_fAtRoot || m_enabled != 0) && InTargetThread()) { + // Only account for vprof stuff on the primary thread. + // if( !Plat_IsPrimaryThread() ) + // return; + + // ExitScope will indicate whether we should back up to our parent (we may + // be profiling a recursive function) + if (m_pCurNode->ExitScope()) { + m_pCurNode = m_pCurNode->GetParent(); + } + m_fAtRoot = (m_pCurNode == &m_Root); + } +} + +//------------------------------------- + +inline void CVProfile::Pause() { + m_pausedEnabledDepth = m_enabled; + m_enabled = 0; + if (!AtRoot()) m_Root.Pause(); +} + +//------------------------------------- + +inline void CVProfile::Resume() { + m_enabled = m_pausedEnabledDepth; + if (!AtRoot()) m_Root.Resume(); +} + +//------------------------------------- + +inline void CVProfile::Reset() { + m_Root.Reset(); + m_nFrames = 0; +} + +//------------------------------------- + +inline void CVProfile::ResetPeaks() { m_Root.ResetPeak(); } + +//------------------------------------- + +inline void CVProfile::MarkFrame() { + if (m_enabled) { + ++m_nFrames; + m_Root.ExitScope(); + m_Root.MarkFrame(); + m_Root.EnterScope(); + +#ifdef _X360 + // update the CPU trace state machine if enabled + switch (GetCPUTraceMode()) { + case kAllNodesInFrame_WaitingForMark: + // mark! Start recording a zillion traces. + m_iCPUTraceEnabled = kAllNodesInFrame_Recording; + break; + case kAllNodesInFrame_WaitingForMarkMultiFrame: + m_iCPUTraceEnabled = kAllNodesInFrame_RecordingMultiFrame; + break; + case kAllNodesInFrame_Recording: + // end of frame. stop recording if no more frames needed + m_iCPUTraceEnabled = kDisabled; + Msg("Frame ended. Recording no more CPU traces\n"); + + break; + case kAllNodesInFrame_RecordingMultiFrame: + // end of frame. stop recording if no more frames needed + if (--m_nFramesRemaining == 0) { + m_iCPUTraceEnabled = kDisabled; + Msg("Frames ended. Recording no more CPU traces\n"); + + SpewWorstMultiFrame(); + } + + ++m_nFrameCount; + + break; + default: + // no default + break; + } +#endif + } +} + +//------------------------------------- + +inline double CVProfile::GetTotalTimeSampled() { return m_Root.GetTotalTime(); } + +//------------------------------------- + +inline double CVProfile::GetPeakFrameTime() { return m_Root.GetPeakTime(); } + +//------------------------------------- + +inline double CVProfile::GetTimeLastFrame() { return m_Root.GetCurTime(); } + +//------------------------------------- + +inline CVProfNode *CVProfile::GetRoot() { return &m_Root; } + +//------------------------------------- + +inline CVProfNode *CVProfile::GetCurrentNode() { return m_pCurNode; } + +inline const tchar *CVProfile::GetBudgetGroupName(int budgetGroupID) { + Assert(budgetGroupID >= 0 && budgetGroupID < m_nBudgetGroupNames); + return m_pBudgetGroups[budgetGroupID].m_pName; +} + +inline int CVProfile::GetBudgetGroupFlags(int budgetGroupID) const { + Assert(budgetGroupID >= 0 && budgetGroupID < m_nBudgetGroupNames); + return m_pBudgetGroups[budgetGroupID].m_BudgetFlags; +} + +#ifdef _X360 + +inline CVProfile::CPUTraceState CVProfile::GetCPUTraceMode() { + return m_iCPUTraceEnabled; +} + +inline void CVProfile::SetCPUTraceEnabled(CPUTraceState enabled, + bool bTraceCompleteEvent /*=true*/, + int nNumFrames /*= -1*/) { + m_iCPUTraceEnabled = enabled; + m_bTraceCompleteEvent = bTraceCompleteEvent; + if (nNumFrames != -1) { + m_nFramesRemaining = nNumFrames; + m_nFrameCount = 0; + m_WorstCycles = 0; + m_WorstTraceFilename[0] = 0; + } +} + +inline void CVProfile::IncrementMultiTraceIndex() { ++m_iSuccessiveTraceIndex; } + +inline unsigned int CVProfile::GetMultiTraceIndex() { + return m_iSuccessiveTraceIndex; +} + +#endif + +//----------------------------------------------------------------------------- + +inline CVProfScope::CVProfScope(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, + bool bAssertAccounted, int budgetFlags) + : CVProfSnMarkerScope(pszName), + m_bEnabled(g_VProfCurrentProfile.IsEnabled()) { + if (m_bEnabled) { + g_VProfCurrentProfile.EnterScope(pszName, detailLevel, pBudgetGroupName, + bAssertAccounted, budgetFlags); + } +} + +//------------------------------------- + +inline CVProfScope::~CVProfScope() { + if (m_bEnabled) { + g_VProfCurrentProfile.ExitScope(); + } +} + +class CVProfCounter { + public: + CVProfCounter(const tchar *pName, + CounterGroup_t group = COUNTER_GROUP_DEFAULT) { + m_pCounter = g_VProfCurrentProfile.FindOrCreateCounter(pName, group); + Assert(m_pCounter); + } + ~CVProfCounter() = default; + void Increment(int val) { + Assert(m_pCounter); + *m_pCounter += val; + } + void Set(int val) { + Assert(m_pCounter); + *m_pCounter = val; + } + + private: + int *m_pCounter; +}; + +#endif + +#ifdef _X360 + +#include "xbox/xbox_console.h" +#include "tracerecording.h" +#include "tier1/fmtstr.h" +#pragma comment(lib, "tracerecording.lib") +#pragma comment(lib, "xbdm.lib") + +class CPIXRecorder { + public: + CPIXRecorder() : m_bActive(false) {} + ~CPIXRecorder() { Stop(); } + + void Start(const char *pszFilename = "capture") { + if (!m_bActive) { + if (!XTraceStartRecording(CFmtStr("e:\\%s.pix2", pszFilename))) { + Msg("XTraceStartRecording failed, error code %d\n", GetLastError()); + } else { + m_bActive = true; + } + } + } + + void Stop() { + if (m_bActive) { + m_bActive = false; + if (XTraceStopRecording()) { + Msg("CPU trace finished.\n"); + // signal VXConsole that trace is completed + XBX_rTraceComplete(); + } + } + } + + private: + bool m_bActive; +}; + +#define VPROF_BEGIN_PIX_BLOCK(convar) \ + { \ + bool bRunPix = 0; \ + static CFastTimer PIXTimer; \ + extern ConVar convar; \ + ConVar &PIXConvar = convar; \ + CPIXRecorder PIXRecorder; \ + { \ + PIXLabel: \ + if (bRunPix) { \ + PIXRecorder.Start(); \ + } else { \ + if (PIXConvar.GetBool()) { \ + PIXTimer.Start(); \ + } \ + } \ + { +#define VPROF_END_PIX_BLOCK() \ + } \ + \ + if (!bRunPix) { \ + if (PIXConvar.GetBool()) { \ + PIXTimer.End(); \ + if (PIXTimer.GetDuration().GetMillisecondsF() > PIXConvar.GetFloat()) { \ + PIXConvar.SetValue(0); \ + bRunPix = true; \ + goto PIXLabel; \ + } \ + } \ + } else { \ + PIXRecorder.Stop(); \ + } \ + } \ + } +#else +#define VPROF_BEGIN_PIX_BLOCK(PIXConvar) { +#define VPROF_END_PIX_BLOCK() } +#endif + +#ifdef VPROF_UNDO_PIX +#undef USE_PIX +#undef _PIX_H_ +#undef PIXBeginNamedEvent +#undef PIXEndNamedEvent +#undef PIXSetMarker +#undef PIXNameThread +#include +#endif + +#endif // VPC_TIER0_VPROF_H_ diff --git a/public/tier0/vprof_sn.h b/public/tier0/vprof_sn.h new file mode 100644 index 0000000..b48be8d --- /dev/null +++ b/public/tier0/vprof_sn.h @@ -0,0 +1,28 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_VROF_SN_H_ +#define VPC_TIER0_VROF_SN_H_ + +// enable this to get detailed SN Tuner markers. PS3 specific +#if defined(SN_TARGET_PS3) && !defined(_CERT) +#define VPROF_SN_LEVEL 0 + +extern "C" void (*g_pfnPushMarker)(const char* pName); +extern "C" void (*g_pfnPopMarker)(); + +class CVProfSnMarkerScope { + public: + CVProfSnMarkerScope(const char* pszName) { g_pfnPushMarker(pszName); } + ~CVProfSnMarkerScope() { g_pfnPopMarker(); } +}; + +#else + +class CVProfSnMarkerScope { + public: + CVProfSnMarkerScope(const char*) {} +}; + +#endif + +#endif // VPC_TIER0_VROF_SN_H_ \ No newline at end of file diff --git a/public/tier0/wchartypes.h b/public/tier0/wchartypes.h new file mode 100644 index 0000000..5325ed7 --- /dev/null +++ b/public/tier0/wchartypes.h @@ -0,0 +1,94 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: All of our code is completely Unicode. Instead of char, you +// should use wchar, uint8, or char8, as explained below. + +#ifndef VPC_TIER0_WCHARTYPES_H_ +#define VPC_TIER0_WCHARTYPES_H_ + +#include + +#ifdef _INC_TCHAR +#error "Must include tier0 type headers before tchar.h" +#endif + +// Temporarily turn off Valve defines +#include "tier0/valve_off.h" + +#if !defined(_WCHAR_T_DEFINED) && !defined(__WCHAR_TYPE__) && !defined(GNUC) +typedef unsigned short wchar_t; +#define _WCHAR_T_DEFINED +#endif + +// char8 +// char8 is equivalent to char, and should be used when you really need a char +// (for example, when calling an external function that's declared to take +// chars). +typedef char char8; + +// uint8 +// uint8 is equivalent to byte (but is preferred over byte for clarity). Use +// this whenever you mean a byte (for example, one byte of a network packet). +// uint8 itself is defined in platform.h +typedef unsigned char BYTE; +typedef unsigned char byte; + +// wchar +// wchar is a single character of text (currently 16 bits, as all of our text is +// Unicode). Use this whenever you mean a piece of text (for example, in a +// string). +typedef wchar_t wchar; +// typedef char wchar; + +// __WFILE__ +// This is a Unicode version of __FILE__ +#define WIDEN2(x) L##x +#define WIDEN(x) WIDEN2(x) +#define __WFILE__ WIDEN(__FILE__) + +#ifdef STEAM +#ifndef _UNICODE +#define FORCED_UNICODE +#endif +#define _UNICODE +#endif + +#if defined(POSIX) || defined(_PS3) +#define _tcsstr strstr +#define _tcsicmp stricmp +#define _tcscmp strcmp +#define _tcscpy strcpy +#define _tcsncpy strncpy +#define _tcsrchr strrchr +#define _tcslen strlen +#define _tfopen fopen +#define _stprintf sprintf +#define _ftprintf fprintf +#define _vsntprintf _vsnprintf +#define _tprintf printf +#define _sntprintf _snprintf +#define _T(s) s +#else +#include +#endif + +#if defined(_UNICODE) +typedef wchar tchar; +#define tstring wstring +#define __TFILE__ __WFILE__ +#define TCHAR_IS_WCHAR +#else +typedef char tchar; +#define tstring string +#define __TFILE__ __FILE__ +#define TCHAR_IS_CHAR +#endif + +#ifdef FORCED_UNICODE +#undef _UNICODE +#endif + +// Turn valve defines back on +#include "tier0/valve_on.h" + +#endif // VPC_TIER0_WCHARTYPES_H_ diff --git a/public/tier0/win32consoleio.h b/public/tier0/win32consoleio.h new file mode 100644 index 0000000..89f9e07 --- /dev/null +++ b/public/tier0/win32consoleio.h @@ -0,0 +1,30 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Win32 Console API helpers + +#ifndef VPC_TIER0_WIN32_CONSOLE_IO_H_ +#define VPC_TIER0_WIN32_CONSOLE_IO_H_ + +// Function to attach a console for I/O to a Win32 GUI application in a +// reasonably smart fashion. +PLATFORM_INTERFACE bool SetupWin32ConsoleIO(); + +// Win32 Console Color API Helpers, originally from cmdlib. +struct Win32ConsoleColorContext_t { + int m_InitialColor; + uint16 m_LastColor; + uint16 m_BadColor; + uint16 m_BackgroundFlags; +}; + +PLATFORM_INTERFACE void InitWin32ConsoleColorContext( + Win32ConsoleColorContext_t *pContext); + +PLATFORM_INTERFACE uint16 +SetWin32ConsoleColor(Win32ConsoleColorContext_t *pContext, int nRed, int nGreen, + int nBlue, int nIntensity); + +PLATFORM_INTERFACE void RestoreWin32ConsoleColor( + Win32ConsoleColorContext_t *pContext, uint16 prevColor); + +#endif // VPC_TIER0_WIN32_CONSOLE_IO_H_ diff --git a/public/tier0/xbox_codeline_defines.h b/public/tier0/xbox_codeline_defines.h new file mode 100644 index 0000000..3d1b754 --- /dev/null +++ b/public/tier0/xbox_codeline_defines.h @@ -0,0 +1,9 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef XBOX_CODELINE_DEFINES_H_ +#define XBOX_CODELINE_DEFINES_H_ + +// In the regular src_main codeline, we leave this out. +//#define IN_XBOX_CODELINE + +#endif // XBOX_CODELINE_DEFINES_H_ diff --git a/public/tier1/byteswap.h b/public/tier1/byteswap.h new file mode 100644 index 0000000..811a87c --- /dev/null +++ b/public/tier1/byteswap.h @@ -0,0 +1,255 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Low level byte swapping routines. + +#ifndef VPC_TIER1_BYTESWAP_H_ +#define VPC_TIER1_BYTESWAP_H_ + +#include "tier0/dbg.h" +#include "datamap.h" // needed for typedescription_t. note datamap.h is tier1 as well. + +class CByteswap { + public: + CByteswap() { + // Default behavior sets the target endian to match the machine native + // endian (no swap). + SetTargetBigEndian(IsMachineBigEndian()); + } + + //----------------------------------------------------------------------------- + // Write a single field. + //----------------------------------------------------------------------------- + void SwapFieldToTargetEndian(void *pOutputBuffer, void *pData, + typedescription_t *pField); + + //----------------------------------------------------------------------------- + // Write a block of fields. Works a bit like the saverestore code. + //----------------------------------------------------------------------------- + void SwapFieldsToTargetEndian(void *pOutputBuffer, void *pBaseData, + datamap_t *pDataMap); + + // Swaps fields for the templated type to the output buffer. + template + inline void SwapFieldsToTargetEndian(T *pOutputBuffer, void *pBaseData, + unsigned int objectCount = 1) { + for (unsigned int i = 0; i < objectCount; ++i, ++pOutputBuffer) { + SwapFieldsToTargetEndian((void *)pOutputBuffer, pBaseData, &T::m_DataMap); + pBaseData = (byte *)pBaseData + sizeof(T); + } + } + + // Swaps fields for the templated type in place. + template + inline void SwapFieldsToTargetEndian(T *pOutputBuffer, + unsigned int objectCount = 1) { + SwapFieldsToTargetEndian(pOutputBuffer, (void *)pOutputBuffer, + objectCount); + } + + //----------------------------------------------------------------------------- + // True if the current machine is detected as big endian. + // (Endienness is effectively detected at compile time when optimizations are + // enabled) + //----------------------------------------------------------------------------- + static bool IsMachineBigEndian() { + short nIsBigEndian = 1; + + // if we are big endian, the first byte will be a 0, if little endian, it + // will be a one. + return (bool)(0 == *(char *)&nIsBigEndian); + } + + //----------------------------------------------------------------------------- + // Sets the target byte ordering we are swapping to or from. + // + // Braindead Endian Reference: + // x86 is LITTLE Endian + // PowerPC is BIG Endian + //----------------------------------------------------------------------------- + inline void SetTargetBigEndian(bool bigEndian) { + m_bBigEndian = bigEndian; + m_bSwapBytes = IsMachineBigEndian() != bigEndian; + } + + // Changes target endian + inline void FlipTargetEndian(void) { + m_bSwapBytes = !m_bSwapBytes; + m_bBigEndian = !m_bBigEndian; + } + + // Forces byte swapping state, regardless of endianess + inline void ActivateByteSwapping(bool bActivate) { + SetTargetBigEndian(IsMachineBigEndian() != bActivate); + } + + //----------------------------------------------------------------------------- + // Returns true if the target machine is the same as this one in endianness. + // + // Used to determine when a byteswap needs to take place. + //----------------------------------------------------------------------------- + inline bool IsSwappingBytes(void) // Are bytes being swapped? + { + return m_bSwapBytes; + } + + inline bool IsTargetBigEndian(void) // What is the current target endian? + { + return m_bBigEndian; + } + + //----------------------------------------------------------------------------- + // IsByteSwapped() + // + // When supplied with a chunk of input data and a constant or magic number + // (in native format) determines the endienness of the current machine in + // relation to the given input data. + // + // Returns: + // 1 if input is the same as nativeConstant. + // 0 if input is byteswapped relative to nativeConstant. + // -1 if input is not the same as nativeConstant and not + //byteswapped either. + // + // ( This is useful for detecting byteswapping in magic numbers in structure + // headers for example. ) + //----------------------------------------------------------------------------- + template + inline int SourceIsNativeEndian(T input, T nativeConstant) { + // If it's the same, it isn't byteswapped: + if (input == nativeConstant) return 1; + + int output; + LowLevelByteSwap(&output, &input); + if (output == nativeConstant) return 0; + + Assert(0); // if we get here, input is neither a swapped nor unswapped + // version of nativeConstant. + return -1; + } + + //----------------------------------------------------------------------------- + // Swaps an input buffer full of type T into the given output buffer. + // + // Swaps [count] items from the inputBuffer to the outputBuffer. + // If inputBuffer is omitted or NULL, then it is assumed to be the same as + // outputBuffer - effectively swapping the contents of the buffer in place. + //----------------------------------------------------------------------------- + template + inline void SwapBuffer(T *outputBuffer, T *inputBuffer = NULL, + int count = 1) { + Assert(count >= 0); + Assert(outputBuffer); + + // Fail gracefully in release: + if (count <= 0 || !outputBuffer) return; + + // Optimization for the case when we are swapping in place. + if (inputBuffer == NULL) { + inputBuffer = outputBuffer; + } + + // Swap everything in the buffer: + for (int i = 0; i < count; i++) { + LowLevelByteSwap(&outputBuffer[i], &inputBuffer[i]); + } + } + + //----------------------------------------------------------------------------- + // Swaps an input buffer full of type T into the given output buffer. + // + // Swaps [count] items from the inputBuffer to the outputBuffer. + // If inputBuffer is omitted or NULL, then it is assumed to be the same as + // outputBuffer - effectively swapping the contents of the buffer in place. + //----------------------------------------------------------------------------- + template + inline void SwapBufferToTargetEndian(T *outputBuffer, T *inputBuffer = NULL, + int count = 1) { + Assert(count >= 0); + Assert(outputBuffer); + + // Fail gracefully in release: + if (count <= 0 || !outputBuffer) return; + + // Optimization for the case when we are swapping in place. + if (inputBuffer == NULL) { + inputBuffer = outputBuffer; + } + + // Are we already the correct endienness? ( or are we swapping 1 byte items? + // ) + if (!m_bSwapBytes || (sizeof(T) == 1)) { + // Otherwise copy the inputBuffer to the outputBuffer: + if (outputBuffer != inputBuffer) + memcpy(outputBuffer, inputBuffer, count * sizeof(T)); + return; + } + + // Swap everything in the buffer: + for (int i = 0; i < count; i++) { + LowLevelByteSwap(&outputBuffer[i], &inputBuffer[i]); + } + } + + private: + //----------------------------------------------------------------------------- + // The lowest level byte swapping workhorse of doom. output always contains + // the swapped version of input. ( Doesn't compare machine to target + // endianness ) + //----------------------------------------------------------------------------- + template + static void LowLevelByteSwap(T *output, T *input) { + T temp = *output; +#if defined(_X360) + // Intrinsics need the source type to be fixed-point + DWORD *word = (DWORD *)input; + switch (sizeof(T)) { + case 8: { + __storewordbytereverse(*(word + 1), 0, &temp); + __storewordbytereverse(*(word + 0), 4, &temp); + } break; + + case 4: + __storewordbytereverse(*word, 0, &temp); + break; + + case 2: + __storeshortbytereverse(*input, 0, &temp); + break; + + case 1: + V_memcpy(&temp, input, 1); + break; + + default: + Assert("Invalid size in CByteswap::LowLevelByteSwap" && 0); + } +#else + for (unsigned int i = 0; i < sizeof(T); i++) { + ((unsigned char *)&temp)[i] = + ((unsigned char *)input)[sizeof(T) - (i + 1)]; + } +#endif + V_memcpy(output, &temp, sizeof(T)); + } + +#if defined(_X360) + // specialized for void * to get 360 XDK compile working despite changelist + // 281331 + //----------------------------------------------------------------------------- + // The lowest level byte swapping workhorse of doom. output always contains + // the swapped version of input. ( Doesn't compare machine to target + // endianness ) + //----------------------------------------------------------------------------- + template <> + static void LowLevelByteSwap(void **output, void **input) { + AssertMsgOnce(sizeof(void *) == sizeof(unsigned int), + "void *'s on this platform are not four bytes!"); + __storewordbytereverse(*reinterpret_cast(input), 0, output); + } +#endif + + unsigned int m_bSwapBytes : 1; + unsigned int m_bBigEndian : 1; +}; + +#endif // VPC_TIER1_BYTESWAP_H_ diff --git a/public/tier1/characterset.h b/public/tier1/characterset.h new file mode 100644 index 0000000..f6ff1ac --- /dev/null +++ b/public/tier1/characterset.h @@ -0,0 +1,27 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Shared code for parsing / searching for characters in a string using +// lookup tables + +#ifndef VPC_TIER1_CHARACTERSET_H_ +#define VPC_TIER1_CHARACTERSET_H_ + +struct characterset_t { + char set[256]; +}; + +// This is essentially a strpbrk() using a precalculated lookup table +// +// Purpose: builds a simple lookup table of a group of important characters +// Input : *pSetBuffer - pointer to the buffer for the group +// *pSetString - list of characters to flag +extern void CharacterSetBuild(characterset_t *pSetBuffer, + const char *pSetString); + +// Input : *pSetBuffer - pre-build group buffer +// character - character to lookup +// Output : int - 1 if the character was in the set +#define IN_CHARACTERSET(SetBuffer, character) \ + ((SetBuffer).set[(unsigned char)(character)]) + +#endif // VPC_TIER1_CHARACTERSET_H_ diff --git a/public/tier1/checksum_crc.h b/public/tier1/checksum_crc.h new file mode 100644 index 0000000..d79e36c --- /dev/null +++ b/public/tier1/checksum_crc.h @@ -0,0 +1,27 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Generic CRC functions + +#ifndef VPC_TIER1_CHECKSUM_CRC_H_ +#define VPC_TIER1_CHECKSUM_CRC_H_ + +#include + +using CRC32_t = uint32_t; + +void CRC32_Init(CRC32_t *pulCRC); +void CRC32_ProcessBuffer(CRC32_t *pulCRC, const void *p, ptrdiff_t len); +void CRC32_Final(CRC32_t *pulCRC); +CRC32_t CRC32_GetTableEntry(unsigned int slot); + +inline CRC32_t CRC32_ProcessSingleBuffer(const void *p, ptrdiff_t len) { + CRC32_t crc; + + CRC32_Init(&crc); + CRC32_ProcessBuffer(&crc, p, len); + CRC32_Final(&crc); + + return crc; +} + +#endif // VPC_TIER1_CHECKSUM_CRC_H_ diff --git a/public/tier1/checksum_md5.h b/public/tier1/checksum_md5.h new file mode 100644 index 0000000..07a1243 --- /dev/null +++ b/public/tier1/checksum_md5.h @@ -0,0 +1,27 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Generic MD5 hashing algo + +#ifndef VPC_TIER1_CHECKSUM_MD5_H_ +#define VPC_TIER1_CHECKSUM_MD5_H_ + +// 16 bytes == 128 bit digest +#define MD5_DIGEST_LENGTH 16 + +// MD5 Hash +struct MD5Context_t { + unsigned int buf[4]; + unsigned int bits[2]; + unsigned char in[64]; +}; + +void MD5Init(MD5Context_t *context); +void MD5Update(MD5Context_t *context, unsigned char const *buf, + size_t len); +void MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5Context_t *context); + +char *MD5_Print(unsigned char *digest, int hashlen); + +unsigned int MD5_PseudoRandom(unsigned int nSeed); + +#endif // VPC_TIER1_CHECKSUM_MD5_H_ diff --git a/public/tier1/convar.h b/public/tier1/convar.h new file mode 100644 index 0000000..bface57 --- /dev/null +++ b/public/tier1/convar.h @@ -0,0 +1,916 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_CONVAR_H_ +#define VPC_TIER1_CONVAR_H_ + +#include "tier0/dbg.h" +#include "tier1/iconvar.h" +#include "tier1/utlvector.h" +#include "tier1/utlstring.h" +#include "color.h" +#include "icvar.h" + +#ifdef _WIN32 +#define FORCEINLINE_CVAR FORCEINLINE +#elif POSIX +#define FORCEINLINE_CVAR inline +#elif defined(_PS3) +#define FORCEINLINE_CVAR __attribute__((always_inline)) FORCEINLINE +#else +#error "implement me" +#endif + +//----------------------------------------------------------------------------- +// Uncomment me to test for threading issues for material system convars +// NOTE: You want to disable all threading when you do this +// +host_thread_mode 0 +r_threaded_particles 0 +sv_parallel_packentities 0 +// +sv_disable_querycache 0 +//----------------------------------------------------------------------------- +//#define CONVAR_TEST_MATERIAL_THREAD_CONVARS 1 + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class ConVar; +class CCommand; +class ConCommand; +class ConCommandBase; +struct characterset_t; + +//----------------------------------------------------------------------------- +// Any executable that wants to use ConVars need to implement one of +// these to hook up access to console variables. +//----------------------------------------------------------------------------- +class IConCommandBaseAccessor { + public: + // Flags is a combination of FCVAR flags in cvar.h. + // hOut is filled in with a handle to the variable. + virtual bool RegisterConCommandBase(ConCommandBase *pVar) = 0; +}; + +//----------------------------------------------------------------------------- +// Helper method for console development +//----------------------------------------------------------------------------- +#if defined(USE_VXCONSOLE) +void ConVar_PublishToVXConsole(); +#else +inline void ConVar_PublishToVXConsole() {} +#endif + +//----------------------------------------------------------------------------- +// Called when a ConCommand needs to execute +//----------------------------------------------------------------------------- +typedef void (*FnCommandCallbackV1_t)(void); +typedef void (*FnCommandCallback_t)(const CCommand &command); + +#define COMMAND_COMPLETION_MAXITEMS 64 +#define COMMAND_COMPLETION_ITEM_LENGTH 64 + +//----------------------------------------------------------------------------- +// Returns 0 to COMMAND_COMPLETION_MAXITEMS worth of completion strings +//----------------------------------------------------------------------------- +typedef int (*FnCommandCompletionCallback)( + const char *partial, + char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]); + +//----------------------------------------------------------------------------- +// Interface version +//----------------------------------------------------------------------------- +class ICommandCallback { + public: + virtual void CommandCallback(const CCommand &command) = 0; +}; + +class ICommandCompletionCallback { + public: + virtual int CommandCompletionCallback(const char *pPartial, + CUtlVector &commands) = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: The base console invoked command/cvar interface +//----------------------------------------------------------------------------- +class ConCommandBase { + friend class CCvar; + friend class ConVar; + friend class ConCommand; + friend void ConVar_Register(int nCVarFlag, + IConCommandBaseAccessor *pAccessor); + friend void ConVar_PublishToVXConsole(); + + // FIXME: Remove when ConVar changes are done + friend class CDefaultCvar; + + public: + ConCommandBase(void); + ConCommandBase(const char *pName, const char *pHelpString = 0, int flags = 0); + + virtual ~ConCommandBase(void); + + virtual bool IsCommand(void) const; + + // Check flag + virtual bool IsFlagSet(int flag) const; + // Set flag + virtual void AddFlags(int flags); + // Clear flag + virtual void RemoveFlags(int flags); + + virtual int GetFlags() const; + + // Return name of cvar + virtual const char *GetName(void) const; + + // Return help text for cvar + virtual const char *GetHelpText(void) const; + + // Deal with next pointer + const ConCommandBase *GetNext(void) const; + ConCommandBase *GetNext(void); + + virtual bool IsRegistered(void) const; + + // Returns the DLL identifier + virtual CVarDLLIdentifier_t GetDLLIdentifier() const; + + protected: + virtual void Create(const char *pName, const char *pHelpString = 0, + int flags = 0); + + // Used internally by OneTimeInit to initialize/shutdown + virtual void Init(); + void Shutdown(); + + // Internal copy routine ( uses new operator from correct module ) + char *CopyString(const char *from); + + private: + // Next ConVar in chain + // Prior to register, it points to the next convar in the DLL. + // Once registered, though, m_pNext is reset to point to the next + // convar in the global list + ConCommandBase *m_pNext; + + // Has the cvar been added to the global list? + bool m_bRegistered; + + // Static data + const char *m_pszName; + const char *m_pszHelpString; + + // ConVar flags + int m_nFlags; + + protected: + // ConVars add themselves to this list for the executable. + // Then ConVar_Register runs through all the console variables + // and registers them into a global list stored in vstdlib.dll + static ConCommandBase *s_pConCommandBases; + + // ConVars in this executable use this 'global' to access values. + static IConCommandBaseAccessor *s_pAccessor; +}; + +//----------------------------------------------------------------------------- +// Command tokenizer +//----------------------------------------------------------------------------- +class CCommand { + public: + CCommand(); + CCommand(int nArgC, const char **ppArgV); + bool Tokenize(const char *pCommand, characterset_t *pBreakSet = NULL); + void Reset(); + + int ArgC() const; + const char **ArgV() const; + const char *ArgS() + const; // All args that occur after the 0th arg, in string form + const char *GetCommandString() + const; // The entire command in string form, including the 0th arg + const char *operator[](int nIndex) const; // Gets at arguments + const char *Arg(int nIndex) const; // Gets at arguments + + // Helper functions to parse arguments to commands. + const char *FindArg(const char *pName) const; + int FindArgInt(const char *pName, int nDefaultVal) const; + + static int MaxCommandLength(); + static characterset_t *DefaultBreakSet(); + + private: + enum { + COMMAND_MAX_ARGC = 64, + COMMAND_MAX_LENGTH = 512, + }; + + int m_nArgc; + intp m_nArgv0Size; + char m_pArgSBuffer[COMMAND_MAX_LENGTH]; + char m_pArgvBuffer[COMMAND_MAX_LENGTH]; + const char *m_ppArgv[COMMAND_MAX_ARGC]; +}; + +inline int CCommand::MaxCommandLength() { return COMMAND_MAX_LENGTH - 1; } + +inline int CCommand::ArgC() const { return m_nArgc; } + +inline const char **CCommand::ArgV() const { + return m_nArgc ? (const char **)m_ppArgv : NULL; +} + +inline const char *CCommand::ArgS() const { + return m_nArgv0Size ? &m_pArgSBuffer[m_nArgv0Size] : ""; +} + +inline const char *CCommand::GetCommandString() const { + return m_nArgc ? m_pArgSBuffer : ""; +} + +inline const char *CCommand::Arg(int nIndex) const { + // FIXME: Many command handlers appear to not be particularly careful + // about checking for valid argc range. For now, we're going to + // do the extra check and return an empty string if it's out of range + if (nIndex < 0 || nIndex >= m_nArgc) return ""; + return m_ppArgv[nIndex]; +} + +inline const char *CCommand::operator[](int nIndex) const { + return Arg(nIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: The console invoked command +//----------------------------------------------------------------------------- +class ConCommand : public ConCommandBase { + friend class CCvar; + + public: + typedef ConCommandBase BaseClass; + + ConCommand(const char *pName, FnCommandCallbackV1_t callback, + const char *pHelpString = 0, int flags = 0, + FnCommandCompletionCallback completionFunc = 0); + ConCommand(const char *pName, FnCommandCallback_t callback, + const char *pHelpString = 0, int flags = 0, + FnCommandCompletionCallback completionFunc = 0); + ConCommand(const char *pName, ICommandCallback *pCallback, + const char *pHelpString = 0, int flags = 0, + ICommandCompletionCallback *pCommandCompletionCallback = 0); + + virtual ~ConCommand(void); + + virtual bool IsCommand(void) const; + + virtual int AutoCompleteSuggest(const char *partial, + CUtlVector &commands); + + virtual bool CanAutoComplete(void); + + // Invoke the function + virtual void Dispatch(const CCommand &command); + + private: + // NOTE: To maintain backward compat, we have to be very careful: + // All public virtual methods must appear in the same order always + // since engine code will be calling into this code, which *does not match* + // in the mod code; it's using slightly different, but compatible versions + // of this class. Also: Be very careful about adding new fields to this class. + // Those fields will not exist in the version of this class that is instanced + // in mod code. + + // Call this function when executing the command + union { + FnCommandCallbackV1_t m_fnCommandCallbackV1; + FnCommandCallback_t m_fnCommandCallback; + ICommandCallback *m_pCommandCallback; + }; + + union { + FnCommandCompletionCallback m_fnCompletionCallback; + ICommandCompletionCallback *m_pCommandCompletionCallback; + }; + + bool m_bHasCompletionCallback : 1; + bool m_bUsingNewCommandCallback : 1; + bool m_bUsingCommandCallbackInterface : 1; +}; + +//----------------------------------------------------------------------------- +// Purpose: A console variable +//----------------------------------------------------------------------------- +class ConVar : public ConCommandBase, public IConVar { + friend class CCvar; + friend class ConVarRef; + friend class SplitScreenConVarRef; + + public: + typedef ConCommandBase BaseClass; + + ConVar(const char *pName, const char *pDefaultValue, int flags = 0); + + ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString); + ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax); + ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString, FnChangeCallback_t callback); + ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax, + FnChangeCallback_t callback); + + virtual ~ConVar(void); + + virtual bool IsFlagSet(int flag) const; + virtual const char *GetHelpText(void) const; + virtual bool IsRegistered(void) const; + virtual const char *GetName(void) const; + // Return name of command (usually == GetName(), except in case of + // FCVAR_SS_ADDED vars + virtual const char *GetBaseName(void) const; + virtual int GetSplitScreenPlayerSlot() const; + + virtual void AddFlags(int flags); + virtual int GetFlags() const; + virtual bool IsCommand(void) const; + + // Install a change callback (there shouldn't already be one....) + void InstallChangeCallback(FnChangeCallback_t callback, bool bInvoke = true); + void RemoveChangeCallback(FnChangeCallback_t callbackToRemove); + + intp GetChangeCallbackCount() const { + return m_pParent->m_fnChangeCallbacks.Count(); + } + FnChangeCallback_t GetChangeCallback(intp slot) const { + return m_pParent->m_fnChangeCallbacks[slot]; + } + + // Retrieve value + FORCEINLINE_CVAR float GetFloat(void) const; + FORCEINLINE_CVAR int GetInt(void) const; + FORCEINLINE_CVAR Color GetColor(void) const; + FORCEINLINE_CVAR bool GetBool() const { return !!GetInt(); } + FORCEINLINE_CVAR char const *GetString(void) const; + + // Compiler driven selection for template use + template + T Get(void) const; + template + T Get(T *) const; + + // Any function that allocates/frees memory needs to be virtual or else you'll + // have crashes + // from alloc/free across dll/exe boundaries. + + // These just call into the IConCommandBaseAccessor to check flags and set the + // var (which ends up calling InternalSetValue). + virtual void SetValue(const char *value); + virtual void SetValue(float value); + virtual void SetValue(int value); + virtual void SetValue(Color value); + + // Reset to default value + void Revert(void); + + // True if it has a min/max setting + bool HasMin() const; + bool HasMax() const; + + bool GetMin(float &minVal) const; + bool GetMax(float &maxVal) const; + + float GetMinValue() const; + float GetMaxValue() const; + + const char *GetDefault(void) const; + void SetDefault(const char *pszDefault); + + // Value + struct CVValue_t { + char *m_pszString; + intp m_StringLength; + + // Values + float m_fValue; + int m_nValue; + }; + + FORCEINLINE_CVAR CVValue_t &GetRawValue() { return m_Value; } + FORCEINLINE_CVAR const CVValue_t &GetRawValue() const { return m_Value; } + + private: + bool InternalSetColorFromString(const char *value); + // Called by CCvar when the value of a var is changing. + virtual void InternalSetValue(const char *value); + // For CVARs marked FCVAR_NEVER_AS_STRING + virtual void InternalSetFloatValue(float fNewValue); + virtual void InternalSetIntValue(int nValue); + virtual void InternalSetColorValue(Color value); + + virtual bool ClampValue(float &value); + virtual void ChangeStringValue(const char *tempVal, float flOldValue); + + virtual void Create(const char *pName, const char *pDefaultValue, + int flags = 0, const char *pHelpString = 0, + bool bMin = false, float fMin = 0.0, bool bMax = false, + float fMax = false, FnChangeCallback_t callback = 0); + + // Used internally by OneTimeInit to initialize. + virtual void Init(); + + protected: + // This either points to "this" or it points to the original declaration of a + // ConVar. This allows ConVars to exist in separate modules, and they all use + // the first one to be declared. m_pParent->m_pParent must equal m_pParent + // (ie: m_pParent must be the root, or original, ConVar). + ConVar *m_pParent; + + // Static data + const char *m_pszDefaultValue; + + CVValue_t m_Value; + + // Min/Max values + bool m_bHasMin; + float m_fMinVal; + bool m_bHasMax; + float m_fMaxVal; + + // Call this function when ConVar changes + CUtlVector m_fnChangeCallbacks; +}; + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a float +// Output : float +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR float ConVar::GetFloat(void) const { +#ifdef CONVAR_TEST_MATERIAL_THREAD_CONVARS + Assert(ThreadInMainThread() || + IsFlagSet(FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS)); +#endif + return m_pParent->m_Value.m_fValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as an int +// Output : int +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR int ConVar::GetInt(void) const { +#ifdef CONVAR_TEST_MATERIAL_THREAD_CONVARS + Assert(ThreadInMainThread() || + IsFlagSet(FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS)); +#endif + return m_pParent->m_Value.m_nValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a color +// Output : Color +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR Color ConVar::GetColor(void) const { +#ifdef CONVAR_TEST_MATERIAL_THREAD_CONVARS + Assert(ThreadInMainThread() || + IsFlagSet(FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS)); +#endif + unsigned char *pColorElement = + ((unsigned char *)&m_pParent->m_Value.m_nValue); + return Color(pColorElement[0], pColorElement[1], pColorElement[2], + pColorElement[3]); +} + +//----------------------------------------------------------------------------- + +template <> +FORCEINLINE_CVAR float ConVar::Get(void) const { + return GetFloat(); +} +template <> +FORCEINLINE_CVAR int ConVar::Get(void) const { + return GetInt(); +} +template <> +FORCEINLINE_CVAR bool ConVar::Get(void) const { + return GetBool(); +} +template <> +FORCEINLINE_CVAR const char *ConVar::Get(void) const { + return GetString(); +} +template <> +FORCEINLINE_CVAR float ConVar::Get(float *p) const { + return (*p = GetFloat()); +} +template <> +FORCEINLINE_CVAR int ConVar::Get(int *p) const { + return (*p = GetInt()); +} +template <> +FORCEINLINE_CVAR bool ConVar::Get(bool *p) const { + return (*p = GetBool()); +} +template <> +FORCEINLINE_CVAR const char *ConVar::Get(char const **p) const { + return (*p = GetString()); +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a string, return "" for bogus string pointer, +// etc. Output : const char * +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR const char *ConVar::GetString(void) const { +#ifdef CONVAR_TEST_MATERIAL_THREAD_CONVARS + Assert(ThreadInMainThread() || + IsFlagSet(FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS)); +#endif + if (m_nFlags & FCVAR_NEVER_AS_STRING) return "FCVAR_NEVER_AS_STRING"; + + char const *str = m_pParent->m_Value.m_pszString; + return str ? str : ""; +} + +class CSplitScreenAddedConVar : public ConVar { + typedef ConVar BaseClass; + + public: + CSplitScreenAddedConVar(int nSplitScreenSlot, const char *pName, + const ConVar *pBaseVar) + : BaseClass( + pName, pBaseVar->GetDefault(), + // Keep basevar flags, except remove _SS and add _SS_ADDED instead + (pBaseVar->GetFlags() & ~FCVAR_SS) | FCVAR_SS_ADDED, + pBaseVar->GetHelpText(), pBaseVar->HasMin(), + pBaseVar->GetMinValue(), pBaseVar->HasMax(), + pBaseVar->GetMaxValue()), + m_pBaseVar(pBaseVar), + m_nSplitScreenSlot(nSplitScreenSlot) { + for (intp i = 0; i < pBaseVar->GetChangeCallbackCount(); ++i) { + InstallChangeCallback(pBaseVar->GetChangeCallback(i), false); + } + Assert(nSplitScreenSlot >= 1); + Assert(nSplitScreenSlot < MAX_SPLITSCREEN_CLIENTS); + Assert(m_pBaseVar); + Assert(IsFlagSet(FCVAR_SS_ADDED)); + Assert(!IsFlagSet(FCVAR_SS)); + } + + const ConVar *GetBaseVar() const; + virtual const char *GetBaseName() const; + void SetSplitScreenPlayerSlot(int nSlot); + virtual int GetSplitScreenPlayerSlot() const; + + protected: + const ConVar *m_pBaseVar; + int m_nSplitScreenSlot; +}; + +FORCEINLINE_CVAR const ConVar *CSplitScreenAddedConVar::GetBaseVar() const { + Assert(m_pBaseVar); + return m_pBaseVar; +} + +FORCEINLINE_CVAR const char *CSplitScreenAddedConVar::GetBaseName() const { + Assert(m_pBaseVar); + return m_pBaseVar->GetName(); +} + +FORCEINLINE_CVAR void CSplitScreenAddedConVar::SetSplitScreenPlayerSlot( + int nSlot) { + m_nSplitScreenSlot = nSlot; +} + +FORCEINLINE_CVAR int CSplitScreenAddedConVar::GetSplitScreenPlayerSlot() const { + return m_nSplitScreenSlot; +} + +//----------------------------------------------------------------------------- +// Used to read/write convars that already exist (replaces the FindVar method) +//----------------------------------------------------------------------------- +class ConVarRef { + public: + ConVarRef(const char *pName); + ConVarRef(const char *pName, bool bIgnoreMissing); + ConVarRef(IConVar *pConVar); + + void Init(const char *pName, bool bIgnoreMissing); + bool IsValid() const; + bool IsFlagSet(int nFlags) const; + IConVar *GetLinkedConVar(); + + // Get/Set value + float GetFloat(void) const; + int GetInt(void) const; + Color GetColor(void) const; + bool GetBool() const { return !!GetInt(); } + const char *GetString(void) const; + + void SetValue(const char *pValue); + void SetValue(float flValue); + void SetValue(int nValue); + void SetValue(Color value); + void SetValue(bool bValue); + + const char *GetName() const; + + const char *GetDefault() const; + + const char *GetBaseName() const; + + int GetSplitScreenPlayerSlot() const; + + private: + // High-speed method to read convar data + IConVar *m_pConVar; + ConVar *m_pConVarState; +}; + +//----------------------------------------------------------------------------- +// Did we find an existing convar of that name? +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR bool ConVarRef::IsFlagSet(int nFlags) const { + return (m_pConVar->IsFlagSet(nFlags) != 0); +} + +FORCEINLINE_CVAR IConVar *ConVarRef::GetLinkedConVar() { return m_pConVar; } + +FORCEINLINE_CVAR const char *ConVarRef::GetName() const { + return m_pConVar->GetName(); +} + +FORCEINLINE_CVAR const char *ConVarRef::GetBaseName() const { + return m_pConVar->GetBaseName(); +} + +FORCEINLINE_CVAR int ConVarRef::GetSplitScreenPlayerSlot() const { + return m_pConVar->GetSplitScreenPlayerSlot(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a float +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR float ConVarRef::GetFloat(void) const { + return m_pConVarState->m_Value.m_fValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as an int +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR int ConVarRef::GetInt(void) const { + return m_pConVarState->m_Value.m_nValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a color +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR Color ConVarRef::GetColor(void) const { + return m_pConVarState->GetColor(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a string, return "" for bogus string pointer, +// etc. +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR const char *ConVarRef::GetString(void) const { + Assert(!IsFlagSet(FCVAR_NEVER_AS_STRING)); + return m_pConVarState->m_Value.m_pszString; +} + +FORCEINLINE_CVAR void ConVarRef::SetValue(const char *pValue) { + m_pConVar->SetValue(pValue); +} + +FORCEINLINE_CVAR void ConVarRef::SetValue(float flValue) { + m_pConVar->SetValue(flValue); +} + +FORCEINLINE_CVAR void ConVarRef::SetValue(int nValue) { + m_pConVar->SetValue(nValue); +} + +FORCEINLINE_CVAR void ConVarRef::SetValue(Color value) { + m_pConVar->SetValue(value); +} + +FORCEINLINE_CVAR void ConVarRef::SetValue(bool bValue) { + m_pConVar->SetValue(bValue ? 1 : 0); +} + +FORCEINLINE_CVAR const char *ConVarRef::GetDefault() const { + return m_pConVarState->m_pszDefaultValue; +} + +//----------------------------------------------------------------------------- +// Helper for referencing splitscreen convars (i.e., "name" and "name2") +//----------------------------------------------------------------------------- +class SplitScreenConVarRef { + public: + SplitScreenConVarRef(const char *pName); + SplitScreenConVarRef(const char *pName, bool bIgnoreMissing); + SplitScreenConVarRef(IConVar *pConVar); + + void Init(const char *pName, bool bIgnoreMissing); + bool IsValid() const; + bool IsFlagSet(int nFlags) const; + + // Get/Set value + float GetFloat(int nSlot) const; + int GetInt(int nSlot) const; + Color GetColor(int nSlot) const; + bool GetBool(int nSlot) const { return !!GetInt(nSlot); } + const char *GetString(int nSlot) const; + + void SetValue(int nSlot, const char *pValue); + void SetValue(int nSlot, float flValue); + void SetValue(int nSlot, int nValue); + void SetValue(int nSlot, Color value); + void SetValue(int nSlot, bool bValue); + + const char *GetName(int nSlot) const; + + const char *GetDefault() const; + + const char *GetBaseName() const; + + private: + struct cv_t { + IConVar *m_pConVar; + ConVar *m_pConVarState; + }; + + cv_t m_Info[MAX_SPLITSCREEN_CLIENTS]; +}; + +//----------------------------------------------------------------------------- +// Did we find an existing convar of that name? +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR bool SplitScreenConVarRef::IsFlagSet(int nFlags) const { + return (m_Info[0].m_pConVar->IsFlagSet(nFlags) != 0); +} + +FORCEINLINE_CVAR const char *SplitScreenConVarRef::GetName(int nSlot) const { + return m_Info[nSlot].m_pConVar->GetName(); +} + +FORCEINLINE_CVAR const char *SplitScreenConVarRef::GetBaseName() const { + return m_Info[0].m_pConVar->GetBaseName(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a float +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR float SplitScreenConVarRef::GetFloat(int nSlot) const { + return m_Info[nSlot].m_pConVarState->m_Value.m_fValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as an int +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR int SplitScreenConVarRef::GetInt(int nSlot) const { + return m_Info[nSlot].m_pConVarState->m_Value.m_nValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as an int +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR Color SplitScreenConVarRef::GetColor(int nSlot) const { + return m_Info[nSlot].m_pConVarState->GetColor(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a string, return "" for bogus string pointer, +// etc. +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR const char *SplitScreenConVarRef::GetString(int nSlot) const { + Assert(!IsFlagSet(FCVAR_NEVER_AS_STRING)); + return m_Info[nSlot].m_pConVarState->m_Value.m_pszString; +} + +FORCEINLINE_CVAR void SplitScreenConVarRef::SetValue(int nSlot, + const char *pValue) { + m_Info[nSlot].m_pConVar->SetValue(pValue); +} + +FORCEINLINE_CVAR void SplitScreenConVarRef::SetValue(int nSlot, float flValue) { + m_Info[nSlot].m_pConVar->SetValue(flValue); +} + +FORCEINLINE_CVAR void SplitScreenConVarRef::SetValue(int nSlot, int nValue) { + m_Info[nSlot].m_pConVar->SetValue(nValue); +} + +FORCEINLINE_CVAR void SplitScreenConVarRef::SetValue(int nSlot, Color value) { + m_Info[nSlot].m_pConVar->SetValue(value); +} + +FORCEINLINE_CVAR void SplitScreenConVarRef::SetValue(int nSlot, bool bValue) { + m_Info[nSlot].m_pConVar->SetValue(bValue ? 1 : 0); +} + +FORCEINLINE_CVAR const char *SplitScreenConVarRef::GetDefault() const { + return m_Info[0].m_pConVarState->m_pszDefaultValue; +} + +//----------------------------------------------------------------------------- +// Called by the framework to register ConCommands with the ICVar +//----------------------------------------------------------------------------- +void ConVar_Register(int nCVarFlag = 0, + IConCommandBaseAccessor *pAccessor = NULL); +void ConVar_Unregister(); + +//----------------------------------------------------------------------------- +// Utility methods +//----------------------------------------------------------------------------- +void ConVar_PrintDescription(const ConCommandBase *pVar); + +//----------------------------------------------------------------------------- +// Purpose: Utility class to quickly allow ConCommands to call member methods +//----------------------------------------------------------------------------- +template +class CConCommandMemberAccessor : public ConCommand, + public ICommandCallback, + public ICommandCompletionCallback { + typedef ConCommand BaseClass; + typedef void (T::*FnMemberCommandCallback_t)(const CCommand &command); + typedef int (T::*FnMemberCommandCompletionCallback_t)( + const char *pPartial, CUtlVector &commands); + + public: + CConCommandMemberAccessor( + T *pOwner, const char *pName, FnMemberCommandCallback_t callback, + const char *pHelpString = 0, int flags = 0, + FnMemberCommandCompletionCallback_t completionFunc = 0) + : BaseClass(pName, this, pHelpString, flags, + (completionFunc != 0) ? this : NULL) { + m_pOwner = pOwner; + m_Func = callback; + m_CompletionFunc = completionFunc; + } + + ~CConCommandMemberAccessor() { Shutdown(); } + + void SetOwner(T *pOwner) { m_pOwner = pOwner; } + + virtual void CommandCallback(const CCommand &command) { + Assert(m_pOwner && m_Func); + (m_pOwner->*m_Func)(command); + } + + virtual int CommandCompletionCallback(const char *pPartial, + CUtlVector &commands) { + Assert(m_pOwner && m_CompletionFunc); + return (m_pOwner->*m_CompletionFunc)(pPartial, commands); + } + + private: + T *m_pOwner; + FnMemberCommandCallback_t m_Func; + FnMemberCommandCompletionCallback_t m_CompletionFunc; +}; + +//----------------------------------------------------------------------------- +// Purpose: Utility macros to quicky generate a simple console command +//----------------------------------------------------------------------------- +#define CON_COMMAND(name, description) \ + static void name(const CCommand &args); \ + static ConCommand name##_command(#name, name, description); \ + static void name(const CCommand &args) + +#define CON_COMMAND_F(name, description, flags) \ + static void name(const CCommand &args); \ + static ConCommand name##_command(#name, name, description, flags); \ + static void name(const CCommand &args) + +#define CON_COMMAND_F_COMPLETION(name, description, flags, completion) \ + static void name(const CCommand &args); \ + static ConCommand name##_command(#name, name, description, flags, \ + completion); \ + static void name(const CCommand &args) + +#define CON_COMMAND_EXTERN(name, _funcname, description) \ + void _funcname(const CCommand &args); \ + static ConCommand name##_command(#name, _funcname, description); \ + void _funcname(const CCommand &args) + +#define CON_COMMAND_EXTERN_F(name, _funcname, description, flags) \ + void _funcname(const CCommand &args); \ + static ConCommand name##_command(#name, _funcname, description, flags); \ + void _funcname(const CCommand &args) + +#define CON_COMMAND_MEMBER_F(_thisclass, name, _funcname, description, flags) \ + void _funcname(const CCommand &args); \ + friend class CCommandMemberInitializer_##_funcname; \ + class CCommandMemberInitializer_##_funcname { \ + public: \ + CCommandMemberInitializer_##_funcname() \ + : m_ConCommandAccessor(NULL, name, &_thisclass::_funcname, \ + description, flags) { \ + m_ConCommandAccessor.SetOwner( \ + GET_OUTER(_thisclass, m_##_funcname##_register)); \ + } \ + \ + private: \ + CConCommandMemberAccessor<_thisclass> m_ConCommandAccessor; \ + }; \ + \ + CCommandMemberInitializer_##_funcname m_##_funcname##_register; + +#endif // VPC_TIER1_CONVAR_H_ diff --git a/public/tier1/convar_serverbounded.h b/public/tier1/convar_serverbounded.h new file mode 100644 index 0000000..c355893 --- /dev/null +++ b/public/tier1/convar_serverbounded.h @@ -0,0 +1,45 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Helper class for cvars that have restrictions on their value. + +#ifndef VPC_TIER1_CONVAR_SERVERBOUNDED_H_ +#define VPC_TIER1_CONVAR_SERVERBOUNDED_H_ + +// This class is used to virtualize a ConVar's value, so the client can restrict +// its value while connected to a server. When using this across modules, it's +// important to dynamic_cast it to a ConVar_ServerBounded or you won't get the +// restricted value. +// +// NOTE: FCVAR_USERINFO vars are not virtualized before they are sent to the +// server +// (we have no way to detect if the virtualized value would change), so +// if you want to use a bounded cvar's value on the server, you must +// rebound it the same way the client does. +class ConVar_ServerBounded : public ConVar { + public: + ConVar_ServerBounded(char const *pName, char const *pDefaultValue, int flags, + char const *pHelpString) + : ConVar(pName, pDefaultValue, flags, pHelpString) {} + + ConVar_ServerBounded(char const *pName, char const *pDefaultValue, int flags, + char const *pHelpString, FnChangeCallback_t callback) + : ConVar(pName, pDefaultValue, flags, pHelpString, callback) {} + + ConVar_ServerBounded(char const *pName, char const *pDefaultValue, int flags, + char const *pHelpString, bool bMin, float fMin, + bool bMax, float fMax) + : ConVar(pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, + fMax) {} + + // You must implement GetFloat. + virtual float GetFloat() const = 0; + + // You can optionally implement these. + virtual int GetInt() const { return (int)GetFloat(); } + virtual bool GetBool() const { return (GetInt() != 0); } + + // Use this to get the underlying cvar's value. + float GetBaseFloatValue() const { return ConVar::GetFloat(); } +}; + +#endif // VPC_TIER1_CONVAR_SERVERBOUNDED_H_ diff --git a/public/tier1/exprevaluator.h b/public/tier1/exprevaluator.h new file mode 100644 index 0000000..558777a --- /dev/null +++ b/public/tier1/exprevaluator.h @@ -0,0 +1,69 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: ExprSimplifier builds a binary tree from an infix expression (in the +// form of a character array). + +#ifndef VPC_TIER1_EXPREVALUATOR_H_ +#define VPC_TIER1_EXPREVALUATOR_H_ + +static const char OR_OP = '|'; +static const char AND_OP = '&'; +static const char NOT_OP = '!'; + +#define MAX_IDENTIFIER_LEN 128 +enum Kind { CONDITIONAL, NOT, LITERAL }; + +struct ExprNode { + ExprNode *left; // left sub-expression + ExprNode *right; // right sub-expression + Kind kind; // kind of node this is + union { + char cond; // the conditional + bool value; // the value + } data; +}; + +using ExprTree = ExprNode *; + +// callback to evaluate a $ during evaluation, return true or false +using GetSymbolProc_t = bool (*)(const char *pKey); +using SyntaxErrorProc_t = void (*)(const char *pReason); + +class CExpressionEvaluator { + public: + CExpressionEvaluator(); + ~CExpressionEvaluator(); + bool Evaluate(bool &result, const char *pInfixExpression, + GetSymbolProc_t pGetSymbolProc = 0, + SyntaxErrorProc_t pSyntaxErrorProc = 0); + + private: + CExpressionEvaluator( + CExpressionEvaluator &); // prevent copy constructor being used + + char GetNextToken(void); + void FreeNode(ExprNode *pNode); + ExprNode *AllocateNode(void); + void FreeTree(ExprTree &node); + bool IsConditional(bool &bCondition, const char token); + bool IsNotOp(const char token); + bool IsIdentifierOrConstant(const char token); + bool MakeExprNode(ExprTree &tree, char token, Kind kind, ExprTree left, + ExprTree right); + bool MakeFactor(ExprTree &tree); + bool MakeTerm(ExprTree &tree); + bool MakeExpression(ExprTree &tree); + bool BuildExpression(void); + bool SimplifyNode(ExprTree &node); + + ExprTree m_ExprTree; // Tree representation of the expression + char m_CurToken; // Current token read from the input expression + const char *m_pExpression; // Array of the expression characters + int m_CurPosition; // Current position in the input expression + char m_Identifier[MAX_IDENTIFIER_LEN]; // Stores the identifier string + GetSymbolProc_t m_pGetSymbolProc; + SyntaxErrorProc_t m_pSyntaxErrorProc; + bool m_bSetup; +}; + +#endif // VPC_TIER1_EXPREVALUATOR_H_ diff --git a/public/tier1/fmtstr.h b/public/tier1/fmtstr.h new file mode 100644 index 0000000..6cff7fc --- /dev/null +++ b/public/tier1/fmtstr.h @@ -0,0 +1,370 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A simple class for performing safe and in-expression sprintf-style +// string formatting + +#ifndef VPC_TIER1_FMTSTR_H_ +#define VPC_TIER1_FMTSTR_H_ + +#include +#include + +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "tier1/strtools.h" + +#if defined(POSIX) +#pragma GCC visibility push(hidden) +#endif + +//============================================================================= + +// using macro to be compatable with GCC +#define FmtStrVSNPrintf(szBuf, nBufSize, bQuietTruncation, ppszFormat, \ + nPrevLen, lastArg) \ + do { \ + int result; \ + va_list arg_ptr; \ + bool bTruncated = false; \ + static int scAsserted = 0; \ + \ + va_start(arg_ptr, lastArg); \ + result = V_vsnprintfRet((szBuf), (nBufSize)-1, (*(ppszFormat)), arg_ptr, \ + &bTruncated); \ + va_end(arg_ptr); \ + \ + (szBuf)[(nBufSize)-1] = 0; \ + if (bTruncated && !(bQuietTruncation) && scAsserted < 5) { \ + Warning( \ + "FmtStrVSNPrintf truncated to %d without QUIET_TRUNCATION " \ + "specified!\n", \ + (int)(nBufSize)); \ + AssertMsg( \ + 0, \ + "FmtStrVSNPrintf truncated without QUIET_TRUNCATION specified!\n"); \ + scAsserted++; \ + } \ + m_nLength = nPrevLen + result; \ + } while (0) + +// using macro to be compatible with GCC +#define FmtStrVSNPrintfNoLengthFixup(szBuf, nBufSize, bQuietTruncation, \ + ppszFormat, nPrevLen, lastArg) \ + do { \ + int result; \ + va_list arg_ptr; \ + bool bTruncated = false; \ + static int scAsserted = 0; \ + \ + va_start(arg_ptr, lastArg); \ + result = V_vsnprintfRet((szBuf), (nBufSize)-1, (*(ppszFormat)), arg_ptr, \ + &bTruncated); \ + va_end(arg_ptr); \ + \ + (szBuf)[(nBufSize)-1] = 0; \ + if (bTruncated && !(bQuietTruncation) && scAsserted < 5) { \ + Warning( \ + "FmtStrVSNPrintf truncated to %d without QUIET_TRUNCATION " \ + "specified!\n", \ + (int)(nBufSize)); \ + AssertMsg( \ + 0, \ + "FmtStrVSNPrintf truncated without QUIET_TRUNCATION specified!\n"); \ + scAsserted++; \ + } \ + } while (0) + +//----------------------------------------------------------------------------- +// +// Purpose: String formatter with specified size +// + +template +class CFmtStrN { + public: + CFmtStrN() { + InitQuietTruncation(); + m_szBuf[0] = 0; + m_nLength = 0; + } + + // Standard C formatting + CFmtStrN(PRINTF_FORMAT_STRING const char *pszFormat, ...) FMTFUNCTION(2, 3) { + InitQuietTruncation(); + FmtStrVSNPrintf(m_szBuf, SIZE_BUF, m_bQuietTruncation, &pszFormat, 0, + pszFormat); + } + + // Use this for pass-through formatting + CFmtStrN(const char **ppszFormat, ...) { + InitQuietTruncation(); + FmtStrVSNPrintf(m_szBuf, SIZE_BUF, m_bQuietTruncation, ppszFormat, 0, + ppszFormat); + } + + // Explicit reformat + const char *sprintf(PRINTF_FORMAT_STRING const char *pszFormat, ...) + FMTFUNCTION(2, 3) { + InitQuietTruncation(); + FmtStrVSNPrintf(m_szBuf, SIZE_BUF, m_bQuietTruncation, &pszFormat, 0, + pszFormat); + return m_szBuf; + } + + // Use this for va_list formatting + const char *sprintf_argv(const char *pszFormat, va_list arg_ptr) { + int result; + bool bTruncated = false; + static int s_nWarned = 0; + + InitQuietTruncation(); + result = + V_vsnprintfRet(m_szBuf, SIZE_BUF - 1, pszFormat, arg_ptr, &bTruncated); + m_szBuf[SIZE_BUF - 1] = 0; + if (bTruncated && !m_bQuietTruncation && (s_nWarned < 5)) { + Warning("CFmtStr truncated to %d without QUIET_TRUNCATION specified!\n", + SIZE_BUF); + AssertMsg(0, "CFmtStr truncated without QUIET_TRUNCATION specified!\n"); + s_nWarned++; + } + m_nLength = V_strlen(m_szBuf); + return m_szBuf; + } + + // Use this for pass-through formatting + void VSprintf(const char **ppszFormat, ...) { + InitQuietTruncation(); + FmtStrVSNPrintf(m_szBuf, SIZE_BUF, m_bQuietTruncation, ppszFormat, 0, + ppszFormat); + } + + // Compatible API with CUtlString for converting to const char* + const char *Get() const { return m_szBuf; } + const char *String() const { return m_szBuf; } + // Use for access + operator const char *() const { return m_szBuf; } + char *Access() { return m_szBuf; } + + // Access template argument + static inline int GetMaxLength() { return SIZE_BUF - 1; } + + CFmtStrN &operator=(const char *pchValue) { + V_strncpy(m_szBuf, pchValue, SIZE_BUF); + m_nLength = V_strlen(m_szBuf); + return *this; + } + + CFmtStrN &operator+=(const char *pchValue) { + Append(pchValue); + return *this; + } + + int Length() const { return m_nLength; } + + void SetLength(int nLength) { + m_nLength = Min(nLength, SIZE_BUF - 1); + m_szBuf[m_nLength] = '\0'; + } + + void Clear() { + m_szBuf[0] = 0; + m_nLength = 0; + } + + void AppendFormat(PRINTF_FORMAT_STRING const char *pchFormat, ...) + FMTFUNCTION(2, 3) { + char *pchEnd = m_szBuf + m_nLength; + FmtStrVSNPrintf(pchEnd, SIZE_BUF - m_nLength, m_bQuietTruncation, + &pchFormat, m_nLength, pchFormat); + } + + void AppendFormatV(const char *pchFormat, va_list args); + + void Append(const char *pchValue) { + // This function is close to the metal to cut down on the CPU cost + // of the previous incantation of Append which was implemented as + // AppendFormat( "%s", pchValue ). This implementation, though not + // as easy to read, instead does a strcpy from the existing end + // point of the CFmtStrN. This brings something like a 10-20x speedup + // in my rudimentary tests. It isn't using V_strncpy because that + // function doesn't return the number of characters copied, which + // we need to adjust m_nLength. Doing the V_strncpy with a V_strlen + // afterwards took twice as long as this implementations in tests, + // so V_strncpy's implementation was used to write this method. + char *pDest = m_szBuf + m_nLength; + const int maxLen = SIZE_BUF - m_nLength; + char *pLast = pDest + maxLen - 1; + while ((pDest < pLast) && (*pchValue != 0)) { + *pDest = *pchValue; + ++pDest; + ++pchValue; + } + *pDest = 0; + m_nLength = pDest - m_szBuf; + } + + // optimized version of append for just adding a single character + void Append(char ch) { + if (m_nLength < SIZE_BUF - 1) { + m_szBuf[m_nLength] = ch; + m_nLength++; + m_szBuf[m_nLength] = '\0'; + } + } + + void AppendIndent(uint32 unCount, char chIndent = '\t'); + + void SetQuietTruncation(bool bQuiet) { m_bQuietTruncation = bQuiet; } + + protected: + virtual void InitQuietTruncation() { m_bQuietTruncation = QUIET_TRUNCATION; } + + bool m_bQuietTruncation; + + private: + char m_szBuf[SIZE_BUF]; + int m_nLength; +}; + +// Version which will not assert if strings are truncated + +template +class CFmtStrQuietTruncationN : public CFmtStrN {}; + +template +void CFmtStrN::AppendIndent(uint32 unCount, + char chIndent) { + Assert(Length() + unCount < SIZE_BUF); + if (Length() + unCount >= SIZE_BUF) unCount = SIZE_BUF - (1 + Length()); + for (uint32 x = 0; x < unCount; x++) { + m_szBuf[m_nLength++] = chIndent; + } + m_szBuf[m_nLength] = '\0'; +} + +template +void CFmtStrN::AppendFormatV(const char *pchFormat, + va_list args) { + int cubPrinted = + V_vsnprintf(m_szBuf + Length(), SIZE_BUF - Length(), pchFormat, args); + m_nLength += cubPrinted; +} + +#if defined(POSIX) +#pragma GCC visibility pop +#endif + +//----------------------------------------------------------------------------- +// +// Purpose: Default-sized string formatter +// + +#define FMTSTR_STD_LEN 512 + +typedef CFmtStrN CFmtStr; +typedef CFmtStrQuietTruncationN CFmtStrQuietTruncation; +typedef CFmtStrN<1024> CFmtStr1024; +typedef CFmtStrN<8192> CFmtStrMax; + +//----------------------------------------------------------------------------- +// Purpose: Fast-path number-to-string helper (with optional quoting) +// Derived off of the Steam CNumStr but with a few tweaks, such +//as trimming off the in-our-cases-unnecessary strlen calls (by not storing the +//length in the class). +//----------------------------------------------------------------------------- + +class CNumStr { + public: + CNumStr() { m_szBuf[0] = 0; } + + explicit CNumStr(bool b) { SetBool(b); } + + explicit CNumStr(int8 n8) { SetInt8(n8); } + explicit CNumStr(uint8 un8) { SetUint8(un8); } + explicit CNumStr(int16 n16) { SetInt16(n16); } + explicit CNumStr(uint16 un16) { SetUint16(un16); } + explicit CNumStr(int32 n32) { SetInt32(n32); } + explicit CNumStr(uint32 un32) { SetUint32(un32); } + explicit CNumStr(int64 n64) { SetInt64(n64); } + explicit CNumStr(uint64 un64) { SetUint64(un64); } + +#if defined(COMPILER_GCC) && defined(PLATFORM_64BITS) + explicit CNumStr(lint64 n64) { SetInt64((int64)n64); } + explicit CNumStr(ulint64 un64) { SetUint64((uint64)un64); } +#endif + + explicit CNumStr(double f) { SetDouble(f); } + explicit CNumStr(float f) { SetFloat(f); } + + inline void SetBool(bool b) { V_memcpy(m_szBuf, b ? "1" : "0", 2); } + +#ifdef _WIN32 + inline void SetInt8(int8 n8) { _itoa((int32)n8, m_szBuf, 10); } + inline void SetUint8(uint8 un8) { _itoa((int32)un8, m_szBuf, 10); } + inline void SetInt16(int16 n16) { _itoa((int32)n16, m_szBuf, 10); } + inline void SetUint16(uint16 un16) { _itoa((int32)un16, m_szBuf, 10); } + inline void SetInt32(int32 n32) { _itoa(n32, m_szBuf, 10); } + inline void SetUint32(uint32 un32) { _i64toa((int64)un32, m_szBuf, 10); } + inline void SetInt64(int64 n64) { _i64toa(n64, m_szBuf, 10); } + inline void SetUint64(uint64 un64) { _ui64toa(un64, m_szBuf, 10); } +#else + inline void SetInt8(int8 n8) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%d", (int32)n8); + } + inline void SetUint8(uint8 un8) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%d", (int32)un8); + } + inline void SetInt16(int16 n16) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%d", (int32)n16); + } + inline void SetUint16(uint16 un16) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%d", (int32)un16); + } + inline void SetInt32(int32 n32) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%d", n32); + } + inline void SetUint32(uint32 un32) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%u", un32); + } + inline void SetInt64(int64 n64) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%lld", n64); + } + inline void SetUint64(uint64 un64) { + Q_snprintf(m_szBuf, sizeof(m_szBuf), "%llu", un64); + } +#endif + + inline void SetDouble(double f) { + V_snprintf(m_szBuf, sizeof(m_szBuf), "%.18g", f); + } + inline void SetFloat(float f) { + V_snprintf(m_szBuf, sizeof(m_szBuf), "%.18g", f); + } + + inline void SetHexUint64(uint64 un64) { + V_binarytohex((byte *)&un64, sizeof(un64), m_szBuf, sizeof(m_szBuf)); + } + + operator const char *() const { return m_szBuf; } + const char *String() const { return m_szBuf; } + + void AddQuotes() { + Assert(m_szBuf[0] != '"'); + const intp nLength = V_strlen(m_szBuf); + V_memmove(m_szBuf + 1, m_szBuf, nLength); + m_szBuf[0] = '"'; + m_szBuf[nLength + 1] = '"'; + m_szBuf[nLength + 2] = 0; + } + + protected: + char m_szBuf[28]; // long enough to hold 18 digits of precision, a decimal, a + // - sign, e+### suffix, and quotes +}; + +bool BGetLocalFormattedDateAndTime(time_t timeVal, char *pchDate, int cubDate, + char *pchTime, int cubTime); +bool BGetLocalFormattedDate(time_t timeVal, char *pchDate, int cubDate); +bool BGetLocalFormattedTime(time_t timeVal, char *pchTime, int cubTime); + +#endif // VPC_TIER1_FMTSTR_H_ diff --git a/public/tier1/functors.h b/public/tier1/functors.h new file mode 100644 index 0000000..ca7759a --- /dev/null +++ b/public/tier1/functors.h @@ -0,0 +1,1574 @@ +// Copyright Valve Corporation, All rights reserved. ======== +// +// Purpose: Implements a generic infrastucture for functors combining +// a number of techniques to provide transparent parameter type +// deduction and packaging. Supports both member and non-member functions. +// +// See also: https://en.wikipedia.org/wiki/Function_object +// +// Note that getting into the guts of this file is not for the +// feint of heart. The core concept here is that the compiler can +// figure out all the parameter types. +// +// E.g.: +// +// struct CMyClass +// { +// void foo( int i) {} +// }; +// +// int bar(int i) { return i; } +// +// CMyClass myInstance; +// +// CFunctor *pFunctor = CreateFunctor( &myInstance, CMyClass::foo, 8675 ); +// CFunctor *pFunctor2 = CreateFunctor( &bar, 309 ); +// +// void CallEm() +// { +// (*pFunctor)(); +// (*pFunctor2)(); +// } + +#ifndef VPC_TIER1_FUNCTORS_H_ +#define VPC_TIER1_FUNCTORS_H_ + +#include "tier0/platform.h" +#include "tier1/refcount.h" +#include "tier1/utlenvelope.h" + +#include + +//----------------------------------------------------------------------------- +// +// Macros used as basis for template generation. Just ignore the man behind the +// curtain +// +//----------------------------------------------------------------------------- + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_0 +#define FUNC_TEMPLATE_ARG_PARAMS_0 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_0 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_0 +#define FUNC_ARG_MEMBERS_0 +#define FUNC_ARG_FORMAL_PARAMS_0 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_0 +#define FUNC_CALL_ARGS_INIT_0 +#define FUNC_SOLO_CALL_ARGS_INIT_0 +#define FUNC_CALL_MEMBER_ARGS_0 +#define FUNC_CALL_ARGS_0 +#define FUNC_CALL_DATA_ARGS_0(_var) +#define FUNC_FUNCTOR_CALL_ARGS_0 +#define FUNC_TEMPLATE_FUNC_PARAMS_0 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_0 +#define FUNC_VALIDATION_STRING_0 V_snprintf(pString, nBufLen, "method( void )"); +#define FUNC_SEPARATOR_0 + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_1 typename ARG_TYPE_1 +#define FUNC_TEMPLATE_ARG_PARAMS_1 , typename ARG_TYPE_1 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_1 , ARG_TYPE_1 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_1 ARG_TYPE_1 +#define FUNC_ARG_MEMBERS_1 ARG_TYPE_1 m_arg1 +#define FUNC_ARG_FORMAL_PARAMS_1 , const ARG_TYPE_1 &arg1 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_1 const ARG_TYPE_1 &arg1 +#define FUNC_CALL_ARGS_INIT_1 , m_arg1(arg1) +#define FUNC_SOLO_CALL_ARGS_INIT_1 : m_arg1( arg1 ) +#define FUNC_CALL_MEMBER_ARGS_1 m_arg1 +#define FUNC_CALL_ARGS_1 arg1 +#define FUNC_CALL_DATA_ARGS_1(_var) _var->m_arg1 +#define FUNC_FUNCTOR_CALL_ARGS_1 , arg1 +#define FUNC_TEMPLATE_FUNC_PARAMS_1 , typename FUNC_ARG_TYPE_1 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_1 FUNC_ARG_TYPE_1 +#define FUNC_VALIDATION_STRING_1 \ + V_snprintf(pString, nBufLen, "method( %s )", typeid(ARG_TYPE_1).name()); +#define FUNC_SEPARATOR_1 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_2 typename ARG_TYPE_1, typename ARG_TYPE_2 +#define FUNC_TEMPLATE_ARG_PARAMS_2 , typename ARG_TYPE_1, typename ARG_TYPE_2 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_2 , ARG_TYPE_1, ARG_TYPE_2 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_2 ARG_TYPE_1, ARG_TYPE_2 +#define FUNC_ARG_MEMBERS_2 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2 +#define FUNC_ARG_FORMAL_PARAMS_2 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_2 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2 +#define FUNC_CALL_ARGS_INIT_2 , m_arg1(arg1), m_arg2(arg2) +#define FUNC_SOLO_CALL_ARGS_INIT_2 : m_arg1( arg1 ), m_arg2( arg2 ) +#define FUNC_CALL_MEMBER_ARGS_2 m_arg1, m_arg2 +#define FUNC_CALL_ARGS_2 arg1, arg2 +#define FUNC_CALL_DATA_ARGS_2(_var) _var->m_arg1, _var->m_arg2 +#define FUNC_FUNCTOR_CALL_ARGS_2 , arg1, arg2 +#define FUNC_TEMPLATE_FUNC_PARAMS_2 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_2 FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2 +#define FUNC_VALIDATION_STRING_2 \ + V_snprintf(pString, nBufLen, "method( %s, %s )", typeid(ARG_TYPE_1).name(), \ + typeid(ARG_TYPE_2).name()); +#define FUNC_SEPARATOR_2 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_3 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3 +#define FUNC_TEMPLATE_ARG_PARAMS_3 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_3 , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_3 ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3 +#define FUNC_ARG_MEMBERS_3 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3 +#define FUNC_ARG_FORMAL_PARAMS_3 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_3 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3 +#define FUNC_CALL_ARGS_INIT_3 , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3) +#define FUNC_SOLO_CALL_ARGS_INIT_3 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ) +#define FUNC_CALL_MEMBER_ARGS_3 m_arg1, m_arg2, m_arg3 +#define FUNC_CALL_ARGS_3 arg1, arg2, arg3 +#define FUNC_CALL_DATA_ARGS_3(_var) _var->m_arg1, _var->m_arg2, _var->m_arg3 +#define FUNC_FUNCTOR_CALL_ARGS_3 , arg1, arg2, arg3 +#define FUNC_TEMPLATE_FUNC_PARAMS_3 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, typename FUNC_ARG_TYPE_3 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_3 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3 +#define FUNC_VALIDATION_STRING_3 \ + V_snprintf(pString, nBufLen, "method( %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name()); +#define FUNC_SEPARATOR_3 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_4 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4 +#define FUNC_TEMPLATE_ARG_PARAMS_4 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_4 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_4 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4 +#define FUNC_ARG_MEMBERS_4 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4 +#define FUNC_ARG_FORMAL_PARAMS_4 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_4 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4 +#define FUNC_CALL_ARGS_INIT_4 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4) +#define FUNC_SOLO_CALL_ARGS_INIT_4 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ) +#define FUNC_CALL_MEMBER_ARGS_4 m_arg1, m_arg2, m_arg3, m_arg4 +#define FUNC_CALL_ARGS_4 arg1, arg2, arg3, arg4 +#define FUNC_CALL_DATA_ARGS_4(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4 +#define FUNC_FUNCTOR_CALL_ARGS_4 , arg1, arg2, arg3, arg4 +#define FUNC_TEMPLATE_FUNC_PARAMS_4 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_4 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4 +#define FUNC_VALIDATION_STRING_4 \ + V_snprintf(pString, nBufLen, "method( %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name()); +#define FUNC_SEPARATOR_4 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_5 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5 +#define FUNC_TEMPLATE_ARG_PARAMS_5 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_5 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_5 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5 +#define FUNC_ARG_MEMBERS_5 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5 +#define FUNC_ARG_FORMAL_PARAMS_5 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_5 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5 +#define FUNC_CALL_ARGS_INIT_5 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5) +#define FUNC_SOLO_CALL_ARGS_INIT_5 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ) +#define FUNC_CALL_MEMBER_ARGS_5 m_arg1, m_arg2, m_arg3, m_arg4, m_arg5 +#define FUNC_CALL_ARGS_5 arg1, arg2, arg3, arg4, arg5 +#define FUNC_CALL_DATA_ARGS_5(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5 +#define FUNC_FUNCTOR_CALL_ARGS_5 , arg1, arg2, arg3, arg4, arg5 +#define FUNC_TEMPLATE_FUNC_PARAMS_5 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_5 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5 +#define FUNC_VALIDATION_STRING_5 \ + V_snprintf(pString, nBufLen, "method( %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name()); +#define FUNC_SEPARATOR_5 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_6 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6 +#define FUNC_TEMPLATE_ARG_PARAMS_6 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_6 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_6 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6 +#define FUNC_ARG_MEMBERS_6 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6 +#define FUNC_ARG_FORMAL_PARAMS_6 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_6 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6 +#define FUNC_CALL_ARGS_INIT_6 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6) +#define FUNC_SOLO_CALL_ARGS_INIT_6 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ) +#define FUNC_CALL_MEMBER_ARGS_6 m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6 +#define FUNC_CALL_ARGS_6 arg1, arg2, arg3, arg4, arg5, arg6 +#define FUNC_CALL_DATA_ARGS_6(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6 +#define FUNC_FUNCTOR_CALL_ARGS_6 , arg1, arg2, arg3, arg4, arg5, arg6 +#define FUNC_TEMPLATE_FUNC_PARAMS_6 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_6 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6 +#define FUNC_VALIDATION_STRING_6 \ + V_snprintf(pString, nBufLen, "method( %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name()); +#define FUNC_SEPARATOR_6 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_7 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7 +#define FUNC_TEMPLATE_ARG_PARAMS_7 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_7 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_7 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7 +#define FUNC_ARG_MEMBERS_7 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; +#define FUNC_ARG_FORMAL_PARAMS_7 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_7 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7 +#define FUNC_CALL_ARGS_INIT_7 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7) +#define FUNC_SOLO_CALL_ARGS_INIT_7 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ) +#define FUNC_CALL_MEMBER_ARGS_7 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7 +#define FUNC_CALL_ARGS_7 arg1, arg2, arg3, arg4, arg5, arg6, arg7 +#define FUNC_CALL_DATA_ARGS_7(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7 +#define FUNC_FUNCTOR_CALL_ARGS_7 , arg1, arg2, arg3, arg4, arg5, arg6, arg7 +#define FUNC_TEMPLATE_FUNC_PARAMS_7 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_7 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7 +#define FUNC_VALIDATION_STRING_7 \ + V_snprintf(pString, nBufLen, "method( %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name()); +#define FUNC_SEPARATOR_7 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_8 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8 +#define FUNC_TEMPLATE_ARG_PARAMS_8 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_8 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_8 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8 +#define FUNC_ARG_MEMBERS_8 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; \ + ARG_TYPE_8 m_arg8; +#define FUNC_ARG_FORMAL_PARAMS_8 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_8 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8 +#define FUNC_CALL_ARGS_INIT_8 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7), m_arg8(arg8) +#define FUNC_SOLO_CALL_ARGS_INIT_8 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ), m_arg8( arg8 ) +#define FUNC_CALL_MEMBER_ARGS_8 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8 +#define FUNC_CALL_ARGS_8 arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 +#define FUNC_CALL_DATA_ARGS_8(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7, _var->m_arg8 +#define FUNC_FUNCTOR_CALL_ARGS_8 \ + , arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 +#define FUNC_TEMPLATE_FUNC_PARAMS_8 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7, typename FUNC_ARG_TYPE_8 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_8 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7, FUNC_ARG_TYPE_8 +#define FUNC_VALIDATION_STRING_8 \ + V_snprintf(pString, nBufLen, "method( %s, %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name(), typeid(ARG_TYPE_8).name()); +#define FUNC_SEPARATOR_8 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_9 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9 +#define FUNC_TEMPLATE_ARG_PARAMS_9 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_9 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_9 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9 +#define FUNC_ARG_MEMBERS_9 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; \ + ARG_TYPE_8 m_arg8; \ + ARG_TYPE_9 m_arg9; +#define FUNC_ARG_FORMAL_PARAMS_9 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_9 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9 +#define FUNC_CALL_ARGS_INIT_9 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7), m_arg8(arg8), m_arg9(arg9) +#define FUNC_SOLO_CALL_ARGS_INIT_9 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ), m_arg8( arg8 ), m_arg9( arg9 ) +#define FUNC_CALL_MEMBER_ARGS_9 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8, m_arg9 +#define FUNC_CALL_ARGS_9 arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 +#define FUNC_CALL_DATA_ARGS_9(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7, _var->m_arg8, _var->m_arg9 +#define FUNC_FUNCTOR_CALL_ARGS_9 \ + , arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 +#define FUNC_TEMPLATE_FUNC_PARAMS_9 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7, typename FUNC_ARG_TYPE_8, \ + typename FUNC_ARG_TYPE_9 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_9 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7, FUNC_ARG_TYPE_8, \ + FUNC_ARG_TYPE_9 +#define FUNC_VALIDATION_STRING_9 \ + V_snprintf(pString, nBufLen, "method( %s, %s, %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name(), typeid(ARG_TYPE_8).name(), \ + typeid(ARG_TYPE_9).name()); +#define FUNC_SEPARATOR_9 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_10 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10 +#define FUNC_TEMPLATE_ARG_PARAMS_10 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_10 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_10 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10 +#define FUNC_ARG_MEMBERS_10 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; \ + ARG_TYPE_8 m_arg8; \ + ARG_TYPE_9 m_arg9; \ + ARG_TYPE_10 m_arg10; +#define FUNC_ARG_FORMAL_PARAMS_10 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_10 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10 +#define FUNC_CALL_ARGS_INIT_10 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7), m_arg8(arg8), m_arg9(arg9), m_arg10(arg10) +#define FUNC_SOLO_CALL_ARGS_INIT_10 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ), m_arg8( arg8 ), m_arg9( arg9 ), m_arg10( arg10 ) +#define FUNC_CALL_MEMBER_ARGS_10 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8, m_arg9, \ + m_arg10 +#define FUNC_CALL_ARGS_10 \ + arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 +#define FUNC_CALL_DATA_ARGS_10(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7, _var->m_arg8, _var->m_arg9, _var->m_arg10 +#define FUNC_FUNCTOR_CALL_ARGS_10 \ + , arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 +#define FUNC_TEMPLATE_FUNC_PARAMS_10 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7, typename FUNC_ARG_TYPE_8, \ + typename FUNC_ARG_TYPE_9, typename FUNC_ARG_TYPE_10 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_10 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7, FUNC_ARG_TYPE_8, \ + FUNC_ARG_TYPE_9, FUNC_ARG_TYPE_10 +#define FUNC_VALIDATION_STRING_10 \ + V_snprintf(pString, nBufLen, \ + "method( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name(), typeid(ARG_TYPE_8).name(), \ + typeid(ARG_TYPE_9).name(), typeid(ARG_TYPE_10).name()); +#define FUNC_SEPARATOR_10 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_11 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11 +#define FUNC_TEMPLATE_ARG_PARAMS_11 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_11 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_11 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11 +#define FUNC_ARG_MEMBERS_11 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; \ + ARG_TYPE_8 m_arg8; \ + ARG_TYPE_9 m_arg9; \ + ARG_TYPE_10 m_arg10; \ + ARG_TYPE_11 m_arg11 +#define FUNC_ARG_FORMAL_PARAMS_11 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_11 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11 +#define FUNC_CALL_ARGS_INIT_11 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7), m_arg8(arg8), m_arg9(arg9), m_arg10(arg10), \ + m_arg11(arg11) +#define FUNC_SOLO_CALL_ARGS_INIT_11 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ), m_arg8( arg8 ), m_arg9( arg9 ), m_arg10( arg10 ), m_arg11( arg11 ) +#define FUNC_CALL_MEMBER_ARGS_11 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8, m_arg9, \ + m_arg10, m_arg11 +#define FUNC_CALL_ARGS_11 \ + arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 +#define FUNC_CALL_DATA_ARGS_11(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7, _var->m_arg8, _var->m_arg9, _var->m_arg10, \ + _var->m_arg11 +#define FUNC_FUNCTOR_CALL_ARGS_11 \ + , arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 +#define FUNC_TEMPLATE_FUNC_PARAMS_11 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7, typename FUNC_ARG_TYPE_8, \ + typename FUNC_ARG_TYPE_9, typename FUNC_ARG_TYPE_10, \ + typename FUNC_ARG_TYPE_11 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_11 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7, FUNC_ARG_TYPE_8, \ + FUNC_ARG_TYPE_9, FUNC_ARG_TYPE_10, FUNC_ARG_TYPE_11 +#define FUNC_VALIDATION_STRING_11 \ + V_snprintf(pString, nBufLen, \ + "method( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name(), typeid(ARG_TYPE_8).name(), \ + typeid(ARG_TYPE_9).name(), typeid(ARG_TYPE_10).name(), \ + typeid(ARG_TYPE_11).name()); +#define FUNC_SEPARATOR_11 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_12 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11, typename ARG_TYPE_12 +#define FUNC_TEMPLATE_ARG_PARAMS_12 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11, typename ARG_TYPE_12 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_12 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11, \ + ARG_TYPE_12 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_12 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11, \ + ARG_TYPE_12 +#define FUNC_ARG_MEMBERS_12 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; \ + ARG_TYPE_8 m_arg8; \ + ARG_TYPE_9 m_arg9; \ + ARG_TYPE_10 m_arg10; \ + ARG_TYPE_11 m_arg11; \ + ARG_TYPE_12 m_arg12 +#define FUNC_ARG_FORMAL_PARAMS_12 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11, \ + const ARG_TYPE_12 &arg12 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_12 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11, \ + const ARG_TYPE_12 &arg12 +#define FUNC_CALL_ARGS_INIT_12 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7), m_arg8(arg8), m_arg9(arg9), m_arg10(arg10), \ + m_arg11(arg11), m_arg12(arg12) +#define FUNC_SOLO_CALL_ARGS_INIT_12 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ), m_arg8( arg8 ), m_arg9( arg9 ), m_arg10( arg10 ), m_arg11( arg11 ), m_arg12( arg12 ) +#define FUNC_CALL_MEMBER_ARGS_12 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8, m_arg9, \ + m_arg10, m_arg11, m_arg12 +#define FUNC_CALL_ARGS_12 \ + arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 +#define FUNC_CALL_DATA_ARGS_12(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7, _var->m_arg8, _var->m_arg9, _var->m_arg10, \ + _var->m_arg11, _var->m_arg12 +#define FUNC_FUNCTOR_CALL_ARGS_12 \ + , arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 +#define FUNC_TEMPLATE_FUNC_PARAMS_12 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7, typename FUNC_ARG_TYPE_8, \ + typename FUNC_ARG_TYPE_9, typename FUNC_ARG_TYPE_10, \ + typename FUNC_ARG_TYPE_11, typename FUNC_ARG_TYPE_12 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_12 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7, FUNC_ARG_TYPE_8, \ + FUNC_ARG_TYPE_9, FUNC_ARG_TYPE_10, FUNC_ARG_TYPE_11, FUNC_ARG_TYPE_12 +#define FUNC_VALIDATION_STRING_12 \ + V_snprintf(pString, nBufLen, \ + "method( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name(), typeid(ARG_TYPE_8).name(), \ + typeid(ARG_TYPE_9).name(), typeid(ARG_TYPE_10).name(), \ + typeid(ARG_TYPE_11).name(), typeid(ARG_TYPE_12).name()); +#define FUNC_SEPARATOR_12 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_13 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11, typename ARG_TYPE_12, \ + typename ARG_TYPE_13 +#define FUNC_TEMPLATE_ARG_PARAMS_13 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11, typename ARG_TYPE_12, \ + typename ARG_TYPE_13 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_13 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11, \ + ARG_TYPE_12, ARG_TYPE_13 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_13 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11, \ + ARG_TYPE_12, ARG_TYPE_13 +#define FUNC_ARG_MEMBERS_13 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; \ + ARG_TYPE_8 m_arg8; \ + ARG_TYPE_9 m_arg9; \ + ARG_TYPE_10 m_arg10; \ + ARG_TYPE_11 m_arg11; \ + ARG_TYPE_12 m_arg12; \ + ARG_TYPE_13 m_arg13 +#define FUNC_ARG_FORMAL_PARAMS_13 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11, \ + const ARG_TYPE_12 &arg12, const ARG_TYPE_13 &arg13 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_13 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11, \ + const ARG_TYPE_12 &arg12, const ARG_TYPE_13 &arg13 +#define FUNC_CALL_ARGS_INIT_13 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7), m_arg8(arg8), m_arg9(arg9), m_arg10(arg10), \ + m_arg11(arg11), m_arg12(arg12), m_arg13(arg13) +#define FUNC_SOLO_CALL_ARGS_INIT_13 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ), m_arg8( arg8 ), m_arg9( arg9 ), m_arg10( arg10 ), m_arg11( arg11 ), m_arg12( arg12 ), m_arg13( arg13 ) +#define FUNC_CALL_MEMBER_ARGS_13 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8, m_arg9, \ + m_arg10, m_arg11, m_arg12, m_arg13 +#define FUNC_CALL_ARGS_13 \ + arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, \ + arg13 +#define FUNC_CALL_DATA_ARGS_13(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7, _var->m_arg8, _var->m_arg9, _var->m_arg10, \ + _var->m_arg11, _var->m_arg12, _var->m_arg13 +#define FUNC_FUNCTOR_CALL_ARGS_13 \ + , arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, \ + arg13 +#define FUNC_TEMPLATE_FUNC_PARAMS_13 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7, typename FUNC_ARG_TYPE_8, \ + typename FUNC_ARG_TYPE_9, typename FUNC_ARG_TYPE_10, \ + typename FUNC_ARG_TYPE_11, typename FUNC_ARG_TYPE_12, \ + typename FUNC_ARG_TYPE_13 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_13 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7, FUNC_ARG_TYPE_8, \ + FUNC_ARG_TYPE_9, FUNC_ARG_TYPE_10, FUNC_ARG_TYPE_11, FUNC_ARG_TYPE_12, \ + FUNC_ARG_TYPE_13 +#define FUNC_VALIDATION_STRING_13 \ + V_snprintf(pString, nBufLen, \ + "method( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name(), typeid(ARG_TYPE_8).name(), \ + typeid(ARG_TYPE_9).name(), typeid(ARG_TYPE_10).name(), \ + typeid(ARG_TYPE_11).name(), typeid(ARG_TYPE_12).name(), \ + typeid(ARG_TYPE_13).name()); +#define FUNC_SEPARATOR_13 , + +#define FUNC_SOLO_TEMPLATE_ARG_PARAMS_14 \ + typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11, typename ARG_TYPE_12, \ + typename ARG_TYPE_13, typename ARG_TYPE_14 +#define FUNC_TEMPLATE_ARG_PARAMS_14 \ + , typename ARG_TYPE_1, typename ARG_TYPE_2, typename ARG_TYPE_3, \ + typename ARG_TYPE_4, typename ARG_TYPE_5, typename ARG_TYPE_6, \ + typename ARG_TYPE_7, typename ARG_TYPE_8, typename ARG_TYPE_9, \ + typename ARG_TYPE_10, typename ARG_TYPE_11, typename ARG_TYPE_12, \ + typename ARG_TYPE_13, typename ARG_TYPE_14 +#define FUNC_BASE_TEMPLATE_ARG_PARAMS_14 \ + , ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11, \ + ARG_TYPE_12, ARG_TYPE_13, ARG_TYPE_14 +#define FUNC_SOLO_BASE_TEMPLATE_ARG_PARAMS_14 \ + ARG_TYPE_1, ARG_TYPE_2, ARG_TYPE_3, ARG_TYPE_4, ARG_TYPE_5, ARG_TYPE_6, \ + ARG_TYPE_7, ARG_TYPE_8, ARG_TYPE_9, ARG_TYPE_10, ARG_TYPE_11, \ + ARG_TYPE_12, ARG_TYPE_13, ARG_TYPE_14 +#define FUNC_ARG_MEMBERS_14 \ + ARG_TYPE_1 m_arg1; \ + ARG_TYPE_2 m_arg2; \ + ARG_TYPE_3 m_arg3; \ + ARG_TYPE_4 m_arg4; \ + ARG_TYPE_5 m_arg5; \ + ARG_TYPE_6 m_arg6; \ + ARG_TYPE_7 m_arg7; \ + ARG_TYPE_8 m_arg8; \ + ARG_TYPE_9 m_arg9; \ + ARG_TYPE_10 m_arg10; \ + ARG_TYPE_11 m_arg11; \ + ARG_TYPE_12 m_arg12; \ + ARG_TYPE_13 m_arg13; \ + ARG_TYPE_14 m_arg14 +#define FUNC_ARG_FORMAL_PARAMS_14 \ + , const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11, \ + const ARG_TYPE_12 &arg12, const ARG_TYPE_13 &arg13, \ + const ARG_TYPE_14 &arg14 +#define FUNC_PROXY_ARG_FORMAL_PARAMS_14 \ + const ARG_TYPE_1 &arg1, const ARG_TYPE_2 &arg2, const ARG_TYPE_3 &arg3, \ + const ARG_TYPE_4 &arg4, const ARG_TYPE_5 &arg5, const ARG_TYPE_6 &arg6, \ + const ARG_TYPE_7 &arg7, const ARG_TYPE_8 &arg8, const ARG_TYPE_9 &arg9, \ + const ARG_TYPE_10 &arg10, const ARG_TYPE_11 &arg11, \ + const ARG_TYPE_12 &arg12, const ARG_TYPE_13 &arg13, \ + const ARG_TYPE_14 &arg14 +#define FUNC_CALL_ARGS_INIT_14 \ + , m_arg1(arg1), m_arg2(arg2), m_arg3(arg3), m_arg4(arg4), m_arg5(arg5), \ + m_arg6(arg6), m_arg7(arg7), m_arg8(arg8), m_arg9(arg9), m_arg10(arg10), \ + m_arg11(arg11), m_arg12(arg12), m_arg13(arg13), m_arg14(arg14) +#define FUNC_SOLO_CALL_ARGS_INIT_14 : m_arg1( arg1 ), m_arg2( arg2 ), m_arg3( arg3 ), m_arg4( arg4 ), m_arg5( arg5 ), m_arg6( arg6 ), m_arg7( arg7 ), m_arg8( arg8 ), m_arg9( arg9 ), m_arg10( arg10 ), m_arg11( arg11 ), m_arg12( arg12 ), m_arg13( arg13 ), m_arg14( arg14 ) +#define FUNC_CALL_MEMBER_ARGS_14 \ + m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8, m_arg9, \ + m_arg10, m_arg11, m_arg12, m_arg13, m_arg14 +#define FUNC_CALL_ARGS_14 \ + arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, \ + arg13, arg14 +#define FUNC_CALL_DATA_ARGS_14(_var) \ + _var->m_arg1, _var->m_arg2, _var->m_arg3, _var->m_arg4, _var->m_arg5, \ + _var->m_arg6, _var->m_arg7, _var->m_arg8, _var->m_arg9, _var->m_arg10, \ + _var->m_arg11, _var->m_arg12, _var->m_arg13, _var->m_arg14 +#define FUNC_FUNCTOR_CALL_ARGS_14 \ + , arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, \ + arg13, arg14 +#define FUNC_TEMPLATE_FUNC_PARAMS_14 \ + , typename FUNC_ARG_TYPE_1, typename FUNC_ARG_TYPE_2, \ + typename FUNC_ARG_TYPE_3, typename FUNC_ARG_TYPE_4, \ + typename FUNC_ARG_TYPE_5, typename FUNC_ARG_TYPE_6, \ + typename FUNC_ARG_TYPE_7, typename FUNC_ARG_TYPE_8, \ + typename FUNC_ARG_TYPE_9, typename FUNC_ARG_TYPE_10, \ + typename FUNC_ARG_TYPE_11, typename FUNC_ARG_TYPE_12, \ + typename FUNC_ARG_TYPE_13, typename FUNC_ARG_TYPE_14 +#define FUNC_BASE_TEMPLATE_FUNC_PARAMS_14 \ + FUNC_ARG_TYPE_1, FUNC_ARG_TYPE_2, FUNC_ARG_TYPE_3, FUNC_ARG_TYPE_4, \ + FUNC_ARG_TYPE_5, FUNC_ARG_TYPE_6, FUNC_ARG_TYPE_7, FUNC_ARG_TYPE_8, \ + FUNC_ARG_TYPE_9, FUNC_ARG_TYPE_10, FUNC_ARG_TYPE_11, FUNC_ARG_TYPE_12, \ + FUNC_ARG_TYPE_13, FUNC_ARG_TYPE_14 +#define FUNC_VALIDATION_STRING_14 \ + V_snprintf( \ + pString, nBufLen, \ + "method( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )", \ + typeid(ARG_TYPE_1).name(), typeid(ARG_TYPE_2).name(), \ + typeid(ARG_TYPE_3).name(), typeid(ARG_TYPE_4).name(), \ + typeid(ARG_TYPE_5).name(), typeid(ARG_TYPE_6).name(), \ + typeid(ARG_TYPE_7).name(), typeid(ARG_TYPE_8).name(), \ + typeid(ARG_TYPE_9).name(), typeid(ARG_TYPE_10).name(), \ + typeid(ARG_TYPE_11).name(), typeid(ARG_TYPE_12).name(), \ + typeid(ARG_TYPE_13).name(), typeid(ARG_TYPE_14).name()); +#define FUNC_SEPARATOR_14 , + +#define FUNC_GENERATE_ALL_BUT0(INNERMACRONAME) \ + INNERMACRONAME(1); \ + INNERMACRONAME(2); \ + INNERMACRONAME(3); \ + INNERMACRONAME(4); \ + INNERMACRONAME(5); \ + INNERMACRONAME(6); \ + INNERMACRONAME(7); \ + INNERMACRONAME(8); \ + INNERMACRONAME(9); \ + INNERMACRONAME(10); \ + INNERMACRONAME(11); \ + INNERMACRONAME(12); \ + INNERMACRONAME(13); \ + INNERMACRONAME(14) + +#define FUNC_GENERATE_ALL(INNERMACRONAME) \ + INNERMACRONAME(0); \ + FUNC_GENERATE_ALL_BUT0(INNERMACRONAME) + +//----------------------------------------------------------------------------- +// +// Purpose: Base class of all function objects +// +//----------------------------------------------------------------------------- +abstract_class CFunctor : public IRefCounted { + public: + CFunctor() { +#ifdef DEBUG + m_nUserID = 0; +#endif + } + virtual ~CFunctor() {} + virtual void operator()() = 0; + +#ifdef DEBUG + unsigned m_nUserID; // For debugging +#endif +}; + +//----------------------------------------------------------------------------- +// NOTE: Functor data + functor callback are tied together +// The basic idea is that someone creates the functor data. At a later point, +// the functor data is passed to a functor callback. Validation strings +// are compared in debug builds to ensure the data matches the callback +//----------------------------------------------------------------------------- +abstract_class CFunctorData : public IRefCounted { + public: + virtual void ComputeValidationString(char *pString, int nBufLen) const = 0; +}; + +abstract_class CFunctorCallback : public IRefCounted { + public: + virtual bool IsEqual(CFunctorCallback * pSrc) const = 0; + virtual void operator()(CFunctorData *pData) = 0; + virtual void ComputeValidationString(char *pString, int nBufLen) const = 0; + virtual const char *GetImplClassName() const = 0; + virtual const void *GetTarget() const = 0; +}; + +//----------------------------------------------------------------------------- +// When calling through a functor, care needs to be taken to not pass objects +// that might go away. Since this code determines the type to store in the +// functor based on the actual arguments, this is achieved by changing the point +// of call. +// +// See also CUtlEnvelope +//----------------------------------------------------------------------------- +// convert a reference to a passable value +template +inline T RefToVal(const T &item) { + return item; +} + +//----------------------------------------------------------------------------- +// This class can be used to pass into a functor a proxy object for a pointer +// to be resolved later. For example, you can execute a "call" on a resource +// whose actual value is not known until a later time +//----------------------------------------------------------------------------- +template +class CLateBoundPtr { + public: + CLateBoundPtr(T **ppObject) : m_ppObject(ppObject) {} + + T *operator->() { return *m_ppObject; } + T &operator*() { return **m_ppObject; } + operator T *() const { return (T *)(*m_ppObject); } + operator void *() { return *m_ppObject; } + + private: + T **m_ppObject; +}; + +//----------------------------------------------------------------------------- +// +// Purpose: Classes to define memory management policies when operating +// on pointers to members +// +//----------------------------------------------------------------------------- +class CFuncMemPolicyNone { + public: + static void OnAcquire(void *pObject) {} + static void OnRelease(void *pObject) {} +}; + +template +class CFuncMemPolicyRefCount { + public: + static void OnAcquire(OBJECT_TYPE_PTR pObject) { pObject->AddRef(); } + static void OnRelease(OBJECT_TYPE_PTR pObject) { pObject->Release(); } +}; + +//----------------------------------------------------------------------------- +// +// Purpose: Function proxy is a generic facility for holding a function +// pointer. Can be used on own, though primarily for use +// by this file +// +//----------------------------------------------------------------------------- +template +class CMemberFuncProxyBase { + public: + bool operator==(const CMemberFuncProxyBase &src) const { + return m_pfnProxied == src.m_pfnProxied && m_pObject == src.m_pObject; + } + + const void *GetTarget() const { return m_pObject; } + + protected: + CMemberFuncProxyBase(OBJECT_TYPE_PTR pObject, FUNCTION_TYPE pfnProxied) + : m_pObject(pObject), m_pfnProxied(pfnProxied) { + MEM_POLICY::OnAcquire(m_pObject); + } + + ~CMemberFuncProxyBase() { MEM_POLICY::OnRelease(m_pObject); } + + void Set(OBJECT_TYPE_PTR pObject, FUNCTION_TYPE pfnProxied) { + m_pfnProxied = pfnProxied; + m_pObject = pObject; + } + + void OnCall() { Assert((void *)m_pObject != NULL); } + + FUNCTION_TYPE m_pfnProxied; + OBJECT_TYPE_PTR m_pObject; +}; + +#define DEFINE_MEMBER_FUNC_PROXY(N) \ + template \ + class CMemberFuncProxy##N \ + : public CMemberFuncProxyBase { \ + public: \ + CMemberFuncProxy##N(OBJECT_TYPE_PTR pObject = NULL, \ + FUNCTION_TYPE pfnProxied = NULL) \ + : CMemberFuncProxyBase( \ + pObject, pfnProxied) {} \ + \ + void operator()(FUNC_PROXY_ARG_FORMAL_PARAMS_##N) { \ + this->OnCall(); \ + ((*this->m_pObject).*this->m_pfnProxied)(FUNC_CALL_ARGS_##N); \ + } \ + } + +FUNC_GENERATE_ALL(DEFINE_MEMBER_FUNC_PROXY); + +//----------------------------------------------------------------------------- +// +// The actual functor implementation +// +//----------------------------------------------------------------------------- + +#include "tier0/memdbgon.h" + +typedef CRefCounted1 CFunctorBase; + +#define DEFINE_FUNCTOR_TEMPLATE(N) \ + template \ + class CFunctor##N : public CFunctorBase { \ + public: \ + CFunctor##N(FUNC_TYPE pfnProxied FUNC_ARG_FORMAL_PARAMS_##N) \ + : m_pfnProxied(pfnProxied) FUNC_CALL_ARGS_INIT_##N {} \ + void operator()() { m_pfnProxied(FUNC_CALL_MEMBER_ARGS_##N); } \ + \ + private: \ + FUNC_TYPE m_pfnProxied; \ + FUNC_ARG_MEMBERS_##N; \ + } + +FUNC_GENERATE_ALL(DEFINE_FUNCTOR_TEMPLATE); + +#define DEFINE_MEMBER_FUNCTOR(N) \ + template \ + class CMemberFunctor##N : public FUNCTOR_BASE { \ + public: \ + CMemberFunctor##N(OBJECT_TYPE_PTR pObject, \ + FUNCTION_TYPE pfnProxied FUNC_ARG_FORMAL_PARAMS_##N) \ + : m_Proxy(pObject, pfnProxied) FUNC_CALL_ARGS_INIT_##N {} \ + void operator()() { m_Proxy(FUNC_CALL_MEMBER_ARGS_##N); } \ + \ + private: \ + CMemberFuncProxy##N \ + m_Proxy; \ + FUNC_ARG_MEMBERS_##N; \ + }; + +FUNC_GENERATE_ALL(DEFINE_MEMBER_FUNCTOR); + +typedef CRefCounted1 CFunctorDataBase; +class CFunctorCallbackBase + : public CRefCounted1 { + protected: + virtual void ValidateFunctorData(CFunctorData *pData) { +#ifdef _DEBUG + char pDataString[1024]; + char pCallbackString[1024]; + ComputeValidationString(pCallbackString, sizeof(pCallbackString)); + pData->ComputeValidationString(pDataString, sizeof(pDataString)); + bool bMatch = !V_stricmp(pDataString, pCallbackString); + if (!bMatch) { + Warning( + "Functor doesn't match data!\n\tExpected:\t%s\n\tEncountered:\t%s\n", + pCallbackString, pDataString); + Assert(0); + } +#endif + } +}; + +#define DEFINE_FUNCTOR_DATA_TEMPLATE(N) \ + template \ + class CFunctorData##N : public CFunctorDataBase { \ + public: \ + CFunctorData##N(FUNC_PROXY_ARG_FORMAL_PARAMS_##N) \ + FUNC_SOLO_CALL_ARGS_INIT_##N {} \ + virtual void ComputeValidationString(char *pString, int nBufLen) const { \ + FUNC_VALIDATION_STRING_##N} FUNC_ARG_MEMBERS_##N; \ + } + +class CFunctorData0 : public CFunctorDataBase { + public: + CFunctorData0() {} + virtual void ComputeValidationString(char *pString, int nBufLen) const { + FUNC_VALIDATION_STRING_0 + } +}; + +FUNC_GENERATE_ALL_BUT0(DEFINE_FUNCTOR_DATA_TEMPLATE); + +#define DEFINE_FUNCTOR_CALLBACK_TEMPLATE(N) \ + template \ + class CFunctorCallback##N : public CFunctorCallbackBase { \ + typedef void (*Callback_t)(FUNC_PROXY_ARG_FORMAL_PARAMS_##N); \ + \ + public: \ + CFunctorCallback##N(Callback_t pfnProxied) : m_pfnProxied(pfnProxied) {} \ + void operator()(CFunctorData *pFunctorDataBase) { \ + ValidateFunctorData(pFunctorDataBase); \ + CFunctorData##N *pFunctorData = \ + static_cast< \ + CFunctorData##N *>( \ + pFunctorDataBase); \ + m_pfnProxied(FUNC_CALL_DATA_ARGS_##N(pFunctorData)); \ + } \ + virtual bool IsEqual(CFunctorCallback *pSrc) const { \ + return !V_stricmp(GetImplClassName(), pSrc->GetImplClassName()) && \ + (m_pfnProxied == \ + static_cast(pSrc)->m_pfnProxied); \ + } \ + virtual void ComputeValidationString(char *pString, int nBufLen) const { \ + FUNC_VALIDATION_STRING_##N \ + } \ + virtual const char *GetImplClassName() const { \ + return "CFunctorCallback" #N; \ + } \ + virtual const void *GetTarget() const { return m_pfnProxied; } \ + \ + private: \ + Callback_t m_pfnProxied; \ + } + +class CFunctorCallback0 : public CFunctorCallbackBase { + typedef void (*Callback_t)(); + + public: + CFunctorCallback0(Callback_t pfnProxied) : m_pfnProxied(pfnProxied) {} + void operator()(CFunctorData *pFunctorDataBase) { + ValidateFunctorData(pFunctorDataBase); + m_pfnProxied(); + } + virtual void ComputeValidationString(char *pString, int nBufLen) const { + FUNC_VALIDATION_STRING_0 + } + virtual bool IsEqual(CFunctorCallback *pSrc) const { + if (V_stricmp(GetImplClassName(), pSrc->GetImplClassName())) return false; + return m_pfnProxied == static_cast(pSrc)->m_pfnProxied; + } + virtual const char *GetImplClassName() const { return "CFunctorCallback0"; } + virtual const void *GetTarget() const { return (void *)m_pfnProxied; } + + private: + Callback_t m_pfnProxied; +}; + +FUNC_GENERATE_ALL_BUT0(DEFINE_FUNCTOR_CALLBACK_TEMPLATE); + +#define DEFINE_MEMBER_FUNCTOR_CALLBACK_TEMPLATE(N) \ + template \ + class CMemberFunctorCallback##N : public CFunctorCallbackBase { \ + typedef void (FUNCTION_CLASS::*MemberCallback_t)( \ + FUNC_PROXY_ARG_FORMAL_PARAMS_##N); \ + \ + public: \ + CMemberFunctorCallback##N(FUNCTION_CLASS *pObject, \ + MemberCallback_t pfnProxied) \ + : m_Proxy(pObject, pfnProxied) {} \ + void operator()(CFunctorData *pFunctorDataBase) { \ + ValidateFunctorData(pFunctorDataBase); \ + CFunctorData##N *pFunctorData = \ + static_cast< \ + CFunctorData##N *>( \ + pFunctorDataBase); \ + m_Proxy(FUNC_CALL_DATA_ARGS_##N(pFunctorData)); \ + } \ + virtual void ComputeValidationString(char *pString, int nBufLen) const { \ + FUNC_VALIDATION_STRING_##N \ + } \ + virtual bool IsEqual(CFunctorCallback *pSrc) const { \ + return !V_stricmp(GetImplClassName(), pSrc->GetImplClassName()) && \ + (m_Proxy == \ + static_cast(pSrc)->m_Proxy); \ + } \ + virtual const char *GetImplClassName() const { \ + return "CMemberFunctorCallback" #N; \ + } \ + virtual const void *GetTarget() const { return m_Proxy.GetTarget(); } \ + \ + private: \ + CMemberFuncProxy##N \ + m_Proxy; \ + } + +template +class CMemberFunctorCallback0 : public CFunctorCallbackBase { + typedef void (FUNCTION_CLASS::*MemberCallback_t)(); + + public: + CMemberFunctorCallback0(FUNCTION_CLASS *pObject, MemberCallback_t pfnProxied) + : m_Proxy(pObject, pfnProxied) {} + void operator()(CFunctorData *pFunctorDataBase) { + ValidateFunctorData(pFunctorDataBase); + m_Proxy(); + } + virtual void ComputeValidationString(char *pString, int nBufLen) const { + FUNC_VALIDATION_STRING_0 + } + virtual bool IsEqual(CFunctorCallback *pSrc) const { + if (V_stricmp(GetImplClassName(), pSrc->GetImplClassName())) return false; + return m_Proxy == static_cast(pSrc)->m_Proxy; + } + virtual const char *GetImplClassName() const { + return "CMemberFunctorCallback0"; + } + virtual const void *GetTarget() const { return m_Proxy.GetTarget(); } + + private: + CMemberFuncProxy0 m_Proxy; +}; + +FUNC_GENERATE_ALL_BUT0(DEFINE_MEMBER_FUNCTOR_CALLBACK_TEMPLATE); + +//----------------------------------------------------------------------------- +// +// The real magic, letting the compiler figure out all the right template +// parameters +// +//----------------------------------------------------------------------------- + +#define DEFINE_NONMEMBER_FUNCTOR_FACTORY(N) \ + template \ + inline CFunctor *CreateFunctor(FUNCTION_RETTYPE (*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + typedef FUNCTION_RETTYPE (*Func_t)(FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N); \ + return new CFunctor##N( \ + pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_NONMEMBER_FUNCTOR_FACTORY); + +//------------------------------------- + +#define DEFINE_MEMBER_FUNCTOR_FACTORY(N) \ + template \ + inline CFunctor *CreateFunctor( \ + OBJECT_TYPE_PTR pObject, \ + FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new CMemberFunctor##N( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_MEMBER_FUNCTOR_FACTORY); + +//------------------------------------- + +#define DEFINE_CONST_MEMBER_FUNCTOR_FACTORY(N) \ + template \ + inline CFunctor *CreateFunctor( \ + OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) \ + const FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new CMemberFunctor##N( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_CONST_MEMBER_FUNCTOR_FACTORY); + +//------------------------------------- + +#define DEFINE_REF_COUNTING_MEMBER_FUNCTOR_FACTORY(N) \ + template \ + inline CFunctor *CreateRefCountingFunctor( \ + OBJECT_TYPE_PTR pObject, \ + FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new CMemberFunctor##N>( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_REF_COUNTING_MEMBER_FUNCTOR_FACTORY); + +//------------------------------------- + +#define DEFINE_REF_COUNTING_CONST_MEMBER_FUNCTOR_FACTORY(N) \ + template \ + inline CFunctor *CreateRefCountingFunctor( \ + OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) \ + const FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new CMemberFunctor##N>( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_REF_COUNTING_CONST_MEMBER_FUNCTOR_FACTORY); + +#define DEFINE_FUNCTOR_DATA_FACTORY(N) \ + template \ + inline CFunctorData *CreateFunctorData(FUNC_PROXY_ARG_FORMAL_PARAMS_##N) { \ + return new CFunctorData##N( \ + FUNC_CALL_ARGS_##N); \ + } + +inline CFunctorData *CreateFunctorData() { return new CFunctorData0(); } + +FUNC_GENERATE_ALL_BUT0(DEFINE_FUNCTOR_DATA_FACTORY); + +#define DEFINE_FUNCTOR_CALLBACK_FACTORY(N) \ + template \ + inline CFunctorCallback *CreateFunctorCallback( \ + void (*pfnProxied)(FUNC_PROXY_ARG_FORMAL_PARAMS_##N)) { \ + return new CFunctorCallback##N( \ + pfnProxied); \ + } + +inline CFunctorCallback *CreateFunctorCallback(void (*pfnProxied)()) { + return new CFunctorCallback0(pfnProxied); +} + +FUNC_GENERATE_ALL_BUT0(DEFINE_FUNCTOR_CALLBACK_FACTORY); + +#define DEFINE_MEMBER_FUNCTOR_CALLBACK_FACTORY(N) \ + template \ + inline CFunctorCallback *CreateFunctorCallback( \ + FUNCTION_CLASS *pObject, \ + void (FUNCTION_CLASS::*pfnProxied)(FUNC_PROXY_ARG_FORMAL_PARAMS_##N)) { \ + return new CMemberFunctorCallback##N< \ + FUNCTION_CLASS FUNC_BASE_TEMPLATE_ARG_PARAMS_##N>(pObject, \ + pfnProxied); \ + } + +FUNC_GENERATE_ALL(DEFINE_MEMBER_FUNCTOR_CALLBACK_FACTORY); + +//----------------------------------------------------------------------------- +// +// Templates to assist early-out direct call code +// +//----------------------------------------------------------------------------- + +#define DEFINE_NONMEMBER_FUNCTOR_DIRECT(N) \ + template \ + inline void FunctorDirectCall(FUNCTION_RETTYPE (*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + (*pfnProxied)(FUNC_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_NONMEMBER_FUNCTOR_DIRECT); + +//------------------------------------- + +#define DEFINE_MEMBER_FUNCTOR_DIRECT(N) \ + template \ + inline void FunctorDirectCall( \ + OBJECT_TYPE_PTR pObject, \ + FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + ((*pObject).*pfnProxied)(FUNC_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_MEMBER_FUNCTOR_DIRECT); + +//------------------------------------- + +#define DEFINE_CONST_MEMBER_FUNCTOR_DIRECT(N) \ + template \ + inline void FunctorDirectCall( \ + OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) \ + const FUNC_ARG_FORMAL_PARAMS_##N) { \ + ((*pObject).*pfnProxied)(FUNC_CALL_ARGS_##N); \ + } + +FUNC_GENERATE_ALL(DEFINE_CONST_MEMBER_FUNCTOR_DIRECT); + +#include "tier0/memdbgoff.h" + +//----------------------------------------------------------------------------- +// Factory class useable as templated traits +//----------------------------------------------------------------------------- + +class CDefaultFunctorFactory { + public: + FUNC_GENERATE_ALL(DEFINE_NONMEMBER_FUNCTOR_FACTORY); + FUNC_GENERATE_ALL(DEFINE_MEMBER_FUNCTOR_FACTORY); + FUNC_GENERATE_ALL(DEFINE_CONST_MEMBER_FUNCTOR_FACTORY); + FUNC_GENERATE_ALL(DEFINE_REF_COUNTING_MEMBER_FUNCTOR_FACTORY); + FUNC_GENERATE_ALL(DEFINE_REF_COUNTING_CONST_MEMBER_FUNCTOR_FACTORY); +}; + +template +class CCustomizedFunctorFactory { + public: + void SetAllocator(CAllocator *pAllocator) { m_pAllocator = pAllocator; } + +#define DEFINE_NONMEMBER_FUNCTOR_FACTORY_CUSTOM(N) \ + template \ + inline CFunctor *CreateFunctor(FUNCTION_RETTYPE (*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + typedef FUNCTION_RETTYPE (*Func_t)(FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N); \ + return new (m_pAllocator->Alloc( \ + sizeof(CFunctor##N))) \ + CFunctor##N( \ + pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + + FUNC_GENERATE_ALL(DEFINE_NONMEMBER_FUNCTOR_FACTORY_CUSTOM); + + //------------------------------------- + +#define DEFINE_MEMBER_FUNCTOR_FACTORY_CUSTOM(N) \ + template \ + inline CFunctor *CreateFunctor( \ + OBJECT_TYPE_PTR pObject, \ + FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new (m_pAllocator->Alloc( \ + sizeof(CMemberFunctor##N))) \ + CMemberFunctor##N( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + + FUNC_GENERATE_ALL(DEFINE_MEMBER_FUNCTOR_FACTORY_CUSTOM); + + //------------------------------------- + +#define DEFINE_CONST_MEMBER_FUNCTOR_FACTORY_CUSTOM(N) \ + template \ + inline CFunctor *CreateFunctor( \ + OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) \ + const FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new (m_pAllocator->Alloc( \ + sizeof(CMemberFunctor##N))) \ + CMemberFunctor##N( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + + FUNC_GENERATE_ALL(DEFINE_CONST_MEMBER_FUNCTOR_FACTORY_CUSTOM); + + //------------------------------------- + +#define DEFINE_REF_COUNTING_MEMBER_FUNCTOR_FACTORY_CUSTOM(N) \ + template \ + inline CFunctor *CreateRefCountingFunctor( \ + OBJECT_TYPE_PTR pObject, \ + FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new (m_pAllocator->Alloc( \ + sizeof(CMemberFunctor##N>))) \ + CMemberFunctor##N>( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + + FUNC_GENERATE_ALL(DEFINE_REF_COUNTING_MEMBER_FUNCTOR_FACTORY_CUSTOM); + + //------------------------------------- + +#define DEFINE_REF_COUNTING_CONST_MEMBER_FUNCTOR_FACTORY_CUSTOM(N) \ + template \ + inline CFunctor *CreateRefCountingFunctor( \ + OBJECT_TYPE_PTR pObject, FUNCTION_RETTYPE (FUNCTION_CLASS::*pfnProxied)( \ + FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N) \ + const FUNC_ARG_FORMAL_PARAMS_##N) { \ + return new (m_pAllocator->Alloc( \ + sizeof(CMemberFunctor##N>))) \ + CMemberFunctor##N>( \ + pObject, pfnProxied FUNC_FUNCTOR_CALL_ARGS_##N); \ + } + + FUNC_GENERATE_ALL(DEFINE_REF_COUNTING_CONST_MEMBER_FUNCTOR_FACTORY_CUSTOM); + + private: + CAllocator *m_pAllocator; +}; + +//----------------------------------------------------------------------------- + +#endif // VPC_TIER1_FUNCTORS_H_ diff --git a/public/tier1/generichash.h b/public/tier1/generichash.h new file mode 100644 index 0000000..f722f50 --- /dev/null +++ b/public/tier1/generichash.h @@ -0,0 +1,93 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Variant Pearson Hash general purpose hashing algorithm described by +// Cargill in C++ Report 1994. Generates a 16-bit result. + +#ifndef VPC_TIER1_GENERICHASH_H_ +#define VPC_TIER1_GENERICHASH_H_ + +#include "tier0/platform.h" + +unsigned FASTCALL HashString(const char *pszKey); +unsigned FASTCALL HashStringCaseless(const char *pszKey); +unsigned FASTCALL HashStringCaselessConventional(const char *pszKey); +unsigned FASTCALL Hash4(const void *pKey); +unsigned FASTCALL Hash8(const void *pKey); +unsigned FASTCALL Hash12(const void *pKey); +unsigned FASTCALL Hash16(const void *pKey); +unsigned FASTCALL HashBlock(const void *pKey, unsigned size); + +unsigned FASTCALL HashInt(const int key); + +// hash a uint32 into a uint32 +FORCEINLINE uint32 HashIntAlternate(uint32 n) { + n = (n + 0x7ed55d16) + (n << 12); + n = (n ^ 0xc761c23c) ^ (n >> 19); + n = (n + 0x165667b1) + (n << 5); + n = (n + 0xd3a2646c) ^ (n << 9); + n = (n + 0xfd7046c5) + (n << 3); + n = (n ^ 0xb55a4f09) ^ (n >> 16); + return n; +} + +// faster but less effective +inline unsigned HashIntConventional(const int n) { + // first byte + unsigned hash = 0xAAAAAAAA + (n & 0xFF); + // second byte + hash = (hash << 5) + hash + ((n >> 8) & 0xFF); + // third byte + hash = (hash << 5) + hash + ((n >> 16) & 0xFF); + // fourth byte + hash = (hash << 5) + hash + ((n >> 24) & 0xFF); + + return hash; +} + +template +inline unsigned HashItem(const T &item) { + // TODO: Confirm comiler optimizes out unused paths + if constexpr (sizeof(item) == 4) return Hash4(&item); + + if constexpr (sizeof(item) == 8) return Hash8(&item); + + if constexpr (sizeof(item) == 12) return Hash12(&item); + + if constexpr (sizeof(item) == 16) return Hash16(&item); + + return HashBlock(&item, sizeof(item)); +} + +template <> +inline unsigned HashItem(const int &key) { + return HashInt(key); +} + +template <> +inline unsigned HashItem(const unsigned &key) { + return HashInt((int)key); +} + +template <> +inline unsigned HashItem(const char *const &pszKey) { + return HashString(pszKey); +} + +template <> +inline unsigned HashItem(char *const &pszKey) { + return HashString(pszKey); +} + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Murmur hash +//----------------------------------------------------------------------------- +uint32 MurmurHash2(const void *key, int len, uint32 seed); + +// return murmurhash2 of a downcased string +uint32 MurmurHash2LowerCase(char const *pString, uint32 nSeed); + +uint64 MurmurHash64(const void *key, int len, uint32 seed); + +#endif // VPC_TIER1_GENERICHASH_H_ diff --git a/public/tier1/iconvar.h b/public/tier1/iconvar.h new file mode 100644 index 0000000..0250bfd --- /dev/null +++ b/public/tier1/iconvar.h @@ -0,0 +1,139 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_ICONVAR_H_ +#define VPC_TIER1_ICONVAR_H_ + +#include "tier0/dbg.h" +#include "tier0/platform.h" +#include "tier1/strtools.h" +#include "color.h" + +// Forward declarations +class IConVar; +class CCommand; + +// ConVar flags +// +// The default, no flags at all +#define FCVAR_NONE 0 + +// Command to ConVars and ConCommands +// ConVar Systems + +// If this is set, don't add to linked list, etc. +#define FCVAR_UNREGISTERED (1 << 0) +// Hidden in released products. Flag is removed automatically if +// ALLOW_DEVELOPMENT_CVARS is defined. +#define FCVAR_DEVELOPMENTONLY (1 << 1) +#define FCVAR_GAMEDLL (1 << 2) // defined by the game DLL +#define FCVAR_CLIENTDLL (1 << 3) // defined by the client DLL +// Hidden. Doesn't appear in find or auto complete. Like DEVELOPMENTONLY, but +// can't be compiled out. +#define FCVAR_HIDDEN (1 << 4) + +// ConVar only + +// It's a server cvar, but we don't send the data since it's a +// password, etc. Sends 1 if it's not bland/zero, 0 otherwise as +// value +#define FCVAR_PROTECTED (1 << 5) +// This cvar cannot be changed by clients connected to a multiplayer +// server. +#define FCVAR_SPONLY (1 << 6) +#define FCVAR_ARCHIVE (1 << 7) // set to cause it to be saved to vars.rc +#define FCVAR_NOTIFY (1 << 8) // notifies players when changed +#define FCVAR_USERINFO (1 << 9) // changes the client's info string + +// This cvar's string cannot contain unprintable characters ( e.g., +// used for player name etc ). +#define FCVAR_PRINTABLEONLY (1 << 10) +// If this is a FCVAR_SERVER, don't log changes to the log file / +// console if we are creating a log +#define FCVAR_UNLOGGED (1 << 11) +// never try to print that cvar +#define FCVAR_NEVER_AS_STRING (1 << 12) + +// It's a ConVar that's shared between the client and the server. +// At signon, the values of all such ConVars are sent from the server to the +// client (skipped for local client, of course). If a change is requested it +// must come from the console (i.e., no remote client changes). If a value is +// changed while a server is active, it's replicated to all connected clients +// +// server setting enforced on clients, TODO rename to FCAR_SERVER at some time +#define FCVAR_REPLICATED (1 << 13) +// Only useable in singleplayer / debug / multiplayer & sv_cheats +#define FCVAR_CHEAT (1 << 14) +// causes varnameN where N == 2 through max splitscreen slots for mod to be +// autogenerated +#define FCVAR_SS (1 << 15) +// record this cvar when starting a demo file +#define FCVAR_DEMO (1 << 16) +// don't record these command in demofiles +#define FCVAR_DONTRECORD (1 << 17) +// This is one of the "added" FCVAR_SS variables for the splitscreen players +#define FCVAR_SS_ADDED (1 << 18) +// Cvars tagged with this are the only cvars avaliable to customers +#define FCVAR_RELEASE (1 << 19) +// If this cvar changes, it forces a material reload +#define FCVAR_RELOAD_MATERIALS (1 << 20) +// If this cvar changes, if forces a texture reload +#define FCVAR_RELOAD_TEXTURES (1 << 21) + +// cvar cannot be changed by a client that is connected to a server +#define FCVAR_NOT_CONNECTED (1 << 22) +// Indicates this cvar is read from the material system thread +#define FCVAR_MATERIAL_SYSTEM_THREAD (1 << 23) +// cvar written to config.cfg on the Xbox +#define FCVAR_ARCHIVE_GAMECONSOLE (1 << 24) + +// the server is allowed to execute this command on clients via +// ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd. +#define FCVAR_SERVER_CAN_EXECUTE (1 << 28) +// If this is set, then the server is not allowed to query this cvar's value +// (via IServerPluginHelpers::StartQueryCvarValue). +#define FCVAR_SERVER_CANNOT_QUERY (1 << 29) +// IVEngineClient::ClientCmd is allowed to execute this command. +// Note: IVEngineClient::ClientCmd_Unrestricted can run any client command. +#define FCVAR_CLIENTCMD_CAN_EXECUTE (1 << 30) + +// used as a debugging tool necessary to check material system +// thread convars +#define FCVAR_ACCESSIBLE_FROM_THREADS (1 << 25) + +// #define FCVAR_AVAILABLE (1<<26) +// #define FCVAR_AVAILABLE (1<<27) +// #define FCVAR_AVAILABLE (1<<31) + +#define FCVAR_MATERIAL_THREAD_MASK \ + (FCVAR_RELOAD_MATERIALS | FCVAR_RELOAD_TEXTURES | \ + FCVAR_MATERIAL_SYSTEM_THREAD) + +// Called when a ConVar changes value +// NOTE: For FCVAR_NEVER_AS_STRING ConVars, pOldValue == NULL +using FnChangeCallback_t = void (*)(IConVar *var, const char *pOldValue, + float flOldValue); + +// Abstract interface for ConVars +abstract_class IConVar { + public: + // Value set + virtual void SetValue(const char *pValue) = 0; + virtual void SetValue(float flValue) = 0; + virtual void SetValue(int nValue) = 0; + virtual void SetValue(Color value) = 0; + + // Return name of command + virtual const char *GetName(void) const = 0; + + // Return name of command (usually == GetName(), except in case of + // FCVAR_SS_ADDED vars + virtual const char *GetBaseName(void) const = 0; + + // Accessors.. not as efficient as using GetState()/GetInfo() + // if you call these methods multiple times on the same IConVar + virtual bool IsFlagSet(int nFlag) const = 0; + + virtual int GetSplitScreenPlayerSlot() const = 0; +}; + +#endif // VPC_TIER1_ICONVAR_H_ diff --git a/public/tier1/interface.h b/public/tier1/interface.h new file mode 100644 index 0000000..37c484d --- /dev/null +++ b/public/tier1/interface.h @@ -0,0 +1,219 @@ +// Copyright Valve Corporation, All rights reserved. + +// This header defines the interface convention used in the valve engine. +// To make an interface and expose it: +// 1. The interface must be ALL pure virtuals, and have no data members. +// 2. Define a name for it. +// 3. In its implementation file, use EXPOSE_INTERFACE or +// EXPOSE_SINGLE_INTERFACE. + +// Versioning +// There are two versioning cases that are handled by this: +// 1. You add functions to the end of an interface, so it is binary compatible +// with the previous interface. In this case, +// you need two EXPOSE_INTERFACEs: one to expose your class as the old +// interface and one to expose it as the new interface. +// 2. You update an interface so it's not compatible anymore (but you still want +// to be able to expose the old interface +// for legacy code). In this case, you need to make a new version name for +// your new interface, and make a wrapper interface and expose it for the old +// interface. + +// Static Linking: +// Must mimic unique separate class 'InterfaceReg' constructors per subsystem. +// Each subsystem can then import and export interfaces as expected. +// This is achieved through unique namespacing 'InterfaceReg' via symbol +// _SUBSYSTEM. Static Linking also needs to generate unique symbols per +// interface so as to provide a 'stitching' method whereby these interface +// symbols can be referenced via the lib's primary module (usually the lib's +// interface exposure) therby stitching all of that lib's code/data together for +// eventual final exe link inclusion. + +#ifndef INTERFACE_H +#define INTERFACE_H + +// TODO: move interface.cpp into tier0 library. +// Need to include platform.h in case _PS3 and other tokens are not yet defined +#include "tier0/platform.h" + +#if defined(POSIX) && !defined(_PS3) + +#include // dlopen,dlclose, et al +#include + +#define GetProcAddress dlsym + +#ifdef _snprintf +#undef _snprintf +#endif +#define _snprintf snprintf +#endif // POSIX && !_PS3 + +// All interfaces derive from this. +struct IBaseInterface { + virtual ~IBaseInterface() {} +}; + +#if !defined(_X360) +#define CREATEINTERFACE_PROCNAME "CreateInterface" +#else +// x360 only allows ordinal exports, .def files export "CreateInterface" at 1 +#define CREATEINTERFACE_PROCNAME ((const char *)1) +#endif + +typedef void *(*CreateInterfaceFn)(const char *pName, int *pReturnCode); +typedef void *(*InstantiateInterfaceFn)(); + +// Used internally to register classes. +class InterfaceReg { + public: + InterfaceReg(InstantiateInterfaceFn fn, const char *pName); + + public: + InstantiateInterfaceFn m_CreateFn; + const char *m_pName; + + InterfaceReg *m_pNext; // For the global list. +}; + +// Use this to expose an interface that can have multiple instances, e.g.: +// EXPOSE_INTERFACE(CInterfaceImp, IInterface, "MyInterface001") +// +// This will expose a class called CInterfaceImp that implements IInterface (a +// pure class) clients can receive a pointer to this class by calling +// CreateInterface("MyInterface001") +// +// In practice, the shared header file defines the interface (IInterface) and +// version name ("MyInterface001") so that each component can use these +// names/vtables to communicate +// +// A single class can support multiple interfaces through multiple inheritance +// +// Use this if you want to write the factory function. +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ + static InterfaceReg __g_Create##interfaceName##_reg(functionName, \ + versionName); +#else +#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ + namespace _SUBSYSTEM { \ + static InterfaceReg __g_Create##interfaceName##_reg(functionName, \ + versionName); \ + } +#endif + +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + static void *__Create##className##_interface() { \ + return static_cast(new className); \ + } \ + static InterfaceReg __g_Create##className##_reg( \ + __Create##className##_interface, versionName); +#else +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + namespace _SUBSYSTEM { \ + static void *__Create##className##_interface() { \ + return static_cast(new className); \ + } \ + static InterfaceReg __g_Create##className##_reg( \ + __Create##className##_interface, versionName); \ + } +#endif + +// Use this to expose a singleton interface with a global variable you've +// created. +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, \ + versionName, globalVarName) \ + static void *__Create##className##interfaceName##_interface() { \ + return static_cast(&globalVarName); \ + } \ + static InterfaceReg __g_Create##className##interfaceName##_reg( \ + __Create##className##interfaceName##_interface, versionName); +#else +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, \ + versionName, globalVarName) \ + namespace _SUBSYSTEM { \ + static void *__Create##className##interfaceName##_interface() { \ + return static_cast(&globalVarName); \ + } \ + static InterfaceReg __g_Create##className##interfaceName##_reg( \ + __Create##className##interfaceName##_interface, versionName); \ + } +#endif + +// Use this to expose a singleton interface. This creates the global variable +// for you automatically. +#if !defined(_STATIC_LINKED) || !defined(_SUBSYSTEM) +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + static className __g_##className##_singleton; \ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, \ + __g_##className##_singleton) +#else +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + namespace _SUBSYSTEM { \ + static className __g_##className##_singleton; \ + } \ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, \ + __g_##className##_singleton) +#endif + +// load/unload components +class CSysModule; + +// interface return status +enum { IFACE_OK = 0, IFACE_FAILED }; + +// This function is automatically exported and allows you to access any +// interfaces exposed with the above macros. if pReturnCode is set, it will +// return one of the following values (IFACE_OK, IFACE_FAILED) extend this for +// other error conditions/code +DLL_EXPORT void *CreateInterface(const char *pName, int *pReturnCode); + +#if defined(_X360) +DLL_EXPORT void *CreateInterfaceThunk(const char *pName, int *pReturnCode); +#endif + +// UNDONE: This is obsolete, use the module load/unload/get instead!!! +extern CreateInterfaceFn Sys_GetFactory(CSysModule *pModule); +extern CreateInterfaceFn Sys_GetFactory(const char *pModuleName); +extern CreateInterfaceFn Sys_GetFactoryThis(void); + +// Load & Unload should be called in exactly one place for each module +// The factory for that module should be passed on to dependent components for +// proper versioning. +extern CSysModule *Sys_LoadModule(const char *pModuleName); +extern void Sys_UnloadModule(CSysModule *pModule); + +// Determines if current process is running with any debug modules +extern bool Sys_RunningWithDebugModules(); + +// This is a helper function to load a module, get its factory, and get a +// specific interface. You are expected to free all of these things. Returns +// false and cleans up if any of the steps fail. +bool Sys_LoadInterface(const char *pModuleName, + const char *pInterfaceVersionName, + CSysModule **pOutModule, void **pOutInterface); + +bool Sys_IsDebuggerPresent(); + +// Purpose: Place this as a singleton at module scope (e.g.) and use it to get +// the factory from the specified module name. +// +// When the singleton goes out of scope (.dll unload if at module scope), +// then it'll call Sys_UnloadModule on the module so that the refcount is +// decremented and the .dll actually can unload from memory. +class CDllDemandLoader { + public: + CDllDemandLoader(char const *pchModuleName); + virtual ~CDllDemandLoader(); + CreateInterfaceFn GetFactory(); + void Unload(); + + private: + char const *m_pchModuleName; + CSysModule *m_hModule; + bool m_bLoadAttempted; +}; + +#endif diff --git a/public/tier1/keyvalues.h b/public/tier1/keyvalues.h new file mode 100644 index 0000000..4f6199d --- /dev/null +++ b/public/tier1/keyvalues.h @@ -0,0 +1,570 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_KEYVALUES_H_ +#define VPC_TIER1_KEYVALUES_H_ + +#include "utlvector.h" +#include "color.h" +#include "exprevaluator.h" + +#define FOR_EACH_SUBKEY(kvRoot, kvSubKey) \ + for (KeyValues *kvSubKey = kvRoot->GetFirstSubKey(); kvSubKey != NULL; \ + kvSubKey = kvSubKey->GetNextKey()) + +#define FOR_EACH_TRUE_SUBKEY(kvRoot, kvSubKey) \ + for (KeyValues *kvSubKey = kvRoot->GetFirstTrueSubKey(); kvSubKey != NULL; \ + kvSubKey = kvSubKey->GetNextTrueSubKey()) + +#define FOR_EACH_VALUE(kvRoot, kvValue) \ + for (KeyValues *kvValue = kvRoot->GetFirstValue(); kvValue != NULL; \ + kvValue = kvValue->GetNextValue()) + +class IBaseFileSystem; +class CUtlBuffer; +class Color; +class KeyValues; +class IKeyValuesDumpContext; +typedef void *FileHandle_t; +class CKeyValuesGrowableStringTable; + +// single byte identifies a xbox kv file in binary format +// strings are pooled from a searchpath/zip mounted symbol table +#define KV_BINARY_POOLED_FORMAT 0xAA + +#define FOR_EACH_SUBKEY(kvRoot, kvSubKey) \ + for (KeyValues *kvSubKey = kvRoot->GetFirstSubKey(); kvSubKey != NULL; \ + kvSubKey = kvSubKey->GetNextKey()) + +#define FOR_EACH_TRUE_SUBKEY(kvRoot, kvSubKey) \ + for (KeyValues *kvSubKey = kvRoot->GetFirstTrueSubKey(); kvSubKey != NULL; \ + kvSubKey = kvSubKey->GetNextTrueSubKey()) + +#define FOR_EACH_VALUE(kvRoot, kvValue) \ + for (KeyValues *kvValue = kvRoot->GetFirstValue(); kvValue != NULL; \ + kvValue = kvValue->GetNextValue()) + +// Purpose: Simple recursive data access class +// +// Used in vgui for message parameters and resource files. Destructor deletes +// all child KeyValues nodes Data is stored in key (string names) - +// (string/int/float) value pairs called nodes. + +// About KeyValues Text File Format: +// +// It has 3 control characters '{', '}' and '"'. Names and values may be +// quoted or not. The quote '"' character must not be used within name or +// values, only for quoting whole tokens. You may use escape sequences wile +// parsing and add within a quoted token a \" to add quotes within your name or +// token. +// +// When using Escape Sequence the parser must now that by setting +// KeyValues::UsesEscapeSequences(true), which it's off by default. +// +// Non-quoted tokens ends with a whitespace, '{', '}' and '"'. So you may use +// '{' and '}' within quoted tokens, but not for non-quoted tokens. +// +// An open bracket '{' after a key name indicates a list of subkeys which is +// finished with a closing bracket '}'. Subkeys use the same definitions +// recursively. +// +// Whitespaces are space, return, newline and tabulator. Allowed +// Escape sequences are \n, \t, \\, \n and \". The number character '#' is used +// for macro purposes (eg #include), don't use it as first character in key +// names. +class KeyValues { + public: + // By default, the KeyValues class uses a string table for the key names + // that is limited to 4MB. The game will exit in error if this space is + // exhausted. In general this is preferable for game code for performance + // and memory fragmentation reasons. + // + // If this is not acceptable, you can use this call to switch to a table + // that can grow arbitrarily. This call must be made before any KeyValues + // objects are allocated or it will result in undefined behavior. If + // you use the growable string table, you cannot share KeyValues pointers + // directly with any other module. You can serialize them across module + // boundaries. These limitations are acceptable in the Steam backend code + // this option was written for, but may not be in other situations. Make sure + // to understand the implications before using this. + static void SetUseGrowableStringTable(bool bUseGrowableTable); + + explicit KeyValues(const char *setName); + + // AutoDelete class to automatically free the keyvalues. Simply construct it + // with the keyvalues you allocated and it will free them when falls out of + // scope. When you decide that keyvalues shouldn't be deleted call + // Assign(nullptr) on it. If you constructed AutoDelete(nullptr) you can + // later assign the keyvalues to be deleted with Assign(pKeyValues). + class AutoDelete { + public: + explicit inline AutoDelete(KeyValues *pKeyValues) + : m_pKeyValues(pKeyValues) {} + explicit inline AutoDelete(const char *pchKVName) + : m_pKeyValues(new KeyValues(pchKVName)) {} + inline ~AutoDelete(void) { + if (m_pKeyValues) m_pKeyValues->deleteThis(); + } + inline void Assign(KeyValues *pKeyValues) { m_pKeyValues = pKeyValues; } + KeyValues *operator->() { return m_pKeyValues; } + operator KeyValues *() { return m_pKeyValues; } + + private: + AutoDelete(AutoDelete const &x); // forbid + AutoDelete &operator=(AutoDelete const &x); // forbid + protected: + KeyValues *m_pKeyValues; + }; + + // + // AutoDeleteInline is useful when you want to hold your keyvalues object + // inside and delete it right after using. You can also pass temporary + // KeyValues object as an argument to a function by wrapping it into + // KeyValues::AutoDeleteInline instance: call_my_function( + // KeyValues::AutoDeleteInline( new KeyValues( "test" ) ) ) + // + class AutoDeleteInline : public AutoDelete { + public: + explicit inline AutoDeleteInline(KeyValues *pKeyValues) + : AutoDelete(pKeyValues) {} + inline operator KeyValues *() const { return m_pKeyValues; } + inline KeyValues *Get() const { return m_pKeyValues; } + }; + + // Quick setup constructors + KeyValues(const char *setName, const char *firstKey, const char *firstValue); + KeyValues(const char *setName, const char *firstKey, + const wchar_t *firstValue); + KeyValues(const char *setName, const char *firstKey, int firstValue); + KeyValues(const char *setName, const char *firstKey, const char *firstValue, + const char *secondKey, const char *secondValue); + KeyValues(const char *setName, const char *firstKey, int firstValue, + const char *secondKey, int secondValue); + + // Section name + const char *GetName() const; + void SetName(const char *setName); + + // gets the name as a unique int + int GetNameSymbol() const; + int GetNameSymbolCaseSensitive() const; + + // File access. Set UsesEscapeSequences true, if resource file/buffer uses + // Escape Sequences (eg \n, \t) + void UsesEscapeSequences(bool state); // default false + bool LoadFromFile(IBaseFileSystem *filesystem, const char *resourceName, + const char *pathID = NULL, + GetSymbolProc_t pfnEvaluateSymbolProc = NULL); + bool SaveToFile(IBaseFileSystem *filesystem, const char *resourceName, + const char *pathID = NULL); + + // Read from a buffer... Note that the buffer must be null terminated + bool LoadFromBuffer(char const *resourceName, const char *pBuffer, + IBaseFileSystem *pFileSystem = NULL, + const char *pPathID = NULL, + GetSymbolProc_t pfnEvaluateSymbolProc = NULL); + + // Read from a utlbuffer... + bool LoadFromBuffer(char const *resourceName, CUtlBuffer &buf, + IBaseFileSystem *pFileSystem = NULL, + const char *pPathID = NULL, + GetSymbolProc_t pfnEvaluateSymbolProc = NULL); + + // Find a keyValue, create it if it is not found. + // Set bCreate to true to create the key if it doesn't already exist (which + // ensures a valid pointer will be returned) + KeyValues *FindKey(const char *keyName, bool bCreate = false); + KeyValues *FindKey(int keySymbol) const; + KeyValues *CreateNewKey(); // creates a new key, with an autogenerated name. + // name is guaranteed to be an integer, of value 1 + // higher than the highest other integer key name + void AddSubKey(KeyValues *pSubkey); // Adds a subkey. Make sure the subkey + // isn't a child of some other keyvalues + void RemoveSubKey( + KeyValues *subKey); // removes a subkey from the list, DOES NOT DELETE IT + void InsertSubKey(int nIndex, + KeyValues *pSubKey); // Inserts the given sub-key before + // the Nth child location + bool ContainsSubKey( + KeyValues *pSubKey); // Returns true if this key values contains the + // specified sub key, false otherwise. + void SwapSubKey(KeyValues *pExistingSubKey, + KeyValues *pNewSubKey); // Swaps an existing subkey for a new + // one, DOES NOT DELETE THE OLD ONE + // but takes ownership of the new one + void ElideSubKey( + KeyValues *pSubKey); // Removes a subkey but inserts all of its children + // in its place, in-order (flattens a tree, like + // firing a manager!) + + // Key iteration. + // + // NOTE: GetFirstSubKey/GetNextKey will iterate keys AND values. Use the + // functions below if you want to iterate over just the keys or just the + // values. + // + KeyValues *GetFirstSubKey(); // returns the first subkey in the list + KeyValues *GetNextKey(); // returns the next subkey + void SetNextKey(KeyValues *pDat); + + // + // These functions can be used to treat it like a true key/values tree instead + // of confusing values with keys. + // + // So if you wanted to iterate all subkeys, then all values, it would look + // like this: + // for ( KeyValues *pKey = pRoot->GetFirstTrueSubKey(); pKey; pKey = + // pKey->GetNextTrueSubKey() ) + // { + // Msg( "Key name: %s\n", pKey->GetName() ); + // } + // for ( KeyValues *pValue = pRoot->GetFirstValue(); pKey; pKey = + // pKey->GetNextValue() ) + // { + // Msg( "Int value: %d\n", pValue->GetInt() ); // Assuming + // pValue->GetDataType() == TYPE_INT... + // } + KeyValues *GetFirstTrueSubKey(); + KeyValues *GetNextTrueSubKey(); + + KeyValues *GetFirstValue(); // When you get a value back, you can use GetX + // and pass in NULL to get the value. + KeyValues *GetNextValue(); + + // Data access + int GetInt(const char *keyName = NULL, int defaultValue = 0); + uint64 GetUint64(const char *keyName = NULL, uint64 defaultValue = 0); + float GetFloat(const char *keyName = NULL, float defaultValue = 0.0f); + const char *GetString(const char *keyName = NULL, + const char *defaultValue = ""); + const wchar_t *GetWString(const char *keyName = NULL, + const wchar_t *defaultValue = L""); + void *GetPtr(const char *keyName = NULL, void *defaultValue = (void *)0); + Color GetColor(const char *keyName = NULL, + const Color &defaultColor = Color(0, 0, 0, 0)); + bool GetBool(const char *keyName = NULL, bool defaultValue = false) { + return GetInt(keyName, defaultValue ? 1 : 0) ? true : false; + } + bool IsEmpty(const char *keyName = NULL); + + // Data access + int GetInt(int keySymbol, int defaultValue = 0); + uint64 GetUint64(int keySymbol, uint64 defaultValue = 0); + float GetFloat(int keySymbol, float defaultValue = 0.0f); + const char *GetString(int keySymbol, const char *defaultValue = ""); + const wchar_t *GetWString(int keySymbol, const wchar_t *defaultValue = L""); + void *GetPtr(int keySymbol, void *defaultValue = (void *)0); + Color GetColor(int keySymbol /* default value is all black */); + bool GetBool(int keySymbol, bool defaultValue = false) { + return GetInt(keySymbol, defaultValue ? 1 : 0) ? true : false; + } + bool IsEmpty(int keySymbol); + + // Key writing + void SetWString(const char *keyName, const wchar_t *value); + void SetString(const char *keyName, const char *value); + void SetInt(const char *keyName, int value); + void SetUint64(const char *keyName, uint64 value); + void SetFloat(const char *keyName, float value); + void SetPtr(const char *keyName, void *value); + void SetColor(const char *keyName, Color value); + void SetBool(const char *keyName, bool value) { + SetInt(keyName, value ? 1 : 0); + } + + // Memory allocation (optimized) + void *operator new(size_t iAllocSize); + void *operator new(size_t iAllocSize, int nBlockUse, const char *pFileName, + int nLine); + void operator delete(void *pMem); + void operator delete(void *pMem, int nBlockUse, const char *pFileName, + int nLine); + + KeyValues &operator=(KeyValues &src); + + // Adds a chain... if we don't find stuff in this keyvalue, we'll look + // in the one we're chained to. + void ChainKeyValue(KeyValues *pChain); + + void RecursiveSaveToFile(CUtlBuffer &buf, int indentLevel); + + bool WriteAsBinary(CUtlBuffer &buffer) const; + bool ReadAsBinary(CUtlBuffer &buffer); + + // Allocate & create a new copy of the keys + KeyValues *MakeCopy(void) const; + + // Make a new copy of all subkeys, add them all to the passed-in keyvalues + void CopySubkeys(KeyValues *pParent) const; + + // Clear out all subkeys, and the current value + void Clear(void); + + // Data type + enum types_t : char { + TYPE_NONE = 0, + TYPE_STRING, + TYPE_INT, + TYPE_FLOAT, + TYPE_PTR, + TYPE_WSTRING, + TYPE_COLOR, + TYPE_UINT64, + TYPE_COMPILED_INT_BYTE, // hack to collapse 1 byte ints in the compiled + // format + TYPE_COMPILED_INT_0, // hack to collapse 0 in the compiled format + TYPE_COMPILED_INT_1, // hack to collapse 1 in the compiled format + TYPE_NUMTYPES, + }; + types_t GetDataType(const char *keyName = NULL); + + // Virtual deletion function - ensures that KeyValues object is deleted from + // correct heap + void deleteThis(); + + void SetStringValue(char const *strValue); + + // unpack a key values list into a structure + void UnpackIntoStructure(struct KeyValuesUnpackStructure const *pUnpackTable, + void *pDest); + + // Process conditional keys for widescreen support. + bool ProcessResolutionKeys(const char *pResString); + + // Dump keyvalues recursively into a dump context + bool Dump(IKeyValuesDumpContext *pDump, int nIndentLevel = 0); + + // Merge operations describing how two keyvalues can be combined + enum MergeKeyValuesOp_t { + MERGE_KV_ALL, + MERGE_KV_UPDATE, // update values are copied into storage, adding new keys + // to storage or updating existing ones + MERGE_KV_DELETE, // update values specify keys that get deleted from + // storage + MERGE_KV_BORROW, // update values only update existing keys in storage, + // keys in update that do not exist in storage are + // discarded + }; + void MergeFrom(KeyValues *kvMerge, MergeKeyValuesOp_t eOp = MERGE_KV_ALL); + + // Assign keyvalues from a string + static KeyValues *FromString(char const *szName, char const *szStringVal, + char const **ppEndOfParse = NULL); + + protected: + KeyValues(KeyValues &); // prevent copy constructor being used + + // prevent delete being called except through deleteThis() + ~KeyValues(); + + KeyValues *CreateKey(const char *keyName); + + void RecursiveCopyKeyValues(KeyValues &src); + void RemoveEverything(); + // void RecursiveSaveToFile( IBaseFileSystem *filesystem, CUtlBuffer + //&buffer, int indentLevel ); void WriteConvertedString( CUtlBuffer + //&buffer, const char *pszString ); + + // NOTE: If both filesystem and pBuf are non-null, it'll save to both of them. + // If filesystem is null, it'll ignore f. + void RecursiveSaveToFile(IBaseFileSystem *filesystem, FileHandle_t f, + CUtlBuffer *pBuf, int indentLevel); + void WriteConvertedString(IBaseFileSystem *filesystem, FileHandle_t f, + CUtlBuffer *pBuf, const char *pszString); + + void RecursiveLoadFromBuffer(char const *resourceName, CUtlBuffer &buf, + GetSymbolProc_t pfnEvaluateSymbolProc); + + // for handling #include "filename" + void AppendIncludedKeys(CUtlVector &includedKeys); + void ParseIncludedKeys(char const *resourceName, const char *filetoinclude, + IBaseFileSystem *pFileSystem, const char *pPathID, + CUtlVector &includedKeys, + GetSymbolProc_t pfnEvaluateSymbolProc); + + // For handling #base "filename" + void MergeBaseKeys(CUtlVector &baseKeys); + void RecursiveMergeKeyValues(KeyValues *baseKV); + + // NOTE: If both filesystem and pBuf are non-null, it'll save to both of them. + // If filesystem is null, it'll ignore f. + void InternalWrite(IBaseFileSystem *filesystem, FileHandle_t f, + CUtlBuffer *pBuf, const void *pData, intp len); + + void Init(); + const char *ReadToken(CUtlBuffer &buf, bool &wasQuoted, bool &wasConditional); + void WriteIndents(IBaseFileSystem *filesystem, FileHandle_t f, + CUtlBuffer *pBuf, int indentLevel); + + void FreeAllocatedValue(); + void AllocateValueBlock(int size); + + bool ReadAsBinaryPooledFormat(CUtlBuffer &buf, IBaseFileSystem *pFileSystem, + unsigned int poolKey, + GetSymbolProc_t pfnEvaluateSymbolProc); + + bool EvaluateConditional(const char *pExpressionString, + GetSymbolProc_t pfnEvaluateSymbolProc); + + uint32 m_iKeyName : 24; // keyname is a symbol defined in KeyValuesSystem + uint32 m_iKeyNameCaseSensitive1 : 8; // 1st part of case sensitive symbol + // defined in KeyValueSystem + + // These are needed out of the union because the API returns string pointers + char *m_sValue; + wchar_t *m_wsValue; + + // we don't delete these + union { + int m_iValue; + float m_flValue; + void *m_pValue; + unsigned char m_Color[4]; + }; + + char m_iDataType; + char m_bHasEscapeSequences; // true, if while parsing this KeyValue, Escape + // Sequences are used (default false) + uint16 m_iKeyNameCaseSensitive2; // 2nd part of case sensitive symbol defined + // in KeyValueSystem; + + KeyValues *m_pPeer; // pointer to next key in list + KeyValues *m_pSub; // pointer to Start of a new sub key list + KeyValues *m_pChain; // Search here if it's not in our list + + GetSymbolProc_t m_pExpressionGetSymbolProc; + + private: + // Statics to implement the optional growable string table + // Function pointers that will determine which mode we are in + static intp (*s_pfGetSymbolForString)(const char *name, bool bCreate); + static const char *(*s_pfGetStringForSymbol)(intp symbol); + static CKeyValuesGrowableStringTable *s_pGrowableStringTable; + + public: + // Functions that invoke the default behavior + static intp GetSymbolForStringClassic(const char *name, bool bCreate = true); + static const char *GetStringForSymbolClassic(intp symbol); + + // Functions that use the growable string table + static intp GetSymbolForStringGrowable(const char *name, bool bCreate = true); + static const char *GetStringForSymbolGrowable(intp symbol); +}; + +typedef KeyValues::AutoDelete KeyValuesAD; + +enum KeyValuesUnpackDestinationTypes_t { + UNPACK_TYPE_FLOAT, // dest is a float + UNPACK_TYPE_VECTOR, // dest is a Vector + UNPACK_TYPE_VECTOR_COLOR, // dest is a vector, src is a color + UNPACK_TYPE_STRING, // dest is a char *. unpacker will allocate. + UNPACK_TYPE_INT, // dest is an int + UNPACK_TYPE_FOUR_FLOATS, // dest is an array of 4 floats. source is a string + // like "1 2 3 4" + UNPACK_TYPE_TWO_FLOATS, // dest is an array of 2 floats. source is a string + // like "1 2" +}; + +#define UNPACK_FIXED(kname, kdefault, dtype, ofs) \ + { kname, kdefault, dtype, ofs, 0 } +#define UNPACK_VARIABLE(kname, kdefault, dtype, ofs, sz) \ + { kname, kdefault, dtype, ofs, sz } +#define UNPACK_END_MARKER \ + { NULL, NULL, UNPACK_TYPE_FLOAT, 0 } + +struct KeyValuesUnpackStructure { + char const *m_pKeyName; // null to terminate tbl + char const *m_pKeyDefault; // null ok + KeyValuesUnpackDestinationTypes_t m_eDataType; // UNPACK_TYPE_INT, .. + size_t m_nFieldOffset; // use offsetof to set + size_t m_nFieldSize; // for strings or other variable length +}; + +//----------------------------------------------------------------------------- +// inline methods +//----------------------------------------------------------------------------- +inline int KeyValues::GetInt(int keySymbol, int defaultValue) { + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->GetInt((const char *)NULL, defaultValue) : defaultValue; +} + +inline uint64 KeyValues::GetUint64(int keySymbol, uint64 defaultValue) { + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->GetUint64((const char *)NULL, defaultValue) : defaultValue; +} + +inline float KeyValues::GetFloat(int keySymbol, float defaultValue) { + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->GetFloat((const char *)NULL, defaultValue) : defaultValue; +} + +inline const char *KeyValues::GetString(int keySymbol, + const char *defaultValue) { + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->GetString((const char *)NULL, defaultValue) : defaultValue; +} + +inline const wchar_t *KeyValues::GetWString(int keySymbol, + const wchar_t *defaultValue) { + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->GetWString((const char *)NULL, defaultValue) : defaultValue; +} + +inline void *KeyValues::GetPtr(int keySymbol, void *defaultValue) { + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->GetPtr((const char *)NULL, defaultValue) : defaultValue; +} + +inline Color KeyValues::GetColor(int keySymbol) { + Color defaultValue(0, 0, 0, 0); + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->GetColor() : defaultValue; +} + +inline bool KeyValues::IsEmpty(int keySymbol) { + KeyValues *dat = FindKey(keySymbol); + return dat ? dat->IsEmpty() : true; +} + +// +// KeyValuesDumpContext and generic implementations +// + +class IKeyValuesDumpContext { + public: + virtual bool KvBeginKey(KeyValues *pKey, int nIndentLevel) = 0; + virtual bool KvWriteValue(KeyValues *pValue, int nIndentLevel) = 0; + virtual bool KvEndKey(KeyValues *pKey, int nIndentLevel) = 0; +}; + +class IKeyValuesDumpContextAsText : public IKeyValuesDumpContext { + public: + virtual bool KvBeginKey(KeyValues *pKey, int nIndentLevel); + virtual bool KvWriteValue(KeyValues *pValue, int nIndentLevel); + virtual bool KvEndKey(KeyValues *pKey, int nIndentLevel); + + public: + virtual bool KvWriteIndent(int nIndentLevel); + virtual bool KvWriteText(char const *szText) = 0; +}; + +class CKeyValuesDumpContextAsDevMsg : public IKeyValuesDumpContextAsText { + public: + // Overrides developer level to dump in DevMsg, zero to dump as Msg + CKeyValuesDumpContextAsDevMsg(int nDeveloperLevel = 1) + : m_nDeveloperLevel(nDeveloperLevel) {} + + public: + virtual bool KvBeginKey(KeyValues *pKey, int nIndentLevel); + virtual bool KvWriteText(char const *szText); + + protected: + int m_nDeveloperLevel; +}; + +inline bool KeyValuesDumpAsDevMsg(KeyValues *pKeyValues, int nIndentLevel = 0, + int nDeveloperLevel = 1) { + CKeyValuesDumpContextAsDevMsg ctx(nDeveloperLevel); + return pKeyValues->Dump(&ctx, nIndentLevel); +} + +#endif // VPC_TIER1_KEYVALUES_H_ diff --git a/public/tier1/mempool.h b/public/tier1/mempool.h new file mode 100644 index 0000000..ccada70 --- /dev/null +++ b/public/tier1/mempool.h @@ -0,0 +1,624 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_MEMPOOL_H_ +#define VPC_TIER1_MEMPOOL_H_ + +#include "tier0/memalloc.h" +#include "tier0/tslist.h" +#include "tier0/platform.h" +#include "tier1/utlvector.h" +#include "tier1/utlrbtree.h" + +// Purpose: Optimized pool memory allocator + +using MemoryPoolReportFunc_t = void (*)(char const *pMsg, ...); + +class CUtlMemoryPool { + public: + // Ways the memory pool can grow when it needs to make a new blob. + enum MemoryPoolGrowType_t { + GROW_NONE = 0, // Don't allow new blobs. + // New blob size is numElements * (i+1) (ie: the blocks it + // allocates get larger and larger each time it allocates one). + GROW_FAST = 1, + GROW_SLOW = 2 // New blob size is numElements. + }; + + CUtlMemoryPool(int blockSize, int numElements, int growMode = GROW_FAST, + const char *pszAllocOwner = NULL, + unsigned short nAlignment = 0); + ~CUtlMemoryPool(); + + void *Alloc(); // Allocate the element size you specified in the constructor. + void *Alloc(size_t amount); + void *AllocZero(); // Allocate the element size you specified in the + // constructor, zero the memory before construction + void *AllocZero(size_t amount); + void Free(void *pMem); + + // Frees everything + void Clear(); + + // Error reporting... + static void SetErrorReportFunc(MemoryPoolReportFunc_t func); + + // returns number of allocated blocks + int Count() const { return m_BlocksAllocated; } + int PeakCount() const { return m_PeakAlloc; } + int BlockSize() const { return m_BlockSize; } + int Size() const { return m_NumBlobs * m_BlocksPerBlob * m_BlockSize; } + + bool IsAllocationWithinPool(void *pMem) const; + + protected: + class CBlob { + public: + CBlob *m_pPrev, *m_pNext; + int m_NumBytes; // Number of bytes in this blob. + char m_Data[1]; + char m_Padding[3]; // to int align the struct + }; + + // Resets the pool + void Init(); + void AddNewBlob(); + void ReportLeaks(); + + int m_BlockSize; + int m_BlocksPerBlob; + + int m_GrowMode; // GROW_ enum. + + // FIXME: Change m_ppMemBlob into a growable array? + void *m_pHeadOfFreeList; + int m_BlocksAllocated; + int m_PeakAlloc; + unsigned short m_nAlignment; + unsigned short m_NumBlobs; + const char *m_pszAllocOwner; + // CBlob could be not a multiple of 4 bytes so stuff it at the end here to + // keep us otherwise aligned + CBlob m_BlobHead; + + static MemoryPoolReportFunc_t g_ReportFunc; +}; + +// Multi-thread/Thread Safe Memory Class +class CMemoryPoolMT : public CUtlMemoryPool { + public: + CMemoryPoolMT(int blockSize, int numElements, int growMode = GROW_FAST, + const char *pszAllocOwner = NULL, unsigned short nAlignment = 0) + : CUtlMemoryPool(blockSize, numElements, growMode, pszAllocOwner, + nAlignment) {} + + void *Alloc() { + AUTO_LOCK(m_mutex); + return CUtlMemoryPool::Alloc(); + } + void *Alloc(size_t amount) { + AUTO_LOCK(m_mutex); + return CUtlMemoryPool::Alloc(amount); + } + void *AllocZero() { + AUTO_LOCK(m_mutex); + return CUtlMemoryPool::AllocZero(); + } + void *AllocZero(size_t amount) { + AUTO_LOCK(m_mutex); + return CUtlMemoryPool::AllocZero(amount); + } + void Free(void *pMem) { + AUTO_LOCK(m_mutex); + CUtlMemoryPool::Free(pMem); + } + + // Frees everything + void Clear() { + AUTO_LOCK(m_mutex); + return CUtlMemoryPool::Clear(); + } + + private: + CThreadFastMutex m_mutex; // @TODO: Rework to use tslist (toml 7/6/2007) +}; + +// Wrapper macro to make an allocator that returns particular typed allocations +// and construction and destruction of objects. +template +class CClassMemoryPool : public CUtlMemoryPool { + public: + CClassMemoryPool(int numElements, int growMode = GROW_FAST, + int nAlignment = 0) + : CUtlMemoryPool(sizeof(T), numElements, growMode, MEM_ALLOC_CLASSNAME(T), + nAlignment) {} + + T *Alloc(); + T *AllocZero(); + void Free(T *pMem); + + void Clear(); +}; + +// Specialized pool for aligned data management (e.g., Xbox textures) +template +class CAlignedMemPool { + enum { + BLOCK_SIZE = COMPILETIME_MAX(ALIGN_VALUE(ITEM_SIZE, ALIGNMENT), 8), + }; + + public: + CAlignedMemPool(); + + void *Alloc(); + void Free(void *p); + + static int __cdecl CompareChunk(void *const *ppLeft, void *const *ppRight); + void Compact(); + + int NumTotal() { + AUTO_LOCK(m_mutex); + return m_Chunks.Count() * (CHUNK_SIZE / BLOCK_SIZE); + } + int NumAllocated() { + AUTO_LOCK(m_mutex); + return NumTotal() - m_nFree; + } + int NumFree() { + AUTO_LOCK(m_mutex); + return m_nFree; + } + + int BytesTotal() { + AUTO_LOCK(m_mutex); + return NumTotal() * BLOCK_SIZE; + } + int BytesAllocated() { + AUTO_LOCK(m_mutex); + return NumAllocated() * BLOCK_SIZE; + } + int BytesFree() { + AUTO_LOCK(m_mutex); + return NumFree() * BLOCK_SIZE; + } + + int ItemSize() { return ITEM_SIZE; } + int BlockSize() { return BLOCK_SIZE; } + int ChunkSize() { return CHUNK_SIZE; } + + private: + struct FreeBlock_t { + FreeBlock_t *pNext; + byte reserved[BLOCK_SIZE - sizeof(FreeBlock_t *)]; + }; + + CUtlVector m_Chunks; // Chunks are tracked outside blocks (unlike + // CUtlMemoryPool) to simplify alignment issues + FreeBlock_t *m_pFirstFree; + int m_nFree; + CAllocator m_Allocator; + double m_TimeLastCompact; + + CThreadFastMutex m_mutex; +}; + +// Pool variant using standard allocation +template +class CObjectPool { + public: + CObjectPool() { + int i = nInitialCount; + while (i-- > 0) { + m_AvailableObjects.PushItem(new T); + } + } + + ~CObjectPool() { Purge(); } + + int NumAvailable() { return m_AvailableObjects.Count(); } + + void Purge() { + T *p = NULL; + while (m_AvailableObjects.PopItem(&p)) { + delete p; + } + } + + T *GetObject(bool bCreateNewIfEmpty = bDefCreateNewIfEmpty) { + T *p = NULL; + if (!m_AvailableObjects.PopItem(&p)) { + p = (bCreateNewIfEmpty) ? new T : NULL; + } + return p; + } + + void PutObject(T *p) { m_AvailableObjects.PushItem(p); } + + private: + CTSList m_AvailableObjects; +}; + +// Fixed budget pool with overflow to malloc +template +class CFixedBudgetMemoryPool { + public: + CFixedBudgetMemoryPool() { + m_pBase = m_pLimit = 0; + static_assert(ITEM_SIZE % 4 == 0); + } + + bool Owns(void *p) { return (p >= m_pBase && p < m_pLimit); } + + void *Alloc() { + MEM_ALLOC_CREDIT_CLASS(); +#ifndef USE_MEM_DEBUG + if (!m_pBase) { + LOCAL_THREAD_LOCK(); + if (!m_pBase) { + byte *pMemory = m_pBase = (byte *)malloc(ITEM_COUNT * ITEM_SIZE); + m_pLimit = m_pBase + (ITEM_COUNT * ITEM_SIZE); + + for (int i = 0; i < ITEM_COUNT; i++) { + m_freeList.Push((TSLNodeBase_t *)pMemory); + pMemory += ITEM_SIZE; + } + } + } + + void *p = m_freeList.Pop(); + if (p) return p; +#endif + return malloc(ITEM_SIZE); + } + + void Free(void *p) { +#ifndef USE_MEM_DEBUG + if (Owns(p)) + m_freeList.Push((TSLNodeBase_t *)p); + else +#endif + free(p); + } + + void Clear() { +#ifndef USE_MEM_DEBUG + if (m_pBase) { + free(m_pBase); + } + m_pBase = m_pLimit = 0; + Construct(&m_freeList); +#endif + } + + bool IsEmpty() { +#ifndef USE_MEM_DEBUG + if (m_pBase && m_freeList.Count() != ITEM_COUNT) return false; +#endif + return true; + } + + enum { ITEM_SIZE = ALIGN_VALUE(PROVIDED_ITEM_SIZE, TSLIST_NODE_ALIGNMENT) }; + + CTSListBase m_freeList; + byte *m_pBase; + byte *m_pLimit; +}; + +#define BIND_TO_FIXED_BUDGET_POOL(poolName) \ + inline void *operator new(size_t size) { return poolName.Alloc(); } \ + inline void *operator new(size_t size, int nBlockUse, const char *pFileName, \ + int nLine) { \ + return poolName.Alloc(); \ + } \ + inline void operator delete(void *p) { poolName.Free(p); } \ + inline void operator delete(void *p, int nBlockUse, const char *pFileName, \ + int nLine) { \ + poolName.Free(p); \ + } + +template +inline T *CClassMemoryPool::Alloc() { + T *pRet; + + { + MEM_ALLOC_CREDIT_CLASS(); + pRet = (T *)CUtlMemoryPool::Alloc(); + } + + if (pRet) { + Construct(pRet); + } + return pRet; +} + +template +inline T *CClassMemoryPool::AllocZero() { + T *pRet; + + { + MEM_ALLOC_CREDIT_CLASS(); + pRet = (T *)CUtlMemoryPool::AllocZero(); + } + + if (pRet) { + Construct(pRet); + } + return pRet; +} + +template +inline void CClassMemoryPool::Free(T *pMem) { + if (pMem) { + Destruct(pMem); + } + + CUtlMemoryPool::Free(pMem); +} + +template +inline void CClassMemoryPool::Clear() { + CUtlRBTree freeBlocks; + SetDefLessFunc(freeBlocks); + + void *pCurFree = m_pHeadOfFreeList; + while (pCurFree != NULL) { + freeBlocks.Insert(pCurFree); + pCurFree = *((void **)pCurFree); + } + + for (CBlob *pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; + pCur = pCur->m_pNext) { + T *p = (T *)pCur->m_Data; + T *pLimit = (T *)(pCur->m_Data + pCur->m_NumBytes); + while (p < pLimit) { + if (freeBlocks.Find(p) == freeBlocks.InvalidIndex()) { + Destruct(p); + } + p++; + } + } + + CUtlMemoryPool::Clear(); +} + +// Macros that make it simple to make a class use a fixed-size allocator +// Put DECLARE_FIXEDSIZE_ALLOCATOR in the private section of a class, +// Put DEFINE_FIXEDSIZE_ALLOCATOR in the CPP file +#define DECLARE_FIXEDSIZE_ALLOCATOR(_class) \ + public: \ + inline void *operator new(size_t size) { \ + MEM_ALLOC_CREDIT_(#_class " pool"); \ + return s_Allocator.Alloc(size); \ + } \ + inline void *operator new(size_t size, int nBlockUse, const char *pFileName, \ + int nLine) { \ + MEM_ALLOC_CREDIT_(#_class " pool"); \ + return s_Allocator.Alloc(size); \ + } \ + inline void operator delete(void *p) { s_Allocator.Free(p); } \ + inline void operator delete(void *p, int nBlockUse, const char *pFileName, \ + int nLine) { \ + s_Allocator.Free(p); \ + } \ + \ + private: \ + static CUtlMemoryPool s_Allocator + +#define DEFINE_FIXEDSIZE_ALLOCATOR(_class, _initsize, _grow) \ + CUtlMemoryPool _class::s_Allocator(sizeof(_class), _initsize, _grow, \ + #_class " pool") + +#define DEFINE_FIXEDSIZE_ALLOCATOR_ALIGNED(_class, _initsize, _grow, \ + _alignment) \ + CUtlMemoryPool _class::s_Allocator(sizeof(_class), _initsize, _grow, \ + #_class " pool", _alignment) + +#define DECLARE_FIXEDSIZE_ALLOCATOR_MT(_class) \ + public: \ + inline void *operator new(size_t size) { \ + MEM_ALLOC_CREDIT_(#_class " pool"); \ + return s_Allocator.Alloc(size); \ + } \ + inline void *operator new(size_t size, int nBlockUse, const char *pFileName, \ + int nLine) { \ + MEM_ALLOC_CREDIT_(#_class " pool"); \ + return s_Allocator.Alloc(size); \ + } \ + inline void operator delete(void *p) { s_Allocator.Free(p); } \ + inline void operator delete(void *p, int nBlockUse, const char *pFileName, \ + int nLine) { \ + s_Allocator.Free(p); \ + } \ + \ + private: \ + static CMemoryPoolMT s_Allocator + +#define DEFINE_FIXEDSIZE_ALLOCATOR_MT(_class, _initsize, _grow) \ + CMemoryPoolMT _class::s_Allocator(sizeof(_class), _initsize, _grow, \ + #_class " pool") + +// Macros that make it simple to make a class use a fixed-size allocator +// This version allows us to use a memory pool which is externally defined... +// Put DECLARE_FIXEDSIZE_ALLOCATOR_EXTERNAL in the private section of a class, +// Put DEFINE_FIXEDSIZE_ALLOCATOR_EXTERNAL in the CPP file +#define DECLARE_FIXEDSIZE_ALLOCATOR_EXTERNAL(_class) \ + public: \ + inline void *operator new(size_t size) { \ + MEM_ALLOC_CREDIT_(#_class " pool"); \ + return s_pAllocator->Alloc(size); \ + } \ + inline void *operator new(size_t size, int nBlockUse, const char *pFileName, \ + int nLine) { \ + MEM_ALLOC_CREDIT_(#_class " pool"); \ + return s_pAllocator->Alloc(size); \ + } \ + inline void operator delete(void *p) { s_pAllocator->Free(p); } \ + \ + private: \ + static CUtlMemoryPool *s_pAllocator + +#define DEFINE_FIXEDSIZE_ALLOCATOR_EXTERNAL(_class, _allocator) \ + CUtlMemoryPool *_class::s_pAllocator = _allocator + +template +inline CAlignedMemPool::CAlignedMemPool() + : m_pFirstFree(0), m_nFree(0), m_TimeLastCompact(0) { + static_assert(sizeof(FreeBlock_t) >= BLOCK_SIZE); + static_assert(ALIGN_VALUE(sizeof(FreeBlock_t), ALIGNMENT) == + sizeof(FreeBlock_t)); +} + +template +inline void *CAlignedMemPool::Alloc() { + AUTO_LOCK(m_mutex); + + if (!m_pFirstFree) { + if (!GROWMODE && m_Chunks.Count()) { + return NULL; + } + + FreeBlock_t *pNew = (FreeBlock_t *)m_Allocator.Alloc(CHUNK_SIZE); + Assert((unsigned)pNew % ALIGNMENT == 0); + m_Chunks.AddToTail(pNew); + m_nFree = CHUNK_SIZE / BLOCK_SIZE; + m_pFirstFree = pNew; + for (int i = 0; i < m_nFree - 1; i++) { + pNew->pNext = pNew + 1; + pNew++; + } + pNew->pNext = NULL; + } + + void *p = m_pFirstFree; + m_pFirstFree = m_pFirstFree->pNext; + m_nFree--; + + return p; +} + +template +inline void CAlignedMemPool::Free(void *p) { + AUTO_LOCK(m_mutex); + + // Insertion sort to encourage allocation clusters in chunks + FreeBlock_t *pFree = ((FreeBlock_t *)p); + FreeBlock_t *pCur = m_pFirstFree; + FreeBlock_t *pPrev = NULL; + + while (pCur && pFree > pCur) { + pPrev = pCur; + pCur = pCur->pNext; + } + + pFree->pNext = pCur; + + if (pPrev) { + pPrev->pNext = pFree; + } else { + m_pFirstFree = pFree; + } + m_nFree++; + + if (m_nFree >= (CHUNK_SIZE / BLOCK_SIZE) * COMPACT_THRESHOLD) { + double time = Plat_FloatTime(); + double compactTime = + (m_nFree >= (CHUNK_SIZE / BLOCK_SIZE) * COMPACT_THRESHOLD * 4) ? 15.0 + : 30.0; + if (m_TimeLastCompact > time || m_TimeLastCompact + compactTime < time) { + Compact(); + m_TimeLastCompact = time; + } + } +} + +template +inline int __cdecl CAlignedMemPool< + ITEM_SIZE, ALIGNMENT, CHUNK_SIZE, CAllocator, GROWMODE, + COMPACT_THRESHOLD>::CompareChunk(void *const *ppLeft, + void *const *ppRight) { + return ((unsigned)*ppLeft) - ((unsigned)*ppRight); +} + +template +inline void CAlignedMemPool::Compact() { + FreeBlock_t *pCur = m_pFirstFree; + FreeBlock_t *pPrev = NULL; + + m_Chunks.Sort(CompareChunk); + +#ifdef VALIDATE_ALIGNED_MEM_POOL + { + FreeBlock_t *p = m_pFirstFree; + while (p) { + if (p->pNext && p > p->pNext) { + __asm { int 3 } + } + p = p->pNext; + } + + for (int i = 0; i < m_Chunks.Count(); i++) { + if (i + 1 < m_Chunks.Count()) { + if (m_Chunks[i] > m_Chunks[i + 1]) { + __asm { int 3 } + } + } + } + } +#endif + + int i; + + for (i = 0; i < m_Chunks.Count(); i++) { + int nBlocksPerChunk = CHUNK_SIZE / BLOCK_SIZE; + FreeBlock_t *pChunkLimit = ((FreeBlock_t *)m_Chunks[i]) + nBlocksPerChunk; + int nFromChunk = 0; + if (pCur == m_Chunks[i]) { + FreeBlock_t *pFirst = pCur; + while (pCur && pCur >= m_Chunks[i] && pCur < pChunkLimit) { + pCur = pCur->pNext; + nFromChunk++; + } + pCur = pFirst; + } + + while (pCur && pCur >= m_Chunks[i] && pCur < pChunkLimit) { + if (nFromChunk != nBlocksPerChunk) { + if (pPrev) { + pPrev->pNext = pCur; + } else { + m_pFirstFree = pCur; + } + pPrev = pCur; + } else if (pPrev) { + pPrev->pNext = NULL; + } else { + m_pFirstFree = NULL; + } + + pCur = pCur->pNext; + } + + if (nFromChunk == nBlocksPerChunk) { + m_Allocator.Free(m_Chunks[i]); + m_nFree -= nBlocksPerChunk; + m_Chunks[i] = 0; + } + } + + for (i = m_Chunks.Count() - 1; i >= 0; i--) { + if (!m_Chunks[i]) { + m_Chunks.FastRemove(i); + } + } +} + +#endif // VPC_TIER1_MEMPOOL_H_ diff --git a/public/tier1/memstack.h b/public/tier1/memstack.h new file mode 100644 index 0000000..3265e16 --- /dev/null +++ b/public/tier1/memstack.h @@ -0,0 +1,247 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A fast stack memory allocator that uses virtual memory if available + +#ifndef VPC_TIER1_MEMSTACK_H_ +#define VPC_TIER1_MEMSTACK_H_ + +#include "tier1/utlvector.h" + +#if defined(_WIN32) || defined(_PS3) +#define MEMSTACK_VIRTUAL_MEMORY_AVAILABLE +#endif + +//----------------------------------------------------------------------------- + +typedef uintp MemoryStackMark_t; + +class CMemoryStack { + public: + CMemoryStack(); + ~CMemoryStack(); + + bool Init(const char *pszAllocOwner, size_t maxSize = 0, + size_t commitSize = 0, size_t initialCommit = 0, + unsigned alignment = 16); +#ifdef _GAMECONSOLE + bool InitPhysical(const char *pszAllocOwner, size_t size, + size_t nBaseAddrAlignment, uint alignment = 16, + uint32 nAdditionalFlags = 0); +#endif + void Term(); + + intp GetSize(); + size_t GetMaxSize(); + intp GetUsed(); + + void *Alloc(size_t bytes, bool bClear = false) RESTRICT; + + MemoryStackMark_t GetCurrentAllocPoint(); + void FreeToAllocPoint(MemoryStackMark_t mark, bool bDecommit = true); + void FreeAll(bool bDecommit = true); + + void Access(void **ppRegion, intp *pBytes); + + void PrintContents(); + + void *GetBase(); + const void *GetBase() const { + return const_cast(this)->GetBase(); + } + + bool CommitSize(intp); + + void SetAllocOwner(const char *pszAllocOwner); + + private: + bool CommitTo(byte *) RESTRICT; + void RegisterAllocation(); + void RegisterDeallocation(bool bShouldSpew); + + byte *m_pNextAlloc; + byte *m_pCommitLimit; + byte *m_pAllocLimit; + + byte *m_pBase; + bool m_bRegisteredAllocation; + bool m_bPhysical; + char *m_pszAllocOwner; + + size_t m_maxSize; + unsigned m_alignment; +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + size_t m_commitSize; + size_t m_minCommit; +#endif +#if defined(MEMSTACK_VIRTUAL_MEMORY_AVAILABLE) && defined(_PS3) + IVirtualMemorySection *m_pVirtualMemorySection; +#endif +}; + +//------------------------------------- + +FORCEINLINE void *CMemoryStack::Alloc(size_t bytes, bool bClear) RESTRICT { + Assert(m_pBase); + + bytes = MAX(bytes, m_alignment); + bytes = AlignValue(bytes, m_alignment); + + void *pResult = m_pNextAlloc; + byte *pNextAlloc = m_pNextAlloc + bytes; + + if (pNextAlloc > m_pCommitLimit) { + if (!CommitTo(pNextAlloc)) { + return NULL; + } + } + + if (bClear) { + memset(pResult, 0, bytes); + } + + m_pNextAlloc = pNextAlloc; + + return pResult; +} + +//------------------------------------- + +inline bool CMemoryStack::CommitSize(intp nBytes) { + if (GetSize() != nBytes) { + return CommitTo(m_pBase + nBytes); + } + return true; +} + +//------------------------------------- + +inline size_t CMemoryStack::GetMaxSize() { return m_maxSize; } + +//------------------------------------- + +inline intp CMemoryStack::GetUsed() { return (m_pNextAlloc - m_pBase); } + +//------------------------------------- + +inline void *CMemoryStack::GetBase() { return m_pBase; } + +//------------------------------------- + +inline MemoryStackMark_t CMemoryStack::GetCurrentAllocPoint() { + return (m_pNextAlloc - m_pBase); +} + +//----------------------------------------------------------------------------- +// The CUtlMemoryStack class: +// A fixed memory class +//----------------------------------------------------------------------------- +template +class CUtlMemoryStack { + public: + // constructor, destructor + CUtlMemoryStack(intp nGrowSize = 0, intp nInitSize = 0) { + m_MemoryStack.Init("CUtlMemoryStack", MAX_SIZE * sizeof(T), + COMMIT_SIZE * sizeof(T), INITIAL_COMMIT * sizeof(T), 4); + static_assert(sizeof(T) % 4 == 0); + } + CUtlMemoryStack(T *pMemory, intp numElements) = delete; + + // Can we use this index? + bool IsIdxValid(I i) const { + long x = i; + return (x >= 0) && (x < m_nAllocated); + } + + // Specify the invalid ('null') index that we'll only return on failure + static const I INVALID_INDEX = (I)-1; // For use with static_assert + static I InvalidIndex() { return INVALID_INDEX; } + + class Iterator_t { + Iterator_t(I i) : index(i) {} + I index; + friend class CUtlMemoryStack; + + public: + bool operator==(const Iterator_t it) const { return index == it.index; } + bool operator!=(const Iterator_t it) const { return index != it.index; } + }; + Iterator_t First() const { + return Iterator_t(m_nAllocated ? 0 : InvalidIndex()); + } + Iterator_t Next(const Iterator_t &it) const { + return Iterator_t(it.index < m_nAllocated ? it.index + 1 : InvalidIndex()); + } + I GetIndex(const Iterator_t &it) const { return it.index; } + bool IsIdxAfter(I i, const Iterator_t &it) const { return i > it.index; } + bool IsValidIterator(const Iterator_t &it) const { + long x = it.index; + return x >= 0 && x < m_nAllocated; + } + Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex()); } + + // Gets the base address + T *Base() { return (T *)m_MemoryStack.GetBase(); } + const T *Base() const { return (const T *)m_MemoryStack.GetBase(); } + + // element access + T &operator[](I i) { + Assert(IsIdxValid(i)); + return Base()[i]; + } + const T &operator[](I i) const { + Assert(IsIdxValid(i)); + return Base()[i]; + } + T &Element(I i) { + Assert(IsIdxValid(i)); + return Base()[i]; + } + const T &Element(I i) const { + Assert(IsIdxValid(i)); + return Base()[i]; + } + + // Attaches the buffer to external memory.... + void SetExternalBuffer(T *pMemory, int numElements) { Assert(0); } + + // Size + intp NumAllocated() const { return m_nAllocated; } + intp Count() const { return m_nAllocated; } + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow(intp num = 1) { + Assert(num > 0); + m_nAllocated += num; + m_MemoryStack.Alloc(num * sizeof(T)); + } + + // Makes sure we've got at least this much memory + void EnsureCapacity(intp num) { + Assert(num <= MAX_SIZE); + if (m_nAllocated < num) Grow(num - m_nAllocated); + } + + // Memory deallocation + void Purge() { + m_MemoryStack.FreeAll(); + m_nAllocated = 0; + } + + // is the memory externally allocated? + bool IsExternallyAllocated() const { return false; } + + // Set the size by which the memory grows + void SetGrowSize(intp size) { Assert(0); } + + // Identify the owner of this memory stack's memory + void SetAllocOwner(const char *pszAllocOwner) { + m_MemoryStack.SetAllocOwner(pszAllocOwner); + } + + private: + CMemoryStack m_MemoryStack; + intp m_nAllocated; +}; + +#endif // VPC_TIER1_MEMSTACK_H_ diff --git a/public/tier1/netadr.h b/public/tier1/netadr.h new file mode 100644 index 0000000..56b7184 --- /dev/null +++ b/public/tier1/netadr.h @@ -0,0 +1,78 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_NETADR_H_ +#define VPC_TIER1_NETADR_H_ + +#include "tier0/platform.h" +#undef SetPort + +enum netadrtype_t { + NA_NULL = 0, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, +}; + +struct netadr_t { + netadr_t() { + SetIP(0); + SetPort(0); + SetType(NA_IP); + } + + netadr_t(uint unIP, uint16 usPort) { + SetIP(unIP); + SetPort(usPort); + SetType(NA_IP); + } + + netadr_t(const char *pch) { SetFromString(pch); } + + void Clear(); // invalids Address + + void SetType(netadrtype_t type); + void SetPort(unsigned short port); + bool SetFromSockadr(const struct sockaddr *s); + void SetIP(uint8 b1, uint8 b2, uint8 b3, uint8 b4); + void SetIP(uint unIP); // Sets IP. unIP is in host order (little-endian) + void SetIPAndPort(uint unIP, unsigned short usPort) { + SetIP(unIP); + SetPort(usPort); + } + void SetFromString(const char *pch, + bool bUseDNS = false); // if bUseDNS is true then do a DNS + // lookup if needed + + bool CompareAdr(const netadr_t &a, bool onlyBase = false) const; + bool CompareClassBAdr(const netadr_t &a) const; + bool CompareClassCAdr(const netadr_t &a) const; + + netadrtype_t GetType() const; + unsigned short GetPort() const; + const char *ToString( + bool onlyBase = false) const; // returns xxx.xxx.xxx.xxx:ppppp + void ToSockadr(struct sockaddr *s) const; + unsigned int GetIP() const; + + bool IsLocalhost() const; // true, if this is the localhost IP + bool IsLoopback() const; // true if engine loopback buffers are used + bool IsReservedAdr() const; // true, if this is a private LAN IP + bool IsValid() const; // ip & port != 0 + bool IsBaseAdrValid() const; // ip != 0 + + void SetFromSocket(int hSocket); + + // These function names are decorated because the Xbox360 defines macros for + // ntohl and htonl + unsigned long addr_ntohl() const; + unsigned long addr_htonl() const; + bool operator==(const netadr_t &netadr) const { return (CompareAdr(netadr)); } + bool operator<(const netadr_t &netadr) const; + + // members are public to avoid to much changes + netadrtype_t type; + unsigned char ip[4]; + unsigned short port; +}; + +#endif // VPC_TIER1_NETADR_H_ diff --git a/public/tier1/refcount.h b/public/tier1/refcount.h new file mode 100644 index 0000000..aea576a --- /dev/null +++ b/public/tier1/refcount.h @@ -0,0 +1,326 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Tools for correctly implementing & handling reference counted +// objects + +#ifndef VPC_TIER1_REFCOUNT_H_ +#define VPC_TIER1_REFCOUNT_H_ + +#include "tier0/threadtools.h" + +// Purpose: Implement a standard reference counted interface. Use of this +// is optional insofar as all the concrete tools only require at compile time +// that the function signatures match. +struct IRefCounted { + virtual int AddRef() = 0; + virtual int Release() = 0; +}; + +// Purpose: Release a pointer and mark it NULL +template +inline int SafeRelease(REFCOUNTED_ITEM_PTR &pRef) { + // Use funny syntax so that this works on "auto pointers" + REFCOUNTED_ITEM_PTR *ppRef = &pRef; + if (*ppRef) { + int result = (*ppRef)->Release(); + *ppRef = NULL; + return result; + } + return 0; +} + +// Purpose: Maintain a reference across a scope +template +class CAutoRef { + public: + CAutoRef(T *pRef) : m_pRef(pRef) { + if (m_pRef) m_pRef->AddRef(); + } + + ~CAutoRef() { + if (m_pRef) m_pRef->Release(); + } + + private: + T *m_pRef; +}; + +// Purpose: Do a an inline AddRef then return the pointer, useful when +// returning an object from a function + +#define RetAddRef(p) ((p)->AddRef(), (p)) +#define InlineAddRef(p) ((p)->AddRef(), (p)) + +// Purpose: A class to both hold a pointer to an object and its reference. +// Base exists to support other cleanup models +template +class CBaseAutoPtr { + public: + CBaseAutoPtr() : m_pObject(0) {} + CBaseAutoPtr(T *pFrom) : m_pObject(pFrom) {} + + operator const void *() const { return m_pObject; } + operator void *() { return m_pObject; } + + operator const T *() const { return m_pObject; } + operator const T *() { return m_pObject; } + operator T *() { return m_pObject; } + + int operator=(int i) { + AssertMsg(i == 0, "Only NULL allowed on integer assign"); + m_pObject = 0; + return 0; + } + T *operator=(T *p) { + m_pObject = p; + return p; + } + + bool operator!() const { return (!m_pObject); } + bool operator!=(int i) const { + AssertMsg(i == 0, "Only NULL allowed on integer compare"); + return (m_pObject != NULL); + } + bool operator==(const void *p) const { return (m_pObject == p); } + bool operator!=(const void *p) const { return (m_pObject != p); } + bool operator==(T *p) const { return operator==((void *)p); } + bool operator!=(T *p) const { return operator!=((void *)p); } + bool operator==(const CBaseAutoPtr &p) const { + return operator==((const void *)p); + } + bool operator!=(const CBaseAutoPtr &p) const { + return operator!=((const void *)p); + } + + T *operator->() { return m_pObject; } + T &operator*() { return *m_pObject; } + T **operator&() { return &m_pObject; } + + const T *operator->() const { return m_pObject; } + const T &operator*() const { return *m_pObject; } + T *const *operator&() const { return &m_pObject; } + + protected: + CBaseAutoPtr(const CBaseAutoPtr &from) : m_pObject(from.m_pObject) {} + void operator=(const CBaseAutoPtr &from) { m_pObject = from.m_pObject; } + + T *m_pObject; +}; + +template +class CRefPtr : public CBaseAutoPtr { + typedef CBaseAutoPtr BaseClass; + + public: + CRefPtr() {} + CRefPtr(T *pInit) : BaseClass(pInit) {} + CRefPtr(const CRefPtr &from) : BaseClass(from) {} + ~CRefPtr() { + if (BaseClass::m_pObject) BaseClass::m_pObject->Release(); + } + + void operator=(const CRefPtr &from) { BaseClass::operator=(from); } + + int operator=(int i) { return BaseClass::operator=(i); } + T *operator=(T *p) { return BaseClass::operator=(p); } + + operator bool() const { return !BaseClass::operator!(); } + operator bool() { return !BaseClass::operator!(); } + + void SafeRelease() { + if (BaseClass::m_pObject) BaseClass::m_pObject->Release(); + BaseClass::m_pObject = 0; + } + void AssignAddRef(T *pFrom) { + SafeRelease(); + if (pFrom) pFrom->AddRef(); + BaseClass::m_pObject = pFrom; + } + void AddRefAssignTo(T *&pTo) { + ::SafeRelease(pTo); + if (BaseClass::m_pObject) BaseClass::m_pObject->AddRef(); + pTo = BaseClass::m_pObject; + } +}; + +// Purpose: Traits classes defining reference count threading model +class CRefMT { + public: + static int Increment(int *p) { + return ThreadInterlockedIncrement((int32 *)p); + } + static int Decrement(int *p) { + return ThreadInterlockedDecrement((int32 *)p); + } +}; + +class CRefST { + public: + static int Increment(int *p) { return ++(*p); } + static int Decrement(int *p) { return --(*p); } +}; + +// Purpose: Actual reference counting implementation. Pulled out to reduce +// code bloat. +template +class NO_VTABLE CRefCountServiceBase { + protected: + CRefCountServiceBase() : m_iRefs(1) {} + + virtual ~CRefCountServiceBase() {} + + virtual bool OnFinalRelease() { return true; } + + int GetRefCount() const { return m_iRefs; } + + int DoAddRef() { return CRefThreading::Increment(&m_iRefs); } + + int DoRelease() { + int result = CRefThreading::Decrement(&m_iRefs); + if (result) return result; + if (OnFinalRelease() && bSelfDelete) delete this; + return 0; + } + + private: + int m_iRefs; +}; + +class CRefCountServiceNull { + protected: + static int DoAddRef() { return 1; } + static int DoRelease() { return 1; } +}; + +template +class NO_VTABLE CRefCountServiceDestruct { + protected: + CRefCountServiceDestruct() : m_iRefs(1) {} + + virtual ~CRefCountServiceDestruct() {} + + int GetRefCount() const { return m_iRefs; } + + int DoAddRef() { return CRefThreading::Increment(&m_iRefs); } + + int DoRelease() { + int result = CRefThreading::Decrement(&m_iRefs); + if (result) return result; + this->~CRefCountServiceDestruct(); + return 0; + } + + private: + int m_iRefs; +}; + +typedef CRefCountServiceBase CRefCountServiceST; +typedef CRefCountServiceBase CRefCountServiceNoDeleteST; + +typedef CRefCountServiceBase CRefCountServiceMT; +typedef CRefCountServiceBase CRefCountServiceNoDeleteMT; + +// Default to threadsafe +typedef CRefCountServiceNoDeleteMT CRefCountServiceNoDelete; +typedef CRefCountServiceMT CRefCountService; + +// Purpose: Base classes to implement reference counting + +template +class NO_VTABLE CRefCounted : public REFCOUNT_SERVICE { + public: + virtual ~CRefCounted() {} + int AddRef() { return REFCOUNT_SERVICE::DoAddRef(); } + int Release() { return REFCOUNT_SERVICE::DoRelease(); } +}; + +template +class NO_VTABLE CRefCounted1 : public BASE1, public REFCOUNT_SERVICE { + public: + virtual ~CRefCounted1() {} + int AddRef() { return REFCOUNT_SERVICE::DoAddRef(); } + int Release() { return REFCOUNT_SERVICE::DoRelease(); } +}; + +template +class NO_VTABLE CRefCounted2 : public BASE1, + public BASE2, + public REFCOUNT_SERVICE { + public: + virtual ~CRefCounted2() {} + int AddRef() { return REFCOUNT_SERVICE::DoAddRef(); } + int Release() { return REFCOUNT_SERVICE::DoRelease(); } +}; + +template +class NO_VTABLE CRefCounted3 : public BASE1, + public BASE2, + public BASE3, + public REFCOUNT_SERVICE { + virtual ~CRefCounted3() {} + int AddRef() { return REFCOUNT_SERVICE::DoAddRef(); } + int Release() { return REFCOUNT_SERVICE::DoRelease(); } +}; + +template +class NO_VTABLE CRefCounted4 : public BASE1, + public BASE2, + public BASE3, + public BASE4, + public REFCOUNT_SERVICE { + public: + virtual ~CRefCounted4() {} + int AddRef() { return REFCOUNT_SERVICE::DoAddRef(); } + int Release() { return REFCOUNT_SERVICE::DoRelease(); } +}; + +template +class NO_VTABLE CRefCounted5 : public BASE1, + public BASE2, + public BASE3, + public BASE4, + public BASE5, + public REFCOUNT_SERVICE { + public: + virtual ~CRefCounted5() {} + int AddRef() { return REFCOUNT_SERVICE::DoAddRef(); } + int Release() { return REFCOUNT_SERVICE::DoRelease(); } +}; + +// Purpose: Class to throw around a reference counted item to debug +// referencing problems +#ifdef _DEBUG +template +class CRefDebug : public BASE_REFCOUNTED { + public: + CRefDebug() { + AssertMsg(this->GetRefCount() == 1, "Expected initial ref count of 1"); + DevMsg("%s:create 0x%x\n", (pszName) ? pszName : "", this); + } + + virtual ~CRefDebug() { + AssertDevMsg(this->GetRefCount() == FINAL_REFS, + "Object still referenced on destroy?"); + DevMsg("%s:destroy 0x%x\n", (pszName) ? pszName : "", this); + } + + int AddRef() { + DevMsg("%s:(0x%x)->AddRef() --> %d\n", (pszName) ? pszName : "", this, + this->GetRefCount() + 1); + return BASE_REFCOUNTED::AddRef(); + } + + int Release() { + DevMsg("%s:(0x%x)->Release() --> %d\n", (pszName) ? pszName : "", this, + this->GetRefCount() - 1); + Assert(this->GetRefCount() > 0); + return BASE_REFCOUNTED::Release(); + } +}; +#endif + +#endif // VPC_TIER1_REFCOUNT_H_ diff --git a/public/tier1/stringpool.h b/public/tier1/stringpool.h new file mode 100644 index 0000000..dfe8b40 --- /dev/null +++ b/public/tier1/stringpool.h @@ -0,0 +1,83 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_STRINGPOOL_H_ +#define VPC_TIER1_STRINGPOOL_H_ + +#include "utlrbtree.h" +#include "utlvector.h" +#include "utlbuffer.h" + +// Purpose: Allocates memory for strings, checking for duplicates first, reusing +// exising strings if duplicate found. +enum StringPoolCase_t { StringPoolCaseInsensitive, StringPoolCaseSensitive }; + +class CStringPool { + public: + CStringPool(StringPoolCase_t caseSensitivity = StringPoolCaseInsensitive); + ~CStringPool(); + + unsigned int Count() const; + + const char *Allocate(const char *pszValue); + // This feature is deliberately not supported because it's pretty dangerous + // given current uses of CStringPool, which assume they can copy string + // pointers without any refcounts. + // void Free( const char *pszValue ); + void FreeAll(); + + // searches for a string already in the pool + const char *Find(const char *pszValue); + + protected: + typedef CUtlRBTree CStrSet; + + CStrSet m_Strings; +}; + +// Purpose: A reference counted string pool. +// +// Elements are stored more efficiently than in the conventional string pool, +// quicker to look up, and storage is tracked via reference counts. +// +// At some point this should replace CStringPool +class CCountedStringPool { + public: + // HACK: hash_item_t structure should not be public. + struct hash_item_t { + char *pString; + unsigned short nNextElement; + unsigned char nReferenceCount; + unsigned char pad; + }; + + enum { INVALID_ELEMENT = 0, MAX_REFERENCE = 0xFF, HASH_TABLE_SIZE = 1024 }; + + CUtlVector m_HashTable; // Points to each element + CUtlVector m_Elements; + unsigned short m_FreeListStart; + StringPoolCase_t m_caseSensitivity; + + public: + CCountedStringPool( + StringPoolCase_t caseSensitivity = StringPoolCaseInsensitive); + virtual ~CCountedStringPool(); + + void FreeAll(); + + char *FindString(const char *pIntrinsic); + char *ReferenceString(const char *pIntrinsic); + void DereferenceString(const char *pIntrinsic); + + // These are only reliable if there are less than 64k strings in your string + // pool + unsigned short FindStringHandle(const char *pIntrinsic); + unsigned short ReferenceStringHandle(const char *pIntrinsic); + char *HandleToString(unsigned short handle); + void SpewStrings(); + unsigned Hash(const char *pszKey); + + bool SaveToBuffer(CUtlBuffer &buffer); + bool RestoreFromBuffer(CUtlBuffer &buffer); +}; + +#endif // VPC_TIER1_STRINGPOOL_H_ diff --git a/public/tier1/strtools.h b/public/tier1/strtools.h new file mode 100644 index 0000000..450028b --- /dev/null +++ b/public/tier1/strtools.h @@ -0,0 +1,600 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_STRTOOLS_H_ +#define VPC_TIER1_STRTOOLS_H_ + +#include "tier0/basetypes.h" + +#ifdef _WIN32 +#pragma once +#elif POSIX +#include +#include +#include +#include +#endif + +#include +#include + +// 3d memcpy. Copy (up-to) 3 dimensional data with arbitrary source and +// destination strides. Optimizes to just a single memcpy when possible. For 2d +// data, set numslices to 1. +void CopyMemory3D(void *pDestAdr, void const *pSrcAdr, intp nNumCols, + intp nNumRows, intp nNumSlices, // dimensions of copy + intp nSrcBytesPerRow, + intp nSrcBytesPerSlice, // strides for source. + intp nDestBytesPerRow, + intp nDestBytesPerSlice // strides for dest +); + +template +class CUtlMemory; +template +class CUtlVector; + +//----------------------------------------------------------------------------- +// Portable versions of standard string functions +//----------------------------------------------------------------------------- +void _V_memset(void *dest, int fill, intp count); +void _V_memcpy(void *dest, const void *src, intp count); +void _V_memmove(void *dest, const void *src, intp count); +int _V_memcmp(const void *m1, const void *m2, intp count); +intp _V_strlen(const char *str); +void _V_strcpy(char *dest, const char *src); +char *_V_strrchr(const char *s, char c); +int _V_strcmp(const char *s1, const char *s2); +int _V_wcscmp(const wchar_t *s1, const wchar_t *s2); +int _V_stricmp(const char *s1, const char *s2); +char *_V_strstr(const char *s1, const char *search); +char *_V_strupr(char *start); +char *_V_strlower(char *start); +intp _V_wcslen(const wchar_t *pwch); + +wchar_t *_V_wcslower(const char *file, int line, wchar_t *start); +wchar_t *_V_wcsupr(const char *file, int line, wchar_t *start); + +#ifdef POSIX +inline char *strupr(char *start) { + char *str = start; + while (str && *str) { + *str = (char)toupper(*str); + str++; + } + return start; +} + +inline char *strlwr(char *start) { + char *str = start; + while (str && *str) { + *str = (char)tolower(*str); + str++; + } + return start; +} + +inline wchar_t *_wcslwr(wchar_t *start) { + wchar_t *str = start; + while (str && *str) { + *str = (wchar_t)towlower(static_cast(*str)); + str++; + } + return start; +}; + +inline wchar_t *_wcsupr(wchar_t *start) { + wchar_t *str = start; + while (str && *str) { + *str = (wchar_t)towupper(static_cast(*str)); + str++; + } + return start; +}; + +#endif // POSIX + +// there are some users of these via tier1 templates in used in tier0. but tier0 +// can't depend on vstdlib which means in tier0 we always need the inlined ones +#if (!defined(TIER0_DLL_EXPORT)) + +#if !defined(_DEBUG) && defined(_PS3) + +#include "tier1/strtools_inlines.h" + +// To avoid cross-prx calls, making the V_* fucntions that don't do anything but +// debug checks and call through to the non V_* function go ahead and call the +// non-V_* functions directly. +#define V_memset(dest, fill, count) memset((dest), (fill), (count)) +#define V_memcpy(dest, src, count) memcpy((dest), (src), (count)) +#define V_memmove(dest, src, count) memmove((dest), (src), (count)) +#define V_memcmp(m1, m2, count) memcmp((m1), (m2), (count)) +#define V_strcpy(dest, src) strcpy((dest), (src)) +#define V_strcmp(s1, s2) strcmp((s1), (s2)) +#define V_strupr(start) strupr((start)) +#define V_strlower(start) strlwr((start)) +#define V_wcslen(pwch) wcslen((pwch)) +// To avoid cross-prx calls, using inline versions of these custom functions: +#define V_strlen(str) _V_strlen_inline((str)) +#define V_strrchr(s, c) _V_strrchr_inline((s), (c)) +#define V_wcscmp(s1, s2) _V_wcscmp_inline((s1), (s2)) +#define V_stricmp(s1, s2) _V_stricmp_inline((s1), (s2)) +#define V_strstr(s1, search) _V_strstr_inline((s1), (search)) + +#else + +#define V_memset(dest, fill, count) _V_memset((dest), (fill), (count)) +#define V_memcpy(dest, src, count) _V_memcpy((dest), (src), (count)) +#define V_memmove(dest, src, count) _V_memmove((dest), (src), (count)) +#define V_memcmp(m1, m2, count) _V_memcmp((m1), (m2), (count)) +#define V_strlen(str) _V_strlen((str)) +#define V_strcpy(dest, src) _V_strcpy((dest), (src)) +#define V_strrchr(s, c) _V_strrchr((s), (c)) +#define V_strcmp(s1, s2) _V_strcmp((s1), (s2)) +#define V_wcscmp(s1, s2) _V_wcscmp((s1), (s2)) +#define V_stricmp(s1, s2) _V_stricmp((s1), (s2)) +#define V_strstr(s1, search) _V_strstr((s1), (search)) +#define V_strupr(start) _V_strupr((start)) +#define V_strlower(start) _V_strlower((start)) +#define V_wcslen(pwch) _V_wcslen((pwch)) + +#endif + +#else + +inline void V_memset(void *dest, int fill, intp count) { + memset(dest, fill, count); +} +inline void V_memcpy(void *dest, const void *src, intp count) { + memcpy(dest, src, count); +} +inline void V_memmove(void *dest, const void *src, intp count) { + memmove(dest, src, count); +} +inline int V_memcmp(const void *m1, const void *m2, intp count) { + return memcmp(m1, m2, count); +} +inline intp V_strlen(const char *str) { return (intp)strlen(str); } +inline void V_strcpy(char *dest, const char *src) { strcpy(dest, src); } +inline intp V_wcslen(const wchar_t *pwch) { return (intp)wcslen(pwch); } +inline char *V_strrchr(const char *s, char c) { return (char *)strrchr(s, c); } +inline int V_strcmp(const char *s1, const char *s2) { return strcmp(s1, s2); } +inline int V_wcscmp(const wchar_t *s1, const wchar_t *s2) { + return wcscmp(s1, s2); +} +inline int V_stricmp(const char *s1, const char *s2) { return stricmp(s1, s2); } +inline char *V_strstr(const char *s1, const char *search) { + return (char *)strstr(s1, search); +} +#ifndef COMPILER_PS3 +inline char *V_strupr(char *start) { return strupr(start); } +inline char *V_strlower(char *start) { return strlwr(start); } +inline wchar_t *V_wcsupr(wchar_t *start) { return _wcsupr(start); } +#endif + +#endif + +int V_strncmp(const char *s1, const char *s2, intp count); +int V_strcasecmp(const char *s1, const char *s2); +int V_strncasecmp(const char *s1, const char *s2, intp n); +int V_strnicmp(const char *s1, const char *s2, intp n); +int V_atoi(const char *str); +int64 V_atoi64(const char *str); +uint64 V_atoui64(const char *str); +float V_atof(const char *str); +char *V_stristr(char *pStr, const char *pSearch); +const char *V_stristr(const char *pStr, const char *pSearch); +const char *V_strnistr(const char *pStr, const char *pSearch, intp n); +const char *V_strnchr(const char *pStr, char c, intp n); + +// returns string immediately following prefix, (ie str+strlen(prefix)) or NULL +// if prefix not found +const char *StringAfterPrefix(const char *str, const char *prefix); +const char *StringAfterPrefixCaseSensitive(const char *str, const char *prefix); +inline bool StringHasPrefix(const char *str, const char *prefix) { + return StringAfterPrefix(str, prefix) != NULL; +} +inline bool StringHasPrefixCaseSensitive(const char *str, const char *prefix) { + return StringAfterPrefixCaseSensitive(str, prefix) != NULL; +} + +// Normalizes a float string in place. +// (removes leading zeros, trailing zeros after the decimal point, and the +// decimal point itself where possible) +void V_normalizeFloatString(char *pFloat); + +inline bool V_isspace(int c) { +// The standard white-space characters are the following: space, tab, +// carriage-return, newline, vertical tab, and form-feed. In the C locale, +// V_isspace() returns true only for the standard white-space characters. +// return c == ' ' || c == 9 /*horizontal tab*/ || c == '\r' || c == '\n' || c +// == 11 /*vertical tab*/ || c == '\f'; +// codes of whitespace symbols: 9 HT, 10 \n, 11 VT, 12 form feed, 13 \r, 32 +// space + +// easy to understand version, validated: +// return ((1 << (c-1)) & 0x80001F00) != 0 && ((c-1)&0xE0) == 0; + +// 5% faster on Core i7, 35% faster on Xbox360, no branches, validated: +#ifdef _X360 + return ((1 << (c - 1)) & 0x80001F00 & ~(-int((c - 1) & 0xE0))) != 0; +#else + // this is 11% faster on Core i7 than the previous, VC2005 compiler generates + // a seemingly unbalanced search tree that's faster + switch (c) { + case ' ': + case 9: + case '\r': + case '\n': + case 11: + case '\f': + return true; + default: + return false; + } +#endif +} + +// These are versions of functions that guarantee NULL termination. +// +// maxLen is the maximum number of bytes in the destination string. +// pDest[maxLen-1] is always NULL terminated if pSrc's length is >= maxLen. +// +// This means the last parameter can usually be a sizeof() of a string. +void V_strncpy(char *pDest, const char *pSrc, intp maxLen); +int V_snprintf(char *pDest, int destLen, + PRINTF_FORMAT_STRING const char *pFormat, ...) + FMTFUNCTION(3, 4); +void V_wcsncpy(wchar_t *pDest, wchar_t const *pSrc, intp maxLenInBytes); +int V_snwprintf(wchar_t *pDest, int maxLenInNumWideCharacters, + PRINTF_FORMAT_STRING const wchar_t *pFormat, ...); + +#define COPY_ALL_CHARACTERS (intp)-1 +char *V_strncat(char *, const char *, size_t maxLenInBytes, + intp max_chars_to_copy = COPY_ALL_CHARACTERS); +wchar_t *V_wcsncat(wchar_t *, const wchar_t *, size_t maxLenInBytes, + intp max_chars_to_copy = COPY_ALL_CHARACTERS); +char *V_strnlwr(char *, size_t); + +// UNDONE: Find a non-compiler-specific way to do this +#ifdef _WIN32 +#ifndef _VA_LIST_DEFINED + +#ifdef _M_ALPHA + +struct va_list { + char *a0; /* pointer to first homed integer argument */ + int offset; /* byte offset of next parameter */ +}; + +#else // !_M_ALPHA + +typedef char *va_list; + +#endif // !_M_ALPHA + +#define _VA_LIST_DEFINED + +#endif // _VA_LIST_DEFINED + +#elif POSIX +#include +#endif + +#ifdef _WIN32 +#define CORRECT_PATH_SEPARATOR '\\' +#define CORRECT_PATH_SEPARATOR_S "\\" +#define INCORRECT_PATH_SEPARATOR '/' +#define INCORRECT_PATH_SEPARATOR_S "/" +#elif POSIX || defined(_PS3) +#define CORRECT_PATH_SEPARATOR '/' +#define CORRECT_PATH_SEPARATOR_S "/" +#define INCORRECT_PATH_SEPARATOR '\\' +#define INCORRECT_PATH_SEPARATOR_S "\\" +#endif + +int V_vsnprintf(char *pDest, int maxLen, const char *pFormat, va_list params); +int V_vsnprintfRet(char *pDest, int maxLen, const char *pFormat, va_list params, + bool *pbTruncated); + +// Prints out a pretified memory counter string value ( e.g., 7,233.27 Mb, +// 1,298.003 Kb, 127 bytes ) +char *V_pretifymem(float value, int digitsafterdecimal = 2, + bool usebinaryonek = false); + +// Prints out a pretified integer with comma separators (eg, 7,233,270,000) +char *V_pretifynum(int64 value); + +// Functions for converting hexidecimal character strings back into binary data +// etc. +// +// e.g., +// int output; +// V_hextobinary( "ffffffff", 8, &output, sizeof( output ) ); +// would make output == 0xfffffff or -1 +// Similarly, +// char buffer[ 9 ]; +// V_binarytohex( &output, sizeof( output ), buffer, sizeof( buffer ) ); +// would put "ffffffff" into buffer (note null terminator!!!) +void V_hextobinary(char const *in, intp numchars, byte *out, intp maxoutputbytes); +void V_binarytohex(const byte *in, intp inputbytes, char *out, intp outsize); + +// Tools for working with filenames +// Extracts the base name of a file (no path, no extension, assumes '/' or '\' +// as path separator) +void V_FileBase(const char *in, char *out, intp maxlen); +// Remove the final characters of ppath if it's '\' or '/'. +void V_StripTrailingSlash(char *ppath); +// Remove any extension from in and return resulting string in out +void V_StripExtension(const char *in, char *out, intp outLen); +// Make path end with extension if it doesn't already have an extension +void V_DefaultExtension(char *path, const char *extension, + intp pathStringLength); +// Strips any current extension from path and ensures that extension is the new +// extension. NOTE: extension string MUST include the . character +void V_SetExtension(char *path, const char *extension, intp pathStringLength); +// Removes any filename from path ( strips back to previous / or \ character ) +void V_StripFilename(char *path); +// Remove the final directory from the path +bool V_StripLastDir(char *dirName, intp maxlen); +// Returns a pointer to the unqualified file name (no path) of a file name +const char *V_UnqualifiedFileName(const char *in); +char *V_UnqualifiedFileName(char *in); +// Given a path and a filename, composes "path\filename", inserting the (OS +// correct) separator if necessary +void V_ComposeFileName(const char *path, const char *filename, char *dest, + intp destSize); + +// Copy out the path except for the stuff after the final pathseparator +bool V_ExtractFilePath(const char *path, char *dest, intp destSize); +// Copy out the file extension into dest +void V_ExtractFileExtension(const char *path, char *dest, intp destSize); + +const char *V_GetFileExtension(const char *path); + +// returns a pointer to just the filename part of the path +// (everything after the last path seperator) +const char *V_GetFileName(const char *path); + +// This removes "./" and "../" from the pathname. pFilename should be a full +// pathname. Returns false if it tries to ".." past the root directory in the +// drive (in which case it is an invalid path). +bool V_RemoveDotSlashes(char *pFilename, + char separator = CORRECT_PATH_SEPARATOR); + +// If pPath is a relative path, this function makes it into an absolute path +// using the current working directory as the base, or pStartingDir if it's +// non-NULL. Returns false if it runs out of room in the string, or if pPath +// tries to ".." past the root directory. +void V_MakeAbsolutePath(char *pOut, int outLen, const char *pPath, + const char *pStartingDir = NULL); + +// Creates a relative path given two full paths +// The first is the full path of the file to make a relative path for. +// The second is the full path of the directory to make the first file relative +// to Returns false if they can't be made relative (on separate drives, for +// example) +bool V_MakeRelativePath(const char *pFullPath, const char *pDirectory, + char *pRelativePath, intp nBufLen); + +// Fixes up a file name, removing dot slashes, fixing slashes, converting to +// lowercase, etc. +void V_FixupPathName(char *pOut, size_t nOutLen, const char *pPath); + +// Adds a path separator to the end of the string if there isn't one already. +// Returns false if it would run out of space. +void V_AppendSlash(char *pStr, intp strSize); + +// Returns true if the path is an absolute path. +bool V_IsAbsolutePath(const char *pPath); + +// Scans pIn and replaces all occurences of pMatch with pReplaceWith. +// Writes the result to pOut. +// Returns true if it completed successfully. +// If it would overflow pOut, it fills as much as it can and returns false. +bool V_StrSubst(const char *pIn, const char *pMatch, const char *pReplaceWith, + char *pOut, intp outLen, bool bCaseSensitive = false); + +// Split the specified string on the specified separator. +// Returns a list of strings separated by pSeparator. +// You are responsible for freeing the contents of outStrings (call +// outStrings.PurgeAndDeleteElements). +void V_SplitString(const char *pString, const char *pSeparator, + CUtlVector> &outStrings); + +// Just like V_SplitString, but it can use multiple possible separators. +void V_SplitString2(const char *pString, const char **pSeparators, + intp nSeparators, + CUtlVector> &outStrings); + +// Returns false if the buffer is not large enough to hold the working directory +// name. +bool V_GetCurrentDirectory(char *pOut, int maxLen); + +// Set the working directory thus. +bool V_SetCurrentDirectory(const char *pDirName); + +// This function takes a slice out of pStr and stores it in pOut. +// It follows the Python slice convention: +// Negative numbers wrap around the string (-1 references the last character). +// Large numbers are clamped to the end of the string. +void V_StrSlice(const char *pStr, intp firstChar, intp lastCharNonInclusive, + char *pOut, intp outSize); + +// Chop off the left nChars of a string. +void V_StrLeft(const char *pStr, intp nChars, char *pOut, intp outSize); + +// Chop off the right nChars of a string. +void V_StrRight(const char *pStr, intp nChars, char *pOut, intp outSize); + +// change "special" characters to have their c-style backslash sequence. like +// \n, \r, \t, ", etc. returns a pointer to a newly allocated string, which you +// must delete[] when finished with. +char *V_AddBackSlashesToSpecialChars(char const *pSrc); + +// Force slashes of either type to be = separator character +void V_FixSlashes(char *pname, char separator = CORRECT_PATH_SEPARATOR); + +// This function fixes cases of filenames like materials\\blah.vmt or +// somepath\otherpath\\ and removes the extra double slash. +void V_FixDoubleSlashes(char *pStr); + +// Convert multibyte to wchar + back +// Specify -1 for nInSize for null-terminated string +void V_strtowcs(const char *pString, int nInSize, wchar_t *pWString, + int nOutSize); +void V_wcstostr(const wchar_t *pWString, int nInSize, char *pString, + int nOutSize); + +// buffer-safe strcat +inline void V_strcat(char *dest, const char *src, size_t maxLenInBytes) { + V_strncat(dest, src, maxLenInBytes, COPY_ALL_CHARACTERS); +} + +// buffer-safe strcat +inline void V_wcscat(wchar_t *dest, const wchar_t *src, size_t maxLenInBytes) { + V_wcsncat(dest, src, maxLenInBytes, COPY_ALL_CHARACTERS); +} + +// Convert from a string to an array of integers. +void V_StringToIntArray(int *pVector, intp count, const char *pString); + +// Convert from a string to a 4 byte color value. +void V_StringToColor32(color32 *color, const char *pString); + +// Convert \r\n (Windows linefeeds) to \n (Unix linefeeds). +void V_TranslateLineFeedsToUnix(char *pStr); + +//----------------------------------------------------------------------------- +// generic unique name helper functions +//----------------------------------------------------------------------------- + +// returns -1 if no match, nDefault if pName==prefix, and N if pName==prefix+N +inline intp V_IndexAfterPrefix(const char *pName, const char *prefix, + intp nDefault = 0) { + if (!pName || !prefix) return -1; + + const char *pIndexStr = StringAfterPrefix(pName, prefix); + if (!pIndexStr) return -1; + + if (!*pIndexStr) return nDefault; + + return strtoll(pIndexStr, nullptr, 10); +} + +// returns startindex if none found, 2 if "prefix" found, and n+1 if "prefixn" +// found +template +intp V_GenerateUniqueNameIndex(const char *prefix, const NameArray &nameArray, + intp startindex = 0) { + if (!prefix) return 0; + + intp freeindex = startindex; + + intp nNames = nameArray.Count(); + for (intp i = 0; i < nNames; ++i) { + intp index = V_IndexAfterPrefix( + nameArray[i], prefix, + 1); // returns -1 if no match, 0 for exact match, N for + if (index >= freeindex) { + // TODO - check that there isn't more junk after the index in pElementName + freeindex = index + 1; + } + } + + return freeindex; +} + +template +bool V_GenerateUniqueName(char *name, intp memsize, const char *prefix, + const NameArray &nameArray) { + if (name == NULL || memsize == 0) return false; + + if (prefix == NULL) { + name[0] = '\0'; + return false; + } + + intp prefixLength = V_strlen(prefix); + if (prefixLength + 1 > memsize) { + name[0] = '\0'; + return false; + } + + intp i = V_GenerateUniqueNameIndex(prefix, nameArray); + if (i <= 0) { + V_strncpy(name, prefix, memsize); + return true; + } + + intp newlen = prefixLength + (int)log10((float)i) + 1; + if (newlen + 1 > memsize) { + V_strncpy(name, prefix, memsize); + return false; + } + + V_snprintf(name, memsize, "%s%zi", prefix, i); + return true; +} + +extern bool V_StringToBin(const char *pString, void *pBin, size_t nBinSize); +extern bool V_BinToString(char *pString, void *pBin, size_t nBinSize); + +template +struct BinString_t { + BinString_t() {} + BinString_t(const char *pStr) { + V_strncpy(m_string, pStr, sizeof(m_string)); + ToBin(); + } + BinString_t(const T &that) { + m_bin = that; + ToString(); + } + bool ToBin() { return V_StringToBin(m_string, &m_bin, sizeof(m_bin)); } + void ToString() { V_BinToString(m_string, &m_bin, sizeof(m_bin)); } + T m_bin; + char m_string[sizeof(T) * 2 + + 2]; // 0-terminated string representing the binary data in hex +}; + +template +inline BinString_t MakeBinString(const T &that) { + return BinString_t(that); +} + +#if defined(_PS3) || defined(POSIX) +#define PRI_WS_FOR_WS L"%ls" +#define PRI_WS_FOR_S "%ls" +#define PRI_S_FOR_WS L"%s" +#define PRI_S_FOR_S "%s" +#else +#define PRI_WS_FOR_WS L"%s" +#define PRI_WS_FOR_S "%S" +#define PRI_S_FOR_WS L"%S" +#define PRI_S_FOR_S "%s" +#endif + +namespace AsianWordWrap { + +// Functions used by Asian language line wrapping to determine if a character +// can end a line, begin a line, or be broken up when repeated (eg: "...") +bool CanEndLine(wchar_t wcCandidate); +bool CanBeginLine(wchar_t wcCandidate); +bool CanBreakRepeated(wchar_t wcCandidate); + +// Used to determine if we can break a line between the first two characters +// passed; calls the above functions on each character +bool CanBreakAfter(const wchar_t *wsz); + +} // namespace AsianWordWrap + +// We use this function to determine where it is permissible to break lines +// of text while wrapping them. On most platforms, the native iswspace() +// function returns FALSE for the "non-breaking space" characters 0x00a0 and +// 0x202f, and so we don't break on them. On the 360, however, iswspace returns +// TRUE for them. So, on that platform, we work around it by defining this +// wrapper which returns false for   and calls through to the library +// function for everything else. +int isbreakablewspace(wchar_t ch); + +#endif // VPC_TIER1_STRTOOLS_H_ diff --git a/public/tier1/tier1.h b/public/tier1/tier1.h new file mode 100644 index 0000000..57057b8 --- /dev/null +++ b/public/tier1/tier1.h @@ -0,0 +1,55 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A higher level link library for general use in the game and tools. + +#ifndef VPC_TIER1_TIER1_H_ +#define VPC_TIER1_TIER1_H_ + +#include "appframework/iappsystem.h" +#include "tier1/convar.h" + +// Call this to connect to/disconnect from all tier 1 libraries. +// It's up to the caller to check the globals it cares about to see if ones are +// missing +void ConnectTier1Libraries(CreateInterfaceFn *pFactoryList, int nFactoryCount); +void DisconnectTier1Libraries(); + +// Helper empty implementation of an IAppSystem for tier2 libraries +template +class CTier1AppSystem : public CTier0AppSystem { + typedef CTier0AppSystem BaseClass; + + public: + virtual bool Connect(CreateInterfaceFn factory) { + if (!BaseClass::Connect(factory)) return false; + + ConnectTier1Libraries(&factory, 1); + return true; + } + + virtual void Disconnect() { + DisconnectTier1Libraries(); + BaseClass::Disconnect(); + } + + virtual InitReturnVal_t Init() { + InitReturnVal_t nRetVal = BaseClass::Init(); + if (nRetVal != INIT_OK) return nRetVal; + + if (g_pCVar) { + ConVar_Register(ConVarFlag); + } + return INIT_OK; + } + + virtual void Shutdown() { + if (g_pCVar) { + ConVar_Unregister(); + } + BaseClass::Shutdown(); + } + + virtual AppSystemTier_t GetTier() { return APP_SYSTEM_TIER1; } +}; + +#endif // VPC_TIER1_TIER1_H_ diff --git a/public/tier1/utlblockmemory.h b/public/tier1/utlblockmemory.h new file mode 100644 index 0000000..75afb1b --- /dev/null +++ b/public/tier1/utlblockmemory.h @@ -0,0 +1,321 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_UTLBLOCKMEMORY_H_ +#define VPC_TIER1_UTLBLOCKMEMORY_H_ + +#include "tier0/dbg.h" +#include "tier0/platform.h" +#include "mathlib/mathlib.h" + +#include "tier0/memalloc.h" +#include "tier0/memdbgon.h" + +#ifdef UTBLOCKLMEMORY_TRACK +#define UTLBLOCKMEMORY_TRACK_ALLOC() \ + MemAlloc_RegisterAllocation("||Sum of all UtlBlockMemory||", 0, \ + NumAllocated() * sizeof(T), \ + NumAllocated() * sizeof(T), 0) +#define UTLBLOCKMEMORY_TRACK_FREE() \ + if (!m_pMemory) \ + ; \ + else \ + MemAlloc_RegisterDeallocation("||Sum of all UtlBlockMemory||", 0, \ + NumAllocated() * sizeof(T), \ + NumAllocated() * sizeof(T), 0) +#else +#define UTLBLOCKMEMORY_TRACK_ALLOC() ((void)0) +#define UTLBLOCKMEMORY_TRACK_FREE() ((void)0) +#endif + +//----------------------------------------------------------------------------- +// The CUtlBlockMemory class: +// A growable memory class that allocates non-sequential blocks, but is indexed +// sequentially +//----------------------------------------------------------------------------- +template +class CUtlBlockMemory { + public: + // constructor, destructor + CUtlBlockMemory(int nGrowSize = 0, int nInitSize = 0); + ~CUtlBlockMemory(); + + // Set the size by which the memory grows - round up to the next power of 2 + void Init(int nGrowSize = 0, int nInitSize = 0); + + // here to match CUtlMemory, but only used by ResetDbgInfo, so it can just + // return NULL + T* Base() { return NULL; } + const T* Base() const { return NULL; } + + class Iterator_t { + public: + Iterator_t(I i) : index(i) {} + I index; + + bool operator==(const Iterator_t it) const { return index == it.index; } + bool operator!=(const Iterator_t it) const { return index != it.index; } + }; + Iterator_t First() const { + return Iterator_t(IsIdxValid(0) ? 0 : InvalidIndex()); + } + Iterator_t Next(const Iterator_t& it) const { + return Iterator_t(IsIdxValid(it.index + 1) ? it.index + 1 : InvalidIndex()); + } + I GetIndex(const Iterator_t& it) const { return it.index; } + bool IsIdxAfter(I i, const Iterator_t& it) const { return i > it.index; } + bool IsValidIterator(const Iterator_t& it) const { + return IsIdxValid(it.index); + } + Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex()); } + + // element access + T& operator[](I i); + const T& operator[](I i) const; + T& Element(I i); + const T& Element(I i) const; + + // Can we use this index? + bool IsIdxValid(I i) const; + static I InvalidIndex() { return (I)-1; } + + void Swap(CUtlBlockMemory& mem); + + // Size + int NumAllocated() const; + int Count() const { return NumAllocated(); } + + // Grows memory by max(num,growsize) rounded up to the next power of 2, and + // returns the allocation index/ptr + void Grow(int num = 1); + + // Makes sure we've got at least this much memory + void EnsureCapacity(int num); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements + void Purge(int numElements); + + protected: + int Index(int major, int minor) const { + return (major << m_nIndexShift) | minor; + } + int MajorIndex(int i) const { return i >> m_nIndexShift; } + int MinorIndex(int i) const { return i & m_nIndexMask; } + void ChangeSize(int nBlocks); + int NumElementsInBlock() const { return m_nIndexMask + 1; } + + T** m_pMemory; + int m_nBlocks; + int m_nIndexMask : 27; + int m_nIndexShift : 5; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlBlockMemory::CUtlBlockMemory(int nGrowSize, int nInitAllocationCount) + : m_pMemory(0), m_nBlocks(0), m_nIndexMask(0), m_nIndexShift(0) { + Init(nGrowSize, nInitAllocationCount); +} + +template +CUtlBlockMemory::~CUtlBlockMemory() { + Purge(); +} + +//----------------------------------------------------------------------------- +// Fast swap +//----------------------------------------------------------------------------- +template +void CUtlBlockMemory::Swap(CUtlBlockMemory& mem) { + V_swap(m_pMemory, mem.m_pMemory); + V_swap(m_nBlocks, mem.m_nBlocks); + V_swap(m_nIndexMask, mem.m_nIndexMask); + V_swap(m_nIndexShift, mem.m_nIndexShift); +} + +//----------------------------------------------------------------------------- +// Set the size by which the memory grows - round up to the next power of 2 +//----------------------------------------------------------------------------- +template +void CUtlBlockMemory::Init(int nGrowSize /* = 0 */, + int nInitSize /* = 0 */) { + Purge(); + + if (nGrowSize == 0) { + // default grow size is smallest size s.t. c++ allocation overhead is ~6% of + // block size + nGrowSize = (127 + sizeof(T)) / sizeof(T); + } + nGrowSize = SmallestPowerOfTwoGreaterOrEqual(nGrowSize); + m_nIndexMask = nGrowSize - 1; + + m_nIndexShift = 0; + while (nGrowSize > 1) { + nGrowSize >>= 1; + ++m_nIndexShift; + } + Assert(m_nIndexMask + 1 == (1 << m_nIndexShift)); + + Grow(nInitSize); +} + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template +inline T& CUtlBlockMemory::operator[](I i) { + Assert(IsIdxValid(i)); + T* pBlock = m_pMemory[MajorIndex(i)]; + return pBlock[MinorIndex(i)]; +} + +template +inline const T& CUtlBlockMemory::operator[](I i) const { + Assert(IsIdxValid(i)); + const T* pBlock = m_pMemory[MajorIndex(i)]; + return pBlock[MinorIndex(i)]; +} + +template +inline T& CUtlBlockMemory::Element(I i) { + Assert(IsIdxValid(i)); + T* pBlock = m_pMemory[MajorIndex(i)]; + return pBlock[MinorIndex(i)]; +} + +template +inline const T& CUtlBlockMemory::Element(I i) const { + Assert(IsIdxValid(i)); + const T* pBlock = m_pMemory[MajorIndex(i)]; + return pBlock[MinorIndex(i)]; +} + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- +template +inline int CUtlBlockMemory::NumAllocated() const { + return m_nBlocks * NumElementsInBlock(); +} + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template +inline bool CUtlBlockMemory::IsIdxValid(I i) const { + return (i >= 0) && (MajorIndex(i) < m_nBlocks); +} + +template +void CUtlBlockMemory::Grow(int num) { + if (num <= 0) return; + + int nBlockSize = NumElementsInBlock(); + int nBlocks = (num + nBlockSize - 1) / nBlockSize; + + ChangeSize(m_nBlocks + nBlocks); +} + +template +void CUtlBlockMemory::ChangeSize(int nBlocks) { + UTLBLOCKMEMORY_TRACK_FREE(); // this must stay before the recalculation of + // m_nBlocks, since it implicitly uses the old + // value + + int nBlocksOld = m_nBlocks; + m_nBlocks = nBlocks; + + UTLBLOCKMEMORY_TRACK_ALLOC(); // this must stay after the recalculation of + // m_nBlocks, since it implicitly uses the new + // value + + if (m_pMemory) { + // free old blocks if shrinking + for (int i = m_nBlocks; i < nBlocksOld; ++i) { + UTLBLOCKMEMORY_TRACK_FREE(); + free((void*)m_pMemory[i]); + } + + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T**)realloc(m_pMemory, m_nBlocks * sizeof(T*)); + Assert(m_pMemory); + } else { + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T**)malloc(m_nBlocks * sizeof(T*)); + Assert(m_pMemory); + } + + if (!m_pMemory) { + Error("CUtlBlockMemory overflow!\n"); + } + + // allocate new blocks if growing + int nBlockSize = NumElementsInBlock(); + for (int i = nBlocksOld; i < m_nBlocks; ++i) { + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory[i] = (T*)malloc(nBlockSize * sizeof(T)); + Assert(m_pMemory[i]); + } +} + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template +inline void CUtlBlockMemory::EnsureCapacity(int num) { + Grow(num - NumAllocated()); +} + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template +void CUtlBlockMemory::Purge() { + if (!m_pMemory) return; + + for (int i = 0; i < m_nBlocks; ++i) { + UTLBLOCKMEMORY_TRACK_FREE(); + free((void*)m_pMemory[i]); + } + m_nBlocks = 0; + + UTLBLOCKMEMORY_TRACK_FREE(); + free((void*)m_pMemory); + m_pMemory = 0; +} + +template +void CUtlBlockMemory::Purge(int numElements) { + Assert(numElements >= 0); + + int nAllocated = NumAllocated(); + if (numElements > nAllocated) { + // Ensure this isn't a grow request in disguise. + Assert(numElements <= nAllocated); + return; + } + + if (numElements <= 0) { + Purge(); + return; + } + + int nBlockSize = NumElementsInBlock(); + int nBlocksOld = m_nBlocks; + int nBlocks = (numElements + nBlockSize - 1) / nBlockSize; + + // If the number of blocks is the same as the allocated number of blocks, we + // are done. + if (nBlocks == m_nBlocks) return; + + ChangeSize(nBlocks); +} + +#include "tier0/memdbgoff.h" + +#endif // VPC_TIER1_UTLBLOCKMEMORY_H_ diff --git a/public/tier1/utlbuffer.h b/public/tier1/utlbuffer.h new file mode 100644 index 0000000..affba92 --- /dev/null +++ b/public/tier1/utlbuffer.h @@ -0,0 +1,1237 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Serialization/unserialization buffer + +#ifndef VPC_TIER1_UTLBUFFER_H_ +#define VPC_TIER1_UTLBUFFER_H_ + +#include "tier1/utlmemory.h" +#include "tier1/byteswap.h" +#include + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +struct characterset_t; + +//----------------------------------------------------------------------------- +// Description of character conversions for string output +// Here's an example of how to use the macros to define a character conversion +// BEGIN_CHAR_CONVERSION( CStringConversion, '\\' ) +// { '\n', "n" }, +// { '\t', "t" } +// END_CHAR_CONVERSION( CStringConversion, '\\' ) +//----------------------------------------------------------------------------- +class CUtlCharConversion { + public: + struct ConversionArray_t { + char m_nActualChar; + const char *m_pReplacementString; + }; + + CUtlCharConversion(char nEscapeChar, const char *pDelimiter, intp nCount, + ConversionArray_t *pArray); + char GetEscapeChar() const; + const char *GetDelimiter() const; + intp GetDelimiterLength() const; + + const char *GetConversionString(char c) const; + intp GetConversionLength(char c) const; + intp MaxConversionLength() const; + + // Finds a conversion for the passed-in string, returns length + virtual char FindConversion(const char *pString, intp *pLength); + + protected: + struct ConversionInfo_t { + intp m_nLength; + const char *m_pReplacementString; + }; + + char m_nEscapeChar; + const char *m_pDelimiter; + intp m_nDelimiterLength; + intp m_nCount; + intp m_nMaxConversionLength; + char m_pList[256]; + ConversionInfo_t m_pReplacements[256]; +}; + +#define BEGIN_CHAR_CONVERSION(_name, _delimiter, _escapeChar) \ + static CUtlCharConversion::ConversionArray_t s_pConversionArray##_name[] = { +#define END_CHAR_CONVERSION(_name, _delimiter, _escapeChar) \ + } \ + ; \ + CUtlCharConversion _name(_escapeChar, _delimiter, \ + sizeof(s_pConversionArray##_name) / \ + sizeof(CUtlCharConversion::ConversionArray_t), \ + s_pConversionArray##_name); + +#define BEGIN_CUSTOM_CHAR_CONVERSION(_className, _name, _delimiter, \ + _escapeChar) \ + static CUtlCharConversion::ConversionArray_t s_pConversionArray##_name[] = { +#define END_CUSTOM_CHAR_CONVERSION(_className, _name, _delimiter, _escapeChar) \ + } \ + ; \ + _className _name(_escapeChar, _delimiter, \ + sizeof(s_pConversionArray##_name) / \ + sizeof(CUtlCharConversion::ConversionArray_t), \ + s_pConversionArray##_name); + +//----------------------------------------------------------------------------- +// Character conversions for C strings +//----------------------------------------------------------------------------- +CUtlCharConversion *GetCStringCharConversion(); + +//----------------------------------------------------------------------------- +// Character conversions for quoted strings, with no escape sequences +//----------------------------------------------------------------------------- +CUtlCharConversion *GetNoEscCharConversion(); + +//----------------------------------------------------------------------------- +// Macro to set overflow functions easily +//----------------------------------------------------------------------------- +#define SetUtlBufferOverflowFuncs(_get, _put) \ + SetOverflowFuncs(static_cast(_get), \ + static_cast(_put)) + +#define FMSTRRETTYPE const char * +#ifdef LINUX +// gcc 4.3 is techinically correct and doesn't like the storage specifier, +// unfortunately, that makes gcc on the mac and VS wind up with multiply defined +// symbols at link time +#define FMSTRRETTYPE const char * +#endif + +typedef unsigned short ushort; + +template +inline const char *GetFmtStr(int nRadix = 10, bool bPrint = true) { + Assert(0); + return ""; +} + +template <> +inline FMSTRRETTYPE GetFmtStr(int nRadix, bool) { + Assert(nRadix == 10); + return "%hd"; +} +template <> +inline FMSTRRETTYPE GetFmtStr(int nRadix, bool) { + Assert(nRadix == 10); + return "%hu"; +} +template <> +inline FMSTRRETTYPE GetFmtStr(int nRadix, bool) { + Assert(nRadix == 10); + return "%d"; +} +template <> +inline FMSTRRETTYPE GetFmtStr(int nRadix, bool) { + Assert(nRadix == 10 || nRadix == 16); + return nRadix == 16 ? "%x" : "%u"; +} +template <> +inline FMSTRRETTYPE GetFmtStr(int nRadix, bool) { + Assert(nRadix == 10); + return "%lld"; +} +template <> +inline FMSTRRETTYPE GetFmtStr(int nRadix, bool) { + Assert(nRadix == 10); + return "%f"; +} +template <> +inline FMSTRRETTYPE GetFmtStr(int nRadix, bool bPrint) { + Assert(nRadix == 10); + return bPrint ? "%.15lf" : "%lf"; +} // force Printf to print DBL_DIG=15 digits of precision for doubles - + // defaults to FLT_DIG=6 + +//----------------------------------------------------------------------------- +// Command parsing.. +//----------------------------------------------------------------------------- +class CUtlBuffer { + // Brian has on his todo list to revisit this as there are issues in some + // cases with CUtlVector using operator = instead of copy construtor in + // InsertMultiple, etc. The unsafe case is something like this: + // CUtlVector< CUtlBuffer > vecFoo; + // + // CUtlBuffer buf; + // buf.Put( xxx ); + // vecFoo.Insert( buf ); + // + // This will cause memory corruption when vecFoo is cleared + // + // private: + // // Disallow copying + // CUtlBuffer( const CUtlBuffer & );// { Assert( 0 ); } + // CUtlBuffer &operator=( const CUtlBuffer & );// { Assert( 0 ); return + //*this; } + + public: + enum SeekType_t { SEEK_HEAD = 0, SEEK_CURRENT, SEEK_TAIL }; + + // flags + enum BufferFlags_t { + TEXT_BUFFER = 0x1, // Describes how get + put work (as strings, or binary) + EXTERNAL_GROWABLE = 0x2, // This is used w/ external buffers and causes the + // utlbuf to switch to reallocatable memory if an + // overflow happens when Putting. + CONTAINS_CRLF = + 0x4, // For text buffers only, does this contain \n or \n\r? + READ_ONLY = + 0x8, // For external buffers; prevents null termination from happening. + AUTO_TABS_DISABLED = 0x10, // Used to disable/enable push/pop tabs + }; + + // Overflow functions when a get or put overflows + typedef bool (CUtlBuffer::*UtlBufferOverflowFunc_t)(intp nSize); + + // Constructors for growable + external buffers for + // serialization/unserialization + CUtlBuffer(intp growSize = 0, intp initSize = 0, int nFlags = 0); + CUtlBuffer(const void *pBuffer, intp size, int nFlags = 0); + // This one isn't actually defined so that we catch contructors that are + // trying to pass a bool in as the third param. + CUtlBuffer(const void *pBuffer, intp size, bool crap); + + unsigned char GetFlags() const; + + // NOTE: This will assert if you attempt to recast it in a way that + // is not compatible. The only valid conversion is binary-> text w/CRLF + void SetBufferType(bool bIsText, bool bContainsCRLF); + + // Makes sure we've got at least this much memory + void EnsureCapacity(intp num); + + // Access for direct read into buffer + void *AccessForDirectRead(intp nBytes); + + // Attaches the buffer to external memory.... + void SetExternalBuffer(void *pMemory, intp nSize, intp nInitialPut, + int nFlags = 0); + bool IsExternallyAllocated() const; + void AssumeMemory(void *pMemory, intp nSize, intp nInitialPut, int nFlags = 0); + void *Detach(); + void *DetachMemory(); + + FORCEINLINE void ActivateByteSwappingIfBigEndian(void) { + if ((IsX360() || IsPS3())) ActivateByteSwapping(true); + } + + // Controls endian-ness of binary utlbufs - default matches the current + // platform + void ActivateByteSwapping(bool bActivate); + void SetBigEndian(bool bigEndian); + bool IsBigEndian(void); + + // Resets the buffer; but doesn't free memory + void Clear(); + + // Clears out the buffer; frees memory + void Purge(); + + // Dump the buffer to stdout + void Spew(); + + // Read stuff out. + // Binary mode: it'll just read the bits directly in, and characters will be + // read for strings until a null character is reached. + // Text mode: it'll parse the file, turning text #s into real numbers. + // GetString will read a string until a space is reached + char GetChar(); + unsigned char GetUnsignedChar(); + short GetShort(); + unsigned short GetUnsignedShort(); + int GetInt(); + int64 GetInt64(); + unsigned int GetIntHex(); + unsigned int GetUnsignedInt(); + float GetFloat(); + double GetDouble(); + void *GetPtr(); + void GetString(char *pString, intp nMaxChars = 0); + void Get(void *pMem, intp size); + void GetLine(char *pLine, intp nMaxChars = 0); + + // Used for getting objects that have a byteswap datadesc defined + template + void GetObjects(T *dest, intp count = 1); + + // This will get at least 1 byte and up to nSize bytes. + // It will return the number of bytes actually read. + intp GetUpTo(void *pMem, intp nSize); + + // This version of GetString converts \" to \\ and " to \, etc. + // It also reads a " at the beginning and end of the string + void GetDelimitedString(CUtlCharConversion *pConv, char *pString, + intp nMaxChars = 0); + char GetDelimitedChar(CUtlCharConversion *pConv); + + // This will return the # of characters of the string about to be read out + // NOTE: The count will *include* the terminating 0!! + // In binary mode, it's the number of characters until the next 0 + // In text mode, it's the number of characters until the next space. + intp PeekStringLength(); + + // This version of PeekStringLength converts \" to \\ and " to \, etc. + // It also reads a " at the beginning and end of the string + // NOTE: The count will *include* the terminating 0!! + // In binary mode, it's the number of characters until the next 0 + // In text mode, it's the number of characters between "s (checking for \") + // Specifying false for bActualSize will return the pre-translated number of + // characters including the delimiters and the escape characters. So, \n + // counts as 2 characters when bActualSize == false and only 1 character when + // bActualSize == true + intp PeekDelimitedStringLength(CUtlCharConversion *pConv, + bool bActualSize = true); + + // Just like scanf, but doesn't work in binary mode + intp Scanf(SCANF_FORMAT_STRING const char *pFmt, ...); + intp VaScanf(const char *pFmt, va_list list); + + // Eats white space, advances Get index + void EatWhiteSpace(); + + // Eats C++ style comments + bool EatCPPComment(); + + // (For text buffers only) + // Parse a token from the buffer: + // Grab all text that lies between a starting delimiter + ending delimiter + // (skipping whitespace that leads + trails both delimiters). + // If successful, the get index is advanced and the function returns true, + // otherwise the index is not advanced and the function returns false. + bool ParseToken(const char *pStartingDelim, const char *pEndingDelim, + char *pString, intp nMaxLen); + + // Advance the get index until after the particular string is found + // Do not eat whitespace before starting. Return false if it failed + // String test is case-insensitive. + bool GetToken(const char *pToken); + + // Parses the next token, given a set of character breaks to stop at + // Returns the length of the token parsed in bytes (-1 if none parsed) + intp ParseToken(characterset_t *pBreaks, char *pTokenBuf, intp nMaxLen, + bool bParseComments = true); + + // Write stuff in + // Binary mode: it'll just write the bits directly in, and strings will be + // written with a null terminating character + // Text mode: it'll convert the numbers to text versions + // PutString will not write a terminating character + void PutChar(char c); + void PutUnsignedChar(unsigned char uc); + void PutShort(short s); + void PutUnsignedShort(unsigned short us); + void PutInt(int i); + void PutInt64(int64 i); + void PutUnsignedInt(unsigned int u); + void PutFloat(float f); + void PutDouble(double d); + void PutPtr(void *); // Writes the pointer, not the pointed to + void PutString(const char *pString); + void Put(const void *pMem, intp size); + + // Used for putting objects that have a byteswap datadesc defined + template + void PutObjects(T *src, intp count = 1); + + // This version of PutString converts \ to \\ and " to \", etc. + // It also places " at the beginning and end of the string + void PutDelimitedString(CUtlCharConversion *pConv, const char *pString); + void PutDelimitedChar(CUtlCharConversion *pConv, char c); + + // Just like printf, writes a terminating zero in binary mode + void Printf(const char *pFmt, ...) FMTFUNCTION(2, 3); + void VaPrintf(const char *pFmt, va_list list); + + // What am I writing (put)/reading (get)? + void *PeekPut(intp offset = 0); + const void *PeekGet(intp offset = 0) const; + const void *PeekGet(intp nMaxSize, intp nOffset); + + // Where am I writing (put)/reading (get)? + intp TellPut() const; + intp TellGet() const; + + // What's the most I've ever written? + intp TellMaxPut() const; + + // How many bytes remain to be read? + // NOTE: This is not accurate for streaming text files; it overshoots + intp GetBytesRemaining() const; + + // Change where I'm writing (put)/reading (get) + void SeekPut(SeekType_t type, intp offset); + void SeekGet(SeekType_t type, intp offset); + + // Buffer base + const void *Base() const; + void *Base(); + + // memory allocation size, does *not* reflect size written or read, + // use TellPut or TellGet for that + intp Size() const; + + // Am I a text buffer? + bool IsText() const; + + // Can I grow if I'm externally allocated? + bool IsGrowable() const; + + // Am I valid? (overflow or underflow error), Once invalid it stays invalid + bool IsValid() const; + + // Do I contain carriage return/linefeeds? + bool ContainsCRLF() const; + + // Am I read-only + bool IsReadOnly() const; + + // Converts a buffer from a CRLF buffer to a CR buffer (and back) + // Returns false if no conversion was necessary (and outBuf is left untouched) + // If the conversion occurs, outBuf will be cleared. + bool ConvertCRLF(CUtlBuffer &outBuf); + + // Push/pop pretty-printing tabs + void PushTab(); + void PopTab(); + + // Temporarily disables pretty print + void EnableTabs(bool bEnable); + +#if !defined(_GAMECONSOLE) + // Swap my internal memory with another buffer, + // and copy all of its other members + void SwapCopy(CUtlBuffer &other); +#endif + + protected: + // error flags + enum { + PUT_OVERFLOW = 0x1, + GET_OVERFLOW = 0x2, + MAX_ERROR_FLAG = GET_OVERFLOW, + }; + + void SetOverflowFuncs(UtlBufferOverflowFunc_t getFunc, + UtlBufferOverflowFunc_t putFunc); + + bool OnPutOverflow(intp nSize); + bool OnGetOverflow(intp nSize); + + protected: + // Checks if a get/put is ok + bool CheckPut(intp size); + bool CheckGet(intp size); + + // NOTE: Pass in nPut here even though it is just a copy of m_Put. This is + // almost always called immediately after modifying m_Put and this lets it + // stay in a register + void AddNullTermination(intp nPut); + + // Methods to help with pretty-printing + bool WasLastCharacterCR(); + void PutTabs(); + + // Help with delimited stuff + char GetDelimitedCharInternal(CUtlCharConversion *pConv); + void PutDelimitedCharInternal(CUtlCharConversion *pConv, char c); + + // Default overflow funcs + bool PutOverflow(intp nSize); + bool GetOverflow(intp nSize); + + // Does the next bytes of the buffer match a pattern? + bool PeekStringMatch(intp nOffset, const char *pString, intp nLen); + + // Peek size of line to come, check memory bound + intp PeekLineLength(); + + // How much whitespace should I skip? + intp PeekWhiteSpace(intp nOffset); + + // Checks if a peek get is ok + bool CheckPeekGet(intp nOffset, intp nSize); + + // Call this to peek arbitrarily long into memory. It doesn't fail unless + // it can't read *anything* new + bool CheckArbitraryPeekGet(intp nOffset, intp &nIncrement); + + template + void GetType(T &dest); + template + void GetTypeBin(T &dest); + template + bool GetTypeText(T &value, int nRadix = 10); + template + void GetObject(T *src); + + template + void PutType(T src); + template + void PutTypeBin(T src); + template + void PutObject(T *src); + + // be sure to also update the copy constructor + // and SwapCopy() when adding members. + CUtlMemory m_Memory; + intp m_Get; + intp m_Put; + + unsigned char m_Error; + unsigned char m_Flags; + unsigned char m_Reserved; +#if defined(_GAMECONSOLE) + unsigned char pad; +#endif + + intp m_nTab; + intp m_nMaxPut; + intp m_nOffset; + + UtlBufferOverflowFunc_t m_GetOverflowFunc; + UtlBufferOverflowFunc_t m_PutOverflowFunc; + + CByteswap m_Byteswap; +}; + +// Stream style output operators for CUtlBuffer +inline CUtlBuffer &operator<<(CUtlBuffer &b, char v) { + b.PutChar(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, unsigned char v) { + b.PutUnsignedChar(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, short v) { + b.PutShort(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, unsigned short v) { + b.PutUnsignedShort(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, int v) { + b.PutInt(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, unsigned int v) { + b.PutUnsignedInt(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, float v) { + b.PutFloat(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, double v) { + b.PutDouble(v); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, const char *pv) { + b.PutString(pv); + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, const Vector &v) { + b << v.x << " " << v.y << " " << v.z; + return b; +} + +inline CUtlBuffer &operator<<(CUtlBuffer &b, const Vector2D &v) { + b << v.x << " " << v.y; + return b; +} + +class CUtlInplaceBuffer : public CUtlBuffer { + public: + CUtlInplaceBuffer(intp growSize = 0, intp initSize = 0, int nFlags = 0); + + // + // Routines returning buffer-inplace-pointers + // + public: + // + // Upon success, determines the line length, fills out the pointer to the + // beginning of the line and the line length, advances the "get" pointer + // offset by the line length and returns "true". + // + // If end of file is reached or upon error returns "false". + // + // Note: the returned length of the line is at least one character + // because the + // trailing newline characters are also included as part of + // the line. + // + // Note: the pointer returned points into the local memory of this + // buffer, in + // case the buffer gets relocated or destroyed the pointer + // becomes invalid. + // + // e.g.: ------------- + // + // char *pszLine; + // int nLineLen; + // while ( pUtlInplaceBuffer->InplaceGetLinePtr( &pszLine, + //&nLineLen ) ) + // { + // ... + // } + // + // ------------- + // + // @param ppszInBufferPtr on return points into this buffer at + // start of line + // @param pnLineLength on return holds num bytes accessible via + // (*ppszInBufferPtr) + // + // @returns true if line was successfully read + // false when EOF is reached or + // error occurs + // + bool InplaceGetLinePtr(/* out */ char **ppszInBufferPtr, + /* out */ intp *pnLineLength); + + // + // Determines the line length, advances the "get" pointer offset by the line + // length, replaces the newline character with null-terminator and returns the + // initial pointer to now null-terminated line. + // + // If end of file is reached or upon error returns NULL. + // + // Note: the pointer returned points into the local memory of this + // buffer, in + // case the buffer gets relocated or destroyed the pointer + // becomes invalid. + // + // e.g.: ------------- + // + // while ( char *pszLine = + // pUtlInplaceBuffer->InplaceGetLinePtr() + //) + // { + // ... + // } + // + // ------------- + // + // @returns ptr-to-zero-terminated-line if line was successfully + // read and buffer modified + // NULL when EOF is reached + //or error occurs + // + char *InplaceGetLinePtr(void); +}; + +//----------------------------------------------------------------------------- +// Where am I reading? +//----------------------------------------------------------------------------- +inline intp CUtlBuffer::TellGet() const { return m_Get; } + +//----------------------------------------------------------------------------- +// How many bytes remain to be read? +//----------------------------------------------------------------------------- +inline intp CUtlBuffer::GetBytesRemaining() const { + return m_nMaxPut - TellGet(); +} + +//----------------------------------------------------------------------------- +// What am I reading? +//----------------------------------------------------------------------------- +inline const void *CUtlBuffer::PeekGet(intp offset) const { + return &m_Memory[m_Get + offset - m_nOffset]; +} + +//----------------------------------------------------------------------------- +// Unserialization +//----------------------------------------------------------------------------- + +template +inline void CUtlBuffer::GetObject(T *dest) { + if (CheckGet(sizeof(T))) { + if (!m_Byteswap.IsSwappingBytes() || (sizeof(T) == 1)) { + *dest = *(T *)PeekGet(); + } else { + m_Byteswap.SwapFieldsToTargetEndian(dest, (T *)PeekGet()); + } + m_Get += sizeof(T); + } else { + V_memset(&dest, 0, sizeof(T)); + } +} + +template +inline void CUtlBuffer::GetObjects(T *dest, intp count) { + for (intp i = 0; i < count; ++i, ++dest) { + GetObject(dest); + } +} + +template +inline void CUtlBuffer::GetTypeBin(T &dest) { + if (CheckGet(sizeof(T))) { + if (!m_Byteswap.IsSwappingBytes() || (sizeof(T) == 1)) { + dest = *(T *)PeekGet(); + } else { + m_Byteswap.SwapBufferToTargetEndian(&dest, (T *)PeekGet()); + } + m_Get += sizeof(T); + } else { + dest = 0; + } +} + +template <> +inline void CUtlBuffer::GetTypeBin(float &dest) { + if (CheckGet(sizeof(float))) { + uintp pData = (uintp)PeekGet(); + if ((IsX360() || IsPS3()) && (pData & 0x03)) { + // handle unaligned read + ((unsigned char *)&dest)[0] = ((unsigned char *)pData)[0]; + ((unsigned char *)&dest)[1] = ((unsigned char *)pData)[1]; + ((unsigned char *)&dest)[2] = ((unsigned char *)pData)[2]; + ((unsigned char *)&dest)[3] = ((unsigned char *)pData)[3]; + } else { + // aligned read + dest = *(float *)pData; + } + if (m_Byteswap.IsSwappingBytes()) { + m_Byteswap.SwapBufferToTargetEndian(&dest, &dest); + } + m_Get += sizeof(float); + } else { + dest = 0; + } +} + +template <> +inline void CUtlBuffer::GetTypeBin(double &dest) { + if (CheckGet(sizeof(double))) { + uintp pData = (uintp)PeekGet(); + if ((IsX360() || IsPS3()) && (pData & 0x07)) { + // handle unaligned read + ((unsigned char *)&dest)[0] = ((unsigned char *)pData)[0]; + ((unsigned char *)&dest)[1] = ((unsigned char *)pData)[1]; + ((unsigned char *)&dest)[2] = ((unsigned char *)pData)[2]; + ((unsigned char *)&dest)[3] = ((unsigned char *)pData)[3]; + ((unsigned char *)&dest)[4] = ((unsigned char *)pData)[4]; + ((unsigned char *)&dest)[5] = ((unsigned char *)pData)[5]; + ((unsigned char *)&dest)[6] = ((unsigned char *)pData)[6]; + ((unsigned char *)&dest)[7] = ((unsigned char *)pData)[7]; + } else { + // aligned read + dest = *(double *)pData; + } + if (m_Byteswap.IsSwappingBytes()) { + m_Byteswap.SwapBufferToTargetEndian(&dest, &dest); + } + m_Get += sizeof(double); + } else { + dest = 0; + } +} + +template +inline T StringToNumber(char *pString, char **ppEnd, int nRadix) { + Assert(0); + *ppEnd = pString; + return 0; +} + +template <> +inline int8 StringToNumber(char *pString, char **ppEnd, int nRadix) { + return (int8)strtol(pString, ppEnd, nRadix); +} + +template <> +inline uint8 StringToNumber(char *pString, char **ppEnd, int nRadix) { + return (uint8)strtoul(pString, ppEnd, nRadix); +} + +template <> +inline int16 StringToNumber(char *pString, char **ppEnd, int nRadix) { + return (int16)strtol(pString, ppEnd, nRadix); +} + +template <> +inline uint16 StringToNumber(char *pString, char **ppEnd, int nRadix) { + return (uint16)strtoul(pString, ppEnd, nRadix); +} + +template <> +inline int32 StringToNumber(char *pString, char **ppEnd, int nRadix) { + return (int32)strtol(pString, ppEnd, nRadix); +} + +template <> +inline uint32 StringToNumber(char *pString, char **ppEnd, int nRadix) { + return (uint32)strtoul(pString, ppEnd, nRadix); +} + +template <> +inline int64 StringToNumber(char *pString, char **ppEnd, int nRadix) { +#if defined(_PS3) || defined(POSIX) + return (int64)strtoll(pString, ppEnd, nRadix); +#else // !_PS3 + return (int64)_strtoi64(pString, ppEnd, nRadix); +#endif // _PS3 +} + +template <> +inline float StringToNumber(char *pString, char **ppEnd, int nRadix) { + NOTE_UNUSED(nRadix); + return (float)strtod(pString, ppEnd); +} + +template <> +inline double StringToNumber(char *pString, char **ppEnd, int nRadix) { + NOTE_UNUSED(nRadix); + return (double)strtod(pString, ppEnd); +} + +template +inline bool CUtlBuffer::GetTypeText(T &value, int nRadix /*= 10*/) { + // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters + intp nLength = 128; + if (!CheckArbitraryPeekGet(0, nLength)) { + value = 0; + return false; + } + + char *pStart = (char *)PeekGet(); + char *pEnd = pStart; + value = StringToNumber(pStart, &pEnd, nRadix); + + intp nBytesRead = pEnd - pStart; + if (nBytesRead == 0) return false; + + m_Get += nBytesRead; + return true; +} + +template +inline void CUtlBuffer::GetType(T &dest) { + if (!IsText()) { + GetTypeBin(dest); + } else { + GetTypeText(dest); + } +} + +inline char CUtlBuffer::GetChar() { + // LEGACY WARNING: this behaves differently than GetUnsignedChar() + char c; + GetTypeBin(c); // always reads as binary + return c; +} + +inline unsigned char CUtlBuffer::GetUnsignedChar() { + // LEGACY WARNING: this behaves differently than GetChar() + unsigned char c; + if (!IsText()) { + GetTypeBin(c); + } else { + c = (unsigned char)GetUnsignedShort(); + } + return c; +} + +inline short CUtlBuffer::GetShort() { + short s; + GetType(s); + return s; +} + +inline unsigned short CUtlBuffer::GetUnsignedShort() { + unsigned short s; + GetType(s); + return s; +} + +inline int CUtlBuffer::GetInt() { + int i; + GetType(i); + return i; +} + +inline int64 CUtlBuffer::GetInt64() { + int64 i; + GetType(i); + return i; +} + +inline unsigned int CUtlBuffer::GetIntHex() { + uint i; + if (!IsText()) { + GetTypeBin(i); + } else { + GetTypeText(i, 16); + } + return i; +} + +inline unsigned int CUtlBuffer::GetUnsignedInt() { + unsigned int i; + GetType(i); + return i; +} + +inline float CUtlBuffer::GetFloat() { + float f; + GetType(f); + return f; +} + +inline double CUtlBuffer::GetDouble() { + double d; + GetType(d); + return d; +} + +inline void *CUtlBuffer::GetPtr() { + void *p; + // LEGACY WARNING: in text mode, PutPtr writes 32 bit pointers in hex, while + // GetPtr reads 32 or 64 bit pointers in decimal +#ifndef X64BITS + p = (void *)GetUnsignedInt(); +#else + p = (void *)GetInt64(); +#endif + return p; +} + +//----------------------------------------------------------------------------- +// Where am I writing? +//----------------------------------------------------------------------------- +inline unsigned char CUtlBuffer::GetFlags() const { return m_Flags; } + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +inline bool CUtlBuffer::IsExternallyAllocated() const { + return m_Memory.IsExternallyAllocated(); +} + +//----------------------------------------------------------------------------- +// Where am I writing? +//----------------------------------------------------------------------------- +inline intp CUtlBuffer::TellPut() const { return m_Put; } + +//----------------------------------------------------------------------------- +// What's the most I've ever written? +//----------------------------------------------------------------------------- +inline intp CUtlBuffer::TellMaxPut() const { return m_nMaxPut; } + +//----------------------------------------------------------------------------- +// What am I reading? +//----------------------------------------------------------------------------- +inline void *CUtlBuffer::PeekPut(intp offset) { + return &m_Memory[m_Put + offset - m_nOffset]; +} + +//----------------------------------------------------------------------------- +// Various put methods +//----------------------------------------------------------------------------- + +template +inline void CUtlBuffer::PutObject(T *src) { + if (CheckPut(sizeof(T))) { + if (!m_Byteswap.IsSwappingBytes() || (sizeof(T) == 1)) { + *(T *)PeekPut() = *src; + } else { + m_Byteswap.SwapFieldsToTargetEndian((T *)PeekPut(), src); + } + m_Put += sizeof(T); + AddNullTermination(m_Put); + } +} + +template +inline void CUtlBuffer::PutObjects(T *src, intp count) { + for (intp i = 0; i < count; ++i, ++src) { + PutObject(src); + } +} + +template +inline void CUtlBuffer::PutTypeBin(T src) { + if (CheckPut(sizeof(T))) { + if (!m_Byteswap.IsSwappingBytes() || (sizeof(T) == 1)) { + *(T *)PeekPut() = src; + } else { + m_Byteswap.SwapBufferToTargetEndian((T *)PeekPut(), &src); + } + m_Put += sizeof(T); + AddNullTermination(m_Put); + } +} + +#if defined(_GAMECONSOLE) +template <> +inline void CUtlBuffer::PutTypeBin(float src) { + if (CheckPut(sizeof(src))) { + if (m_Byteswap.IsSwappingBytes()) { + m_Byteswap.SwapBufferToTargetEndian(&src, &src); + } + + // + // Write the data + // + unsigned pData = (unsigned)PeekPut(); + if (pData & 0x03) { + // handle unaligned write + byte *dst = (byte *)pData; + byte *srcPtr = (byte *)&src; + dst[0] = srcPtr[0]; + dst[1] = srcPtr[1]; + dst[2] = srcPtr[2]; + dst[3] = srcPtr[3]; + } else { + *(float *)pData = src; + } + + m_Put += sizeof(float); + AddNullTermination(m_Put); + } +} + +template <> +inline void CUtlBuffer::PutTypeBin(double src) { + if (CheckPut(sizeof(src))) { + if (m_Byteswap.IsSwappingBytes()) { + m_Byteswap.SwapBufferToTargetEndian(&src, &src); + } + + // + // Write the data + // + unsigned pData = (unsigned)PeekPut(); + if (pData & 0x07) { + // handle unaligned write + byte *dst = (byte *)pData; + byte *srcPtr = (byte *)&src; + dst[0] = srcPtr[0]; + dst[1] = srcPtr[1]; + dst[2] = srcPtr[2]; + dst[3] = srcPtr[3]; + dst[4] = srcPtr[4]; + dst[5] = srcPtr[5]; + dst[6] = srcPtr[6]; + dst[7] = srcPtr[7]; + } else { + *(double *)pData = src; + } + + m_Put += sizeof(double); + AddNullTermination(m_Put); + } +} +#endif + +template +inline void CUtlBuffer::PutType(T src) { + if (!IsText()) { + PutTypeBin(src); + } else { + Printf(GetFmtStr(), src); + } +} + +//----------------------------------------------------------------------------- +// Methods to help with pretty-printing +//----------------------------------------------------------------------------- +inline bool CUtlBuffer::WasLastCharacterCR() { + if (!IsText() || (TellPut() == 0)) return false; + return (*(const char *)PeekPut(-1) == '\n'); +} + +inline void CUtlBuffer::PutTabs() { + intp nTabCount = (m_Flags & AUTO_TABS_DISABLED) ? 0 : m_nTab; + for (intp i = nTabCount; --i >= 0;) { + PutTypeBin('\t'); + } +} + +//----------------------------------------------------------------------------- +// Push/pop pretty-printing tabs +//----------------------------------------------------------------------------- +inline void CUtlBuffer::PushTab() { ++m_nTab; } + +inline void CUtlBuffer::PopTab() { + if (--m_nTab < 0) { + m_nTab = 0; + } +} + +//----------------------------------------------------------------------------- +// Temporarily disables pretty print +//----------------------------------------------------------------------------- +inline void CUtlBuffer::EnableTabs(bool bEnable) { + if (bEnable) { + m_Flags &= ~AUTO_TABS_DISABLED; + } else { + m_Flags |= AUTO_TABS_DISABLED; + } +} + +inline void CUtlBuffer::PutChar(char c) { + if (WasLastCharacterCR()) { + PutTabs(); + } + + PutTypeBin(c); +} + +inline void CUtlBuffer::PutUnsignedChar(unsigned char c) { + if (!IsText()) { + PutTypeBin(c); + } else { + PutUnsignedShort(c); + } +} + +inline void CUtlBuffer::PutShort(short s) { PutType(s); } + +inline void CUtlBuffer::PutUnsignedShort(unsigned short s) { PutType(s); } + +inline void CUtlBuffer::PutInt(int i) { PutType(i); } + +inline void CUtlBuffer::PutInt64(int64 i) { PutType(i); } + +inline void CUtlBuffer::PutUnsignedInt(unsigned int u) { PutType(u); } + +inline void CUtlBuffer::PutFloat(float f) { PutType(f); } + +inline void CUtlBuffer::PutDouble(double d) { PutType(d); } + +inline void CUtlBuffer::PutPtr(void *p) { + // LEGACY WARNING: in text mode, PutPtr writes 32 bit pointers in hex, while + // GetPtr reads 32 or 64 bit pointers in decimal + if (!IsText()) { + PutTypeBin(p); + } else { + Printf("0x%p", p); + } +} + +//----------------------------------------------------------------------------- +// Am I a text buffer? +//----------------------------------------------------------------------------- +inline bool CUtlBuffer::IsText() const { return (m_Flags & TEXT_BUFFER) != 0; } + +//----------------------------------------------------------------------------- +// Can I grow if I'm externally allocated? +//----------------------------------------------------------------------------- +inline bool CUtlBuffer::IsGrowable() const { + return (m_Flags & EXTERNAL_GROWABLE) != 0; +} + +//----------------------------------------------------------------------------- +// Am I valid? (overflow or underflow error), Once invalid it stays invalid +//----------------------------------------------------------------------------- +inline bool CUtlBuffer::IsValid() const { return m_Error == 0; } + +//----------------------------------------------------------------------------- +// Do I contain carriage return/linefeeds? +//----------------------------------------------------------------------------- +inline bool CUtlBuffer::ContainsCRLF() const { + return IsText() && ((m_Flags & CONTAINS_CRLF) != 0); +} + +//----------------------------------------------------------------------------- +// Am I read-only +//----------------------------------------------------------------------------- +inline bool CUtlBuffer::IsReadOnly() const { + return (m_Flags & READ_ONLY) != 0; +} + +//----------------------------------------------------------------------------- +// Buffer base and size +//----------------------------------------------------------------------------- +inline const void *CUtlBuffer::Base() const { return m_Memory.Base(); } + +inline void *CUtlBuffer::Base() { return m_Memory.Base(); } + +inline intp CUtlBuffer::Size() const { return m_Memory.NumAllocated(); } + +//----------------------------------------------------------------------------- +// Clears out the buffer; frees memory +//----------------------------------------------------------------------------- +inline void CUtlBuffer::Clear() { + m_Get = 0; + m_Put = 0; + m_Error = 0; + m_nOffset = 0; + m_nMaxPut = -1; + AddNullTermination(m_Put); +} + +inline void CUtlBuffer::Purge() { + m_Get = 0; + m_Put = 0; + m_nOffset = 0; + m_nMaxPut = 0; + m_Error = 0; + m_Memory.Purge(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +inline void *CUtlBuffer::AccessForDirectRead(intp nBytes) { + Assert(m_Get == 0 && m_Put == 0 && m_nMaxPut == 0); + EnsureCapacity(nBytes); + m_nMaxPut = nBytes; + return Base(); +} + +inline void *CUtlBuffer::Detach() { + void *p = m_Memory.Detach(); + Clear(); + return p; +} + +//----------------------------------------------------------------------------- + +inline void CUtlBuffer::Spew() { + SeekGet(CUtlBuffer::SEEK_HEAD, 0); + + char pTmpLine[1024]; + while (IsValid() && GetBytesRemaining()) { + V_memset(pTmpLine, 0, sizeof(pTmpLine)); + Get(pTmpLine, MIN((size_t)GetBytesRemaining(), sizeof(pTmpLine) - 1)); + Msg(_T( "%s" ), pTmpLine); + } +} + +#if !defined(_GAMECONSOLE) +inline void CUtlBuffer::SwapCopy(CUtlBuffer &other) { + m_Get = other.m_Get; + m_Put = other.m_Put; + m_Error = other.m_Error; + m_Flags = other.m_Flags; + m_Reserved = other.m_Reserved; + m_nTab = other.m_nTab; + m_nMaxPut = other.m_nMaxPut; + m_nOffset = other.m_nOffset; + m_GetOverflowFunc = other.m_GetOverflowFunc; + m_PutOverflowFunc = other.m_PutOverflowFunc; + m_Byteswap = other.m_Byteswap; + + m_Memory.Swap(other.m_Memory); +} +#endif + +#endif // VPC_TIER1_UTLBUFFER_H_ diff --git a/public/tier1/utldict.h b/public/tier1/utldict.h new file mode 100644 index 0000000..2b39ef6 --- /dev/null +++ b/public/tier1/utldict.h @@ -0,0 +1,296 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A dictionary mapping from symbol to structure. + +#ifndef VPC_TIER1_UTLDICT_H_ +#define VPC_TIER1_UTLDICT_H_ + +#include "tier0/dbg.h" +#include "tier1/utlmap.h" + +// Include this because tons of code was implicitly getting utlsymbol or +// utlvector via utldict.h +#include "tier1/utlsymbol.h" + +#include "tier0/memdbgon.h" + +enum EDictCompareType { + k_eDictCompareTypeCaseSensitive = 0, + k_eDictCompareTypeCaseInsensitive = 1, + k_eDictCompareTypeFilenames // Slashes and backslashes count as the same + // character.. +}; + +//----------------------------------------------------------------------------- +// A dictionary mapping from symbol to structure +//----------------------------------------------------------------------------- +#define FOR_EACH_DICT(dictName, iteratorName) \ + for (int iteratorName = dictName.First(); \ + iteratorName != dictName.InvalidIndex(); \ + iteratorName = dictName.Next(iteratorName)) + +// faster iteration, but in an unspecified order +#define FOR_EACH_DICT_FAST(dictName, iteratorName) \ + for (int iteratorName = 0; iteratorName < dictName.MaxElement(); \ + ++iteratorName) \ + if (!dictName.IsValidIndex(iteratorName)) \ + continue; \ + else + +//----------------------------------------------------------------------------- +// A dictionary mapping from symbol to structure +//----------------------------------------------------------------------------- +template +class CUtlDict { + public: + // constructor, destructor + // Left at growSize = 0, the memory will first allocate 1 element and double + // in size at each increment. + CUtlDict(int compareType = k_eDictCompareTypeCaseInsensitive, + int growSize = 0, int initSize = 0); + ~CUtlDict(); + + void EnsureCapacity(int); + + // gets particular elements + T &Element(I i); + const T &Element(I i) const; + T &operator[](I i); + const T &operator[](I i) const; + + // gets element names + char *GetElementName(I i); + char const *GetElementName(I i) const; + + void SetElementName(I i, char const *pName); + + // Number of elements + unsigned int Count() const; + + // Checks if a node is valid and in the tree + bool IsValidIndex(I i) const; + + // Invalid index + static I InvalidIndex(); + + // Insert method (inserts in order) + I Insert(const char *pName, const T &element); + I Insert(const char *pName); + + // Find method + I Find(const char *pName) const; + + // Remove methods + void RemoveAt(I i); + void Remove(const char *pName); + void RemoveAll(); + + // Purge memory + void Purge(); + void PurgeAndDeleteElements(); // Call delete on each element. + + // Iteration methods + I First() const; + I Next(I i) const; + + // Nested typedefs, for code that might need + // to fish out the index type from a given dict + typedef I IndexType_t; + + protected: + typedef CUtlMap DictElementMap_t; + DictElementMap_t m_Elements; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +template +CUtlDict::CUtlDict(int compareType, int growSize, int initSize) + : m_Elements(growSize, initSize) { + if (compareType == k_eDictCompareTypeFilenames) { + m_Elements.SetLessFunc(CaselessStringLessThanIgnoreSlashes); + } else if (compareType == k_eDictCompareTypeCaseInsensitive) { + m_Elements.SetLessFunc(CaselessStringLessThan); + } else { + m_Elements.SetLessFunc(StringLessThan); + } +} + +template +CUtlDict::~CUtlDict() { + Purge(); +} + +template +inline void CUtlDict::EnsureCapacity(int num) { + return m_Elements.EnsureCapacity(num); +} + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- +template +inline T &CUtlDict::Element(I i) { + return m_Elements[i]; +} + +template +inline const T &CUtlDict::Element(I i) const { + return m_Elements[i]; +} + +//----------------------------------------------------------------------------- +// gets element names +//----------------------------------------------------------------------------- +template +inline char *CUtlDict::GetElementName(I i) { + return (char *)m_Elements.Key(i); +} + +template +inline char const *CUtlDict::GetElementName(I i) const { + return m_Elements.Key(i); +} + +template +inline T &CUtlDict::operator[](I i) { + return Element(i); +} + +template +inline const T &CUtlDict::operator[](I i) const { + return Element(i); +} + +template +inline void CUtlDict::SetElementName(I i, char const *pName) { + MEM_ALLOC_CREDIT_CLASS(); + // TODO: This makes a copy of the old element + // TODO: This relies on the rb tree putting the most recently + // removed element at the head of the insert list + free((void *)m_Elements.Key(i)); + m_Elements.Reinsert(_strdup(pName), i); +} + +//----------------------------------------------------------------------------- +// Num elements +//----------------------------------------------------------------------------- +template +inline unsigned int CUtlDict::Count() const { + return m_Elements.Count(); +} + +//----------------------------------------------------------------------------- +// Checks if a node is valid and in the tree +//----------------------------------------------------------------------------- +template +inline bool CUtlDict::IsValidIndex(I i) const { + return m_Elements.IsValidIndex(i); +} + +//----------------------------------------------------------------------------- +// Invalid index +//----------------------------------------------------------------------------- +template +inline I CUtlDict::InvalidIndex() { + return DictElementMap_t::InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Delete a node from the tree +//----------------------------------------------------------------------------- +template +void CUtlDict::RemoveAt(I elem) { + free((void *)m_Elements.Key(elem)); + m_Elements.RemoveAt(elem); +} + +//----------------------------------------------------------------------------- +// remove a node in the tree +//----------------------------------------------------------------------------- +template +void CUtlDict::Remove(const char *search) { + I node = Find(search); + if (node != InvalidIndex()) { + RemoveAt(node); + } +} + +//----------------------------------------------------------------------------- +// Removes all nodes from the tree +//----------------------------------------------------------------------------- +template +void CUtlDict::RemoveAll() { + typename DictElementMap_t::IndexType_t index = m_Elements.FirstInorder(); + while (index != m_Elements.InvalidIndex()) { + const char *pKey = m_Elements.Key(index); + free(const_cast(pKey)); + index = m_Elements.NextInorder(index); + } + + m_Elements.RemoveAll(); +} + +template +void CUtlDict::Purge() { + RemoveAll(); +} + +template +void CUtlDict::PurgeAndDeleteElements() { + // Delete all the elements. + I index = m_Elements.FirstInorder(); + while (index != m_Elements.InvalidIndex()) { + const char *pKey = m_Elements.Key(index); + free(const_cast(pKey)); + delete m_Elements[index]; + index = m_Elements.NextInorder(index); + } + + m_Elements.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// inserts a node into the tree +//----------------------------------------------------------------------------- +template +I CUtlDict::Insert(const char *pName, const T &element) { + MEM_ALLOC_CREDIT_CLASS(); + return m_Elements.Insert(_strdup(pName), element); +} + +template +I CUtlDict::Insert(const char *pName) { + MEM_ALLOC_CREDIT_CLASS(); + return m_Elements.Insert(_strdup(pName)); +} + +//----------------------------------------------------------------------------- +// finds a node in the tree +//----------------------------------------------------------------------------- +template +I CUtlDict::Find(const char *pName) const { + MEM_ALLOC_CREDIT_CLASS(); + if (pName) + return m_Elements.Find(pName); + else + return InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Iteration methods +//----------------------------------------------------------------------------- +template +I CUtlDict::First() const { + return m_Elements.FirstInorder(); +} + +template +I CUtlDict::Next(I i) const { + return m_Elements.NextInorder(i); +} + +#include "tier0/memdbgoff.h" + +#endif // VPC_TIER1_UTLDICT_H_ diff --git a/public/tier1/utlenvelope.h b/public/tier1/utlenvelope.h new file mode 100644 index 0000000..f66a10e --- /dev/null +++ b/public/tier1/utlenvelope.h @@ -0,0 +1,184 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A class to wrap data for transport over a boundary like a thread or +// window. + +#ifndef VPC_TIER1_UTLENVELOPE_H_ +#define VPC_TIER1_UTLENVELOPE_H_ + +#include "tier1/utlstring.h" +#include "tier0/basetypes.h" + +class CUtlDataEnvelope { + public: + CUtlDataEnvelope(const void *pData, int nBytes); + CUtlDataEnvelope(const CUtlDataEnvelope &from); + ~CUtlDataEnvelope(); + + CUtlDataEnvelope &operator=(const CUtlDataEnvelope &from); + + operator void *(); + operator void *() const; + + private: + void Assign(const void *pData, int nBytes); + void Assign(const CUtlDataEnvelope &from); + void Purge(); + + // TODO: switch to a reference counted array? + union { + byte *m_pData; + byte m_data[4]; + }; + int m_nBytes; +}; + +//----------------------------------------------------------------------------- + +template +class CUtlEnvelope : protected CUtlDataEnvelope { + public: + CUtlEnvelope(const T *pData, int nElems = 1); + CUtlEnvelope(const CUtlEnvelope &from); + + CUtlEnvelope &operator=(const CUtlEnvelope &from); + + operator T *(); + operator T *() const; + + operator void *(); + operator void *() const; +}; + +//----------------------------------------------------------------------------- + +template <> +class CUtlEnvelope { + public: + CUtlEnvelope(const char *pData) { m_string = pData; } + + CUtlEnvelope(const CUtlEnvelope &from) { + m_string = from.m_string; + } + + CUtlEnvelope &operator=( + const CUtlEnvelope &from) { + m_string = from.m_string; + return *this; + } + + operator char *() { return (char *)m_string.Get(); } + + operator char *() const { return (char *)m_string.Get(); } + + operator void *() { return (void *)m_string.Get(); } + + operator void *() const { return (void *)m_string.Get(); } + + private: + CUtlString m_string; +}; + +//----------------------------------------------------------------------------- + +#include "tier0/memdbgon.h" + +inline void CUtlDataEnvelope::Assign(const void *pData, int nBytes) { + if (pData) { + m_nBytes = nBytes; + if (m_nBytes > 4) { + m_pData = new byte[nBytes]; + memcpy(m_pData, pData, nBytes); + } else { + memcpy(m_data, pData, nBytes); + } + } else { + m_pData = NULL; + m_nBytes = 0; + } +} + +inline void CUtlDataEnvelope::Assign(const CUtlDataEnvelope &from) { + Assign(from.operator void *(), from.m_nBytes); +} + +inline void CUtlDataEnvelope::Purge() { + if (m_nBytes > 4) delete[] m_pData; + m_nBytes = 0; +} + +inline CUtlDataEnvelope::CUtlDataEnvelope(const void *pData, int nBytes) { + Assign(pData, nBytes); +} + +inline CUtlDataEnvelope::CUtlDataEnvelope(const CUtlDataEnvelope &from) { + Assign(from); +} + +inline CUtlDataEnvelope::~CUtlDataEnvelope() { Purge(); } + +inline CUtlDataEnvelope &CUtlDataEnvelope::operator=( + const CUtlDataEnvelope &from) { + Purge(); + Assign(from); + return *this; +} + +inline CUtlDataEnvelope::operator void *() { + if (!m_nBytes) { + return NULL; + } + + return (m_nBytes > 4) ? m_pData : m_data; +} + +inline CUtlDataEnvelope::operator void *() const { + if (!m_nBytes) { + return NULL; + } + + return (m_nBytes > 4) ? (void *)m_pData : (void *)m_data; +} + +//----------------------------------------------------------------------------- + +template +inline CUtlEnvelope::CUtlEnvelope(const T *pData, int nElems) + : CUtlDataEnvelope(pData, sizeof(T) * nElems) {} + +template +inline CUtlEnvelope::CUtlEnvelope(const CUtlEnvelope &from) + : CUtlDataEnvelope(from) {} + +template +inline CUtlEnvelope &CUtlEnvelope::operator=( + const CUtlEnvelope &from) { + CUtlDataEnvelope::operator=(from); + return *this; +} + +template +inline CUtlEnvelope::operator T *() { + return (T *)CUtlDataEnvelope::operator void *(); +} + +template +inline CUtlEnvelope::operator T *() const { + return (T *)((const_cast *>(this))->operator T *()); +} + +template +inline CUtlEnvelope::operator void *() { + return CUtlDataEnvelope::operator void *(); +} + +template +inline CUtlEnvelope::operator void *() const { + return ((const_cast *>(this))->operator void *()); +} + +//----------------------------------------------------------------------------- + +#include "tier0/memdbgoff.h" + +#endif // VPC_TIER1_UTLENVELOPE_H_ diff --git a/public/tier1/utlfixedmemory.h b/public/tier1/utlfixedmemory.h new file mode 100644 index 0000000..a3e7e9f --- /dev/null +++ b/public/tier1/utlfixedmemory.h @@ -0,0 +1,323 @@ +// Copyright Valve Corporation, All rights reserved. +// +// A growable memory class. + +#ifndef VPC_TIER1_UTLFIXEDMEMORY_H_ +#define VPC_TIER1_UTLFIXEDMEMORY_H_ + +#include "tier0/dbg.h" +#include "tier0/platform.h" + +#include "tier0/memalloc.h" +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- + +#ifdef UTLFIXEDMEMORY_TRACK +#define UTLFIXEDMEMORY_TRACK_ALLOC() \ + MemAlloc_RegisterAllocation("||Sum of all UtlFixedMemory||", 0, \ + NumAllocated() * sizeof(T), \ + NumAllocated() * sizeof(T), 0) +#define UTLFIXEDMEMORY_TRACK_FREE() \ + if (!m_pMemory) \ + ; \ + else \ + MemAlloc_RegisterDeallocation("||Sum of all UtlFixedMemory||", 0, \ + NumAllocated() * sizeof(T), \ + NumAllocated() * sizeof(T), 0) +#else +#define UTLFIXEDMEMORY_TRACK_ALLOC() ((void)0) +#define UTLFIXEDMEMORY_TRACK_FREE() ((void)0) +#endif + +//----------------------------------------------------------------------------- +// The CUtlFixedMemory class: +// A growable memory class that allocates non-sequential blocks, but is indexed +// sequentially +//----------------------------------------------------------------------------- +template +class CUtlFixedMemory { + public: + // constructor, destructor + CUtlFixedMemory(int nGrowSize = 0, int nInitSize = 0); + ~CUtlFixedMemory(); + + // Set the size by which the memory grows + void Init(int nGrowSize = 0, int nInitSize = 0); + + // here to match CUtlMemory, but only used by ResetDbgInfo, so it can just + // return NULL + T* Base() { return NULL; } + const T* Base() const { return NULL; } + + protected: + struct BlockHeader_t; + + public: + class Iterator_t { + public: + Iterator_t(BlockHeader_t* p, intp i) : m_pBlockHeader(p), m_nIndex(i) {} + BlockHeader_t* m_pBlockHeader; + intp m_nIndex; + + bool operator==(const Iterator_t it) const { + return m_pBlockHeader == it.m_pBlockHeader && m_nIndex == it.m_nIndex; + } + bool operator!=(const Iterator_t it) const { + return m_pBlockHeader != it.m_pBlockHeader || m_nIndex != it.m_nIndex; + } + }; + Iterator_t First() const { + return m_pBlocks ? Iterator_t(m_pBlocks, 0) : InvalidIterator(); + } + Iterator_t Next(const Iterator_t& it) const { + Assert(IsValidIterator(it)); + if (!IsValidIterator(it)) return InvalidIterator(); + + BlockHeader_t* RESTRICT pHeader = it.m_pBlockHeader; + if (it.m_nIndex + 1 < pHeader->m_nBlockSize) + return Iterator_t(pHeader, it.m_nIndex + 1); + + return pHeader->m_pNext ? Iterator_t(pHeader->m_pNext, 0) + : InvalidIterator(); + } + intp GetIndex(const Iterator_t& it) const { + Assert(IsValidIterator(it)); + if (!IsValidIterator(it)) return InvalidIndex(); + + return (intp)(HeaderToBlock(it.m_pBlockHeader) + it.m_nIndex); + } + bool IsIdxAfter(intp i, const Iterator_t& it) const { + Assert(IsValidIterator(it)); + if (!IsValidIterator(it)) return false; + + if (IsInBlock(i, it.m_pBlockHeader)) return i > GetIndex(it); + + for (BlockHeader_t* RESTRICT pbh = it.m_pBlockHeader->m_pNext; pbh; + pbh = pbh->m_pNext) { + if (IsInBlock(i, pbh)) return true; + } + return false; + } + bool IsValidIterator(const Iterator_t& it) const { + return it.m_pBlockHeader && it.m_nIndex >= 0 && + it.m_nIndex < it.m_pBlockHeader->m_nBlockSize; + } + Iterator_t InvalidIterator() const { return Iterator_t(NULL, -1); } + + // element access + T& operator[](intp i); + const T& operator[](intp i) const; + T& Element(intp i); + const T& Element(intp i) const; + + // Can we use this index? + bool IsIdxValid(intp i) const; + + // Specify the invalid ('null') index that we'll only return on failure + static const intp INVALID_INDEX = 0; // For use with static_assert + static intp InvalidIndex() { return INVALID_INDEX; } + + // Size + int NumAllocated() const; + int Count() const { return NumAllocated(); } + + // Grows memory by max(num,growsize), and returns the allocation index/ptr + void Grow(int num = 1); + + // Makes sure we've got at least this much memory + void EnsureCapacity(int num); + + // Memory deallocation + void Purge(); + + protected: + // Fast swap - WARNING: Swap invalidates all ptr-based indices!!! + void Swap(CUtlFixedMemory& mem); + + bool IsInBlock(intp i, BlockHeader_t* pBlockHeader) const { + T* p = (T*)i; + const T* p0 = HeaderToBlock(pBlockHeader); + return p >= p0 && p < p0 + pBlockHeader->m_nBlockSize; + } + + struct BlockHeader_t { + BlockHeader_t* m_pNext; + int m_nBlockSize; + }; + + const T* HeaderToBlock(const BlockHeader_t* pHeader) const { + return (T*)(pHeader + 1); + } + const BlockHeader_t* BlockToHeader(const T* pBlock) const { + return (BlockHeader_t*)(pBlock)-1; + } + + BlockHeader_t* m_pBlocks; + int m_nAllocationCount; + int m_nGrowSize; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlFixedMemory::CUtlFixedMemory(int nGrowSize, int nInitAllocationCount) + : m_pBlocks(0), m_nAllocationCount(0), m_nGrowSize(0) { + Init(nGrowSize, nInitAllocationCount); +} + +template +CUtlFixedMemory::~CUtlFixedMemory() { + Purge(); +} + +//----------------------------------------------------------------------------- +// Fast swap - WARNING: Swap invalidates all ptr-based indices!!! +//----------------------------------------------------------------------------- +template +void CUtlFixedMemory::Swap(CUtlFixedMemory& mem) { + V_swap(m_pBlocks, mem.m_pBlocks); + V_swap(m_nAllocationCount, mem.m_nAllocationCount); + V_swap(m_nGrowSize, mem.m_nGrowSize); +} + +//----------------------------------------------------------------------------- +// Set the size by which the memory grows - round up to the next power of 2 +//----------------------------------------------------------------------------- +template +void CUtlFixedMemory::Init(int nGrowSize /* = 0 */, + int nInitSize /* = 0 */) { + Purge(); + + m_nGrowSize = nGrowSize; + + Grow(nInitSize); +} + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template +inline T& CUtlFixedMemory::operator[](intp i) { + Assert(IsIdxValid(i)); + return *(T*)i; +} + +template +inline const T& CUtlFixedMemory::operator[](intp i) const { + Assert(IsIdxValid(i)); + return *(T*)i; +} + +template +inline T& CUtlFixedMemory::Element(intp i) { + Assert(IsIdxValid(i)); + return *(T*)i; +} + +template +inline const T& CUtlFixedMemory::Element(intp i) const { + Assert(IsIdxValid(i)); + return *(T*)i; +} + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- +template +inline int CUtlFixedMemory::NumAllocated() const { + return m_nAllocationCount; +} + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template +inline bool CUtlFixedMemory::IsIdxValid(intp i) const { +#ifdef _DEBUG + for (BlockHeader_t* pbh = m_pBlocks; pbh; pbh = pbh->m_pNext) { + if (IsInBlock(i, pbh)) return true; + } + return false; +#else + return i != InvalidIndex(); +#endif +} + +template +void CUtlFixedMemory::Grow(int num) { + if (num <= 0) return; + + int nBlockSize = m_nGrowSize; + if (nBlockSize == 0) { + if (m_nAllocationCount) { + nBlockSize = m_nAllocationCount; + } else { + // Compute an allocation which is at least as big as a cache line... + nBlockSize = (31 + sizeof(T)) / sizeof(T); + Assert(nBlockSize); + } + } + if (nBlockSize < num) { + int n = (num + nBlockSize - 1) / nBlockSize; + Assert(n * nBlockSize >= num); + Assert((n - 1) * nBlockSize < num); + nBlockSize *= n; + } + m_nAllocationCount += nBlockSize; + + MEM_ALLOC_CREDIT_CLASS(); + BlockHeader_t* RESTRICT pBlockHeader = + (BlockHeader_t*)malloc(sizeof(BlockHeader_t) + nBlockSize * sizeof(T)); + if (!pBlockHeader) { + Error("CUtlFixedMemory overflow!\n"); + return; + } + pBlockHeader->m_pNext = NULL; + pBlockHeader->m_nBlockSize = nBlockSize; + + if (!m_pBlocks) { + m_pBlocks = pBlockHeader; + } else { +#if 1 // IsIdxAfter assumes that newly allocated blocks are at the end + BlockHeader_t* RESTRICT pbh = m_pBlocks; + while (pbh->m_pNext) { + pbh = pbh->m_pNext; + } + pbh->m_pNext = pBlockHeader; +#else + pBlockHeader = m_pBlocks; + pBlockHeader->m_pNext = m_pBlocks; +#endif + } +} + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template +inline void CUtlFixedMemory::EnsureCapacity(int num) { + Grow(num - NumAllocated()); +} + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template +void CUtlFixedMemory::Purge() { + if (!m_pBlocks) return; + + for (BlockHeader_t* pbh = m_pBlocks; pbh;) { + BlockHeader_t* pFree = pbh; + pbh = pbh->m_pNext; + free(pFree); + } + m_pBlocks = NULL; + m_nAllocationCount = 0; +} + +#include "tier0/memdbgoff.h" + +#endif // VPC_TIER1_UTLFIXEDMEMORY_H_ diff --git a/public/tier1/utlgraph.h b/public/tier1/utlgraph.h new file mode 100644 index 0000000..1ca9eb4 --- /dev/null +++ b/public/tier1/utlgraph.h @@ -0,0 +1,591 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_UTLGRAPH_H_ +#define VPC_TIER1_UTLGRAPH_H_ + +#include + +#include "tier1/utlmap.h" +#include "tier1/utlvector.h" + +//------------------------------------- + +//----------------------------------------------------------------------------- +// A Graph class +// +// Nodes must have a unique Node ID. +// +// Edges are unidirectional, specified from the beginning node. +// +//----------------------------------------------------------------------------- + +template +class CUtlGraphVisitor; + +template +class CUtlGraph { + public: + typedef int I; + typedef I IndexType_t; + typedef T NodeID_t; + typedef C CostType_t; + + typedef CUtlGraphVisitor Visitor_t; + + struct Edge_t { + IndexType_t m_DestinationNode; + CostType_t m_EdgeCost; + + Edge_t(IndexType_t i = 0) { + m_DestinationNode = i; + m_EdgeCost = 0; + } + + bool operator==(const Edge_t &that) const { + return m_DestinationNode == that.m_DestinationNode; + } + + static int SortFn(const Edge_t *plhs, const Edge_t *prhs) { + if (plhs->m_EdgeCost < prhs->m_EdgeCost) + return -1; + else if (plhs->m_EdgeCost > prhs->m_EdgeCost) + return 1; + else + return 0; + } + }; + + typedef CUtlVector vecEdges_t; + + // constructor, destructor + CUtlGraph(); + ~CUtlGraph(); + + // Add an edge + bool AddEdge(T SourceNode, T DestNode, C nCost); + + // Remove an edge + bool RemoveEdge(T SourceNode, T DestNode); + + // gets particular elements + T &Element(I i); + T const &Element(I i) const; + T &operator[](I i); + T const &operator[](I i) const; + + // Find a node + I Find(T Node) { return m_Nodes.Find(Node); } + I Find(T Node) const { return m_Nodes.Find(Node); } + + // Num elements + unsigned int Count() const { return m_Nodes.Count(); } + + // Max "size" of the vector + I MaxElement() const { return m_Nodes.MaxElement(); } + + // Checks if a node is valid and in the graph + bool IsValidIndex(I i) const { return m_Nodes.IsValidIndex(i); } + + // Checks if the graph as a whole is valid + bool IsValid() const { return m_Nodes.IsValid(); } + + // Invalid index + static I InvalidIndex() { + return CUtlMap::InvalidIndex(); + } + + // Remove methods + void RemoveAt(I i); + void RemoveAll(); + + // Makes sure we have enough memory allocated to store a requested # of + // elements + void EnsureCapacity(int num); + + // Create Path Matrix once you've added all nodes and edges + void CreatePathMatrix(); + + // For Visitor classes + vecEdges_t *GetEdges(I i); + + // shortest path costs + vecEdges_t *GetPathCosts(I i); + +#ifdef DBGFLAG_VALIDATE + void Validate(CValidator &validator, const char *pchName); +#endif // DBGFLAG_VALIDATE + + protected: + struct Node_t { + vecEdges_t *m_pvecEdges; + vecEdges_t *m_pvecPaths; + + Node_t() { + m_pvecEdges = NULL; + m_pvecPaths = NULL; + } + }; + + CUtlMap m_Nodes; +}; + +//----------------------------------------------------------------------------- +// A Graph "visitor" class +// +// From the specified beginning point, visits each node in an expanding radius +// +//----------------------------------------------------------------------------- +template +class CUtlGraphVisitor { + public: + CUtlGraphVisitor(CUtlGraph &graph); + + bool Begin(T StartNode); + bool Advance(); + + T CurrentNode(); + C AccumulatedCost(); + int CurrentRadius(); + + private: + typedef typename CUtlGraph::IndexType_t IndexType_t; + typedef typename CUtlGraph::Edge_t Edge_t; + typedef CUtlVector vecEdges_t; + + CUtlGraph &m_Graph; + + vecEdges_t m_vecVisitQueue; + int m_iVisiting; + int m_nCurrentRadius; + + vecEdges_t m_vecFringeQueue; + + CUtlVector m_vecNodesVisited; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +inline CUtlGraph::CUtlGraph() { + SetDefLessFunc(m_Nodes); +} + +template +inline CUtlGraph::~CUtlGraph() { + RemoveAll(); +} + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- + +template +inline T &CUtlGraph::Element(I i) { + return m_Nodes.Key(i); +} + +template +inline T const &CUtlGraph::Element(I i) const { + return m_Nodes.Key(i); +} + +template +inline T &CUtlGraph::operator[](I i) { + return Element(i); +} + +template +inline T const &CUtlGraph::operator[](I i) const { + return Element(i); +} + +//----------------------------------------------------------------------------- +// +// various accessors +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Removes all nodes from the tree +//----------------------------------------------------------------------------- + +template +void CUtlGraph::RemoveAll() { + for (unsigned short iNode = 0; + (m_Nodes).IsUtlMap && iNode < (m_Nodes).MaxElement(); ++iNode) + if (!(m_Nodes).IsValidIndex(iNode)) + continue; + else { + delete m_Nodes[iNode].m_pvecEdges; + delete m_Nodes[iNode].m_pvecPaths; + } + + m_Nodes.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- +template +void CUtlGraph::EnsureCapacity(int num) { + m_Nodes.EnsureCapacity(num); +} + +//----------------------------------------------------------------------------- +// Add an edge +//----------------------------------------------------------------------------- +template +bool CUtlGraph::AddEdge(T SourceNode, T DestNode, C nCost) { + auto iSrcNode = m_Nodes.Find(SourceNode); + if (!m_Nodes.IsValidIndex(iSrcNode)) { + Node_t Node; + Node.m_pvecEdges = new vecEdges_t(); + Node.m_pvecPaths = new vecEdges_t(); + iSrcNode = m_Nodes.Insert(SourceNode, Node); + } + + auto iDstNode = m_Nodes.Find(DestNode); + if (!m_Nodes.IsValidIndex(iDstNode)) { + Node_t Node; + Node.m_pvecEdges = new vecEdges_t(); + Node.m_pvecPaths = new vecEdges_t(); + iDstNode = m_Nodes.Insert(DestNode, Node); + } + + vecEdges_t &vecEdges = *m_Nodes[iSrcNode].m_pvecEdges; + +#ifdef _DEBUG + FOR_EACH_VEC(vecEdges, iEdge) { + if (vecEdges[iEdge].m_DestinationNode == iDstNode) return false; + } +#endif + + Edge_t newEdge; + newEdge.m_DestinationNode = iDstNode; + newEdge.m_EdgeCost = nCost; + + vecEdges[vecEdges.AddToTail()] = newEdge; + + return true; +} + +//----------------------------------------------------------------------------- +// Remove an edge +//----------------------------------------------------------------------------- +template +bool CUtlGraph::RemoveEdge(T SourceNode, T DestNode) { + I iSrcNode = m_Nodes.Find(SourceNode); + if (!m_Nodes.IsValidIndex(iSrcNode)) return false; + + I iDstNode = m_Nodes.Find(DestNode); + if (!m_Nodes.IsValidIndex(iDstNode)) return false; + + vecEdges_t &vecEdges = *m_Nodes[iSrcNode].m_pvecEdges; + + FOR_EACH_VEC(vecEdges, iEdge) { + if (vecEdges[iEdge].m_DestinationNode == iDstNode) { + // could use FastRemove, but nodes won't have that + // many edges, and the elements are small, and + // preserving the original ordering is nice + vecEdges.Remove(iEdge); + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Get all of a Node's edges +//----------------------------------------------------------------------------- +template +typename CUtlGraph::vecEdges_t *CUtlGraph::GetEdges(I i) { + return m_Nodes[i].m_pvecEdges; +} + +//----------------------------------------------------------------------------- +// Get all of a Node's edges +//----------------------------------------------------------------------------- +template +typename CUtlGraph::vecEdges_t *CUtlGraph::GetPathCosts(I i) { + return m_Nodes[i].m_pvecPaths; +} + +//----------------------------------------------------------------------------- +// Data and memory validation +//----------------------------------------------------------------------------- +#ifdef DBGFLAG_VALIDATE +template +void CUtlGraph::Validate(CValidator &validator, const char *pchName) { +#ifdef _WIN32 + validator.Push(typeid(*this).raw_name(), this, pchName); +#else + validator.Push(typeid(*this).name(), this, pchName); +#endif + + ValidateObj(m_Nodes); + + FOR_EACH_MAP_FAST(m_Nodes, iNode) { + validator.ClaimMemory(m_Nodes[iNode].m_pvecEdges); + ValidateObj(*m_Nodes[iNode].m_pvecEdges); + validator.ClaimMemory(m_Nodes[iNode].m_pvecPaths); + ValidateObj(*m_Nodes[iNode].m_pvecPaths); + } + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + +//----------------------------------------------------------------------------- +// Get all of a Node's edges +//----------------------------------------------------------------------------- +template +void CUtlGraph::CreatePathMatrix() { + int n = MaxElement(); + + // Notes + // Because CUtlMap stores its nodes in essentially a vector, + // we know that we can use its indices in our own path matrix + // vectors safely (they will be numbers in the range (0,N) where + // N is largest number of nodes ever present in the graph). + // + // This lets us very quickly access previous best-path estimates + // by indexing into a node's vecPaths directly. + // + // When we are all done, we can then compact the vector, removing + // "null" paths, and then sorting by cost. + + // Initialize matrix with all edges + FOR_EACH_MAP_FAST(m_Nodes, iNode) { + vecEdges_t &vecEdges = *m_Nodes.Element(iNode).m_pvecEdges; + vecEdges_t &vecPaths = *m_Nodes.Element(iNode).m_pvecPaths; + + vecPaths.RemoveAll(); + vecPaths.AddMultipleToTail(n); + FOR_EACH_VEC(vecPaths, iPath) { + vecPaths[iPath].m_DestinationNode = InvalidIndex(); + } + + // Path to self + vecPaths[iNode].m_DestinationNode = iNode; + // zero cost to self + vecPaths[iNode].m_EdgeCost = 0; + + FOR_EACH_VEC(vecEdges, iEdge) { + // Path to a neighbor node - we know exactly what the cost is + Edge_t &edge = vecEdges[iEdge]; + vecPaths[edge.m_DestinationNode].m_DestinationNode = + edge.m_DestinationNode; + vecPaths[edge.m_DestinationNode].m_EdgeCost = edge.m_EdgeCost; + } + } + + // Floyd-Warshall + // for k:= 0 to n-1 + // for each (i,j) in (0..n-1) + // path[i][j] = min( path[i][j], path[i][k]+path[k][j] ); + for (int k = 0; k < n; ++k) { + if (!m_Nodes.IsValidIndex(k)) continue; + + // All current known paths from K + vecEdges_t &destMapFromK = *m_Nodes[k].m_pvecPaths; + + for (int i = 0; i < n; ++i) { + if (!m_Nodes.IsValidIndex(i)) continue; + + // All current known paths from J + vecEdges_t &destMapFromI = *m_Nodes[i].m_pvecPaths; + + // Path from I to K? + int iFromIToK = k; + bool bFromIToK = + destMapFromI[iFromIToK].m_DestinationNode != InvalidIndex(); + CostType_t cIToK = + (bFromIToK) ? destMapFromI[iFromIToK].m_EdgeCost : INT_MAX; + + for (int j = 0; j < n; ++j) { + if (!m_Nodes.IsValidIndex(j)) continue; + + // Path from I to J already? + int iFromIToJ = j; + bool bFromIToJ = + destMapFromI[iFromIToJ].m_DestinationNode != InvalidIndex(); + CostType_t cIToJ = + (bFromIToJ) ? destMapFromI[iFromIToJ].m_EdgeCost : INT_MAX; + + // Path from K to J? + int iFromKToJ = j; + bool bFromKToJ = + destMapFromK[iFromKToJ].m_DestinationNode != InvalidIndex(); + CostType_t cKToJ = + (bFromKToJ) ? destMapFromK[iFromKToJ].m_EdgeCost : INT_MAX; + + // Is the new path valid? + bool bNewPathFound = (bFromIToK && bFromKToJ); + + if (bNewPathFound) { + if (bFromIToJ) { + // Pick min of previous best and current path + destMapFromI[iFromIToJ].m_EdgeCost = min(cIToJ, cIToK + cKToJ); + } else { + // Current path is the first, hence the best so far + destMapFromI[iFromIToJ].m_DestinationNode = iFromIToJ; + destMapFromI[iFromIToJ].m_EdgeCost = cIToK + cKToJ; + } + } + } + } + } + + // Clean up and sort the paths + FOR_EACH_MAP_FAST(m_Nodes, iNode) { + vecEdges_t &vecPaths = *m_Nodes.Element(iNode).m_pvecPaths; + FOR_EACH_VEC(vecPaths, iPath) { + Edge_t &edge = vecPaths[iPath]; + if (edge.m_DestinationNode == InvalidIndex()) { + // No path to this destination was found. + // Remove this entry from the vector. + vecPaths.FastRemove(iPath); + --iPath; // adjust for the removal + } + } + + // Sort the vector by cost, given that it + // is likely consumers will want to + // iterate destinations in that order. + vecPaths.Sort(Edge_t::SortFn); + } +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +template +CUtlGraphVisitor::CUtlGraphVisitor(CUtlGraph &graph) + : m_Graph(graph) { + m_iVisiting = 0; + m_nCurrentRadius = 0; +} + +//----------------------------------------------------------------------------- +// Begin visiting the nodes in the graph. Returns false if the start node +// does not exist +//----------------------------------------------------------------------------- +template +bool CUtlGraphVisitor::Begin(T StartNode) { + m_vecVisitQueue.RemoveAll(); + m_vecFringeQueue.RemoveAll(); + m_vecNodesVisited.RemoveAll(); + m_iVisiting = 0; + m_nCurrentRadius = 0; + + IndexType_t iStartNode = m_Graph.Find(StartNode); + + if (!m_Graph.IsValidIndex(iStartNode)) return false; + + vecEdges_t *pvecEdges = m_Graph.GetEdges(iStartNode); + + Edge_t edge; + edge.m_DestinationNode = iStartNode; + edge.m_EdgeCost = 0; + + m_vecVisitQueue[m_vecVisitQueue.AddToTail()] = edge; + + m_vecNodesVisited[m_vecNodesVisited.AddToTail()] = iStartNode; + + m_vecFringeQueue = *pvecEdges; + + // cells actually get marked as "visited" as soon as we put + // them in the fringe queue, so we don't put them in the *next* + // fringe queue (we build the fringe queue before we actually visit + // the nodes in the new visit queue). + FOR_EACH_VEC(m_vecFringeQueue, iFringe) { + m_vecNodesVisited[m_vecNodesVisited.AddToTail()] = + m_vecFringeQueue[iFringe].m_DestinationNode; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Advance to the next node. Returns false when all nodes have been visited +//----------------------------------------------------------------------------- +template +bool CUtlGraphVisitor::Advance() { + m_iVisiting++; + + // Is the VisitQueue empty? move outward one radius if so + + if (m_iVisiting >= m_vecVisitQueue.Count()) { + m_nCurrentRadius++; + m_iVisiting = 0; + m_vecVisitQueue = m_vecFringeQueue; + m_vecFringeQueue.RemoveAll(); + + if (!m_vecVisitQueue.Count()) return false; + + // create new fringe queue + FOR_EACH_VEC(m_vecVisitQueue, iNode) { + Edge_t &node = m_vecVisitQueue[iNode]; + vecEdges_t &vecEdges = *m_Graph.GetEdges(node.m_DestinationNode); + FOR_EACH_VEC(vecEdges, iEdge) { + Edge_t &edge = vecEdges[iEdge]; + if (m_vecNodesVisited.InvalidIndex() == + m_vecNodesVisited.Find(edge.m_DestinationNode)) { + m_vecNodesVisited[m_vecNodesVisited.AddToTail()] = + edge.m_DestinationNode; + + intp iNewFringeNode = m_vecFringeQueue.AddToTail(); + m_vecFringeQueue[iNewFringeNode] = edge; + // Accumulate the cost to get to the current point + m_vecFringeQueue[iNewFringeNode].m_EdgeCost += node.m_EdgeCost; + } + } + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Get the current node in the visit sequence +//----------------------------------------------------------------------------- +template +T CUtlGraphVisitor::CurrentNode() { + if (m_iVisiting >= m_vecVisitQueue.Count()) { + AssertMsg(false, "Visitor invalid"); + return T(); + } + + return m_Graph[m_vecVisitQueue[m_iVisiting].m_DestinationNode]; +} + +//----------------------------------------------------------------------------- +// Get the accumulated cost to traverse the graph to the current node +//----------------------------------------------------------------------------- +template +C CUtlGraphVisitor::AccumulatedCost() { + if (m_iVisiting >= m_vecVisitQueue.Count()) { + AssertMsg(false, "Visitor invalid"); + return C(); + } + + return m_vecVisitQueue[m_iVisiting].m_EdgeCost; +} + +//----------------------------------------------------------------------------- +// Get the current radius from the start point to this node +//----------------------------------------------------------------------------- +template +int CUtlGraphVisitor::CurrentRadius() { + if (m_iVisiting >= m_vecVisitQueue.Count()) { + AssertMsg(false, "Visitor invalid"); + return 0; + } + + return m_nCurrentRadius; +} + +#endif // VPC_TIER1_UTLGRAPH_H_ diff --git a/public/tier1/utlhash.h b/public/tier1/utlhash.h new file mode 100644 index 0000000..b7f641b --- /dev/null +++ b/public/tier1/utlhash.h @@ -0,0 +1,1231 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_UTLHASH_H_ +#define VPC_TIER1_UTLHASH_H_ + +#include + +#include "utlmemory.h" +#include "utlvector.h" +#include "utllinkedlist.h" +#include "utllinkedlist.h" +#include "commonmacros.h" +#include "generichash.h" + +typedef unsigned UtlHashHandle_t; + +template +class CUtlHash { + public: + // compare and key functions - implemented by the + typedef C CompareFunc_t; + typedef K KeyFunc_t; + + // constructor/deconstructor + CUtlHash(int bucketCount = 0, int growCount = 0, int initCount = 0, + CompareFunc_t compareFunc = 0, KeyFunc_t keyFunc = 0); + ~CUtlHash(); + + // invalid handle + static UtlHashHandle_t InvalidHandle(void) { return (UtlHashHandle_t)~0; } + bool IsValidHandle(UtlHashHandle_t handle) const; + + // size + int Count(void) const; + + // memory + void Purge(void); + + // insertion methods + UtlHashHandle_t Insert(Data const &src); + UtlHashHandle_t Insert(Data const &src, bool *pDidInsert); + UtlHashHandle_t AllocEntryFromKey(Data const &src); + + // removal methods + void Remove(UtlHashHandle_t handle); + void RemoveAll(); + + // retrieval methods + UtlHashHandle_t Find(Data const &src) const; + + Data &Element(UtlHashHandle_t handle); + Data const &Element(UtlHashHandle_t handle) const; + Data &operator[](UtlHashHandle_t handle); + Data const &operator[](UtlHashHandle_t handle) const; + + UtlHashHandle_t GetFirstHandle() const; + UtlHashHandle_t GetNextHandle(UtlHashHandle_t h) const; + + // debugging!! + void Log(const char *filename); + void Dump(); + + protected: + int GetBucketIndex(UtlHashHandle_t handle) const; + int GetKeyDataIndex(UtlHashHandle_t handle) const; + UtlHashHandle_t BuildHandle(int ndxBucket, int ndxKeyData) const; + + bool DoFind(Data const &src, unsigned int *pBucket, int *pIndex) const; + + protected: + // handle upper 16 bits = bucket index (bucket heads) + // handle lower 16 bits = key index (bucket list) + typedef CUtlVector HashBucketList_t; + CUtlVector m_Buckets; + + CompareFunc_t + m_CompareFunc; // function used to handle unique compares on data + KeyFunc_t m_KeyFunc; // function used to generate the key value + + bool m_bPowerOfTwo; // if the bucket value is a power of two, + unsigned int m_ModMask; // use the mod mask to "mod" +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +CUtlHash::CUtlHash(int bucketCount, int growCount, int initCount, + CompareFunc_t compareFunc, KeyFunc_t keyFunc) + : m_CompareFunc(compareFunc), m_KeyFunc(keyFunc) { + bucketCount = MIN(bucketCount, 65536); + m_Buckets.SetSize(bucketCount); + for (int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++) { + m_Buckets[ndxBucket].SetSize(initCount); + m_Buckets[ndxBucket].SetGrowSize(growCount); + } + + // check to see if the bucket count is a power of 2 and set up + // optimizations appropriately + m_bPowerOfTwo = IsPowerOfTwo(bucketCount); + m_ModMask = m_bPowerOfTwo ? (bucketCount - 1) : 0; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +CUtlHash::~CUtlHash() { + Purge(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CUtlHash::IsValidHandle(UtlHashHandle_t handle) const { + int ndxBucket = GetBucketIndex(handle); + int ndxKeyData = GetKeyDataIndex(handle); + + // ndxBucket and ndxKeyData can't possibly be less than zero -- take a + // look at the definition of the Get..Index functions for why. However, + // if you override those functions, you will need to override this one + // as well. + if (/*( ndxBucket >= 0 ) && */ (ndxBucket < m_Buckets.Count())) { + if (/*( ndxKeyData >= 0 ) && */ (ndxKeyData < m_Buckets[ndxBucket].Count())) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline int CUtlHash::Count(void) const { + int count = 0; + + int bucketCount = m_Buckets.Count(); + for (int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++) { + count += m_Buckets[ndxBucket].Count(); + } + + return count; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline int CUtlHash::GetBucketIndex(UtlHashHandle_t handle) const { + return (((handle >> 16) & 0x0000ffff)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline int CUtlHash::GetKeyDataIndex(UtlHashHandle_t handle) const { + return (handle & 0x0000ffff); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::BuildHandle(int ndxBucket, + int ndxKeyData) const { + Assert((ndxBucket >= 0) && (ndxBucket < 65536)); + Assert((ndxKeyData >= 0) && (ndxKeyData < 65536)); + + UtlHashHandle_t handle = ndxKeyData; + handle |= (ndxBucket << 16); + + return handle; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Purge(void) { + intp bucketCount = m_Buckets.Count(); + for (intp ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++) { + m_Buckets[ndxBucket].Purge(); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CUtlHash::DoFind(Data const &src, unsigned int *pBucket, + int *pIndex) const { + // generate the data "key" + unsigned int key = m_KeyFunc(src); + + // hash the "key" - get the correct hash table "bucket" + unsigned int ndxBucket; + if (m_bPowerOfTwo) { + *pBucket = ndxBucket = (key & m_ModMask); + } else { + intp bucketCount = m_Buckets.Count(); + *pBucket = ndxBucket = key % bucketCount; + } + + int ndxKeyData = 0; + const CUtlVector &bucket = m_Buckets[ndxBucket]; + intp keyDataCount = bucket.Count(); + for (ndxKeyData = 0; ndxKeyData < keyDataCount; ndxKeyData++) { + if (m_CompareFunc(bucket.Element(ndxKeyData), src)) break; + } + + if (ndxKeyData == keyDataCount) return false; + + *pIndex = ndxKeyData; + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::Find(Data const &src) const { + unsigned int ndxBucket; + int ndxKeyData = 0; + + if (DoFind(src, &ndxBucket, &ndxKeyData)) { + return (BuildHandle(ndxBucket, ndxKeyData)); + } + return (InvalidHandle()); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::Insert(Data const &src) { + unsigned int ndxBucket; + int ndxKeyData = 0; + + if (DoFind(src, &ndxBucket, &ndxKeyData)) { + return (BuildHandle(ndxBucket, ndxKeyData)); + } + + ndxKeyData = m_Buckets[ndxBucket].AddToTail(src); + + return (BuildHandle(ndxBucket, ndxKeyData)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::Insert(Data const &src, + bool *pDidInsert) { + unsigned int ndxBucket; + int ndxKeyData = 0; + + if (DoFind(src, &ndxBucket, &ndxKeyData)) { + *pDidInsert = false; + return (BuildHandle(ndxBucket, ndxKeyData)); + } + + *pDidInsert = true; + ndxKeyData = m_Buckets[ndxBucket].AddToTail(src); + + return (BuildHandle(ndxBucket, ndxKeyData)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::AllocEntryFromKey( + Data const &src) { + unsigned int ndxBucket; + int ndxKeyData = 0; + + if (DoFind(src, &ndxBucket, &ndxKeyData)) { + return (BuildHandle(ndxBucket, ndxKeyData)); + } + + ndxKeyData = m_Buckets[ndxBucket].AddToTail(); + + return (BuildHandle(ndxBucket, ndxKeyData)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Remove(UtlHashHandle_t handle) { + Assert(IsValidHandle(handle)); + + // check to see if the bucket exists + int ndxBucket = GetBucketIndex(handle); + int ndxKeyData = GetKeyDataIndex(handle); + + if (m_Buckets[ndxBucket].IsValidIndex(ndxKeyData)) { + m_Buckets[ndxBucket].FastRemove(ndxKeyData); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::RemoveAll() { + int bucketCount = m_Buckets.Count(); + for (int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++) { + m_Buckets[ndxBucket].RemoveAll(); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data &CUtlHash::Element(UtlHashHandle_t handle) { + int ndxBucket = GetBucketIndex(handle); + int ndxKeyData = GetKeyDataIndex(handle); + + return (m_Buckets[ndxBucket].Element(ndxKeyData)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHash::Element(UtlHashHandle_t handle) const { + int ndxBucket = GetBucketIndex(handle); + int ndxKeyData = GetKeyDataIndex(handle); + + return (m_Buckets[ndxBucket].Element(ndxKeyData)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data &CUtlHash::operator[](UtlHashHandle_t handle) { + int ndxBucket = GetBucketIndex(handle); + int ndxKeyData = GetKeyDataIndex(handle); + + return (m_Buckets[ndxBucket].Element(ndxKeyData)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHash::operator[]( + UtlHashHandle_t handle) const { + int ndxBucket = GetBucketIndex(handle); + int ndxKeyData = GetKeyDataIndex(handle); + + return (m_Buckets[ndxBucket].Element(ndxKeyData)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashHandle_t CUtlHash::GetFirstHandle() const { + return GetNextHandle((UtlHashHandle_t)-1); +} + +template +inline UtlHashHandle_t CUtlHash::GetNextHandle( + UtlHashHandle_t handle) const { + ++handle; // start at the first possible handle after the one given + + int bi = GetBucketIndex(handle); + int ki = GetKeyDataIndex(handle); + + int nBuckets = m_Buckets.Count(); + for (; bi < nBuckets; ++bi) { + if (ki < m_Buckets[bi].Count()) return BuildHandle(bi, ki); + + ki = 0; + } + + return InvalidHandle(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Log(const char *filename) { + FILE *pDebugFp; + pDebugFp = fopen(filename, "w"); + if (!pDebugFp) return; + + int maxBucketSize = 0; + int numBucketsEmpty = 0; + + int bucketCount = m_Buckets.Count(); + fprintf(pDebugFp, "\n%d Buckets\n", bucketCount); + + for (int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++) { + int count = m_Buckets[ndxBucket].Count(); + + if (count > maxBucketSize) { + maxBucketSize = count; + } + if (count == 0) numBucketsEmpty++; + + fprintf(pDebugFp, "Bucket %d: %d\n", ndxBucket, count); + } + + fprintf(pDebugFp, "\nBucketHeads Used: %d\n", bucketCount - numBucketsEmpty); + fprintf(pDebugFp, "Max Bucket Size: %d\n", maxBucketSize); + + fclose(pDebugFp); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CUtlHash::Dump() { + int maxBucketSize = 0; + int numBucketsEmpty = 0; + + int bucketCount = m_Buckets.Count(); + Msg("\n%d Buckets\n", bucketCount); + + for (int ndxBucket = 0; ndxBucket < bucketCount; ndxBucket++) { + int count = m_Buckets[ndxBucket].Count(); + + if (count > maxBucketSize) { + maxBucketSize = count; + } + if (count == 0) numBucketsEmpty++; + + Msg("Bucket %d: %d\n", ndxBucket, count); + } + + Msg("\nBucketHeads Used: %d\n", bucketCount - numBucketsEmpty); + Msg("Max Bucket Size: %d\n", maxBucketSize); +} + +//============================================================================= +// +// Fast Hash +// +// Number of buckets must be a power of 2. +// Key must be 32-bits (unsigned int). +// +typedef int UtlHashFastHandle_t; + +#define UTLHASH_POOL_SCALAR 2 + +class CUtlHashFastNoHash { + public: + static int Hash(int key, int bucketMask) { return (key & bucketMask); } +}; + +class CUtlHashFastGenericHash { + public: + static int Hash(int key, int bucketMask) { + return (HashIntConventional(key) & bucketMask); + } +}; + +template +class CUtlHashFast { + public: + // Constructor/Deconstructor. + CUtlHashFast(); + ~CUtlHashFast(); + + // Memory. + void Purge(void); + + // Invalid handle. + static UtlHashFastHandle_t InvalidHandle(void) { + return (UtlHashFastHandle_t)~0; + } + inline bool IsValidHandle(UtlHashFastHandle_t hHash) const; + + // Initialize. + bool Init(int nBucketCount); + + // Size not available; count is meaningless for multilists. + // int Count( void ) const; + + // Insertion. + UtlHashFastHandle_t Insert(unsigned int uiKey, const Data &data); + UtlHashFastHandle_t FastInsert(unsigned int uiKey, const Data &data); + + // Removal. + void Remove(UtlHashFastHandle_t hHash); + void RemoveAll(void); + + // Retrieval. + UtlHashFastHandle_t Find(unsigned int uiKey) const; + + Data &Element(UtlHashFastHandle_t hHash); + Data const &Element(UtlHashFastHandle_t hHash) const; + Data &operator[](UtlHashFastHandle_t hHash); + Data const &operator[](UtlHashFastHandle_t hHash) const; + + // Iteration + struct UtlHashFastIterator_t { + int bucket; + UtlHashFastHandle_t handle; + + UtlHashFastIterator_t(int _bucket, const UtlHashFastHandle_t &_handle) + : bucket(_bucket), handle(_handle){}; + // inline operator UtlHashFastHandle_t() const { return handle; }; + }; + inline UtlHashFastIterator_t First() const; + inline UtlHashFastIterator_t Next(const UtlHashFastIterator_t &hHash) const; + inline bool IsValidIterator(const UtlHashFastIterator_t &iter) const; + inline Data &operator[](const UtlHashFastIterator_t &iter) { + return (*this)[iter.handle]; + } + inline Data const &operator[](const UtlHashFastIterator_t &iter) const { + return (*this)[iter.handle]; + } + + // protected: + + // Templatized for memory tracking purposes + template + struct HashFastData_t_ { + unsigned int m_uiKey; + HashData m_Data; + }; + + typedef HashFastData_t_ HashFastData_t; + + unsigned int m_uiBucketMask; + CUtlVector m_aBuckets; + CUtlFixedLinkedList m_aDataPool; +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +template +CUtlHashFast::CUtlHashFast() : m_uiBucketMask(0) { + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Deconstructor +//----------------------------------------------------------------------------- +template +CUtlHashFast::~CUtlHashFast() { + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destroy dynamically allocated hash data. +//----------------------------------------------------------------------------- +template +inline void CUtlHashFast::Purge(void) { + m_aBuckets.Purge(); + m_aDataPool.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize the hash - set bucket count and hash grow amount. +//----------------------------------------------------------------------------- +template +bool CUtlHashFast::Init(int nBucketCount) { + // Verify the bucket count is power of 2. + if (!IsPowerOfTwo(nBucketCount)) return false; + + // Set the bucket size. + m_aBuckets.SetSize(nBucketCount); + for (int iBucket = 0; iBucket < nBucketCount; ++iBucket) { + m_aBuckets[iBucket] = m_aDataPool.InvalidIndex(); + } + + // Set the mod mask. + m_uiBucketMask = nBucketCount - 1; + + // Calculate the grow size. + int nGrowSize = UTLHASH_POOL_SCALAR * nBucketCount; + m_aDataPool.SetGrowSize(nGrowSize); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of elements in the hash. +// Not available because count isn't accurately maintained for +// multilists. +//----------------------------------------------------------------------------- +/* +template inline int +CUtlHashFast::Count( void ) const +{ + return m_aDataPool.Count(); +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), with +// a check to see if the element already exists within the tree. +//----------------------------------------------------------------------------- +template +inline UtlHashFastHandle_t CUtlHashFast::Insert( + unsigned int uiKey, const Data &data) { + // Check to see if that key already exists in the buckets (should be unique). + UtlHashFastHandle_t hHash = Find(uiKey); + if (hHash != InvalidHandle()) return hHash; + + return FastInsert(uiKey, data); +} + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), +// without a check to see if the element already exists within the +// tree. +//----------------------------------------------------------------------------- +template +inline UtlHashFastHandle_t CUtlHashFast::FastInsert( + unsigned int uiKey, const Data &data) { + // Get a new element from the pool. + int iHashData = m_aDataPool.Alloc(true); + HashFastData_t *pHashData = &m_aDataPool[iHashData]; + + // Add data to new element. + pHashData->m_uiKey = uiKey; + pHashData->m_Data = data; + + // Link element. + int iBucket = HashFuncs::Hash(uiKey, m_uiBucketMask); + m_aDataPool.LinkBefore(m_aBuckets[iBucket], iHashData); + m_aBuckets[iBucket] = iHashData; + + return iHashData; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a given element from the hash. +//----------------------------------------------------------------------------- +template +inline void CUtlHashFast::Remove(UtlHashFastHandle_t hHash) { + int iBucket = HashFuncs::Hash(m_aDataPool[hHash].m_uiKey, m_uiBucketMask); + if (m_aBuckets[iBucket] == hHash) { + // It is a bucket head. + m_aBuckets[iBucket] = m_aDataPool.Next(hHash); + } else { + // Not a bucket head. + m_aDataPool.Unlink(hHash); + } + + // Remove the element. + m_aDataPool.Remove(hHash); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all elements from the hash +//----------------------------------------------------------------------------- +template +inline void CUtlHashFast::RemoveAll(void) { + m_aBuckets.RemoveAll(); + m_aDataPool.RemoveAll(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashFastHandle_t CUtlHashFast::Find( + unsigned int uiKey) const { + // hash the "key" - get the correct hash table "bucket" + int iBucket = HashFuncs::Hash(uiKey, m_uiBucketMask); + + for (int iElement = m_aBuckets[iBucket]; + iElement != m_aDataPool.InvalidIndex(); + iElement = m_aDataPool.Next(iElement)) { + if (m_aDataPool[iElement].m_uiKey == uiKey) return iElement; + } + + return InvalidHandle(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data &CUtlHashFast::Element(UtlHashFastHandle_t hHash) { + return (m_aDataPool[hHash].m_Data); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHashFast::Element( + UtlHashFastHandle_t hHash) const { + return (m_aDataPool[hHash].m_Data); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data &CUtlHashFast::operator[]( + UtlHashFastHandle_t hHash) { + return (m_aDataPool[hHash].m_Data); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHashFast::operator[]( + UtlHashFastHandle_t hHash) const { + return (m_aDataPool[hHash].m_Data); +} + +//----------------------------------------------------------------------------- +// Purpose: For iterating over the whole hash, return the index of the first +// element +//----------------------------------------------------------------------------- +template +typename CUtlHashFast::UtlHashFastIterator_t +CUtlHashFast::First() const { + // walk through the buckets to find the first one that has some data + int bucketCount = m_aBuckets.Count(); + const UtlHashFastHandle_t invalidIndex = m_aDataPool.InvalidIndex(); + for (int bucket = 0; bucket < bucketCount; ++bucket) { + UtlHashFastHandle_t iElement = + m_aBuckets[bucket]; // get the head of the bucket + if (iElement != invalidIndex) + return UtlHashFastIterator_t(bucket, iElement); + } + + // if we are down here, the list is empty + return UtlHashFastIterator_t(-1, invalidIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: For iterating over the whole hash, return the next element after +// the param one. Or an invalid iterator. +//----------------------------------------------------------------------------- +template +typename CUtlHashFast::UtlHashFastIterator_t +CUtlHashFast::Next( + const typename CUtlHashFast::UtlHashFastIterator_t &iter) + const { + // look for the next entry in the current bucket + UtlHashFastHandle_t next = m_aDataPool.Next(iter.handle); + const UtlHashFastHandle_t invalidIndex = m_aDataPool.InvalidIndex(); + if (next != invalidIndex) { + // this bucket still has more elements in it + return UtlHashFastIterator_t(iter.bucket, next); + } + + // otherwise look for the next bucket with data + int bucketCount = m_aBuckets.Count(); + for (int bucket = iter.bucket + 1; bucket < bucketCount; ++bucket) { + UtlHashFastHandle_t next = + m_aBuckets[bucket]; // get the head of the bucket + if (next != invalidIndex) return UtlHashFastIterator_t(bucket, next); + } + + // if we're here, there's no more data to be had + return UtlHashFastIterator_t(-1, invalidIndex); +} + +template +bool CUtlHashFast::IsValidIterator( + const typename CUtlHashFast::UtlHashFastIterator_t &iter) const { + return ((iter.bucket >= 0) && (m_aDataPool.IsValidIndex(iter.handle))); +} + +template +inline bool CUtlHashFast::IsValidHandle( + UtlHashFastHandle_t hHash) const { + return m_aDataPool.IsValidIndex(hHash); +} + +//============================================================================= +// +// Fixed Hash +// +// Number of buckets must be a power of 2. +// Key must be 32-bits (unsigned int). +// +typedef int UtlHashFixedHandle_t; + +template +class CUtlHashFixedGenericHash { + public: + static int Hash(int key, int bucketMask) { + int hash = HashIntConventional(key); + if (NUM_BUCKETS <= USHRT_MAX) { + hash ^= (hash >> 16); + } + if (NUM_BUCKETS <= UCHAR_MAX) { + hash ^= (hash >> 8); + } + return (hash & bucketMask); + } +}; + +template +class CUtlHashFixed { + public: + // Constructor/Deconstructor. + CUtlHashFixed(); + ~CUtlHashFixed(); + + // Memory. + void Purge(void); + + // Invalid handle. + static UtlHashFixedHandle_t InvalidHandle(void) { + return (UtlHashFixedHandle_t)~0; + } + + // Size. + int Count(void); + + // Insertion. + UtlHashFixedHandle_t Insert(unsigned int uiKey, const Data &data); + UtlHashFixedHandle_t FastInsert(unsigned int uiKey, const Data &data); + + // Removal. + void Remove(UtlHashFixedHandle_t hHash); + void RemoveAll(void); + + // Retrieval. + UtlHashFixedHandle_t Find(unsigned int uiKey); + + Data &Element(UtlHashFixedHandle_t hHash); + Data const &Element(UtlHashFixedHandle_t hHash) const; + Data &operator[](UtlHashFixedHandle_t hHash); + Data const &operator[](UtlHashFixedHandle_t hHash) const; + + // protected: + + // Templatized for memory tracking purposes + template + struct HashFixedData_t_ { + unsigned int m_uiKey; + Data_t m_Data; + }; + + typedef HashFixedData_t_ HashFixedData_t; + + enum { BUCKET_MASK = NUM_BUCKETS - 1 }; + CUtlPtrLinkedList m_aBuckets[NUM_BUCKETS]; + int m_nElements; +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +template +CUtlHashFixed::CUtlHashFixed() { + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Deconstructor +//----------------------------------------------------------------------------- +template +CUtlHashFixed::~CUtlHashFixed() { + Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destroy dynamically allocated hash data. +//----------------------------------------------------------------------------- +template +inline void CUtlHashFixed::Purge(void) { + RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of elements in the hash. +//----------------------------------------------------------------------------- +template +inline int CUtlHashFixed::Count(void) { + return m_nElements; +} + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), with +// a check to see if the element already exists within the tree. +//----------------------------------------------------------------------------- +template +inline UtlHashFixedHandle_t CUtlHashFixed::Insert( + unsigned int uiKey, const Data &data) { + // Check to see if that key already exists in the buckets (should be unique). + UtlHashFixedHandle_t hHash = Find(uiKey); + if (hHash != InvalidHandle()) return hHash; + + return FastInsert(uiKey, data); +} + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), +// without a check to see if the element already exists within the +// tree. +//----------------------------------------------------------------------------- +template +inline UtlHashFixedHandle_t +CUtlHashFixed::FastInsert(unsigned int uiKey, + const Data &data) { + int iBucket = HashFuncs::Hash(uiKey, NUM_BUCKETS - 1); + UtlPtrLinkedListIndex_t iElem = m_aBuckets[iBucket].AddToHead(); + + HashFixedData_t *pHashData = &m_aBuckets[iBucket][iElem]; + + Assert((UtlPtrLinkedListIndex_t)pHashData == iElem); + + // Add data to new element. + pHashData->m_uiKey = uiKey; + pHashData->m_Data = data; + + m_nElements++; + return (UtlHashFixedHandle_t)pHashData; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a given element from the hash. +//----------------------------------------------------------------------------- +template +inline void CUtlHashFixed::Remove( + UtlHashFixedHandle_t hHash) { + HashFixedData_t *pHashData = (HashFixedData_t *)hHash; + Assert(Find(pHashData->m_uiKey) != InvalidHandle()); + int iBucket = HashFuncs::Hash(pHashData->m_uiKey, NUM_BUCKETS - 1); + m_aBuckets[iBucket].Remove((UtlPtrLinkedListIndex_t)pHashData); + m_nElements--; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all elements from the hash +//----------------------------------------------------------------------------- +template +inline void CUtlHashFixed::RemoveAll(void) { + for (int i = 0; i < NUM_BUCKETS; i++) { + m_aBuckets[i].RemoveAll(); + } + m_nElements = 0; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline UtlHashFixedHandle_t CUtlHashFixed::Find( + unsigned int uiKey) { + int iBucket = HashFuncs::Hash(uiKey, NUM_BUCKETS - 1); + CUtlPtrLinkedList &bucket = m_aBuckets[iBucket]; + + for (UtlPtrLinkedListIndex_t iElement = bucket.Head(); + iElement != bucket.InvalidIndex(); iElement = bucket.Next(iElement)) { + if (bucket[iElement].m_uiKey == uiKey) + return (UtlHashFixedHandle_t)iElement; + } + + return InvalidHandle(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data &CUtlHashFixed::Element( + UtlHashFixedHandle_t hHash) { + return ((HashFixedData_t *)hHash)->m_Data; +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHashFixed::Element( + UtlHashFixedHandle_t hHash) const { + return ((HashFixedData_t *)hHash)->m_Data; +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data &CUtlHashFixed::operator[]( + UtlHashFixedHandle_t hHash) { + return ((HashFixedData_t *)hHash)->m_Data; +} + +//----------------------------------------------------------------------------- +// Purpose: Return data given a hash handle. +//----------------------------------------------------------------------------- +template +inline Data const &CUtlHashFixed::operator[]( + UtlHashFixedHandle_t hHash) const { + return ((HashFixedData_t *)hHash)->m_Data; +} + +class CDefaultHash32 { + public: + static inline uint32 HashKey32(uint32 nKey) { + return HashIntConventional(nKey); + } +}; + +class CPassthroughHash32 { + public: + static inline uint32 HashKey32(uint32 nKey) { return nKey; } +}; + +// This is a simpler hash for scalar types that stores the entire hash + buckets +// in a single linear array This is much more cache friendly for small (e.g. +// 32-bit) types stored in the hash +template +class CUtlScalarHash { + public: + // Constructor/Destructor. + CUtlScalarHash(); + ~CUtlScalarHash(); + + // Memory. + // void Purge( void ); + + // Invalid handle. + static const UtlHashFastHandle_t InvalidHandle(void) { + return (unsigned int)~0; + } + + // Initialize. + bool Init(int nBucketCount); + + // Size. + int Count(void) const { return m_dataCount; } + + // Insertion. + UtlHashFastHandle_t Insert(unsigned int uiKey, const Data &data); + + // Removal. + void FindAndRemove(unsigned int uiKey, const Data &dataRecord); + void Remove(UtlHashFastHandle_t hHash); + void RemoveAll(void); + void Grow(); + + // Retrieval. Finds by uiKey and then by comparing dataRecord + UtlHashFastHandle_t Find(unsigned int uiKey, const Data &dataRecord) const; + UtlHashFastHandle_t FindByUniqueKey(unsigned int uiKey) const; + + Data &Element(UtlHashFastHandle_t hHash) { + Assert(unsigned(hHash) <= m_uiBucketMask); + return m_pData[hHash].m_Data; + } + Data const &Element(UtlHashFastHandle_t hHash) const { + Assert(unsigned(hHash) <= m_uiBucketMask); + return m_pData[hHash].m_Data; + } + Data &operator[](UtlHashFastHandle_t hHash) { + Assert(unsigned(hHash) <= m_uiBucketMask); + return m_pData[hHash].m_Data; + } + Data const &operator[](UtlHashFastHandle_t hHash) const { + Assert(unsigned(hHash) <= m_uiBucketMask); + return m_pData[hHash].m_Data; + } + + unsigned int Key(UtlHashFastHandle_t hHash) const { + Assert(unsigned(hHash) <= m_uiBucketMask); + return m_pData[hHash].m_uiKey; + } + + UtlHashFastHandle_t FirstInorder() const { return NextInorder(-1); } + UtlHashFastHandle_t NextInorder(UtlHashFastHandle_t nStart) const { + int nElementCount = m_maxData * 2; + unsigned int nUnusedListElement = (unsigned int)InvalidHandle(); + for (int i = nStart + 1; i < nElementCount; i++) { + if (m_pData[i].m_uiKey != nUnusedListElement) return i; + } + return nUnusedListElement; + } + + // protected: + + struct HashScalarData_t { + unsigned int m_uiKey; + Data m_Data; + }; + + unsigned int m_uiBucketMask; + HashScalarData_t *m_pData; + int m_maxData; + int m_dataCount; +}; + +template +CUtlScalarHash::CUtlScalarHash() { + m_pData = NULL; + m_uiBucketMask = 0; + m_maxData = 0; + m_dataCount = 0; +} + +template +CUtlScalarHash::~CUtlScalarHash() { + delete[] m_pData; +} + +template +bool CUtlScalarHash::Init(int nBucketCount) { + Assert(m_dataCount == 0); + m_maxData = SmallestPowerOfTwoGreaterOrEqual(nBucketCount); + int elementCount = m_maxData * 2; + m_pData = new HashScalarData_t[elementCount]; + m_uiBucketMask = elementCount - 1; + RemoveAll(); + return true; +} + +template +void CUtlScalarHash::Grow() { + ASSERT_NO_REENTRY(); + int oldElementCount = m_maxData * 2; + HashScalarData_t *pOldData = m_pData; + + // Grow to a minimum size of 16 + m_maxData = MAX(oldElementCount, 16); + int elementCount = m_maxData * 2; + m_pData = new HashScalarData_t[elementCount]; + m_uiBucketMask = elementCount - 1; + m_dataCount = 0; + for (int i = 0; i < elementCount; i++) { + m_pData[i].m_uiKey = InvalidHandle(); + } + for (int i = 0; i < oldElementCount; i++) { + if (pOldData[i].m_uiKey != (unsigned)InvalidHandle()) { + Insert(pOldData[i].m_uiKey, pOldData[i].m_Data); + } + } + delete[] pOldData; +} + +template +UtlHashFastHandle_t CUtlScalarHash::Insert( + unsigned int uiKey, const Data &data) { + if (m_dataCount >= m_maxData) { + Grow(); + } + m_dataCount++; + Assert(uiKey != (uint)InvalidHandle()); // This hash stores less data by + // assuming uiKey != ~0 + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while (m_pData[index].m_uiKey != endOfList) { + index = (index + 1) & m_uiBucketMask; + } + m_pData[index].m_uiKey = uiKey; + m_pData[index].m_Data = data; + + return index; +} + +// Removal. +template +void CUtlScalarHash::Remove(UtlHashFastHandle_t hHash) { + int mid = (m_uiBucketMask + 1) / 2; + int lastRemoveIndex = hHash; + // remove the item + m_pData[lastRemoveIndex].m_uiKey = InvalidHandle(); + m_dataCount--; + + // now search for any items needing to be swapped down + unsigned int endOfList = (unsigned int)InvalidHandle(); + for (int index = (hHash + 1) & m_uiBucketMask; + m_pData[index].m_uiKey != endOfList; + index = (index + 1) & m_uiBucketMask) { + int ideal = + CHashFunction::HashKey32(m_pData[index].m_uiKey) & m_uiBucketMask; + + // is the ideal index for this element <= (in a wrapped buffer sense) the + // ideal index of the removed element? if so, swap + int diff = ideal - lastRemoveIndex; + if (diff > mid) { + diff -= (m_uiBucketMask + 1); + } + if (diff < -mid) { + diff += (m_uiBucketMask + 1); + } + + // should I swap this? + if (diff <= 0) { + m_pData[lastRemoveIndex] = m_pData[index]; + lastRemoveIndex = index; + m_pData[index].m_uiKey = InvalidHandle(); + } + } +} + +template +void CUtlScalarHash::FindAndRemove( + unsigned int uiKey, const Data &dataRecord) { + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while (m_pData[index].m_uiKey != endOfList) { + if (m_pData[index].m_uiKey == uiKey && + m_pData[index].m_Data == dataRecord) { + Remove(index); + return; + } + index = (index + 1) & m_uiBucketMask; + } +} + +template +void CUtlScalarHash::RemoveAll(void) { + int elementCount = m_maxData * 2; + for (int i = 0; i < elementCount; i++) { + m_pData[i].m_uiKey = (unsigned)InvalidHandle(); + } + m_dataCount = 0; +} + +// Retrieval. +template +UtlHashFastHandle_t CUtlScalarHash::Find( + unsigned int uiKey, const Data &dataRecord) const { + if (m_pData == NULL) return InvalidHandle(); + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while (m_pData[index].m_uiKey != endOfList) { + if (m_pData[index].m_uiKey == uiKey && m_pData[index].m_Data == dataRecord) + return index; + index = (index + 1) & m_uiBucketMask; + } + return InvalidHandle(); +} + +template +UtlHashFastHandle_t CUtlScalarHash::FindByUniqueKey( + unsigned int uiKey) const { + if (m_pData == NULL) return InvalidHandle(); + int index = CHashFunction::HashKey32(uiKey) & m_uiBucketMask; + unsigned int endOfList = (unsigned int)InvalidHandle(); + while (m_pData[index].m_uiKey != endOfList) { + if (m_pData[index].m_uiKey == uiKey) return index; + index = (index + 1) & m_uiBucketMask; + } + return InvalidHandle(); +} + +#endif // VPC_TIER1_UTLHASH_H_ diff --git a/public/tier1/utllinkedlist.h b/public/tier1/utllinkedlist.h new file mode 100644 index 0000000..b6c9392 --- /dev/null +++ b/public/tier1/utllinkedlist.h @@ -0,0 +1,956 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Linked list container class + +#ifndef VPC_TIER1_UTLLINKEDLIST_H_ +#define VPC_TIER1_UTLLINKEDLIST_H_ + +#include "tier0/basetypes.h" +#include "utlmemory.h" +#include "utlfixedmemory.h" +#include "utlblockmemory.h" +#include "tier0/dbg.h" + +// define to enable asserts griping about things you shouldn't be doing with +// multilists #define MULTILIST_PEDANTIC_ASSERTS 1 + +// This is a useful macro to iterate from head to tail in a linked list. +#define FOR_EACH_LL(listName, iteratorName) \ + for (int iteratorName = listName.Head(); \ + iteratorName != listName.InvalidIndex(); \ + iteratorName = listName.Next(iteratorName)) + +//----------------------------------------------------------------------------- +// class CUtlLinkedList: +// description: +// A lovely index-based linked list! T is the class type, I is the +//index type, which usually should be an unsigned short or smaller. However, you +//must avoid using 16- or 8-bit arithmetic on PowerPC architectures; therefore +//you should not use UtlLinkedListElem_t::I as the type of a local variable... +//ever. PowerPC integer arithmetic must be 32- or 64-bit only; otherwise +//performance plummets. +//----------------------------------------------------------------------------- + +template +struct UtlLinkedListElem_t { + T m_Element; + I m_Previous; + I m_Next; + + private: + // No copy constructor for these... + UtlLinkedListElem_t(const UtlLinkedListElem_t &); +}; + +// Class S is the storage type; the type you can use to save off indices in +// persistent memory. Class I is the iterator type, which is what should be used +// in local scopes. I defaults to be S, but be aware that on the 360, 16-bit +// arithmetic is catastrophically slow. Therefore you should try to save shorts +// in memory, but always operate on 32's or 64's in local scope. +// The ideal parameter order would be TSMI (you are more likely to override M +// than I) but since M depends on I we can't have the defaults in that order, +// alas. +template , I>> +class CUtlLinkedList { + public: + typedef T ElemType_t; + typedef S IndexType_t; // should really be called IndexStorageType_t, but + // that would be a huge change + typedef I IndexLocalType_t; + typedef M MemoryAllocator_t; + + // constructor, destructor + CUtlLinkedList(int growSize = 0, int initSize = 0); + ~CUtlLinkedList(); + + // gets particular elements + T &Element(I i); + T const &Element(I i) const; + T &operator[](I i); + T const &operator[](I i) const; + + // Make sure we have a particular amount of memory + void EnsureCapacity(int num); + + void SetGrowSize(int growSize); + + // Memory deallocation + void Purge(); + + // Delete all the elements then call Purge. + void PurgeAndDeleteElements(); + + // Insertion methods.... + I InsertBefore(I before); + I InsertAfter(I after); + I AddToHead(); + I AddToTail(); + + I InsertBefore(I before, T const &src); + I InsertAfter(I after, T const &src); + I AddToHead(T const &src); + I AddToTail(T const &src); + + // Find an element and return its index or InvalidIndex() if it couldn't be + // found. + I Find(const T &src) const; + + // Look for the element. If it exists, remove it and return true. Otherwise, + // return false. + bool FindAndRemove(const T &src); + + // Removal methods + void Remove(I elem); + void RemoveAll(); + + // Allocation/deallocation methods + // If multilist == true, then list list may contain many + // non-connected lists, and IsInList and Head + Tail are meaningless... + I Alloc(bool multilist = false); + void Free(I elem); + + // Identify the owner of this linked list's memory: + void SetAllocOwner(const char *pszAllocOwner); + + // list modification + void LinkBefore(I before, I elem); + void LinkAfter(I after, I elem); + void Unlink(I elem); + void LinkToHead(I elem); + void LinkToTail(I elem); + + // invalid index (M will never allocate an element at this index) + inline static S InvalidIndex() { return (S)M::InvalidIndex(); } + + // Is a given index valid to use? (representible by S and not the invalid + // index) + static bool IndexInRange(I index); + + inline static size_t ElementSize() { return sizeof(ListElem_t); } + + // list statistics + int Count() const; + I MaxElementIndex() const; + I NumAllocated(void) const { return m_NumAlloced; } + + // Traversing the list + I Head() const; + I Tail() const; + I Previous(I i) const; + I Next(I i) const; + + // Are nodes in the list or valid? + bool IsValidIndex(I i) const; + bool IsInList(I i) const; + + protected: + // What the linked list element looks like + typedef UtlLinkedListElem_t ListElem_t; + + // constructs the class + I AllocInternal(bool multilist = false); + void ConstructList(); + + // Gets at the list element.... + ListElem_t &InternalElement(I i) { return m_Memory[i]; } + ListElem_t const &InternalElement(I i) const { return m_Memory[i]; } + + // copy constructors not allowed + CUtlLinkedList(CUtlLinkedList const &list) = delete; + + M m_Memory; + I m_Head; + I m_Tail; + I m_FirstFree; + I m_ElementCount; // The number actually in the list + I m_NumAlloced; // The number of allocated elements + typename M::Iterator_t m_LastAlloc; // the last index allocated + + // For debugging purposes; + // it's in release builds so this can be used in libraries correctly + ListElem_t *m_pElements; + + FORCEINLINE M const &Memory(void) const { return m_Memory; } + + void ResetDbgInfo() { m_pElements = m_Memory.Base(); } +}; + +// this is kind of ugly, but until C++ gets templatized typedefs in C++0x, it's +// our only choice +template +class CUtlFixedLinkedList + : public CUtlLinkedList>> { + public: + CUtlFixedLinkedList(int growSize = 0, int initSize = 0) + : CUtlLinkedList>>( + growSize, initSize) {} + + bool IsValidIndex(intp i) const { + if (!this->Memory().IsIdxValid(i)) return false; + +#ifdef _DEBUG // it's safe to skip this here, since the only way to get indices + // after m_LastAlloc is to use MaxElementIndex + if (this->Memory().IsIdxAfter(i, this->m_LastAlloc)) { + Assert(0); + return false; // don't read values that have been allocated, but not + // constructed + } +#endif + + return (this->Memory()[i].m_Previous != i) || + (this->Memory()[i].m_Next == i); + } + + private: + int MaxElementIndex() const { + Assert(0); + return this->InvalidIndex(); + } // fixedmemory containers don't support iteration from 0..maxelements-1 + void ResetDbgInfo() {} +}; + +// this is kind of ugly, but until C++ gets templatized typedefs in C++0x, it's +// our only choice +template +class CUtlBlockLinkedList + : public CUtlLinkedList, I>> { + public: + CUtlBlockLinkedList(int growSize = 0, int initSize = 0) + : CUtlLinkedList, I>>( + growSize, initSize) {} + + protected: + void ResetDbgInfo() {} +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlLinkedList::CUtlLinkedList(int growSize, int initSize) + : m_Memory(growSize, initSize), m_LastAlloc(m_Memory.InvalidIterator()) { + // Prevent signed non-intp datatypes + static_assert(sizeof(S) == sizeof(intp) || (((S)-1) > 0)); + ConstructList(); + ResetDbgInfo(); +} + +template +CUtlLinkedList::~CUtlLinkedList() { + RemoveAll(); +} + +template +void CUtlLinkedList::ConstructList() { + m_Head = InvalidIndex(); + m_Tail = InvalidIndex(); + m_FirstFree = InvalidIndex(); + m_ElementCount = 0; + m_NumAlloced = 0; +} + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- + +template +inline T &CUtlLinkedList::Element(I i) { + return m_Memory[i].m_Element; +} + +template +inline T const &CUtlLinkedList::Element(I i) const { + return m_Memory[i].m_Element; +} + +template +inline T &CUtlLinkedList::operator[](I i) { + return m_Memory[i].m_Element; +} + +template +inline T const &CUtlLinkedList::operator[](I i) const { + return m_Memory[i].m_Element; +} + +//----------------------------------------------------------------------------- +// list statistics +//----------------------------------------------------------------------------- + +template +inline int CUtlLinkedList::Count() const { +#ifdef MULTILIST_PEDANTIC_ASSERTS + AssertMsg(!ML, "CUtlLinkedList::Count() is meaningless for linked lists."); +#endif + return m_ElementCount; +} + +template +inline I CUtlLinkedList::MaxElementIndex() const { + return m_Memory.NumAllocated(); +} + +//----------------------------------------------------------------------------- +// Traversing the list +//----------------------------------------------------------------------------- + +template +inline I CUtlLinkedList::Head() const { + return m_Head; +} + +template +inline I CUtlLinkedList::Tail() const { + return m_Tail; +} + +template +inline I CUtlLinkedList::Previous(I i) const { + Assert(IsValidIndex(i)); + return InternalElement(i).m_Previous; +} + +template +inline I CUtlLinkedList::Next(I i) const { + Assert(IsValidIndex(i)); + return InternalElement(i).m_Next; +} + +//----------------------------------------------------------------------------- +// Are nodes in the list or valid? +//----------------------------------------------------------------------------- + +template +inline bool CUtlLinkedList::IndexInRange( + I index) // Static method +{ + // Since S is not necessarily the type returned by M, we need to check that M + // returns indices which are representable by S. A common case is 'S === + // unsigned short', 'I == int', in which case CUtlMemory will have + // 'InvalidIndex == (int)-1' (which casts to 65535 in S), and will happily + // return elements at index 65535 and above. + + // Do some static checks here: + // 'I' needs to be able to store 'S' + static_assert(sizeof(I) >= sizeof(S)); + // 'S' should be unsigned (to avoid signed arithmetic errors for plausibly + // exhaustible ranges) + static_assert((sizeof(S) > 2) || (((S)-1) > 0)); + // M::INVALID_INDEX should be storable in S to avoid ambiguities (e.g. with + // 65536) + static_assert((M::INVALID_INDEX == -1) || + (M::INVALID_INDEX == (S)M::INVALID_INDEX)); + + return (((S)index == index) && ((S)index != InvalidIndex())); +} + +template +inline bool CUtlLinkedList::IsValidIndex(I i) const { + if (!m_Memory.IsIdxValid(i)) return false; + + if (m_Memory.IsIdxAfter(i, m_LastAlloc)) + return false; // don't read values that have been allocated, but not + // constructed + + return (m_Memory[i].m_Previous != i) || (m_Memory[i].m_Next == i); +} + +template +inline bool CUtlLinkedList::IsInList(I i) const { + if (!m_Memory.IsIdxValid(i) || m_Memory.IsIdxAfter(i, m_LastAlloc)) + return false; // don't read values that have been allocated, but not + // constructed + + return Previous(i) != i; +} + +/* +template +inline bool CUtlFixedLinkedList::IsInList( int i ) const +{ + return m_Memory.IsIdxValid( i ) && (Previous( i ) != i); +} +*/ + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- + +template +void CUtlLinkedList::EnsureCapacity(int num) { + MEM_ALLOC_CREDIT_CLASS(); + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + +template +void CUtlLinkedList::SetGrowSize(int growSize) { + RemoveAll(); + m_Memory.Init(growSize); + ResetDbgInfo(); +} + +template +void CUtlLinkedList::SetAllocOwner(const char *pszAllocOwner) { + m_Memory.SetAllocOwner(pszAllocOwner); +} + +//----------------------------------------------------------------------------- +// Deallocate memory +//----------------------------------------------------------------------------- + +template +void CUtlLinkedList::Purge() { + RemoveAll(); + + m_Memory.Purge(); + m_FirstFree = InvalidIndex(); + m_NumAlloced = 0; + + // Routing "m_LastAlloc = m_Memory.InvalidIterator();" through a local const + // to sidestep an internal compiler error on 360 builds + const typename M::Iterator_t scInvalidIterator = m_Memory.InvalidIterator(); + m_LastAlloc = scInvalidIterator; + ResetDbgInfo(); +} + +template +void CUtlLinkedList::PurgeAndDeleteElements() { + I iNext; + for (I i = Head(); i != InvalidIndex(); i = iNext) { + iNext = Next(i); + delete Element(i); + } + + Purge(); +} + +//----------------------------------------------------------------------------- +// Node allocation/deallocation +//----------------------------------------------------------------------------- +template +I CUtlLinkedList::AllocInternal(bool multilist) { + Assert(!multilist || ML); +#ifdef MULTILIST_PEDANTIC_ASSERTS + Assert(multilist == ML); +#endif + I elem; + if (m_FirstFree == InvalidIndex()) { + Assert(m_Memory.IsValidIterator(m_LastAlloc) || m_ElementCount == 0); + + typename M::Iterator_t it = m_Memory.IsValidIterator(m_LastAlloc) + ? m_Memory.Next(m_LastAlloc) + : m_Memory.First(); + + if (!m_Memory.IsValidIterator(it)) { + MEM_ALLOC_CREDIT_CLASS(); + m_Memory.Grow(); + ResetDbgInfo(); + + it = m_Memory.IsValidIterator(m_LastAlloc) ? m_Memory.Next(m_LastAlloc) + : m_Memory.First(); + + Assert(m_Memory.IsValidIterator(it)); + if (!m_Memory.IsValidIterator(it)) { + ExecuteNTimes( + 10, + Warning("CUtlLinkedList overflow! (exhausted memory allocator)\n")); + return InvalidIndex(); + } + } + + // We can overflow before the utlmemory overflows, since S != I + if (!IndexInRange(m_Memory.GetIndex(it))) { + ExecuteNTimes( + 10, Warning("CUtlLinkedList overflow! (exhausted index range)\n")); + return InvalidIndex(); + } + + m_LastAlloc = it; + elem = m_Memory.GetIndex(m_LastAlloc); + m_NumAlloced++; + } else { + elem = m_FirstFree; + m_FirstFree = InternalElement(m_FirstFree).m_Next; + } + + if (!multilist) { + InternalElement(elem).m_Next = elem; + InternalElement(elem).m_Previous = elem; + } else { + InternalElement(elem).m_Next = InvalidIndex(); + InternalElement(elem).m_Previous = InvalidIndex(); + } + + return elem; +} + +template +I CUtlLinkedList::Alloc(bool multilist) { + I elem = AllocInternal(multilist); + if (elem == InvalidIndex()) return elem; + + Construct(&Element(elem)); + + return elem; +} + +template +void CUtlLinkedList::Free(I elem) { + Assert(IsValidIndex(elem) && IndexInRange(elem)); + Unlink(elem); + + ListElem_t &internalElem = InternalElement(elem); + Destruct(&internalElem.m_Element); + internalElem.m_Next = m_FirstFree; + m_FirstFree = elem; +} + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses default constructor) +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::InsertBefore(I before) { + // Make a new node + I newNode = AllocInternal(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkBefore(before, newNode); + + // Construct the data + Construct(&Element(newNode)); + + return newNode; +} + +template +I CUtlLinkedList::InsertAfter(I after) { + // Make a new node + I newNode = AllocInternal(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkAfter(after, newNode); + + // Construct the data + Construct(&Element(newNode)); + + return newNode; +} + +template +inline I CUtlLinkedList::AddToHead() { + return InsertAfter(InvalidIndex()); +} + +template +inline I CUtlLinkedList::AddToTail() { + return InsertBefore(InvalidIndex()); +} + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses copy constructor) +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::InsertBefore(I before, T const &src) { + // Make a new node + I newNode = AllocInternal(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkBefore(before, newNode); + + // Construct the data + CopyConstruct(&Element(newNode), src); + + return newNode; +} + +template +I CUtlLinkedList::InsertAfter(I after, T const &src) { + // Make a new node + I newNode = AllocInternal(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkAfter(after, newNode); + + // Construct the data + CopyConstruct(&Element(newNode), src); + + return newNode; +} + +template +inline I CUtlLinkedList::AddToHead(T const &src) { + return InsertAfter(InvalidIndex(), src); +} + +template +inline I CUtlLinkedList::AddToTail(T const &src) { + return InsertBefore(InvalidIndex(), src); +} + +//----------------------------------------------------------------------------- +// Removal methods +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::Find(const T &src) const { + for (I i = Head(); i != InvalidIndex(); i = Next(i)) { + if (Element(i) == src) return i; + } + return InvalidIndex(); +} + +template +bool CUtlLinkedList::FindAndRemove(const T &src) { + I i = Find(src); + if (i == InvalidIndex()) { + return false; + } else { + Remove(i); + return true; + } +} + +template +void CUtlLinkedList::Remove(I elem) { + Free(elem); +} + +template +void CUtlLinkedList::RemoveAll() { + // Have to do some convoluted stuff to invoke the destructor on all + // valid elements for the multilist case (since we don't have all elements + // connected to each other in a list). + + if (m_LastAlloc == m_Memory.InvalidIterator()) { + Assert(m_Head == InvalidIndex()); + Assert(m_Tail == InvalidIndex()); + Assert(m_FirstFree == InvalidIndex()); + Assert(m_ElementCount == 0); + return; + } + + if (ML) { + for (typename M::Iterator_t it = m_Memory.First(); + it != m_Memory.InvalidIterator(); it = m_Memory.Next(it)) { + I i = m_Memory.GetIndex(it); + if (IsValidIndex(i)) // skip elements already in the free list + { + ListElem_t &internalElem = InternalElement(i); + Destruct(&internalElem.m_Element); + internalElem.m_Previous = i; + internalElem.m_Next = m_FirstFree; + m_FirstFree = i; + } + + if (it == m_LastAlloc) + break; // don't destruct elements that haven't ever been constructed + } + } else { + I i = Head(); + I next; + while (i != InvalidIndex()) { + next = Next(i); + ListElem_t &internalElem = InternalElement(i); + Destruct(&internalElem.m_Element); + internalElem.m_Previous = i; + internalElem.m_Next = next == InvalidIndex() ? m_FirstFree : next; + i = next; + } + if (Head() != InvalidIndex()) { + m_FirstFree = Head(); + } + } + + // Clear everything else out + m_Head = InvalidIndex(); + m_Tail = InvalidIndex(); + m_ElementCount = 0; +} + +//----------------------------------------------------------------------------- +// list modification +//----------------------------------------------------------------------------- + +template +void CUtlLinkedList::LinkBefore(I before, I elem) { + Assert(IsValidIndex(elem)); + + // Unlink it if it's in the list at the moment + Unlink(elem); + + ListElem_t *pNewElem = &InternalElement(elem); + + // The element *after* our newly linked one is the one we linked before. + pNewElem->m_Next = before; + + S newElem_mPrevious; // we need to hang on to this for the compairson against + // InvalidIndex() below; otherwise we get a a + // load-hit-store on pNewElem->m_Previous, even with + if (before == InvalidIndex()) { + // In this case, we're linking to the end of the list, so reset the tail + newElem_mPrevious = m_Tail; + pNewElem->m_Previous = m_Tail; + m_Tail = elem; + } else { + // Here, we're not linking to the end. Set the prev pointer to point to + // the element we're linking. + Assert(IsInList(before)); + ListElem_t *beforeElem = &InternalElement(before); + pNewElem->m_Previous = newElem_mPrevious = beforeElem->m_Previous; + beforeElem->m_Previous = elem; + } + + // Reset the head if we linked to the head of the list + if (newElem_mPrevious == InvalidIndex()) + m_Head = elem; + else + InternalElement(newElem_mPrevious).m_Next = elem; + + // one more element baby + ++m_ElementCount; +} + +template +void CUtlLinkedList::LinkAfter(I after, I elem) { + Assert(IsValidIndex(elem)); + + // Unlink it if it's in the list at the moment + if (IsInList(elem)) Unlink(elem); + + ListElem_t &newElem = InternalElement(elem); + + // The element *before* our newly linked one is the one we linked after + newElem.m_Previous = after; + if (after == InvalidIndex()) { + // In this case, we're linking to the head of the list, reset the head + newElem.m_Next = m_Head; + m_Head = elem; + } else { + // Here, we're not linking to the end. Set the next pointer to point to + // the element we're linking. + Assert(IsInList(after)); + ListElem_t &afterElem = InternalElement(after); + newElem.m_Next = afterElem.m_Next; + afterElem.m_Next = elem; + } + + // Reset the tail if we linked to the tail of the list + if (newElem.m_Next == InvalidIndex()) + m_Tail = elem; + else + InternalElement(newElem.m_Next).m_Previous = elem; + + // one more element baby + ++m_ElementCount; +} + +template +void CUtlLinkedList::Unlink(I elem) { + Assert(IsValidIndex(elem)); + if (IsInList(elem)) { + ListElem_t *pOldElem = &m_Memory[elem]; + + // If we're the first guy, reset the head + // otherwise, make our previous node's next pointer = our next + if (pOldElem->m_Previous != InvalidIndex()) { + m_Memory[pOldElem->m_Previous].m_Next = pOldElem->m_Next; + } else { + m_Head = pOldElem->m_Next; + } + + // If we're the last guy, reset the tail + // otherwise, make our next node's prev pointer = our prev + if (pOldElem->m_Next != InvalidIndex()) { + m_Memory[pOldElem->m_Next].m_Previous = pOldElem->m_Previous; + } else { + m_Tail = pOldElem->m_Previous; + } + + // This marks this node as not in the list, + // but not in the free list either + pOldElem->m_Previous = pOldElem->m_Next = elem; + + // One less puppy + --m_ElementCount; + } +} + +template +inline void CUtlLinkedList::LinkToHead(I elem) { + LinkAfter(InvalidIndex(), elem); +} + +template +inline void CUtlLinkedList::LinkToTail(I elem) { + LinkBefore(InvalidIndex(), elem); +} + +//----------------------------------------------------------------------------- +// Class to drop in to replace a CUtlLinkedList that needs to be more memory +// agressive +//----------------------------------------------------------------------------- + +DECLARE_POINTER_HANDLE(UtlPtrLinkedListIndex_t); // to enforce correct usage + +template +class CUtlPtrLinkedList { + public: + CUtlPtrLinkedList() : m_pFirst(NULL), m_nElems(0) { + static_assert(sizeof(IndexType_t) == sizeof(Node_t *)); + } + + ~CUtlPtrLinkedList() { RemoveAll(); } + + typedef UtlPtrLinkedListIndex_t IndexType_t; + + T &operator[](IndexType_t i) { return ((Node_t *)i)->elem; } + + const T &operator[](IndexType_t i) const { return ((Node_t *)i)->elem; } + + IndexType_t AddToTail() { + return DoInsertBefore((IndexType_t)m_pFirst, NULL); + } + + IndexType_t AddToTail(T const &src) { + return DoInsertBefore((IndexType_t)m_pFirst, &src); + } + + IndexType_t AddToHead() { + IndexType_t result = DoInsertBefore((IndexType_t)m_pFirst, NULL); + m_pFirst = ((Node_t *)result); + return result; + } + + IndexType_t AddToHead(T const &src) { + IndexType_t result = DoInsertBefore((IndexType_t)m_pFirst, &src); + m_pFirst = ((Node_t *)result); + return result; + } + + IndexType_t InsertBefore(IndexType_t before) { + return DoInsertBefore(before, NULL); + } + + IndexType_t InsertAfter(IndexType_t after) { + Node_t *pBefore = ((Node_t *)after)->next; + return DoInsertBefore(pBefore, NULL); + } + + IndexType_t InsertBefore(IndexType_t before, T const &src) { + return DoInsertBefore(before, &src); + } + + IndexType_t InsertAfter(IndexType_t after, T const &src) { + Node_t *pBefore = ((Node_t *)after)->next; + return DoInsertBefore(pBefore, &src); + } + + void Remove(IndexType_t elem) { + Node_t *p = (Node_t *)elem; + + if (p->pNext == p) { + m_pFirst = NULL; + } else { + if (m_pFirst == p) { + m_pFirst = p->pNext; + } + p->pNext->pPrev = p->pPrev; + p->pPrev->pNext = p->pNext; + } + + delete p; + m_nElems--; + } + + void RemoveAll() { + Node_t *p = m_pFirst; + if (p) { + do { + Node_t *pNext = p->pNext; + delete p; + p = pNext; + } while (p != m_pFirst); + } + + m_pFirst = NULL; + m_nElems = 0; + } + + int Count() const { return m_nElems; } + + IndexType_t Head() const { return (IndexType_t)m_pFirst; } + + IndexType_t Next(IndexType_t i) const { + Node_t *p = ((Node_t *)i)->pNext; + if (p != m_pFirst) { + return (IndexType_t)p; + } + return NULL; + } + + bool IsValidIndex(IndexType_t i) const { + Node_t *p = ((Node_t *)i); + return (p && p->pNext && p->pPrev); + } + + inline static IndexType_t InvalidIndex() { return NULL; } + + private: + struct Node_t { + Node_t() : pPrev(nullptr), pNext(nullptr) {} + Node_t(const T &_elem) : elem(_elem), pPrev(nullptr), pNext(nullptr) {} + + T elem; + Node_t *pPrev, *pNext; + }; + + Node_t *AllocNode(const T *pCopyFrom) { + MEM_ALLOC_CREDIT_CLASS(); + Node_t *p; + + if (!pCopyFrom) { + p = new Node_t; + } else { + p = new Node_t(*pCopyFrom); + } + + return p; + } + + IndexType_t DoInsertBefore(IndexType_t before, const T *pCopyFrom) { + Node_t *p = AllocNode(pCopyFrom); + Node_t *pBefore = (Node_t *)before; + if (pBefore) { + p->pNext = pBefore; + p->pPrev = pBefore->pPrev; + pBefore->pPrev = p; + p->pPrev->pNext = p; + } else { + Assert(!m_pFirst); + m_pFirst = p->pNext = p->pPrev = p; + } + + m_nElems++; + return (IndexType_t)p; + } + + Node_t *m_pFirst; + unsigned m_nElems; +}; + +//----------------------------------------------------------------------------- + +#endif // VPC_TIER1_UTLLINKEDLIST_H_ diff --git a/public/tier1/utlmap.h b/public/tier1/utlmap.h new file mode 100644 index 0000000..7586ad8 --- /dev/null +++ b/public/tier1/utlmap.h @@ -0,0 +1,209 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_UTLMAP_H_ +#define VPC_TIER1_UTLMAP_H_ + +#include "tier0/dbg.h" +#include "utlrbtree.h" + +//----------------------------------------------------------------------------- +// +// Purpose: An associative container. Pretty much identical to std::map. +// +//----------------------------------------------------------------------------- + +// This is a useful macro to iterate from start to end in order in a map +#define FOR_EACH_MAP(mapName, iteratorName) \ + for (auto iteratorName = (mapName).FirstInorder(); \ + (mapName).IsUtlMap && iteratorName != (mapName).InvalidIndex(); \ + iteratorName = (mapName).NextInorder(iteratorName)) + +// faster iteration, but in an unspecified order +#define FOR_EACH_MAP_FAST(mapName, iteratorName) \ + for (auto iteratorName = 0; \ + (mapName).IsUtlMap && iteratorName < (mapName).MaxElement(); \ + ++iteratorName) \ + if (!(mapName).IsValidIndex(iteratorName)) \ + continue; \ + else + +struct base_utlmap_t { + public: + static const bool IsUtlMap = true; // Used to match this at compiletime +}; + +#if defined(GNUC) && defined(DEBUG) +const bool base_utlmap_t::IsUtlMap SELECTANY; +#endif + +template +class CUtlMap : public base_utlmap_t { + public: + typedef K KeyType_t; + typedef T ElemType_t; + typedef I IndexType_t; + + // Less func typedef + // Returns true if the first parameter is "less" than the second + typedef bool (*LessFunc_t)(const KeyType_t &, const KeyType_t &); + + // constructor, destructor + // Left at growSize = 0, the memory will first allocate 1 element and double + // in size at each increment. LessFunc_t is required, but may be set after the + // constructor using SetLessFunc() below + CUtlMap(int growSize = 0, int initSize = 0, LessFunc_t lessfunc = 0) + : m_Tree(growSize, initSize, CKeyLess(lessfunc)) {} + + CUtlMap(LessFunc_t lessfunc) : m_Tree(CKeyLess(lessfunc)) {} + + void EnsureCapacity(int num) { m_Tree.EnsureCapacity(num); } + + // gets particular elements + ElemType_t &Element(IndexType_t i) { return m_Tree.Element(i).elem; } + const ElemType_t &Element(IndexType_t i) const { + return m_Tree.Element(i).elem; + } + ElemType_t &operator[](IndexType_t i) { return m_Tree.Element(i).elem; } + const ElemType_t &operator[](IndexType_t i) const { + return m_Tree.Element(i).elem; + } + KeyType_t &Key(IndexType_t i) { return m_Tree.Element(i).key; } + const KeyType_t &Key(IndexType_t i) const { return m_Tree.Element(i).key; } + + // Num elements + unsigned int Count() const { return m_Tree.Count(); } + + // Max "size" of the vector + IndexType_t MaxElement() const { return m_Tree.MaxElement(); } + + // Checks if a node is valid and in the map + bool IsValidIndex(IndexType_t i) const { return m_Tree.IsValidIndex(i); } + + // Checks if the map as a whole is valid + bool IsValid() const { return m_Tree.IsValid(); } + + // Invalid index + static IndexType_t InvalidIndex() { return CTree::InvalidIndex(); } + + // Sets the less func + void SetLessFunc(LessFunc_t func) { m_Tree.SetLessFunc(CKeyLess(func)); } + + // Insert method (inserts in order) + IndexType_t Insert(const KeyType_t &key, const ElemType_t &insert) { + Node_t node; + node.key = key; + node.elem = insert; + return m_Tree.Insert(node); + } + + IndexType_t Insert(const KeyType_t &key) { + Node_t node; + node.key = key; + return m_Tree.Insert(node); + } + + // Find method + IndexType_t Find(const KeyType_t &key) const { + Node_t dummyNode; + dummyNode.key = key; + return m_Tree.Find(dummyNode); + } + + // Remove methods + void RemoveAt(IndexType_t i) { m_Tree.RemoveAt(i); } + bool Remove(const KeyType_t &key) { + Node_t dummyNode; + dummyNode.key = key; + return m_Tree.Remove(dummyNode); + } + + void RemoveAll() { m_Tree.RemoveAll(); } + void Purge() { m_Tree.Purge(); } + + // Purges the list and calls delete on each element in it. + void PurgeAndDeleteElements(); + + // Iteration + IndexType_t FirstInorder() const { return m_Tree.FirstInorder(); } + IndexType_t NextInorder(IndexType_t i) const { return m_Tree.NextInorder(i); } + IndexType_t PrevInorder(IndexType_t i) const { return m_Tree.PrevInorder(i); } + IndexType_t LastInorder() const { return m_Tree.LastInorder(); } + + // If you change the search key, this can be used to reinsert the + // element into the map. + void Reinsert(const KeyType_t &key, IndexType_t i) { + m_Tree[i].key = key; + m_Tree.Reinsert(i); + } + + IndexType_t InsertOrReplace(const KeyType_t &key, const ElemType_t &insert) { + IndexType_t i = Find(key); + if (i != InvalidIndex()) { + Element(i) = insert; + return i; + } + + return Insert(key, insert); + } + + void Swap(CUtlMap &that) { m_Tree.Swap(that.m_Tree); } + + struct Node_t { + Node_t() = default; + + Node_t(const Node_t &from) : key(from.key), elem(from.elem) {} + + KeyType_t key; + ElemType_t elem; + }; + + class CKeyLess { + public: + CKeyLess(LessFunc_t lessFunc) : m_LessFunc(lessFunc) {} + + bool operator!() const { return !m_LessFunc; } + + bool operator()(const Node_t &left, const Node_t &right) const { + return m_LessFunc(left.key, right.key); + } + + LessFunc_t m_LessFunc; + }; + + typedef CUtlRBTree CTree; + + CTree *AccessTree() { return &m_Tree; } + + protected: + CTree m_Tree; +}; + +//----------------------------------------------------------------------------- + +// Purges the list and calls delete on each element in it. +template +inline void CUtlMap::PurgeAndDeleteElements() { + for (I i = 0; i < MaxElement(); ++i) { + if (!IsValidIndex(i)) continue; + + delete Element(i); + } + + Purge(); +} + +//----------------------------------------------------------------------------- + +// This is horrible and slow and meant to be used only when you're dealing with +// really non-time/memory-critical code and desperately want to copy a whole map +// element-by-element for whatever reason. +template +void DeepCopyMap(const CUtlMap &pmapIn, + CUtlMap *out_pmapOut) { + Assert(out_pmapOut); + + out_pmapOut->Purge(); + FOR_EACH_MAP_FAST(pmapIn, i) { out_pmapOut->Insert(i, pmapIn[i]); } +} + +#endif // VPC_TIER1_UTLMAP_H_ diff --git a/public/tier1/utlmemory.h b/public/tier1/utlmemory.h new file mode 100644 index 0000000..1dc7dc4 --- /dev/null +++ b/public/tier1/utlmemory.h @@ -0,0 +1,1044 @@ +// Copyright Valve Corporation, All rights reserved. +// +// A growable memory class. + +#ifndef VPC_TIER1_UTLMEMORY_H_ +#define VPC_TIER1_UTLMEMORY_H_ + +#include + +#include "tier0/dbg.h" +#include "tier0/platform.h" + +#include "tier0/memalloc.h" +#include "mathlib/mathlib.h" +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- + +#ifdef UTLMEMORY_TRACK +#define UTLMEMORY_TRACK_ALLOC() \ + MemAlloc_RegisterAllocation("||Sum of all UtlMemory||", 0, \ + m_nAllocationCount * sizeof(T), \ + m_nAllocationCount * sizeof(T), 0) +#define UTLMEMORY_TRACK_FREE() \ + if (!m_pMemory) \ + ; \ + else \ + MemAlloc_RegisterDeallocation("||Sum of all UtlMemory||", 0, \ + m_nAllocationCount * sizeof(T), \ + m_nAllocationCount * sizeof(T), 0) +#else +#define UTLMEMORY_TRACK_ALLOC() ((void)0) +#define UTLMEMORY_TRACK_FREE() ((void)0) +#endif + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template +class CUtlMemory { + public: + // constructor, destructor + CUtlMemory(intp nGrowSize = 0, intp nInitSize = 0); + CUtlMemory(T* pMemory, intp numElements); + CUtlMemory(const T* pMemory, intp numElements); + ~CUtlMemory(); + + // Set the size by which the memory grows + void Init(intp nGrowSize = 0, intp nInitSize = 0); + + class Iterator_t { + public: + Iterator_t(I i) : index(i) {} + I index; + + bool operator==(const Iterator_t it) const { return index == it.index; } + bool operator!=(const Iterator_t it) const { return index != it.index; } + }; + Iterator_t First() const { + return Iterator_t(IsIdxValid(0) ? 0 : InvalidIndex()); + } + Iterator_t Next(const Iterator_t& it) const { + return Iterator_t(IsIdxValid(it.index + 1) ? it.index + 1 : InvalidIndex()); + } + I GetIndex(const Iterator_t& it) const { return it.index; } + bool IsIdxAfter(I i, const Iterator_t& it) const { return i > it.index; } + bool IsValidIterator(const Iterator_t& it) const { + return IsIdxValid(it.index); + } + Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex()); } + + // element access + T& operator[](I i); + const T& operator[](I i) const; + T& Element(I i); + const T& Element(I i) const; + + // Can we use this index? + bool IsIdxValid(I i) const; + + // Specify the invalid ('null') index that we'll only return on failure + static const I INVALID_INDEX = (I)-1; // For use with static_assert + static I InvalidIndex() { return INVALID_INDEX; } + + // Gets the base address (can change when adding elements!) + T* Base(); + const T* Base() const; + + // Attaches the buffer to external memory.... + void SetExternalBuffer(T* pMemory, intp numElements); + void SetExternalBuffer(const T* pMemory, intp numElements); + void AssumeMemory(T* pMemory, intp nSize); + T* Detach(); + void* DetachMemory(); + + // Fast swap + void Swap(CUtlMemory& mem); + + // Switches the buffer from an external memory buffer to a reallocatable + // buffer Will copy the current contents of the external buffer to the + // reallocatable buffer + void ConvertToGrowableMemory(intp nGrowSize); + + // Size + intp NumAllocated() const; + intp Count() const; + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow(intp num = 1); + + // Makes sure we've got at least this much memory + void EnsureCapacity(intp num); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements + void Purge(intp numElements); + + // is the memory externally allocated? + bool IsExternallyAllocated() const; + + // is the memory read only? + bool IsReadOnly() const; + + // Set the size by which the memory grows + void SetGrowSize(intp size); + + protected: + void ValidateGrowSize() { +#ifdef _X360 + if (m_nGrowSize && m_nGrowSize != EXTERNAL_BUFFER_MARKER) { + // Max grow size at 128 bytes on XBOX + const intp MAX_GROW = 128; + if (m_nGrowSize * sizeof(T) > MAX_GROW) { + m_nGrowSize = max(1, MAX_GROW / sizeof(T)); + } + } +#endif + } + + enum { + EXTERNAL_BUFFER_MARKER = -1, + EXTERNAL_CONST_BUFFER_MARKER = -2, + }; + + T* m_pMemory; + intp m_nAllocationCount; + intp m_nGrowSize; +}; + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template +class CUtlMemoryFixedGrowable : public CUtlMemory { + typedef CUtlMemory BaseClass; + + public: + CUtlMemoryFixedGrowable(intp nGrowSize = 0, intp nInitSize = SIZE) + : BaseClass(m_pFixedMemory, SIZE) { + Assert(nInitSize == 0 || nInitSize == SIZE); + m_nMallocGrowSize = nGrowSize; + } + + void Grow(intp nCount = 1) { + if (this->IsExternallyAllocated()) { + this->ConvertToGrowableMemory(m_nMallocGrowSize); + } + BaseClass::Grow(nCount); + } + + void EnsureCapacity(intp num) { + if (CUtlMemory::m_nAllocationCount >= num) return; + + if (this->IsExternallyAllocated()) { + // Can't grow a buffer whose memory was externally allocated + this->ConvertToGrowableMemory(m_nMallocGrowSize); + } + + BaseClass::EnsureCapacity(num); + } + + private: + intp m_nMallocGrowSize; + T m_pFixedMemory[SIZE]; +}; + +//----------------------------------------------------------------------------- +// The CUtlMemoryFixed class: +// A fixed memory class +//----------------------------------------------------------------------------- +template +class CUtlMemoryFixed { + public: + // constructor, destructor + CUtlMemoryFixed(intp nGrowSize = 0, intp nInitSize = 0) { + Assert(nInitSize == 0 || nInitSize == SIZE); + } + CUtlMemoryFixed(T* pMemory, intp numElements) { Assert(0); } + + // Can we use this index? + bool IsIdxValid(intp i) const { return (i >= 0) && (i < SIZE); } + + // Specify the invalid ('null') index that we'll only return on failure + static const intp INVALID_INDEX = -1; // For use with static_assert + static intp InvalidIndex() { return INVALID_INDEX; } + + // Gets the base address + T* Base() { + if (nAlignment == 0) + return (T*)(&m_Memory[0]); + else + return (T*)AlignValue(&m_Memory[0], nAlignment); + } + const T* Base() const { + if (nAlignment == 0) + return (T*)(&m_Memory[0]); + else + return (T*)AlignValue(&m_Memory[0], nAlignment); + } + + // element access + T& operator[](intp i) { + Assert(IsIdxValid(i)); + return Base()[i]; + } + const T& operator[](intp i) const { + Assert(IsIdxValid(i)); + return Base()[i]; + } + T& Element(intp i) { + Assert(IsIdxValid(i)); + return Base()[i]; + } + const T& Element(intp i) const { + Assert(IsIdxValid(i)); + return Base()[i]; + } + + // Attaches the buffer to external memory.... + void SetExternalBuffer(T* pMemory, intp numElements) { Assert(0); } + + // Size + intp NumAllocated() const { return SIZE; } + intp Count() const { return SIZE; } + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow(intp num = 1) { Assert(0); } + + // Makes sure we've got at least this much memory + void EnsureCapacity(intp num) { Assert(num <= SIZE); } + + // Memory deallocation + void Purge() {} + + // Purge all but the given number of elements (NOT IMPLEMENTED IN + // CUtlMemoryFixed) + void Purge(intp numElements) { Assert(0); } + + // is the memory externally allocated? + bool IsExternallyAllocated() const { return false; } + + // Set the size by which the memory grows + void SetGrowSize(intp size) {} + + class Iterator_t { + public: + Iterator_t(intp i) : index(i) {} + intp index; + bool operator==(const Iterator_t it) const { return index == it.index; } + bool operator!=(const Iterator_t it) const { return index != it.index; } + }; + Iterator_t First() const { + return Iterator_t(IsIdxValid(0) ? 0 : InvalidIndex()); + } + Iterator_t Next(const Iterator_t& it) const { + return Iterator_t(IsIdxValid(it.index + 1) ? it.index + 1 : InvalidIndex()); + } + intp GetIndex(const Iterator_t& it) const { return it.index; } + bool IsIdxAfter(intp i, const Iterator_t& it) const { return i > it.index; } + bool IsValidIterator(const Iterator_t& it) const { + return IsIdxValid(it.index); + } + Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex()); } + + private: + char m_Memory[SIZE * sizeof(T) + nAlignment]; +}; + +#ifdef _LINUX +#define REMEMBER_ALLOC_SIZE_FOR_VALGRIND 1 +#endif + +//----------------------------------------------------------------------------- +// The CUtlMemoryConservative class: +// A dynamic memory class that tries to minimize overhead (itself small, no +// custom grow factor) +//----------------------------------------------------------------------------- +template +class CUtlMemoryConservative { + public: + // constructor, destructor + CUtlMemoryConservative(intp nGrowSize = 0, intp nInitSize = 0) + : m_pMemory(NULL) { +#ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND + m_nCurAllocSize = 0; +#endif + } + CUtlMemoryConservative(T* pMemory, intp numElements) { Assert(0); } + ~CUtlMemoryConservative() { + if (m_pMemory) free(m_pMemory); + } + + // Can we use this index? + bool IsIdxValid(intp i) const { + return (IsDebug()) ? (i >= 0 && i < NumAllocated()) : (i >= 0); + } + static intp InvalidIndex() { return -1; } + + // Gets the base address + T* Base() { return m_pMemory; } + const T* Base() const { return m_pMemory; } + + // element access + T& operator[](intp i) { + Assert(IsIdxValid(i)); + return Base()[i]; + } + const T& operator[](intp i) const { + Assert(IsIdxValid(i)); + return Base()[i]; + } + T& Element(intp i) { + Assert(IsIdxValid(i)); + return Base()[i]; + } + const T& Element(intp i) const { + Assert(IsIdxValid(i)); + return Base()[i]; + } + + // Attaches the buffer to external memory.... + void SetExternalBuffer(T* pMemory, intp numElements) { Assert(0); } + + // Size + FORCEINLINE void RememberAllocSize(size_t sz) { +#ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND + m_nCurAllocSize = sz; +#endif + } + + size_t AllocSize(void) const { +#ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND + return m_nCurAllocSize; +#else + return (m_pMemory) ? g_pMemAlloc->GetSize(m_pMemory) : 0; +#endif + } + + intp NumAllocated() const { return AllocSize() / sizeof(T); } + intp Count() const { return NumAllocated(); } + + FORCEINLINE void ReAlloc(size_t sz) { + m_pMemory = (T*)realloc(m_pMemory, sz); + RememberAllocSize(sz); + } + // Grows the memory, so that at least allocated + num elements are allocated + void Grow(intp num = 1) { + intp nCurN = NumAllocated(); + ReAlloc((nCurN + num) * sizeof(T)); + } + + // Makes sure we've got at least this much memory + void EnsureCapacity(intp num) { + size_t nSize = sizeof(T) * MAX(num, Count()); + ReAlloc(nSize); + } + + // Memory deallocation + void Purge() { + free(m_pMemory); + RememberAllocSize(0); + m_pMemory = NULL; + } + + // Purge all but the given number of elements + void Purge(intp numElements) { ReAlloc(numElements * sizeof(T)); } + + // is the memory externally allocated? + bool IsExternallyAllocated() const { return false; } + + // Set the size by which the memory grows + void SetGrowSize(intp size) {} + + class Iterator_t { + public: + Iterator_t(intp i, intp _limit) : index(i), limit(_limit) {} + intp index; + intp limit; + bool operator==(const Iterator_t it) const { return index == it.index; } + bool operator!=(const Iterator_t it) const { return index != it.index; } + }; + Iterator_t First() const { + intp limit = NumAllocated(); + return Iterator_t(limit ? 0 : InvalidIndex(), limit); + } + Iterator_t Next(const Iterator_t& it) const { + return Iterator_t((it.index + 1 < it.limit) ? it.index + 1 : InvalidIndex(), + it.limit); + } + intp GetIndex(const Iterator_t& it) const { return it.index; } + bool IsIdxAfter(intp i, const Iterator_t& it) const { return i > it.index; } + bool IsValidIterator(const Iterator_t& it) const { + return IsIdxValid(it.index) && (it.index < it.limit); + } + Iterator_t InvalidIterator() const { return Iterator_t(InvalidIndex(), 0); } + + private: + T* m_pMemory; +#ifdef REMEMBER_ALLOC_SIZE_FOR_VALGRIND + size_t m_nCurAllocSize; +#endif +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlMemory::CUtlMemory(intp nGrowSize, intp nInitAllocationCount) + : m_pMemory(0), + m_nAllocationCount(nInitAllocationCount), + m_nGrowSize(nGrowSize) { + ValidateGrowSize(); + Assert(nGrowSize >= 0); + if (m_nAllocationCount) { + UTLMEMORY_TRACK_ALLOC(); + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); + } +} + +template +CUtlMemory::CUtlMemory(T* pMemory, intp numElements) + : m_pMemory(pMemory), m_nAllocationCount(numElements) { + // Special marker indicating externally supplied modifyable memory + m_nGrowSize = EXTERNAL_BUFFER_MARKER; +} + +template +CUtlMemory::CUtlMemory(const T* pMemory, intp numElements) + : m_pMemory((T*)pMemory), m_nAllocationCount(numElements) { + // Special marker indicating externally supplied modifyable memory + m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; +} + +template +CUtlMemory::~CUtlMemory() { + Purge(); + +#ifdef _DEBUG + m_pMemory = reinterpret_cast((uintp)0xFEFEBAAD); + m_nAllocationCount = 0x7BADF00D; +#endif +} + +template +void CUtlMemory::Init(intp nGrowSize /*= 0*/, intp nInitSize /*= 0*/) { + Purge(); + + m_nGrowSize = nGrowSize; + m_nAllocationCount = nInitSize; + ValidateGrowSize(); + Assert(nGrowSize >= 0); + if (m_nAllocationCount) { + UTLMEMORY_TRACK_ALLOC(); + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); + } +} + +//----------------------------------------------------------------------------- +// Fast swap +//----------------------------------------------------------------------------- +template +void CUtlMemory::Swap(CUtlMemory& mem) { + V_swap(m_nGrowSize, mem.m_nGrowSize); + V_swap(m_pMemory, mem.m_pMemory); + V_swap(m_nAllocationCount, mem.m_nAllocationCount); +} + +//----------------------------------------------------------------------------- +// Switches the buffer from an external memory buffer to a reallocatable buffer +//----------------------------------------------------------------------------- +template +void CUtlMemory::ConvertToGrowableMemory(intp nGrowSize) { + if (!IsExternallyAllocated()) return; + + m_nGrowSize = nGrowSize; + if (m_nAllocationCount) { + UTLMEMORY_TRACK_ALLOC(); + MEM_ALLOC_CREDIT_CLASS(); + + intp nNumBytes = m_nAllocationCount * sizeof(T); + T* pMemory = (T*)malloc(nNumBytes); + memcpy(pMemory, m_pMemory, nNumBytes); + m_pMemory = pMemory; + } else { + m_pMemory = NULL; + } +} + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +template +void CUtlMemory::SetExternalBuffer(T* pMemory, intp numElements) { + // Blow away any existing allocated memory + Purge(); + + m_pMemory = pMemory; + m_nAllocationCount = numElements; + + // Indicate that we don't own the memory + m_nGrowSize = EXTERNAL_BUFFER_MARKER; +} + +template +void CUtlMemory::SetExternalBuffer(const T* pMemory, intp numElements) { + // Blow away any existing allocated memory + Purge(); + + m_pMemory = const_cast(pMemory); + m_nAllocationCount = numElements; + + // Indicate that we don't own the memory + m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; +} + +template +void CUtlMemory::AssumeMemory(T* pMemory, intp numElements) { + // Blow away any existing allocated memory + Purge(); + + // Simply take the pointer but don't mark us as external + m_pMemory = pMemory; + m_nAllocationCount = numElements; +} + +template +void* CUtlMemory::DetachMemory() { + if (IsExternallyAllocated()) return NULL; + + void* pMemory = m_pMemory; + m_pMemory = 0; + m_nAllocationCount = 0; + return pMemory; +} + +template +inline T* CUtlMemory::Detach() { + return (T*)DetachMemory(); +} + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template +inline T& CUtlMemory::operator[](I i) { + Assert(!IsReadOnly()); + Assert(IsIdxValid(i)); + return m_pMemory[i]; +} + +template +inline const T& CUtlMemory::operator[](I i) const { + Assert(IsIdxValid(i)); + return m_pMemory[i]; +} + +template +inline T& CUtlMemory::Element(I i) { + Assert(!IsReadOnly()); + Assert(IsIdxValid(i)); + return m_pMemory[i]; +} + +template +inline const T& CUtlMemory::Element(I i) const { + Assert(IsIdxValid(i)); + return m_pMemory[i]; +} + +//----------------------------------------------------------------------------- +// is the memory externally allocated? +//----------------------------------------------------------------------------- +template +bool CUtlMemory::IsExternallyAllocated() const { + return (m_nGrowSize < 0); +} + +//----------------------------------------------------------------------------- +// is the memory read only? +//----------------------------------------------------------------------------- +template +bool CUtlMemory::IsReadOnly() const { + return (m_nGrowSize == EXTERNAL_CONST_BUFFER_MARKER); +} + +template +void CUtlMemory::SetGrowSize(intp nSize) { + Assert(!IsExternallyAllocated()); + Assert(nSize >= 0); + m_nGrowSize = nSize; + ValidateGrowSize(); +} + +//----------------------------------------------------------------------------- +// Gets the base address (can change when adding elements!) +//----------------------------------------------------------------------------- +template +inline T* CUtlMemory::Base() { + Assert(!IsReadOnly()); + return m_pMemory; +} + +template +inline const T* CUtlMemory::Base() const { + return m_pMemory; +} + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- +template +inline intp CUtlMemory::NumAllocated() const { + return m_nAllocationCount; +} + +template +inline intp CUtlMemory::Count() const { + return m_nAllocationCount; +} + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template +inline bool CUtlMemory::IsIdxValid(I i) const { + // GCC warns if I is an unsigned type and we do a ">= 0" against it (since the + // comparison is always 0). We get the warning even if we cast inside the + // expression. It only goes away if we assign to another variable. + intp x = i; + return (x >= 0) && (x < m_nAllocationCount); +} + +//----------------------------------------------------------------------------- +// Grows the memory +//----------------------------------------------------------------------------- +inline intp UtlMemory_CalcNewAllocationCount(intp nAllocationCount, intp nGrowSize, + intp nNewSize, intp nBytesItem) { + if (nGrowSize) { + nAllocationCount = ((1 + ((nNewSize - 1) / nGrowSize)) * nGrowSize); + } else { + if (!nAllocationCount) { + // Compute an allocation which is at least as big as a cache line... + nAllocationCount = (31 + nBytesItem) / nBytesItem; + } + + while (nAllocationCount < nNewSize) { +#ifndef _X360 + nAllocationCount *= 2; +#else + intp nNewAllocationCount = (nAllocationCount * 9) / 8; // 12.5 % + if (nNewAllocationCount > nAllocationCount) + nAllocationCount = nNewAllocationCount; + else + nAllocationCount *= 2; +#endif + } + } + + return nAllocationCount; +} + +template +void CUtlMemory::Grow(intp num) { + Assert(num > 0); + + if (IsExternallyAllocated()) { + // Can't grow a buffer whose memory was externally allocated + Assert(0); + return; + } + + // Make sure we have at least numallocated + num allocations. + // Use the grow rules specified for this memory (in m_nGrowSize) + intp nAllocationRequested = m_nAllocationCount + num; + + UTLMEMORY_TRACK_FREE(); + + intp nNewAllocationCount = UtlMemory_CalcNewAllocationCount( + m_nAllocationCount, m_nGrowSize, nAllocationRequested, sizeof(T)); + + // if m_nAllocationRequested wraps index type I, recalculate + if ((intp)(I)nNewAllocationCount < nAllocationRequested) { + if ((intp)(I)nNewAllocationCount == 0 && + (intp)(I)(nNewAllocationCount - 1) >= nAllocationRequested) { + --nNewAllocationCount; // deal w/ the common case of m_nAllocationCount + // == MAX_USHORT + 1 + } else { + if ((intp)(I)nAllocationRequested != nAllocationRequested) { + // we've been asked to grow memory to a size s.t. the index type can't + // address the requested amount of memory + Assert(0); + return; + } + while ((intp)(I)nNewAllocationCount < nAllocationRequested) { + nNewAllocationCount = (nNewAllocationCount + nAllocationRequested) / 2; + } + } + } + + m_nAllocationCount = nNewAllocationCount; + + UTLMEMORY_TRACK_ALLOC(); + + if (m_pMemory) { + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T*)realloc(m_pMemory, m_nAllocationCount * sizeof(T)); + Assert(m_pMemory); + } else { + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); + Assert(m_pMemory); + } +} + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template +inline void CUtlMemory::EnsureCapacity(intp num) { + if (m_nAllocationCount >= num) return; + + if (IsExternallyAllocated()) { + // Can't grow a buffer whose memory was externally allocated + Assert(0); + return; + } + + UTLMEMORY_TRACK_FREE(); + + m_nAllocationCount = num; + + UTLMEMORY_TRACK_ALLOC(); + + if (m_pMemory) { + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T*)realloc(m_pMemory, m_nAllocationCount * sizeof(T)); + } else { + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T*)malloc(m_nAllocationCount * sizeof(T)); + } +} + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template +void CUtlMemory::Purge() { + if (!IsExternallyAllocated()) { + if (m_pMemory) { + UTLMEMORY_TRACK_FREE(); + free(m_pMemory); + m_pMemory = 0; + } + m_nAllocationCount = 0; + } +} + +template +void CUtlMemory::Purge(intp numElements) { + Assert(numElements >= 0); + + if (numElements > m_nAllocationCount) { + // Ensure this isn't a grow request in disguise. + Assert(numElements <= m_nAllocationCount); + return; + } + + // If we have zero elements, simply do a purge: + if (numElements == 0) { + Purge(); + return; + } + + if (IsExternallyAllocated()) { + // Can't shrink a buffer whose memory was externally allocated, fail + // silently like purge + return; + } + + // If the number of elements is the same as the allocation count, we are done. + if (numElements == m_nAllocationCount) { + return; + } + + if (!m_pMemory) { + // Allocation count is non zero, but memory is null. + Assert(m_pMemory); + return; + } + + UTLMEMORY_TRACK_FREE(); + + m_nAllocationCount = numElements; + + UTLMEMORY_TRACK_ALLOC(); + + // Allocation count > 0, shrink it down. + MEM_ALLOC_CREDIT_CLASS(); + m_pMemory = (T*)realloc(m_pMemory, m_nAllocationCount * sizeof(T)); +} + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template +class CUtlMemoryAligned : public CUtlMemory { + public: + // constructor, destructor + CUtlMemoryAligned(intp nGrowSize = 0, intp nInitSize = 0); + CUtlMemoryAligned(T* pMemory, intp numElements); + CUtlMemoryAligned(const T* pMemory, intp numElements); + ~CUtlMemoryAligned(); + + // Attaches the buffer to external memory.... + void SetExternalBuffer(T* pMemory, intp numElements); + void SetExternalBuffer(const T* pMemory, intp numElements); + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow(intp num = 1); + + // Makes sure we've got at least this much memory + void EnsureCapacity(intp num); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements (NOT IMPLEMENTED IN + // CUtlMemoryAligned) + void Purge(intp numElements) { Assert(0); } + + private: + void* Align(const void* pAddr); +}; + +//----------------------------------------------------------------------------- +// Aligns a pointer +//----------------------------------------------------------------------------- +template +void* CUtlMemoryAligned::Align(const void* pAddr) { + size_t nAlignmentMask = nAlignment - 1; + return (void*)(((size_t)pAddr + nAlignmentMask) & (~nAlignmentMask)); +} + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +template +CUtlMemoryAligned::CUtlMemoryAligned(intp nGrowSize, + intp nInitAllocationCount) { + CUtlMemory::m_pMemory = 0; + CUtlMemory::m_nAllocationCount = nInitAllocationCount; + CUtlMemory::m_nGrowSize = nGrowSize; + this->ValidateGrowSize(); + + // Alignment must be a power of two + static_assert((nAlignment & (nAlignment - 1)) == 0); + Assert((nGrowSize >= 0) && + (nGrowSize != CUtlMemory::EXTERNAL_BUFFER_MARKER)); + if (CUtlMemory::m_nAllocationCount) { + UTLMEMORY_TRACK_ALLOC(); + MEM_ALLOC_CREDIT_CLASS(); + CUtlMemory::m_pMemory = + (T*)_aligned_malloc(nInitAllocationCount * sizeof(T), nAlignment); + } +} + +template +CUtlMemoryAligned::CUtlMemoryAligned(T* pMemory, + intp numElements) { + // Special marker indicating externally supplied memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; + + CUtlMemory::m_pMemory = (T*)Align(pMemory); + CUtlMemory::m_nAllocationCount = + ((intp)(pMemory + numElements) - (intp)CUtlMemory::m_pMemory) / + sizeof(T); +} + +template +CUtlMemoryAligned::CUtlMemoryAligned(const T* pMemory, + intp numElements) { + // Special marker indicating externally supplied memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; + + CUtlMemory::m_pMemory = (T*)Align(pMemory); + CUtlMemory::m_nAllocationCount = + ((intp)(pMemory + numElements) - (intp)CUtlMemory::m_pMemory) / + sizeof(T); +} + +template +CUtlMemoryAligned::~CUtlMemoryAligned() { + Purge(); +} + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +template +void CUtlMemoryAligned::SetExternalBuffer(T* pMemory, + intp numElements) { + // Blow away any existing allocated memory + Purge(); + + CUtlMemory::m_pMemory = (T*)Align(pMemory); + CUtlMemory::m_nAllocationCount = + ((intp)(pMemory + numElements) - (intp)CUtlMemory::m_pMemory) / + sizeof(T); + + // Indicate that we don't own the memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; +} + +template +void CUtlMemoryAligned::SetExternalBuffer(const T* pMemory, + intp numElements) { + // Blow away any existing allocated memory + Purge(); + + CUtlMemory::m_pMemory = (T*)Align(pMemory); + CUtlMemory::m_nAllocationCount = + ((intp)(pMemory + numElements) - (intp)CUtlMemory::m_pMemory) / + sizeof(T); + + // Indicate that we don't own the memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; +} + +//----------------------------------------------------------------------------- +// Grows the memory +//----------------------------------------------------------------------------- +template +void CUtlMemoryAligned::Grow(intp num) { + Assert(num > 0); + + if (this->IsExternallyAllocated()) { + // Can't grow a buffer whose memory was externally allocated + Assert(0); + return; + } + + UTLMEMORY_TRACK_FREE(); + + // Make sure we have at least numallocated + num allocations. + // Use the grow rules specified for this memory (in m_nGrowSize) + intp nAllocationRequested = CUtlMemory::m_nAllocationCount + num; + + CUtlMemory::m_nAllocationCount = UtlMemory_CalcNewAllocationCount( + CUtlMemory::m_nAllocationCount, CUtlMemory::m_nGrowSize, + nAllocationRequested, sizeof(T)); + + UTLMEMORY_TRACK_ALLOC(); + + if (CUtlMemory::m_pMemory) { + MEM_ALLOC_CREDIT_CLASS(); + CUtlMemory::m_pMemory = (T*)MemAlloc_ReallocAligned( + CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), + nAlignment); + Assert(CUtlMemory::m_pMemory); + } else { + MEM_ALLOC_CREDIT_CLASS(); + CUtlMemory::m_pMemory = (T*)MemAlloc_AllocAligned( + CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment); + Assert(CUtlMemory::m_pMemory); + } +} + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template +inline void CUtlMemoryAligned::EnsureCapacity(intp num) { + if (CUtlMemory::m_nAllocationCount >= num) return; + + if (this->IsExternallyAllocated()) { + // Can't grow a buffer whose memory was externally allocated + Assert(0); + return; + } + + UTLMEMORY_TRACK_FREE(); + + CUtlMemory::m_nAllocationCount = num; + + UTLMEMORY_TRACK_ALLOC(); + + if (CUtlMemory::m_pMemory) { + MEM_ALLOC_CREDIT_CLASS(); + CUtlMemory::m_pMemory = (T*)MemAlloc_ReallocAligned( + CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), + nAlignment); + } else { + MEM_ALLOC_CREDIT_CLASS(); + CUtlMemory::m_pMemory = (T*)MemAlloc_AllocAligned( + CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment); + } +} + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template +void CUtlMemoryAligned::Purge() { + if (!this->IsExternallyAllocated()) { + if (CUtlMemory::m_pMemory) { + UTLMEMORY_TRACK_FREE(); + MemAlloc_FreeAligned(CUtlMemory::m_pMemory); + CUtlMemory::m_pMemory = 0; + } + CUtlMemory::m_nAllocationCount = 0; + } +} + +#include "tier0/memdbgoff.h" + +#endif // VPC_TIER1_UTLMEMORY_H_ diff --git a/public/tier1/utlmultilist.h b/public/tier1/utlmultilist.h new file mode 100644 index 0000000..f6820e8 --- /dev/null +++ b/public/tier1/utlmultilist.h @@ -0,0 +1,676 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Multiple linked list container class. + +#ifndef VPC_TIER1_UTLMULTILIST_H_ +#define VPC_TIER1_UTLMULTILIST_H_ + +#include "utllinkedlist.h" + +// memdbgon must be the last include file in a .h file!!! +#include "tier0/memdbgon.h" + +// A lovely index-based linked list! T is the class type, I is the index type, +// which usually should be an unsigned short or smaller. This list can contain +// multiple lists. +template +class CUtlMultiList { + protected: + // What the linked list element looks like + struct ListElem_t { + T m_Element; + I m_Previous; + I m_Next; + }; + + struct List_t { + I m_Head; + I m_Tail; + I m_Count; + }; + + typedef CUtlMemory M; // Keep naming similar to CUtlLinkedList + public: + typedef I ListHandle_t; + + // constructor, destructor + CUtlMultiList(int growSize = 0, int initSize = 0); + CUtlMultiList(void* pMemory, int memsize); + ~CUtlMultiList(); + + // gets particular elements + T& Element(I i); + T const& Element(I i) const; + T& operator[](I i); + T const& operator[](I i) const; + + // Make sure we have a particular amount of memory + void EnsureCapacity(int num); + + // Memory deallocation + void Purge(); + + // List Creation/deletion + ListHandle_t CreateList(); + void DestroyList(ListHandle_t list); + bool IsValidList(ListHandle_t list) const; + + // Insertion methods (call default constructor).... + I InsertBefore(ListHandle_t list, I before); + I InsertAfter(ListHandle_t list, I after); + I AddToHead(ListHandle_t list); + I AddToTail(ListHandle_t list); + + // Insertion methods (call copy constructor).... + I InsertBefore(ListHandle_t list, I before, T const& src); + I InsertAfter(ListHandle_t list, I after, T const& src); + I AddToHead(ListHandle_t list, T const& src); + I AddToTail(ListHandle_t list, T const& src); + + // Removal methods + void Remove(ListHandle_t list, I elem); + + // Removes all items in a single list + void RemoveAll(ListHandle_t list); + + // Removes all items in all lists + void RemoveAll(); + + // Allocation/deallocation methods + // NOTE: To free, it must *not* be in a list! + I Alloc(); + void Free(I elem); + + // list modification + void LinkBefore(ListHandle_t list, I before, I elem); + void LinkAfter(ListHandle_t list, I after, I elem); + void Unlink(ListHandle_t list, I elem); + void LinkToHead(ListHandle_t list, I elem); + void LinkToTail(ListHandle_t list, I elem); + + // invalid index + static I InvalidIndex() { return (I)~0; } + static bool IndexInRange(int index); + static size_t ElementSize() { return sizeof(ListElem_t); } + + // list statistics + int Count(ListHandle_t list) const; + int TotalCount() const; + I MaxElementIndex() const; + + // Traversing the list + I Head(ListHandle_t list) const; + I Tail(ListHandle_t list) const; + I Previous(I element) const; + I Next(I element) const; + + // Are nodes in a list or valid? + bool IsValidIndex(I i) const; + bool IsInList(I i) const; + + protected: + // constructs the class + void ConstructList(); + + // Gets at the list element.... + ListElem_t& InternalElement(I i) { return m_Memory[i]; } + ListElem_t const& InternalElement(I i) const { return m_Memory[i]; } + + // A test for debug mode only... + bool IsElementInList(ListHandle_t list, I elem) const; + + // copy constructors not allowed + CUtlMultiList(CUtlMultiList const& list) = delete; + + M m_Memory; + CUtlLinkedList m_List; + I* m_pElementList; + + I m_FirstFree; + I m_TotalElements; + int m_MaxElementIndex; // The number allocated (use int so we can catch + // overflow) + + void ResetDbgInfo() { + m_pElements = m_Memory.Base(); + +#ifdef _DEBUG + // Allocate space for the element list (which list is each element in) + if (m_Memory.NumAllocated() > 0) { + if (!m_pElementList) { + m_pElementList = (I*)malloc(m_Memory.NumAllocated() * sizeof(I)); + } else { + m_pElementList = + (I*)realloc(m_pElementList, m_Memory.NumAllocated() * sizeof(I)); + } + } +#endif + } + + // For debugging purposes; + // it's in release builds so this can be used in libraries correctly + ListElem_t* m_pElements; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlMultiList::CUtlMultiList(int growSize, int initSize) + : m_Memory(growSize, initSize), m_pElementList(0) { + ConstructList(); +} + +template +CUtlMultiList::CUtlMultiList(void* pMemory, int memsize) + : m_Memory((ListElem_t*)pMemory, memsize / sizeof(ListElem_t)), + m_pElementList(0) { + ConstructList(); +} + +template +CUtlMultiList::~CUtlMultiList() { + RemoveAll(); + if (m_pElementList) free(m_pElementList); +} + +template +void CUtlMultiList::ConstructList() { + m_FirstFree = InvalidIndex(); + m_TotalElements = 0; + m_MaxElementIndex = 0; + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- +template +inline T& CUtlMultiList::Element(I i) { + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlMultiList::Element(I i) const { + return m_Memory[i].m_Element; +} + +template +inline T& CUtlMultiList::operator[](I i) { + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlMultiList::operator[](I i) const { + return m_Memory[i].m_Element; +} + +//----------------------------------------------------------------------------- +// list creation/destruction +//----------------------------------------------------------------------------- +template +typename CUtlMultiList::ListHandle_t CUtlMultiList::CreateList() { + ListHandle_t l = m_List.AddToTail(); + m_List[l].m_Head = m_List[l].m_Tail = InvalidIndex(); + m_List[l].m_Count = 0; + return l; +} + +template +void CUtlMultiList::DestroyList(ListHandle_t list) { + Assert(IsValidList(list)); + RemoveAll(list); + m_List.Remove(list); +} + +template +bool CUtlMultiList::IsValidList(ListHandle_t list) const { + return m_List.IsValidIndex(list); +} + +//----------------------------------------------------------------------------- +// list statistics +//----------------------------------------------------------------------------- +template +inline int CUtlMultiList::TotalCount() const { + return m_TotalElements; +} + +template +inline int CUtlMultiList::Count(ListHandle_t list) const { + Assert(IsValidList(list)); + return m_List[list].m_Count; +} + +template +inline I CUtlMultiList::MaxElementIndex() const { + return m_MaxElementIndex; +} + +//----------------------------------------------------------------------------- +// Traversing the list +//----------------------------------------------------------------------------- +template +inline I CUtlMultiList::Head(ListHandle_t list) const { + Assert(IsValidList(list)); + return m_List[list].m_Head; +} + +template +inline I CUtlMultiList::Tail(ListHandle_t list) const { + Assert(IsValidList(list)); + return m_List[list].m_Tail; +} + +template +inline I CUtlMultiList::Previous(I i) const { + Assert(IsValidIndex(i)); + return InternalElement(i).m_Previous; +} + +template +inline I CUtlMultiList::Next(I i) const { + Assert(IsValidIndex(i)); + return InternalElement(i).m_Next; +} + +//----------------------------------------------------------------------------- +// Are nodes in the list or valid? +//----------------------------------------------------------------------------- + +template +inline bool CUtlMultiList::IndexInRange(int index) // Static method +{ + // Since I is not necessarily the type returned by M (int), we need to check + // that M returns indices which are representable by I. A common case is 'I + // === unsigned short', in which case case CUtlMemory will have 'InvalidIndex + // == (int)-1' (which casts to 65535 in I), and will happily return elements + // at index 65535 and above. + + // Do a couple of static checks here: the invalid index should be (I)~0 given + // how we use m_MaxElementIndex, and 'I' should be unsigned (to avoid signed + // arithmetic errors for plausibly exhaustible ranges). + static_assert((I)M::INVALID_INDEX == (I)~0); + static_assert((sizeof(I) > 2) || (((I)-1) > 0)); + + return (((I)index == index) && ((I)index != InvalidIndex())); +} + +template +inline bool CUtlMultiList::IsValidIndex(I i) const { + // GCC warns if I is an unsigned type and we do a ">= 0" against it (since the + // comparison is always 0). We get the warning even if we cast inside the + // expression. It only goes away if we assign to another variable. + long x = i; + + return (i < m_MaxElementIndex) && (x >= 0) && + ((m_Memory[i].m_Previous != i) || (m_Memory[i].m_Next == i)); +} + +template +inline bool CUtlMultiList::IsInList(I i) const { + // GCC warns if I is an unsigned type and we do a ">= 0" against it (since the + // comparison is always 0). We get the warning even if we cast inside the + // expression. It only goes away if we assign to another variable. + long x = i; + return (i < m_MaxElementIndex) && (x >= 0) && (Previous(i) != i); +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- +template +void CUtlMultiList::EnsureCapacity(int num) { + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// Deallocate memory +//----------------------------------------------------------------------------- +template +void CUtlMultiList::Purge() { + RemoveAll(); + m_List.Purge(); + m_Memory.Purge(); + m_List.Purge(); + m_FirstFree = InvalidIndex(); + m_TotalElements = 0; + m_MaxElementIndex = 0; + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// Node allocation/deallocation +//----------------------------------------------------------------------------- +template +I CUtlMultiList::Alloc() { + I elem; + if (m_FirstFree == InvalidIndex()) { + // We can overflow before the utlmemory overflows, since we have have I != + // int + if (!IndexInRange(m_MaxElementIndex)) { + ExecuteNTimes( + 10, Warning("CUtlMultiList overflow! (exhausted index range)\n")); + return InvalidIndex(); + } + + // Nothing in the free list; add. + // Since nothing is in the free list, m_TotalElements == total # of elements + // the list knows about. + if (m_MaxElementIndex == m_Memory.NumAllocated()) { + m_Memory.Grow(); + ResetDbgInfo(); + + if (m_MaxElementIndex >= m_Memory.NumAllocated()) { + ExecuteNTimes( + 10, + Warning("CUtlMultiList overflow! (exhausted memory allocator)\n")); + return InvalidIndex(); + } + } + + elem = (I)m_MaxElementIndex; + ++m_MaxElementIndex; + } else { + elem = m_FirstFree; + m_FirstFree = InternalElement(m_FirstFree).m_Next; + } + + // Mark the element as not being in a list + InternalElement(elem).m_Next = InternalElement(elem).m_Previous = elem; + + ++m_TotalElements; + + Construct(&Element(elem)); + + return elem; +} + +template +void CUtlMultiList::Free(I elem) { + Assert(IsValidIndex(elem) && !IsInList(elem)); + Destruct(&Element(elem)); + InternalElement(elem).m_Next = m_FirstFree; + m_FirstFree = elem; + --m_TotalElements; +} + +//----------------------------------------------------------------------------- +// A test for debug mode only... +//----------------------------------------------------------------------------- +template +inline bool CUtlMultiList::IsElementInList(ListHandle_t list, + I elem) const { + if (!m_pElementList) return true; + + return m_pElementList[elem] == list; +} + +//----------------------------------------------------------------------------- +// list modification +//----------------------------------------------------------------------------- +template +void CUtlMultiList::LinkBefore(ListHandle_t list, I before, I elem) { + Assert(IsValidIndex(elem) && IsValidList(list)); + + // Unlink it if it's in the list at the moment + Unlink(list, elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *after* our newly linked one is the one we linked before. + newElem.m_Next = before; + + if (before == InvalidIndex()) { + // In this case, we're linking to the end of the list, so reset the tail + newElem.m_Previous = m_List[list].m_Tail; + m_List[list].m_Tail = elem; + } else { + // Here, we're not linking to the end. Set the prev pointer to point to + // the element we're linking. + Assert(IsInList(before)); + ListElem_t& beforeElem = InternalElement(before); + newElem.m_Previous = beforeElem.m_Previous; + beforeElem.m_Previous = elem; + } + + // Reset the head if we linked to the head of the list + if (newElem.m_Previous == InvalidIndex()) + m_List[list].m_Head = elem; + else + InternalElement(newElem.m_Previous).m_Next = elem; + + // one more element baby + ++m_List[list].m_Count; + + // Store the element into the list + if (m_pElementList) m_pElementList[elem] = list; +} + +template +void CUtlMultiList::LinkAfter(ListHandle_t list, I after, I elem) { + Assert(IsValidIndex(elem)); + + // Unlink it if it's in the list at the moment + Unlink(list, elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *before* our newly linked one is the one we linked after + newElem.m_Previous = after; + if (after == InvalidIndex()) { + // In this case, we're linking to the head of the list, reset the head + newElem.m_Next = m_List[list].m_Head; + m_List[list].m_Head = elem; + } else { + // Here, we're not linking to the end. Set the next pointer to point to + // the element we're linking. + Assert(IsInList(after)); + ListElem_t& afterElem = InternalElement(after); + newElem.m_Next = afterElem.m_Next; + afterElem.m_Next = elem; + } + + // Reset the tail if we linked to the tail of the list + if (newElem.m_Next == InvalidIndex()) + m_List[list].m_Tail = elem; + else + InternalElement(newElem.m_Next).m_Previous = elem; + + // one more element baby + ++m_List[list].m_Count; + + // Store the element into the list + if (m_pElementList) m_pElementList[elem] = list; +} + +template +void CUtlMultiList::Unlink(ListHandle_t list, I elem) { + Assert(IsValidIndex(elem) && IsValidList(list)); + + if (IsInList(elem)) { + // Make sure the element is in the right list + Assert(IsElementInList(list, elem)); + ListElem_t& oldElem = InternalElement(elem); + + // If we're the first guy, reset the head + // otherwise, make our previous node's next pointer = our next + if (oldElem.m_Previous != InvalidIndex()) + InternalElement(oldElem.m_Previous).m_Next = oldElem.m_Next; + else + m_List[list].m_Head = oldElem.m_Next; + + // If we're the last guy, reset the tail + // otherwise, make our next node's prev pointer = our prev + if (oldElem.m_Next != InvalidIndex()) + InternalElement(oldElem.m_Next).m_Previous = oldElem.m_Previous; + else + m_List[list].m_Tail = oldElem.m_Previous; + + // This marks this node as not in the list, + // but not in the free list either + oldElem.m_Previous = oldElem.m_Next = elem; + + // One less puppy + --m_List[list].m_Count; + + // Store the element into the list + if (m_pElementList) m_pElementList[elem] = m_List.InvalidIndex(); + } +} + +template +inline void CUtlMultiList::LinkToHead(ListHandle_t list, I elem) { + LinkAfter(list, InvalidIndex(), elem); +} + +template +inline void CUtlMultiList::LinkToTail(ListHandle_t list, I elem) { + LinkBefore(list, InvalidIndex(), elem); +} + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses default constructor) +//----------------------------------------------------------------------------- +template +I CUtlMultiList::InsertBefore(ListHandle_t list, I before) { + // Make a new node + I newNode = Alloc(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkBefore(list, before, newNode); + + // Construct the data + Construct(&Element(newNode)); + + return newNode; +} + +template +I CUtlMultiList::InsertAfter(ListHandle_t list, I after) { + // Make a new node + I newNode = Alloc(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkAfter(list, after, newNode); + + // Construct the data + Construct(&Element(newNode)); + + return newNode; +} + +template +inline I CUtlMultiList::AddToHead(ListHandle_t list) { + return InsertAfter(list, InvalidIndex()); +} + +template +inline I CUtlMultiList::AddToTail(ListHandle_t list) { + return InsertBefore(list, InvalidIndex()); +} + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses copy constructor) +//----------------------------------------------------------------------------- +template +I CUtlMultiList::InsertBefore(ListHandle_t list, I before, T const& src) { + // Make a new node + I newNode = Alloc(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkBefore(list, before, newNode); + + // Construct the data + CopyConstruct(&Element(newNode), src); + + return newNode; +} + +template +I CUtlMultiList::InsertAfter(ListHandle_t list, I after, T const& src) { + // Make a new node + I newNode = Alloc(); + if (newNode == InvalidIndex()) return newNode; + + // Link it in + LinkAfter(list, after, newNode); + + // Construct the data + CopyConstruct(&Element(newNode), src); + + return newNode; +} + +template +inline I CUtlMultiList::AddToHead(ListHandle_t list, T const& src) { + return InsertAfter(list, InvalidIndex(), src); +} + +template +inline I CUtlMultiList::AddToTail(ListHandle_t list, T const& src) { + return InsertBefore(list, InvalidIndex(), src); +} + +//----------------------------------------------------------------------------- +// Removal methods +//----------------------------------------------------------------------------- +template +void CUtlMultiList::Remove(ListHandle_t list, I elem) { + if (IsInList(elem)) Unlink(list, elem); + Free(elem); +} + +// Removes all items in a single list +template +void CUtlMultiList::RemoveAll(ListHandle_t list) { + Assert(IsValidList(list)); + I i = Head(list); + I next; + while (i != InvalidIndex()) { + next = Next(i); + Remove(list, i); + i = next; + } +} + +template +void CUtlMultiList::RemoveAll() { + if (m_MaxElementIndex == 0) return; + + // Put everything into the free list + I prev = InvalidIndex(); + for (int i = (int)m_MaxElementIndex; --i >= 0;) { + // Invoke the destructor + if (IsValidIndex((I)i)) Destruct(&Element((I)i)); + + // next points to the next free list item + InternalElement((I)i).m_Next = prev; + + // Indicates it's in the free list + InternalElement((I)i).m_Previous = (I)i; + prev = (I)i; + } + + // First free points to the first element + m_FirstFree = 0; + + // Clear everything else out + for (I list = m_List.Head(); list != m_List.InvalidIndex(); + list = m_List.Next(list)) { + m_List[list].m_Head = InvalidIndex(); + m_List[list].m_Tail = InvalidIndex(); + m_List[list].m_Count = 0; + } + + m_TotalElements = 0; +} + +#include "tier0/memdbgoff.h" + +#endif // VPC_TIER1_UTLMULTILIST_H_ diff --git a/public/tier1/utlqueue.h b/public/tier1/utlqueue.h new file mode 100644 index 0000000..acb8cdc --- /dev/null +++ b/public/tier1/utlqueue.h @@ -0,0 +1,152 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_UTLQUEUE_H_ +#define VPC_TIER1_UTLQUEUE_H_ + +#include "utlvector.h" + +// T is the type stored in the queue +template > +class CUtlQueue { + public: + // constructor: lessfunc is required, but may be set after the constructor + // with SetLessFunc + CUtlQueue(int growSize = 0, int initSize = 0); + CUtlQueue(T* pMemory, int numElements); + + // element access + T& operator[](int i); + T const& operator[](int i) const; + T& Element(int i); + T const& Element(int i) const; + + // return the item from the front of the queue and delete it + T const& RemoveAtHead(); + // return the item from the end of the queue and delete it + T const& RemoveAtTail(); + + // return item at the front of the queue + T const& Head(); + // return item at the end of the queue + T const& Tail(); + + // put a new item on the queue to the tail. + void Insert(T const& element); + + // checks if an element of this value already exists on the queue, returns + // true if it does + bool Check(T const element); + + // Returns the count of elements in the queue + int Count() const { return m_heap.Count(); } + + // Is element index valid? + bool IsIdxValid(int i) const; + + // doesn't deallocate memory + void RemoveAll() { m_heap.RemoveAll(); } + + // Memory deallocation + void Purge() { m_heap.Purge(); } + + protected: + CUtlVector m_heap; + T m_current; +}; + +//----------------------------------------------------------------------------- +// The CUtlQueueFixed class: +// A queue class with a fixed allocation scheme +//----------------------------------------------------------------------------- +template +class CUtlQueueFixed : public CUtlQueue> { + typedef CUtlQueue> BaseClass; + + public: + // constructor, destructor + CUtlQueueFixed(int growSize = 0, int initSize = 0) + : BaseClass(growSize, initSize) {} + CUtlQueueFixed(T* pMemory, int numElements) + : BaseClass(pMemory, numElements) {} +}; + +template +inline CUtlQueue::CUtlQueue(int growSize, int initSize) + : m_heap(growSize, initSize) {} + +template +inline CUtlQueue::CUtlQueue(T* pMemory, int numElements) + : m_heap(pMemory, numElements) {} + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- + +template +inline T& CUtlQueue::operator[](int i) { + return m_heap[i]; +} + +template +inline T const& CUtlQueue::operator[](int i) const { + return m_heap[i]; +} + +template +inline T& CUtlQueue::Element(int i) { + return m_heap[i]; +} + +template +inline T const& CUtlQueue::Element(int i) const { + return m_heap[i]; +} + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- + +template +inline bool CUtlQueue::IsIdxValid(int i) const { + return (i >= 0) && (i < m_heap.Count()); +} + +template +inline T const& CUtlQueue::RemoveAtHead() { + m_current = m_heap[0]; + m_heap.Remove((int)0); + return m_current; +} + +template +inline T const& CUtlQueue::RemoveAtTail() { + m_current = m_heap[m_heap.Count() - 1]; + m_heap.Remove((int)(m_heap.Count() - 1)); + return m_current; +} + +template +inline T const& CUtlQueue::Head() { + m_current = m_heap[0]; + return m_current; +} + +template +inline T const& CUtlQueue::Tail() { + m_current = m_heap[m_heap.Count() - 1]; + return m_current; +} + +template +void CUtlQueue::Insert(T const& element) { + int index = m_heap.AddToTail(); + m_heap[index] = element; +} + +template +bool CUtlQueue::Check(T const element) { + int index = m_heap.Find(element); + return (index != -1); +} + +#endif // VPC_TIER1_UTLQUEUE_H_ diff --git a/public/tier1/utlrbtree.h b/public/tier1/utlrbtree.h new file mode 100644 index 0000000..7a69995 --- /dev/null +++ b/public/tier1/utlrbtree.h @@ -0,0 +1,1380 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_UTLRBTREE_H_ +#define VPC_TIER1_UTLRBTREE_H_ + +#include "tier1/utlmemory.h" +#include "tier1/utlfixedmemory.h" +#include "tier1/utlblockmemory.h" + +// This is a useful macro to iterate from start to end in order in a map +#define FOR_EACH_UTLRBTREE(treeName, iteratorName) \ + for (int iteratorName = treeName.FirstInorder(); \ + iteratorName != treeName.InvalidIndex(); \ + iteratorName = treeName.NextInorder(iteratorName)) + +// Tool to generate a default compare function for any type that implements +// operator<, including all simple types +template +class CDefOps { + public: + static bool LessFunc(const T &lhs, const T &rhs) { return (lhs < rhs); } +}; + +#define DefLessFunc(type) CDefOps::LessFunc + +inline bool StringLessThan(const char *const &lhs, const char *const &rhs) { + if (!lhs) return false; + if (!rhs) return true; + return (strcmp(lhs, rhs) < 0); +} + +inline bool CaselessStringLessThan(const char *const &lhs, + const char *const &rhs) { + if (!lhs) return false; + if (!rhs) return true; + return (_stricmp(lhs, rhs) < 0); +} + +// Same as CaselessStringLessThan, but it ignores differences in / and \. +inline bool CaselessStringLessThanIgnoreSlashes(const char *const &lhs, + const char *const &rhs) { + const char *pa = lhs; + const char *pb = rhs; + while (*pa && *pb) { + char a = *pa; + char b = *pb; + + // Check for dir slashes. + if (a == '/' || a == '\\') { + if (b != '/' && b != '\\') return ('/' < b); + } else { + if (a >= 'a' && a <= 'z') a = 'A' + (a - 'a'); + + if (b >= 'a' && b <= 'z') b = 'A' + (b - 'a'); + + if (a > b) + return false; + else if (a < b) + return true; + } + ++pa; + ++pb; + } + + // Filenames also must be the same length. + if (*pa != *pb) { + // If pa shorter than pb then it's "less" + return (!*pa); + } + + return false; +} + +//------------------------------------- +// inline these two templates to stop multiple definitions of the same code +template <> +inline bool CDefOps::LessFunc(const char *const &lhs, + const char *const &rhs) { + return StringLessThan(lhs, rhs); +} +template <> +inline bool CDefOps::LessFunc(char *const &lhs, char *const &rhs) { + return StringLessThan(lhs, rhs); +} + +//------------------------------------- + +template +void SetDefLessFunc(RBTREE_T &RBTree) { + RBTree.SetLessFunc(DefLessFunc(typename RBTREE_T::KeyType_t)); +} + +//----------------------------------------------------------------------------- +// A red-black binary search tree +//----------------------------------------------------------------------------- + +template +struct UtlRBTreeLinks_t { + I m_Left; + I m_Right; + I m_Parent; + I m_Tag; +}; + +template +struct UtlRBTreeNode_t : public UtlRBTreeLinks_t { + T m_Data; +}; + +template , I>> +class CUtlRBTree { + public: + typedef T KeyType_t; + typedef T ElemType_t; + typedef I IndexType_t; + + // Less func typedef + // Returns true if the first parameter is "less" than the second + typedef L LessFunc_t; + + // constructor, destructor + // Left at growSize = 0, the memory will first allocate 1 element and double + // in size at each increment. LessFunc_t is required, but may be set after the + // constructor using SetLessFunc() below + CUtlRBTree(int growSize = 0, int initSize = 0, + const LessFunc_t &lessfunc = 0); + CUtlRBTree(const LessFunc_t &lessfunc); + ~CUtlRBTree(); + + void EnsureCapacity(int num); + + // NOTE: CopyFrom is fast but dangerous! It just memcpy's all nodes - it does + // NOT run copy constructors, so + // it is not a true deep copy (i.e 'T' must be POD for this to work - + // e.g CUtlString will not work). + void CopyFrom(const CUtlRBTree &other); + + // gets particular elements + T &Element(I i); + T const &Element(I i) const; + T &operator[](I i); + T const &operator[](I i) const; + + // Gets the root + I Root() const; + + // Num elements + unsigned int Count() const; + + // Max "size" of the vector + // it's not generally safe to iterate from index 0 to MaxElement()-1 (you + // could do this as a potential iteration optimization, IF CUtlMemory is the + // allocator, and IF IsValidIndex() is tested for each element... + // but this should be implemented inside the CUtlRBTree iteration API, if + // anywhere) + I MaxElement() const; + + // Gets the children + I Parent(I i) const; + I LeftChild(I i) const; + I RightChild(I i) const; + + // Tests if a node is a left or right child + bool IsLeftChild(I i) const; + bool IsRightChild(I i) const; + + // Tests if root or leaf + bool IsRoot(I i) const; + bool IsLeaf(I i) const; + + // Checks if a node is valid and in the tree + bool IsValidIndex(I i) const; + + // Checks if the tree as a whole is valid + bool IsValid() const; + + // Invalid index + static I InvalidIndex(); + + // returns the tree depth (not a very fast operation) + int Depth(I node) const; + int Depth() const; + + // Sets the less func + void SetLessFunc(const LessFunc_t &func); + + // Allocation method + I NewNode(); + + // Insert method (inserts in order) + // NOTE: the returned 'index' will be valid as long as the element remains in + // the tree + // (other elements being added/removed will not affect it) + I Insert(T const &insert); + void Insert(const T *pArray, int nItems); + I InsertIfNotFound(T const &insert); + + // Find method + I Find(T const &search) const; + + // Remove methods + void RemoveAt(I i); + bool Remove(T const &remove); + void RemoveAll(); + void Purge(); + + // Allocation, deletion + void FreeNode(I i); + + // Iteration + I FirstInorder() const; + I NextInorder(I i) const; + I PrevInorder(I i) const; + I LastInorder() const; + + I FirstPreorder() const; + I NextPreorder(I i) const; + I PrevPreorder(I i) const; + I LastPreorder() const; + + I FirstPostorder() const; + I NextPostorder(I i) const; + + // If you change the search key, this can be used to reinsert the + // element into the tree. + void Reinsert(I elem); + + // swap in place + void Swap(CUtlRBTree &that); + + private: + // Can't copy the tree this way! + CUtlRBTree &operator=(const CUtlRBTree &other); + + protected: + enum NodeColor_t { RED = 0, BLACK }; + + typedef UtlRBTreeNode_t Node_t; + typedef UtlRBTreeLinks_t Links_t; + + // Sets the children + void SetParent(I i, I parent); + void SetLeftChild(I i, I child); + void SetRightChild(I i, I child); + void LinkToParent(I i, I parent, bool isLeft); + + // Gets at the links + Links_t const &Links(I i) const; + Links_t &Links(I i); + + // Checks if a link is red or black + bool IsRed(I i) const; + bool IsBlack(I i) const; + + // Sets/gets node color + NodeColor_t Color(I i) const; + void SetColor(I i, NodeColor_t c); + + // operations required to preserve tree balance + void RotateLeft(I i); + void RotateRight(I i); + void InsertRebalance(I i); + void RemoveRebalance(I i); + + // Insertion, removal + I InsertAt(I parent, bool leftchild); + + // copy constructors not allowed + CUtlRBTree(CUtlRBTree const &tree); + + // Inserts a node into the tree, doesn't copy the data in. + void FindInsertionPosition(T const &insert, I &parent, bool &leftchild); + + // Remove and add back an element in the tree. + void Unlink(I elem); + void Link(I elem); + + // Used for sorting. + LessFunc_t m_LessFunc; + + M m_Elements; + I m_Root; + I m_NumElements; + I m_FirstFree; + typename M::Iterator_t m_LastAlloc; // the last index allocated + + Node_t *m_pElements; + + FORCEINLINE M const &Elements(void) const { return m_Elements; } + + void ResetDbgInfo() { m_pElements = (Node_t *)m_Elements.Base(); } +}; + +// this is kind of ugly, but until C++ gets templatized typedefs in C++0x, it's +// our only choice +template +class CUtlFixedRBTree + : public CUtlRBTree>> { + public: + typedef L LessFunc_t; + + CUtlFixedRBTree(int growSize = 0, int initSize = 0, + const LessFunc_t &lessfunc = 0) + : CUtlRBTree>>( + growSize, initSize, lessfunc) {} + CUtlFixedRBTree(const LessFunc_t &lessfunc) + : CUtlRBTree>>(lessfunc) {} + + typedef CUtlRBTree>> BaseClass; + bool IsValidIndex(I i) const { + if (!BaseClass::Elements().IsIdxValid(i)) return false; + +#ifdef _DEBUG // it's safe to skip this here, since the only way to get indices + // after m_LastAlloc is to use MaxElement() + if (BaseClass::Elements().IsIdxAfter(i, this->m_LastAlloc)) { + Assert(0); + return false; // don't read values that have been allocated, but not + // constructed + } +#endif + + return LeftChild(i) != i; + } + + protected: + void ResetDbgInfo() {} + + private: + // this doesn't make sense for fixed rbtrees, since there's no useful max + // pointer, and the index space isn't contiguous anyways + I MaxElement() const; +}; + +// this is kind of ugly, but until C++ gets templatized typedefs in C++0x, it's +// our only choice +template +class CUtlBlockRBTree + : public CUtlRBTree, I>> { + public: + typedef L LessFunc_t; + CUtlBlockRBTree(int growSize = 0, int initSize = 0, + const LessFunc_t &lessfunc = 0) + : CUtlRBTree, I>>( + growSize, initSize, lessfunc) {} + CUtlBlockRBTree(const LessFunc_t &lessfunc) + : CUtlRBTree, I>>( + lessfunc) {} + + protected: + void ResetDbgInfo() {} +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +inline CUtlRBTree::CUtlRBTree(int growSize, int initSize, + const LessFunc_t &lessfunc) + : m_LessFunc(lessfunc), + m_Elements(growSize, initSize), + m_Root(InvalidIndex()), + m_NumElements(0), + m_FirstFree(InvalidIndex()), + m_LastAlloc(m_Elements.InvalidIterator()) { + ResetDbgInfo(); +} + +template +inline CUtlRBTree::CUtlRBTree(const LessFunc_t &lessfunc) + : m_Elements((intp)0, (intp)0), + m_LessFunc(lessfunc), + m_Root(InvalidIndex()), + m_NumElements(0), + m_FirstFree(InvalidIndex()), + m_LastAlloc(m_Elements.InvalidIterator()) { + ResetDbgInfo(); +} + +template +inline CUtlRBTree::~CUtlRBTree() { + Purge(); +} + +template +inline void CUtlRBTree::EnsureCapacity(int num) { + m_Elements.EnsureCapacity(num); +} + +template +inline void CUtlRBTree::CopyFrom( + const CUtlRBTree &other) { + Purge(); + m_Elements.EnsureCapacity(other.m_Elements.Count()); + memcpy(m_Elements.Base(), other.m_Elements.Base(), + other.m_Elements.Count() * sizeof(UtlRBTreeNode_t)); + m_LessFunc = other.m_LessFunc; + m_Root = other.m_Root; + m_NumElements = other.m_NumElements; + m_FirstFree = other.m_FirstFree; + m_LastAlloc = other.m_LastAlloc; + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- + +template +inline T &CUtlRBTree::Element(I i) { + return m_Elements[i].m_Data; +} + +template +inline T const &CUtlRBTree::Element(I i) const { + return m_Elements[i].m_Data; +} + +template +inline T &CUtlRBTree::operator[](I i) { + return Element(i); +} + +template +inline T const &CUtlRBTree::operator[](I i) const { + return Element(i); +} + +//----------------------------------------------------------------------------- +// +// various accessors +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Gets the root +//----------------------------------------------------------------------------- + +template +inline I CUtlRBTree::Root() const { + return m_Root; +} + +//----------------------------------------------------------------------------- +// Num elements +//----------------------------------------------------------------------------- + +template +inline unsigned int CUtlRBTree::Count() const { + return (unsigned int)m_NumElements; +} + +//----------------------------------------------------------------------------- +// Max "size" of the vector +//----------------------------------------------------------------------------- + +template +inline I CUtlRBTree::MaxElement() const { + return (I)m_Elements.NumAllocated(); +} + +//----------------------------------------------------------------------------- +// Gets the children +//----------------------------------------------------------------------------- + +template +inline I CUtlRBTree::Parent(I i) const { + return Links(i).m_Parent; +} + +template +inline I CUtlRBTree::LeftChild(I i) const { + return Links(i).m_Left; +} + +template +inline I CUtlRBTree::RightChild(I i) const { + return Links(i).m_Right; +} + +//----------------------------------------------------------------------------- +// Tests if a node is a left or right child +//----------------------------------------------------------------------------- + +template +inline bool CUtlRBTree::IsLeftChild(I i) const { + return LeftChild(Parent(i)) == i; +} + +template +inline bool CUtlRBTree::IsRightChild(I i) const { + return RightChild(Parent(i)) == i; +} + +//----------------------------------------------------------------------------- +// Tests if root or leaf +//----------------------------------------------------------------------------- + +template +inline bool CUtlRBTree::IsRoot(I i) const { + return i == m_Root; +} + +template +inline bool CUtlRBTree::IsLeaf(I i) const { + return (LeftChild(i) == InvalidIndex()) && (RightChild(i) == InvalidIndex()); +} + +//----------------------------------------------------------------------------- +// Checks if a node is valid and in the tree +//----------------------------------------------------------------------------- + +template +inline bool CUtlRBTree::IsValidIndex(I i) const { + if (!m_Elements.IsIdxValid(i)) return false; + + if (m_Elements.IsIdxAfter(i, m_LastAlloc)) + return false; // don't read values that have been allocated, but not + // constructed + + return LeftChild(i) != i; +} + +//----------------------------------------------------------------------------- +// Invalid index +//----------------------------------------------------------------------------- + +template +inline I CUtlRBTree::InvalidIndex() { + return (I)M::InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// returns the tree depth (not a very fast operation) +//----------------------------------------------------------------------------- + +template +inline int CUtlRBTree::Depth() const { + return Depth(Root()); +} + +//----------------------------------------------------------------------------- +// Sets the children +//----------------------------------------------------------------------------- + +template +inline void CUtlRBTree::SetParent(I i, I parent) { + Links(i).m_Parent = parent; +} + +template +inline void CUtlRBTree::SetLeftChild(I i, I child) { + Links(i).m_Left = child; +} + +template +inline void CUtlRBTree::SetRightChild(I i, I child) { + Links(i).m_Right = child; +} + +//----------------------------------------------------------------------------- +// Gets at the links +//----------------------------------------------------------------------------- + +template +inline typename CUtlRBTree::Links_t const & +CUtlRBTree::Links(I i) const { + // Sentinel node, makes life easier + static Links_t s_Sentinel = {InvalidIndex(), InvalidIndex(), InvalidIndex(), + CUtlRBTree::BLACK}; + + return (i != InvalidIndex()) ? *(Links_t *)&m_Elements[i] + : *(Links_t *)&s_Sentinel; +} + +template +inline typename CUtlRBTree::Links_t &CUtlRBTree::Links( + I i) { + Assert(i != InvalidIndex()); + return *(Links_t *)&m_Elements[i]; +} + +//----------------------------------------------------------------------------- +// Checks if a link is red or black +//----------------------------------------------------------------------------- + +template +inline bool CUtlRBTree::IsRed(I i) const { + return (Links(i).m_Tag == RED); +} + +template +inline bool CUtlRBTree::IsBlack(I i) const { + return (Links(i).m_Tag == BLACK); +} + +//----------------------------------------------------------------------------- +// Sets/gets node color +//----------------------------------------------------------------------------- + +template +inline typename CUtlRBTree::NodeColor_t +CUtlRBTree::Color(I i) const { + return (NodeColor_t)Links(i).m_Tag; +} + +template +inline void CUtlRBTree::SetColor( + I i, typename CUtlRBTree::NodeColor_t c) { + Links(i).m_Tag = (I)c; +} + +//----------------------------------------------------------------------------- +// Allocates/ deallocates nodes +//----------------------------------------------------------------------------- +template +I CUtlRBTree::NewNode() { + I elem; + + // Nothing in the free list; add. + if (m_FirstFree == InvalidIndex()) { + Assert(m_Elements.IsValidIterator(m_LastAlloc) || m_NumElements == 0); + typename M::Iterator_t it = m_Elements.IsValidIterator(m_LastAlloc) + ? m_Elements.Next(m_LastAlloc) + : m_Elements.First(); + if (!m_Elements.IsValidIterator(it)) { + MEM_ALLOC_CREDIT_CLASS(); + m_Elements.Grow(); + + it = m_Elements.IsValidIterator(m_LastAlloc) + ? m_Elements.Next(m_LastAlloc) + : m_Elements.First(); + + Assert(m_Elements.IsValidIterator(it)); + if (!m_Elements.IsValidIterator(it)) { + Error("CUtlRBTree overflow!\n"); + } + } + m_LastAlloc = it; + elem = m_Elements.GetIndex(m_LastAlloc); + Assert(m_Elements.IsValidIterator(m_LastAlloc)); + } else { + elem = m_FirstFree; + m_FirstFree = Links(m_FirstFree).m_Right; + } + +#ifdef _DEBUG + // reset links to invalid.... + Links_t &node = Links(elem); + node.m_Left = node.m_Right = node.m_Parent = InvalidIndex(); +#endif + + Construct(&Element(elem)); + ResetDbgInfo(); + + return elem; +} + +template +void CUtlRBTree::FreeNode(I i) { + Assert(IsValidIndex(i) && (i != InvalidIndex())); + Destruct(&Element(i)); + SetLeftChild(i, i); // indicates it's in not in the tree + SetRightChild(i, m_FirstFree); + m_FirstFree = i; +} + +//----------------------------------------------------------------------------- +// Rotates node i to the left +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::RotateLeft(I elem) { + I rightchild = RightChild(elem); + SetRightChild(elem, LeftChild(rightchild)); + if (LeftChild(rightchild) != InvalidIndex()) + SetParent(LeftChild(rightchild), elem); + + if (rightchild != InvalidIndex()) SetParent(rightchild, Parent(elem)); + if (!IsRoot(elem)) { + if (IsLeftChild(elem)) + SetLeftChild(Parent(elem), rightchild); + else + SetRightChild(Parent(elem), rightchild); + } else + m_Root = rightchild; + + SetLeftChild(rightchild, elem); + if (elem != InvalidIndex()) SetParent(elem, rightchild); +} + +//----------------------------------------------------------------------------- +// Rotates node i to the right +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::RotateRight(I elem) { + I leftchild = LeftChild(elem); + SetLeftChild(elem, RightChild(leftchild)); + if (RightChild(leftchild) != InvalidIndex()) + SetParent(RightChild(leftchild), elem); + + if (leftchild != InvalidIndex()) SetParent(leftchild, Parent(elem)); + if (!IsRoot(elem)) { + if (IsRightChild(elem)) + SetRightChild(Parent(elem), leftchild); + else + SetLeftChild(Parent(elem), leftchild); + } else + m_Root = leftchild; + + SetRightChild(leftchild, elem); + if (elem != InvalidIndex()) SetParent(elem, leftchild); +} + +//----------------------------------------------------------------------------- +// Rebalances the tree after an insertion +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::InsertRebalance(I elem) { + while (!IsRoot(elem) && (Color(Parent(elem)) == RED)) { + I parent = Parent(elem); + I grandparent = Parent(parent); + + /* we have a violation */ + if (IsLeftChild(parent)) { + I uncle = RightChild(grandparent); + if (IsRed(uncle)) { + /* uncle is RED */ + SetColor(parent, BLACK); + SetColor(uncle, BLACK); + SetColor(grandparent, RED); + elem = grandparent; + } else { + /* uncle is BLACK */ + if (IsRightChild(elem)) { + /* make x a left child, will change parent and grandparent */ + elem = parent; + RotateLeft(elem); + parent = Parent(elem); + grandparent = Parent(parent); + } + /* recolor and rotate */ + SetColor(parent, BLACK); + SetColor(grandparent, RED); + RotateRight(grandparent); + } + } else { + /* mirror image of above code */ + I uncle = LeftChild(grandparent); + if (IsRed(uncle)) { + /* uncle is RED */ + SetColor(parent, BLACK); + SetColor(uncle, BLACK); + SetColor(grandparent, RED); + elem = grandparent; + } else { + /* uncle is BLACK */ + if (IsLeftChild(elem)) { + /* make x a right child, will change parent and grandparent */ + elem = parent; + RotateRight(parent); + parent = Parent(elem); + grandparent = Parent(parent); + } + /* recolor and rotate */ + SetColor(parent, BLACK); + SetColor(grandparent, RED); + RotateLeft(grandparent); + } + } + } + SetColor(m_Root, BLACK); +} + +//----------------------------------------------------------------------------- +// Insert a node into the tree +//----------------------------------------------------------------------------- + +template +I CUtlRBTree::InsertAt(I parent, bool leftchild) { + I i = NewNode(); + LinkToParent(i, parent, leftchild); + ++m_NumElements; + + Assert(IsValid()); + + return i; +} + +template +void CUtlRBTree::LinkToParent(I i, I parent, bool isLeft) { + Links_t &elem = Links(i); + elem.m_Parent = parent; + elem.m_Left = elem.m_Right = InvalidIndex(); + elem.m_Tag = RED; + + /* insert node in tree */ + if (parent != InvalidIndex()) { + if (isLeft) + Links(parent).m_Left = i; + else + Links(parent).m_Right = i; + } else { + m_Root = i; + } + + InsertRebalance(i); +} + +//----------------------------------------------------------------------------- +// Rebalance the tree after a deletion +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::RemoveRebalance(I elem) { + while (elem != m_Root && IsBlack(elem)) { + I parent = Parent(elem); + + // If elem is the left child of the parent + if (elem == LeftChild(parent)) { + // Get our sibling + I sibling = RightChild(parent); + if (IsRed(sibling)) { + SetColor(sibling, BLACK); + SetColor(parent, RED); + RotateLeft(parent); + + // We may have a new parent now + parent = Parent(elem); + sibling = RightChild(parent); + } + if ((IsBlack(LeftChild(sibling))) && (IsBlack(RightChild(sibling)))) { + if (sibling != InvalidIndex()) SetColor(sibling, RED); + elem = parent; + } else { + if (IsBlack(RightChild(sibling))) { + SetColor(LeftChild(sibling), BLACK); + SetColor(sibling, RED); + RotateRight(sibling); + + // rotation may have changed this + parent = Parent(elem); + sibling = RightChild(parent); + } + SetColor(sibling, Color(parent)); + SetColor(parent, BLACK); + SetColor(RightChild(sibling), BLACK); + RotateLeft(parent); + elem = m_Root; + } + } else { + // Elem is the right child of the parent + I sibling = LeftChild(parent); + if (IsRed(sibling)) { + SetColor(sibling, BLACK); + SetColor(parent, RED); + RotateRight(parent); + + // We may have a new parent now + parent = Parent(elem); + sibling = LeftChild(parent); + } + if ((IsBlack(RightChild(sibling))) && (IsBlack(LeftChild(sibling)))) { + if (sibling != InvalidIndex()) SetColor(sibling, RED); + elem = parent; + } else { + if (IsBlack(LeftChild(sibling))) { + SetColor(RightChild(sibling), BLACK); + SetColor(sibling, RED); + RotateLeft(sibling); + + // rotation may have changed this + parent = Parent(elem); + sibling = LeftChild(parent); + } + SetColor(sibling, Color(parent)); + SetColor(parent, BLACK); + SetColor(LeftChild(sibling), BLACK); + RotateRight(parent); + elem = m_Root; + } + } + } + SetColor(elem, BLACK); +} + +template +void CUtlRBTree::Unlink(I elem) { + if (elem != InvalidIndex()) { + I x, y; + + if ((LeftChild(elem) == InvalidIndex()) || + (RightChild(elem) == InvalidIndex())) { + /* y has a NIL node as a child */ + y = elem; + } else { + /* find tree successor with a NIL node as a child */ + y = RightChild(elem); + while (LeftChild(y) != InvalidIndex()) y = LeftChild(y); + } + + /* x is y's only child */ + if (LeftChild(y) != InvalidIndex()) + x = LeftChild(y); + else + x = RightChild(y); + + /* remove y from the parent chain */ + if (x != InvalidIndex()) SetParent(x, Parent(y)); + if (!IsRoot(y)) { + if (IsLeftChild(y)) + SetLeftChild(Parent(y), x); + else + SetRightChild(Parent(y), x); + } else + m_Root = x; + + // need to store this off now, we'll be resetting y's color + NodeColor_t ycolor = Color(y); + if (y != elem) { + // Standard implementations copy the data around, we cannot here. + // Hook in y to link to the same stuff elem used to. + SetParent(y, Parent(elem)); + SetRightChild(y, RightChild(elem)); + SetLeftChild(y, LeftChild(elem)); + + if (!IsRoot(elem)) + if (IsLeftChild(elem)) + SetLeftChild(Parent(elem), y); + else + SetRightChild(Parent(elem), y); + else + m_Root = y; + + if (LeftChild(y) != InvalidIndex()) SetParent(LeftChild(y), y); + if (RightChild(y) != InvalidIndex()) SetParent(RightChild(y), y); + + SetColor(y, Color(elem)); + } + + if ((x != InvalidIndex()) && (ycolor == BLACK)) RemoveRebalance(x); + } +} + +template +void CUtlRBTree::Link(I elem) { + if (elem != InvalidIndex()) { + I parent = InvalidIndex(); + bool leftchild = false; + + FindInsertionPosition(Element(elem), parent, leftchild); + + LinkToParent(elem, parent, leftchild); + + Assert(IsValid()); + } +} + +//----------------------------------------------------------------------------- +// Delete a node from the tree +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::RemoveAt(I elem) { + if (elem != InvalidIndex()) { + Unlink(elem); + + FreeNode(elem); + --m_NumElements; + + Assert(IsValid()); + } +} + +//----------------------------------------------------------------------------- +// remove a node in the tree +//----------------------------------------------------------------------------- + +template +bool CUtlRBTree::Remove(T const &search) { + I node = Find(search); + if (node != InvalidIndex()) { + RemoveAt(node); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Removes all nodes from the tree +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::RemoveAll() { + // Have to do some convoluted stuff to invoke the destructor on all + // valid elements for the multilist case (since we don't have all elements + // connected to each other in a list). + + if (m_LastAlloc == m_Elements.InvalidIterator()) { + Assert(m_Root == InvalidIndex()); + Assert(m_FirstFree == InvalidIndex()); + Assert(m_NumElements == 0); + return; + } + + for (typename M::Iterator_t it = m_Elements.First(); + it != m_Elements.InvalidIterator(); it = m_Elements.Next(it)) { + I i = m_Elements.GetIndex(it); + if (IsValidIndex(i)) // skip elements in the free list + { + Destruct(&Element(i)); + SetRightChild(i, m_FirstFree); + SetLeftChild(i, i); + m_FirstFree = i; + } + + if (it == m_LastAlloc) + break; // don't destruct elements that haven't ever been constucted + } + + // Clear everything else out + m_Root = InvalidIndex(); + m_NumElements = 0; + + Assert(IsValid()); +} + +//----------------------------------------------------------------------------- +// Removes all nodes from the tree and purges memory +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::Purge() { + RemoveAll(); + m_FirstFree = InvalidIndex(); + m_Elements.Purge(); + m_LastAlloc = m_Elements.InvalidIterator(); +} + +//----------------------------------------------------------------------------- +// iteration +//----------------------------------------------------------------------------- + +template +I CUtlRBTree::FirstInorder() const { + I i = m_Root; + while (LeftChild(i) != InvalidIndex()) i = LeftChild(i); + return i; +} + +template +I CUtlRBTree::NextInorder(I i) const { + Assert(IsValidIndex(i)); + + if (RightChild(i) != InvalidIndex()) { + i = RightChild(i); + while (LeftChild(i) != InvalidIndex()) i = LeftChild(i); + return i; + } + + I parent = Parent(i); + while (IsRightChild(i)) { + i = parent; + if (i == InvalidIndex()) break; + parent = Parent(i); + } + return parent; +} + +template +I CUtlRBTree::PrevInorder(I i) const { + Assert(IsValidIndex(i)); + + if (LeftChild(i) != InvalidIndex()) { + i = LeftChild(i); + while (RightChild(i) != InvalidIndex()) i = RightChild(i); + return i; + } + + I parent = Parent(i); + while (IsLeftChild(i)) { + i = parent; + if (i == InvalidIndex()) break; + parent = Parent(i); + } + return parent; +} + +template +I CUtlRBTree::LastInorder() const { + I i = m_Root; + while (RightChild(i) != InvalidIndex()) i = RightChild(i); + return i; +} + +template +I CUtlRBTree::FirstPreorder() const { + return m_Root; +} + +template +I CUtlRBTree::NextPreorder(I i) const { + if (LeftChild(i) != InvalidIndex()) return LeftChild(i); + + if (RightChild(i) != InvalidIndex()) return RightChild(i); + + I parent = Parent(i); + while (parent != InvalidIndex()) { + if (IsLeftChild(i) && (RightChild(parent) != InvalidIndex())) + return RightChild(parent); + i = parent; + parent = Parent(parent); + } + return InvalidIndex(); +} + +template +I CUtlRBTree::PrevPreorder(I i) const { + Assert(0); // not implemented yet + return InvalidIndex(); +} + +template +I CUtlRBTree::LastPreorder() const { + I i = m_Root; + while (1) { + while (RightChild(i) != InvalidIndex()) i = RightChild(i); + + if (LeftChild(i) != InvalidIndex()) + i = LeftChild(i); + else + break; + } + return i; +} + +template +I CUtlRBTree::FirstPostorder() const { + I i = m_Root; + while (!IsLeaf(i)) { + if (LeftChild(i)) + i = LeftChild(i); + else + i = RightChild(i); + } + return i; +} + +template +I CUtlRBTree::NextPostorder(I i) const { + I parent = Parent(i); + if (parent == InvalidIndex()) return InvalidIndex(); + + if (IsRightChild(i)) return parent; + + if (RightChild(parent) == InvalidIndex()) return parent; + + i = RightChild(parent); + while (!IsLeaf(i)) { + if (LeftChild(i)) + i = LeftChild(i); + else + i = RightChild(i); + } + return i; +} + +template +void CUtlRBTree::Reinsert(I elem) { + Unlink(elem); + Link(elem); +} + +//----------------------------------------------------------------------------- +// returns the tree depth (not a very fast operation) +//----------------------------------------------------------------------------- + +template +int CUtlRBTree::Depth(I node) const { + if (node == InvalidIndex()) return 0; + + int depthright = Depth(RightChild(node)); + int depthleft = Depth(LeftChild(node)); + return MAX(depthright, depthleft) + 1; +} + +//#define UTLTREE_PARANOID + +//----------------------------------------------------------------------------- +// Makes sure the tree is valid after every operation +//----------------------------------------------------------------------------- + +template +bool CUtlRBTree::IsValid() const { + if (!Count()) return true; + + if (m_LastAlloc == m_Elements.InvalidIterator()) return false; + + if (!m_Elements.IsIdxValid(Root())) return false; + + if (Parent(Root()) != InvalidIndex()) return false; + +#ifdef UTLTREE_PARANOID + + // First check to see that mNumEntries matches reality. + // count items on the free list + int numFree = 0; + for (int i = m_FirstFree; i != InvalidIndex(); i = RightChild(i)) { + ++numFree; + if (!m_Elements.IsIdxValid(i)) return false; + } + + // iterate over all elements, looking for validity + // based on the self pointers + int nElements = 0; + int numFree2 = 0; + for (M::Iterator_t it = m_Elements.First(); + it != m_Elements.InvalidIterator(); it = m_Elements.Next(it)) { + I i = m_Elements.GetIndex(it); + if (!IsValidIndex(i)) { + ++numFree2; + } else { + ++nElements; + + int right = RightChild(i); + int left = LeftChild(i); + if ((right == left) && (right != InvalidIndex())) return false; + + if (right != InvalidIndex()) { + if (!IsValidIndex(right)) return false; + if (Parent(right) != i) return false; + if (IsRed(i) && IsRed(right)) return false; + } + + if (left != InvalidIndex()) { + if (!IsValidIndex(left)) return false; + if (Parent(left) != i) return false; + if (IsRed(i) && IsRed(left)) return false; + } + } + + if (it == m_LastAlloc) break; + } + if (numFree2 != numFree) return false; + + if (nElements != m_NumElements) return false; + +#endif // UTLTREE_PARANOID + + return true; +} + +//----------------------------------------------------------------------------- +// Sets the less func +//----------------------------------------------------------------------------- + +template +void CUtlRBTree::SetLessFunc( + const typename CUtlRBTree::LessFunc_t &func) { + if (!m_LessFunc) { + m_LessFunc = func; + } else if (Count() > 0) { + // need to re-sort the tree here.... + Assert(0); + } +} + +//----------------------------------------------------------------------------- +// inserts a node into the tree +//----------------------------------------------------------------------------- + +// Inserts a node into the tree, doesn't copy the data in. +template +void CUtlRBTree::FindInsertionPosition(T const &insert, I &parent, + bool &leftchild) { + Assert(!!m_LessFunc); + + /* find where node belongs */ + I current = m_Root; + parent = InvalidIndex(); + leftchild = false; + while (current != InvalidIndex()) { + parent = current; + if (m_LessFunc(insert, Element(current))) { + leftchild = true; + current = LeftChild(current); + } else { + leftchild = false; + current = RightChild(current); + } + } +} + +template +I CUtlRBTree::Insert(T const &insert) { + // use copy constructor to copy it in + I parent = InvalidIndex(); + bool leftchild = false; + FindInsertionPosition(insert, parent, leftchild); + I newNode = InsertAt(parent, leftchild); + CopyConstruct(&Element(newNode), insert); + return newNode; +} + +template +void CUtlRBTree::Insert(const T *pArray, int nItems) { + while (nItems--) { + Insert(*pArray++); + } +} + +template +I CUtlRBTree::InsertIfNotFound(T const &insert) { + // use copy constructor to copy it in + I parent; + bool leftchild; + + I current = m_Root; + parent = InvalidIndex(); + leftchild = false; + while (current != InvalidIndex()) { + parent = current; + if (m_LessFunc(insert, Element(current))) { + leftchild = true; + current = LeftChild(current); + } else if (m_LessFunc(Element(current), insert)) { + leftchild = false; + current = RightChild(current); + } else + // Match found, no insertion + return InvalidIndex(); + } + + I newNode = InsertAt(parent, leftchild); + CopyConstruct(&Element(newNode), insert); + return newNode; +} + +//----------------------------------------------------------------------------- +// finds a node in the tree +//----------------------------------------------------------------------------- +template +I CUtlRBTree::Find(T const &search) const { + Assert(!!m_LessFunc); + + I current = m_Root; + while (current != InvalidIndex()) { + if (m_LessFunc(search, Element(current))) + current = LeftChild(current); + else if (m_LessFunc(Element(current), search)) + current = RightChild(current); + else + break; + } + return current; +} + +//----------------------------------------------------------------------------- +// swap in place +//----------------------------------------------------------------------------- +template +void CUtlRBTree::Swap(CUtlRBTree &that) { + m_Elements.Swap(that.m_Elements); + V_swap(m_LessFunc, that.m_LessFunc); + V_swap(m_Root, that.m_Root); + V_swap(m_NumElements, that.m_NumElements); + V_swap(m_FirstFree, that.m_FirstFree); + V_swap(m_pElements, that.m_pElements); + V_swap(m_LastAlloc, that.m_LastAlloc); + Assert(IsValid()); + Assert(m_Elements.IsValidIterator(m_LastAlloc) || + (m_NumElements == 0 && m_FirstFree == InvalidIndex())); +} + +#endif // VPC_TIER1_UTLRBTREE_H_ diff --git a/public/tier1/utlsortvector.h b/public/tier1/utlsortvector.h new file mode 100644 index 0000000..85f9615 --- /dev/null +++ b/public/tier1/utlsortvector.h @@ -0,0 +1,305 @@ +// Copyright Valve Corporation, All rights reserved. +// +// A growable array class that keeps all elements in order using binary search + +#ifndef VPC_TIER1_UTLSORTVECTOR_H_ +#define VPC_TIER1_UTLSORTVECTOR_H_ + +#include "utlvector.h" + +// This in an sorted order-preserving vector. Items may be inserted or removed +// at any point in the vector. When an item is inserted, all elements are +// moved down by one element using memmove. When an item is removed, all +// elements are shifted back down. Items are searched for in the vector +// using a binary search technique. Clients must pass in a Less() function +// into the constructor of the vector to determine the sort order. + +#ifndef _WIN32 +// gcc has no qsort_s, so i need to use a static var to hold the sort context. +// this makes cutlsortvector _not_ thread sfae under linux +extern void* g_pUtlSortVectorQSortContext; +#endif + +template +class CUtlSortVectorDefaultLess { + public: + bool Less(const T& lhs, const T& rhs, void*) { return lhs < rhs; } +}; + +template , + class BaseVector = CUtlVector> +class CUtlSortVector : public BaseVector { + public: + using BaseVector::Base; + using BaseVector::Count; + + // constructor + CUtlSortVector(intp nGrowSize = 0, intp initSize = 0); + CUtlSortVector(T* pMemory, intp numElements); + + // inserts (copy constructs) an element in sorted order into the list + intp Insert(const T& src); + + // Finds an element within the list using a binary search + intp Find(const T& search) const; + intp FindLessOrEqual(const T& search) const; + intp FindLess(const T& search) const; + template + intp FindAs(const K& key) const; + + // Removes a particular element + void Remove(const T& search); + void Remove(intp i); + + // Allows methods to set a context to be used with the less function.. + void SetLessContext(void* pCtx); + + // Note that you can only use this index until sorting is redone!!! + intp InsertNoSort(const T& src); + void RedoSort(bool bForceSort = false); + + protected: + // No copy constructor + CUtlSortVector(const CUtlSortVector&); + + // never call these; illegal for this class + int AddToHead(); + int AddToTail(); + int InsertBefore(int elem); + int InsertAfter(int elem); + int AddToHead(const T& src); + int AddToTail(const T& src); + int InsertBefore(int elem, const T& src); + int InsertAfter(int elem, const T& src); + int AddMultipleToHead(int num); + int AddMultipleToTail(int num, const T* pToCopy = NULL); + int InsertMultipleBefore(int elem, int num, const T* pToCopy = NULL); + int InsertMultipleAfter(int elem, int num); + int AddVectorToTail(CUtlVector const& src); + + struct QSortContext_t { + void* m_pLessContext; + LessFunc* m_pLessFunc; + }; + +#ifdef _WIN32 + static int CompareHelper(void* context, const T* lhs, const T* rhs) { + QSortContext_t* ctx = reinterpret_cast(context); + if (ctx->m_pLessFunc->Less(*lhs, *rhs, ctx->m_pLessContext)) return -1; + if (ctx->m_pLessFunc->Less(*rhs, *lhs, ctx->m_pLessContext)) return 1; + return 0; + } +#else + static int CompareHelper(const T* lhs, const T* rhs) { + QSortContext_t* ctx = + reinterpret_cast(g_pUtlSortVectorQSortContext); + if (ctx->m_pLessFunc->Less(*lhs, *rhs, ctx->m_pLessContext)) return -1; + if (ctx->m_pLessFunc->Less(*rhs, *lhs, ctx->m_pLessContext)) return 1; + return 0; + } +#endif + + void* m_pLessContext; + bool m_bNeedsSort; + + private: + void QuickSort(LessFunc& less, intp X, intp I); +}; + +//----------------------------------------------------------------------------- +// constructor +//----------------------------------------------------------------------------- +template +CUtlSortVector::CUtlSortVector(intp nGrowSize, + intp initSize) + : m_pLessContext(NULL), + BaseVector(nGrowSize, initSize), + m_bNeedsSort(false) {} + +template +CUtlSortVector::CUtlSortVector(T* pMemory, + intp numElements) + : m_pLessContext(NULL), + BaseVector(pMemory, numElements), + m_bNeedsSort(false) {} + +//----------------------------------------------------------------------------- +// Allows methods to set a context to be used with the less function.. +//----------------------------------------------------------------------------- +template +void CUtlSortVector::SetLessContext(void* pCtx) { + m_pLessContext = pCtx; +} + +//----------------------------------------------------------------------------- +// grows the vector +//----------------------------------------------------------------------------- +template +intp CUtlSortVector::Insert(const T& src) { + AssertFatal(!m_bNeedsSort); + + intp pos = FindLessOrEqual(src) + 1; + this->GrowVector(); + this->ShiftElementsRight(pos); + CopyConstruct(&this->Element(pos), src); + return pos; +} + +template +intp CUtlSortVector::InsertNoSort(const T& src) { + m_bNeedsSort = true; + intp lastElement = BaseVector::m_Size; + // Just stick the new element at the end of the vector, but don't do a sort + this->GrowVector(); + this->ShiftElementsRight(lastElement); + CopyConstruct(&this->Element(lastElement), src); + return lastElement; +} + +template +void CUtlSortVector::QuickSort(LessFunc& less, + intp nLower, + intp nUpper) { +#ifdef _WIN32 + typedef int(__cdecl * QSortCompareFunc_t)(void* context, const void*, + const void*); + if (this->Count() > 1) { + QSortContext_t ctx; + ctx.m_pLessContext = m_pLessContext; + ctx.m_pLessFunc = &less; + + qsort_s(Base(), Count(), sizeof(T), + (QSortCompareFunc_t)&CUtlSortVector::CompareHelper, + &ctx); + } +#else + typedef int(__cdecl * QSortCompareFunc_t)(const void*, const void*); + if (this->Count() > 1) { + QSortContext_t ctx; + ctx.m_pLessContext = m_pLessContext; + ctx.m_pLessFunc = &less; + g_pUtlSortVectorQSortContext = &ctx; + + qsort(this->Base(), this->Count(), sizeof(T), + (QSortCompareFunc_t)&CUtlSortVector::CompareHelper); + } +#endif +} + +template +void CUtlSortVector::RedoSort( + bool bForceSort /*= false */) { + if (!m_bNeedsSort && !bForceSort) return; + + m_bNeedsSort = false; + LessFunc less; + QuickSort(less, 0, this->Count() - 1); +} + +//----------------------------------------------------------------------------- +// finds a particular element +//----------------------------------------------------------------------------- +template +intp CUtlSortVector::Find(const T& src) const { + AssertFatal(!m_bNeedsSort); + + LessFunc less; + + intp start = 0, end = this->Count() - 1; + while (start <= end) { + intp mid = (start + end) >> 1; + if (less.Less(this->Element(mid), src, m_pLessContext)) { + start = mid + 1; + } else if (less.Less(src, this->Element(mid), m_pLessContext)) { + end = mid - 1; + } else { + return mid; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// finds a particular element +//----------------------------------------------------------------------------- +template +template +intp CUtlSortVector::FindAs(const K& key) const { + AssertFatal(!m_bNeedsSort); + + LessFunc less; + + intp start = 0, end = this->Count() - 1; + while (start <= end) { + intp mid = (start + end) >> 1; + intp nResult = less.Compare(this->Element(mid), key, m_pLessContext); + if (nResult < 0) { + start = mid + 1; + } else if (nResult > 0) { + end = mid - 1; + } else { + return mid; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// finds a particular element +//----------------------------------------------------------------------------- +template +intp CUtlSortVector::FindLessOrEqual( + const T& src) const { + AssertFatal(!m_bNeedsSort); + + LessFunc less; + intp start = 0, end = this->Count() - 1; + while (start <= end) { + intp mid = (start + end) >> 1; + if (less.Less(this->Element(mid), src, m_pLessContext)) { + start = mid + 1; + } else if (less.Less(src, this->Element(mid), m_pLessContext)) { + end = mid - 1; + } else { + return mid; + } + } + return end; +} + +template +intp CUtlSortVector::FindLess(const T& src) const { + AssertFatal(!m_bNeedsSort); + + LessFunc less; + intp start = 0, end = this->Count() - 1; + while (start <= end) { + intp mid = (start + end) >> 1; + if (less.Less(this->Element(mid), src, m_pLessContext)) { + start = mid + 1; + } else { + end = mid - 1; + } + } + return end; +} + +//----------------------------------------------------------------------------- +// Removes a particular element +//----------------------------------------------------------------------------- +template +void CUtlSortVector::Remove(const T& search) { + AssertFatal(!m_bNeedsSort); + + intp pos = Find(search); + if (pos != -1) { + BaseVector::Remove(pos); + } +} + +template +void CUtlSortVector::Remove(intp i) { + BaseVector::Remove(i); +} + +#endif // VPC_TIER1_UTLSORTVECTOR_H_ diff --git a/public/tier1/utlstack.h b/public/tier1/utlstack.h new file mode 100644 index 0000000..b7391d4 --- /dev/null +++ b/public/tier1/utlstack.h @@ -0,0 +1,291 @@ +// Copyright Valve Corporation, All rights reserved. +// +// A stack based on a growable array + +#ifndef VPC_TIER1_UTLSTACK_H_ +#define VPC_TIER1_UTLSTACK_H_ + +#include +#include + +#include "utlmemory.h" + +//----------------------------------------------------------------------------- +// The CUtlStack class: +// A growable stack class which doubles in size by default. +// It will always keep all elements consecutive in memory, and may move the +// elements around in memory (via a realloc) when elements are pushed or +// popped. Clients should therefore refer to the elements of the stack +// by index (they should *never* maintain pointers to elements in the stack). +//----------------------------------------------------------------------------- + +template > +class CUtlStack { + public: + // constructor, destructor + CUtlStack(int growSize = 0, int initSize = 0); + ~CUtlStack(); + + void CopyFrom(const CUtlStack& from); + + // element access + T& operator[](int i); + T const& operator[](int i) const; + T& Element(int i); + T const& Element(int i) const; + + // Gets the base address (can change when adding elements!) + T* Base(); + T const* Base() const; + + // Looks at the stack top + T& Top(); + T const& Top() const; + + // Size + int Count() const; + + // Is element index valid? + bool IsIdxValid(int i) const; + + // Adds an element, uses default constructor + int Push(); + + // Adds an element, uses copy constructor + int Push(T const& src); + + // Pops the stack + void Pop(); + void Pop(T& oldTop); + void PopMultiple(int num); + + // Makes sure we have enough memory allocated to store a requested # of + // elements + void EnsureCapacity(int num); + + // Clears the stack, no deallocation + void Clear(); + + // Memory deallocation + void Purge(); + + private: + // Grows the stack allocation + void GrowStack(); + + // For easier access to the elements through the debugger + void ResetDbgInfo(); + + M m_Memory; + int m_Size; + + // For easier access to the elements through the debugger + T* m_pElements; +}; + +//----------------------------------------------------------------------------- +// For easier access to the elements through the debugger +//----------------------------------------------------------------------------- + +template +inline void CUtlStack::ResetDbgInfo() { + m_pElements = m_Memory.Base(); +} + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlStack::CUtlStack(int growSize, int initSize) + : m_Memory(growSize, initSize), m_Size(0) { + ResetDbgInfo(); +} + +template +CUtlStack::~CUtlStack() { + Purge(); +} + +//----------------------------------------------------------------------------- +// copy into +//----------------------------------------------------------------------------- + +template +void CUtlStack::CopyFrom(const CUtlStack& from) { + Purge(); + EnsureCapacity(from.Count()); + for (int i = 0; i < from.Count(); i++) { + Push(from[i]); + } +} + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- + +template +inline T& CUtlStack::operator[](int i) { + assert(IsIdxValid(i)); + return m_Memory[i]; +} + +template +inline T const& CUtlStack::operator[](int i) const { + assert(IsIdxValid(i)); + return m_Memory[i]; +} + +template +inline T& CUtlStack::Element(int i) { + assert(IsIdxValid(i)); + return m_Memory[i]; +} + +template +inline T const& CUtlStack::Element(int i) const { + assert(IsIdxValid(i)); + return m_Memory[i]; +} + +//----------------------------------------------------------------------------- +// Gets the base address (can change when adding elements!) +//----------------------------------------------------------------------------- + +template +inline T* CUtlStack::Base() { + return m_Memory.Base(); +} + +template +inline T const* CUtlStack::Base() const { + return m_Memory.Base(); +} + +//----------------------------------------------------------------------------- +// Returns the top of the stack +//----------------------------------------------------------------------------- + +template +inline T& CUtlStack::Top() { + assert(m_Size > 0); + return Element(m_Size - 1); +} + +template +inline T const& CUtlStack::Top() const { + assert(m_Size > 0); + return Element(m_Size - 1); +} + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- + +template +inline int CUtlStack::Count() const { + return m_Size; +} + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- + +template +inline bool CUtlStack::IsIdxValid(int i) const { + return (i >= 0) && (i < m_Size); +} + +//----------------------------------------------------------------------------- +// Grows the stack +//----------------------------------------------------------------------------- + +template +void CUtlStack::GrowStack() { + if (m_Size >= m_Memory.NumAllocated()) m_Memory.Grow(); + + ++m_Size; + + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- + +template +void CUtlStack::EnsureCapacity(int num) { + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// Adds an element, uses default constructor +//----------------------------------------------------------------------------- + +template +int CUtlStack::Push() { + GrowStack(); + Construct(&Element(m_Size - 1)); + return m_Size - 1; +} + +//----------------------------------------------------------------------------- +// Adds an element, uses copy constructor +//----------------------------------------------------------------------------- + +template +int CUtlStack::Push(T const& src) { + GrowStack(); + CopyConstruct(&Element(m_Size - 1), src); + return m_Size - 1; +} + +//----------------------------------------------------------------------------- +// Pops the stack +//----------------------------------------------------------------------------- + +template +void CUtlStack::Pop() { + assert(m_Size > 0); + Destruct(&Element(m_Size - 1)); + --m_Size; +} + +template +void CUtlStack::Pop(T& oldTop) { + assert(m_Size > 0); + oldTop = Top(); + Pop(); +} + +template +void CUtlStack::PopMultiple(int num) { + assert(m_Size >= num); + for (int i = 0; i < num; ++i) Destruct(&Element(m_Size - i - 1)); + m_Size -= num; +} + +//----------------------------------------------------------------------------- +// Element removal +//----------------------------------------------------------------------------- + +template +void CUtlStack::Clear() { + for (int i = m_Size; --i >= 0;) Destruct(&Element(i)); + + m_Size = 0; +} + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- + +template +void CUtlStack::Purge() { + Clear(); + m_Memory.Purge(); + ResetDbgInfo(); +} + +#endif // VPC_TIER1_UTLSTACK_H_ diff --git a/public/tier1/utlstring.h b/public/tier1/utlstring.h new file mode 100644 index 0000000..5ab9b8b --- /dev/null +++ b/public/tier1/utlstring.h @@ -0,0 +1,372 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER1_UTLSTRING_H_ +#define VPC_TIER1_UTLSTRING_H_ + +#include + +#include "tier1/utlmemory.h" +#include "tier1/strtools.h" + +//----------------------------------------------------------------------------- +// Base class, containing simple memory management +//----------------------------------------------------------------------------- +class CUtlBinaryBlock { + public: + CUtlBinaryBlock(intp growSize = 0, intp initSize = 0); + ~CUtlBinaryBlock() { +#ifdef _DEBUG + m_nActualLength = 0x7BADF00D; +#else + m_nActualLength = 0; +#endif + } + + // NOTE: nInitialLength indicates how much of the buffer starts full + CUtlBinaryBlock(void *pMemory, intp nSizeInBytes, intp nInitialLength); + CUtlBinaryBlock(const void *pMemory, intp nSizeInBytes); + CUtlBinaryBlock(const CUtlBinaryBlock &src); + + void Get(void *pValue, intp nMaxLen) const; + void Set(const void *pValue, intp nLen); + const void *Get() const; + void *Get(); + + unsigned char &operator[](intp i); + const unsigned char &operator[](intp i) const; + + intp Length() const; + void SetLength(intp nLength); // Undefined memory will result + bool IsEmpty() const; + void Clear(); + void Purge(); + + bool IsReadOnly() const; + + CUtlBinaryBlock &operator=(const CUtlBinaryBlock &src); + + // Test for equality + bool operator==(const CUtlBinaryBlock &src) const; + + private: + CUtlMemory m_Memory; + intp m_nActualLength; +}; + +//----------------------------------------------------------------------------- +// class inlines +//----------------------------------------------------------------------------- +inline const void *CUtlBinaryBlock::Get() const { return m_Memory.Base(); } + +inline void *CUtlBinaryBlock::Get() { return m_Memory.Base(); } + +inline intp CUtlBinaryBlock::Length() const { return m_nActualLength; } + +inline unsigned char &CUtlBinaryBlock::operator[](intp i) { return m_Memory[i]; } + +inline const unsigned char &CUtlBinaryBlock::operator[](intp i) const { + return m_Memory[i]; +} + +inline bool CUtlBinaryBlock::IsReadOnly() const { + return m_Memory.IsReadOnly(); +} + +inline bool CUtlBinaryBlock::IsEmpty() const { return Length() == 0; } + +inline void CUtlBinaryBlock::Clear() { SetLength(0); } + +inline void CUtlBinaryBlock::Purge() { + SetLength(0); + m_Memory.Purge(); +} + +//----------------------------------------------------------------------------- +// Simple string class. +// NOTE: This is *not* optimal! Use in tools, but not runtime code +//----------------------------------------------------------------------------- +class CUtlString { + public: + typedef enum { + PATTERN_NONE = 0x00000000, + PATTERN_DIRECTORY = 0x00000001 + } TUtlStringPattern; + + public: + CUtlString(); + CUtlString(const char *pString); + CUtlString(const CUtlString &string); + + // Attaches the string to external memory. Useful for avoiding a copy + CUtlString(void *pMemory, intp nSizeInBytes, intp nInitialLength); + CUtlString(const void *pMemory, intp nSizeInBytes); + + const char *Get() const; + void Set(const char *pValue); + + void Clear() { Set(NULL); } + + // Converts to c-strings + operator const char *() const; + + // for compatibility switching items from UtlSymbol + const char *String() const { return Get(); } + + // Returns strlen + intp Length() const; + bool IsEmpty() const; + + // Sets the length (used to serialize into the buffer ) + // Note: If nLen != 0, then this adds an extra byte for a null-terminator. + void SetLength(intp nLen); + char *Get(); + void Purge(); + + // Case Change + void ToLower(); + + void Append(const char *pchAddition); + + // Strips the trailing slash + void StripTrailingSlash(); + + CUtlString &operator=(const CUtlString &src); + CUtlString &operator=(const char *src); + + // Test for equality + bool operator==(const CUtlString &src) const; + bool operator==(const char *src) const; + bool operator!=(const CUtlString &src) const { return !operator==(src); } + bool operator!=(const char *src) const { return !operator==(src); } + + // If these are not defined, CUtlString as rhs will auto-convert + // to const char* and do logical operations on the raw pointers. Ugh. + inline friend bool operator==(const char *lhs, const CUtlString &rhs) { + return rhs.operator==(lhs); + } + inline friend bool operator!=(const char *lhs, const CUtlString &rhs) { + return rhs.operator!=(lhs); + } + + CUtlString &operator+=(const CUtlString &rhs); + CUtlString &operator+=(const char *rhs); + CUtlString &operator+=(char c); + CUtlString &operator+=(int rhs); + CUtlString &operator+=(double rhs); + + CUtlString operator+(const char *pOther) const; + + bool MatchesPattern(const CUtlString &Pattern, int nFlags = 0) + const; // case SENSITIVE, use * for wildcard in pattern string + + int Format(PRINTF_FORMAT_STRING const char *pFormat, ...); + void SetDirect(const char *pValue, intp nChars); + + // Defining AltArgumentType_t hints that associative container classes should + // also implement Find/Insert/Remove functions that take const char* params. + typedef const char *AltArgumentType_t; + + // Take a piece out of the string. + // If you only specify nStart, it'll go from nStart to the end. + // You can use negative numbers and it'll wrap around to the start. + CUtlString Slice(intp nStart = 0, intp nEnd = INT_MAX) const; + + // Grab a substring starting from the left or the right side. + CUtlString Left(intp nChars) const; + CUtlString Right(intp nChars) const; + + // Replace all instances of one character with another. + CUtlString Replace(char cFrom, char cTo) const; + + // Calls right through to V_MakeAbsolutePath. + CUtlString AbsPath(const char *pStartingDir = NULL) const; + + // Gets the filename (everything except the path.. c:\a\b\c\somefile.txt -> + // somefile.txt). + CUtlString UnqualifiedFilename() const; + + // Strips off one directory. Uses V_StripLastDir but strips the last slash + // also! + CUtlString DirName() const; + + // Works like V_ComposeFileName. + static CUtlString PathJoin(const char *pStr1, const char *pStr2); + + // These can be used for utlvector sorts. + static int __cdecl SortCaseInsensitive(const CUtlString *pString1, + const CUtlString *pString2); + static int __cdecl SortCaseSensitive(const CUtlString *pString1, + const CUtlString *pString2); + + private: + CUtlBinaryBlock m_Storage; +}; + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- +inline bool CUtlString::IsEmpty() const { return Length() == 0; } + +inline int __cdecl CUtlString::SortCaseInsensitive(const CUtlString *pString1, + const CUtlString *pString2) { + return V_stricmp(pString1->String(), pString2->String()); +} + +inline int __cdecl CUtlString::SortCaseSensitive(const CUtlString *pString1, + const CUtlString *pString2) { + return V_strcmp(pString1->String(), pString2->String()); +} + +//----------------------------------------------------------------------------- +// Purpose: Implementation of low-level string functionality for character +// types. +//----------------------------------------------------------------------------- + +template +class StringFuncs { + public: + static T *Duplicate(const T *pValue); + static void Copy(T *out_pOut, const T *pIn, intp iLength); + static int Compare(const T *pLhs, const T *pRhs); + static ptrdiff_t Length(const T *pValue); + static const T *FindChar(const T *pStr, const T cSearch); + static const T *EmptyString(); +}; + +template <> +class StringFuncs { + public: + static char *Duplicate(const char *pValue) { return _strdup(pValue); } + static void Copy(char *out_pOut, const char *pIn, intp iLength) { + strncpy(out_pOut, pIn, iLength); + } + static int Compare(const char *pLhs, const char *pRhs) { + return strcmp(pLhs, pRhs); + } + static ptrdiff_t Length(const char *pValue) { return strlen(pValue); } + static const char *FindChar(const char *pStr, const char cSearch) { + return strchr(pStr, cSearch); + } + static const char *EmptyString() { return ""; } +}; + +template <> +class StringFuncs { + public: + static wchar_t *Duplicate(const wchar_t *pValue) { return _wcsdup(pValue); } + static void Copy(wchar_t *out_pOut, const wchar_t *pIn, intp iLength) { + wcsncpy(out_pOut, pIn, iLength); + } + static int Compare(const wchar_t *pLhs, const wchar_t *pRhs) { + return wcscmp(pLhs, pRhs); + } + static ptrdiff_t Length(const wchar_t *pValue) { return wcslen(pValue); } + static const wchar_t *FindChar(const wchar_t *pStr, const wchar_t cSearch) { + return wcschr(pStr, cSearch); + } + static const wchar_t *EmptyString() { return L""; } +}; + +//----------------------------------------------------------------------------- +// Dirt-basic auto-release string class. Not intended for manipulation, +// can be stored in a container or forwarded as a functor parameter. +// Note the benefit over CUtlString: sizeof(CUtlConstString) == sizeof(char*). +// Also note: null char* pointers are treated identically to empty strings. +//----------------------------------------------------------------------------- + +template +class CUtlConstStringBase { + public: + CUtlConstStringBase() : m_pString(NULL) {} + CUtlConstStringBase(const T *pString) : m_pString(NULL) { Set(pString); } + CUtlConstStringBase(const CUtlConstStringBase &src) : m_pString(NULL) { + Set(src.m_pString); + } + ~CUtlConstStringBase() { Set(NULL); } + + void Set(const T *pValue); + void Clear() { Set(NULL); } + + const T *Get() const { + return m_pString ? m_pString : StringFuncs::EmptyString(); + } + operator const T *() const { + return m_pString ? m_pString : StringFuncs::EmptyString(); + } + + bool IsEmpty() const { + return m_pString == NULL; + } // Note: empty strings are never stored by Set + + int Compare(const T *rhs) const; + + // Logical ops + bool operator<(const T *rhs) const { return Compare(rhs) < 0; } + bool operator==(const T *rhs) const { return Compare(rhs) == 0; } + bool operator!=(const T *rhs) const { return Compare(rhs) != 0; } + bool operator<(const CUtlConstStringBase &rhs) const { + return Compare(rhs.m_pString) < 0; + } + bool operator==(const CUtlConstStringBase &rhs) const { + return Compare(rhs.m_pString) == 0; + } + bool operator!=(const CUtlConstStringBase &rhs) const { + return Compare(rhs.m_pString) != 0; + } + + // If these are not defined, CUtlConstString as rhs will auto-convert + // to const char* and do logical operations on the raw pointers. Ugh. + inline friend bool operator<(const T *lhs, const CUtlConstStringBase &rhs) { + return rhs.Compare(lhs) > 0; + } + inline friend bool operator==(const T *lhs, const CUtlConstStringBase &rhs) { + return rhs.Compare(lhs) == 0; + } + inline friend bool operator!=(const T *lhs, const CUtlConstStringBase &rhs) { + return rhs.Compare(lhs) != 0; + } + + CUtlConstStringBase &operator=(const T *src) { + Set(src); + return *this; + } + CUtlConstStringBase &operator=(const CUtlConstStringBase &src) { + Set(src.m_pString); + return *this; + } + + // Defining AltArgumentType_t is a hint to containers that they should + // implement Find/Insert/Remove functions that take const char* params. + typedef const T *AltArgumentType_t; + + protected: + const T *m_pString; +}; + +template +void CUtlConstStringBase::Set(const T *pValue) { + if (pValue != m_pString) { + free((void *)m_pString); + m_pString = pValue && pValue[0] ? StringFuncs::Duplicate(pValue) : NULL; + } +} + +template +int CUtlConstStringBase::Compare(const T *rhs) const { + if (m_pString) { + if (rhs) + return StringFuncs::Compare(m_pString, rhs); + else + return 1; + } else { + if (rhs) + return -1; + else + return 0; + } +} + +typedef CUtlConstStringBase CUtlConstString; +typedef CUtlConstStringBase CUtlConstWideString; + +#endif // VPC_TIER1_UTLSTRING_H_ diff --git a/public/tier1/utlsymbol.h b/public/tier1/utlsymbol.h new file mode 100644 index 0000000..a84036a --- /dev/null +++ b/public/tier1/utlsymbol.h @@ -0,0 +1,304 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Defines a symbol table + +#ifndef VPC_TIER1_UTLSYMBOL_H_ +#define VPC_TIER1_UTLSYMBOL_H_ + +#include "tier0/threadtools.h" +#include "tier1/utlrbtree.h" +#include "tier1/utlvector.h" +#include "tier1/utlbuffer.h" +#include "tier1/utllinkedlist.h" +#include "tier1/stringpool.h" + +// forward declarations +class CUtlSymbolTable; +class CUtlSymbolTableMT; + +//----------------------------------------------------------------------------- +// This is a symbol, which is a easier way of dealing with strings. +//----------------------------------------------------------------------------- +typedef unsigned short UtlSymId_t; + +#define UTL_INVAL_SYMBOL ((UtlSymId_t)~0) + +class CUtlSymbol { + public: + // constructor, destructor + CUtlSymbol() : m_Id(UTL_INVAL_SYMBOL) {} + CUtlSymbol(UtlSymId_t id) : m_Id(id) {} + CUtlSymbol(const char* pStr); + CUtlSymbol(CUtlSymbol const& sym) : m_Id(sym.m_Id) {} + + // operator= + CUtlSymbol& operator=(CUtlSymbol const& src) { + m_Id = src.m_Id; + return *this; + } + + // operator== + bool operator==(CUtlSymbol const& src) const { return m_Id == src.m_Id; } + bool operator==(const char* pStr) const; + + // Is valid? + bool IsValid() const { return m_Id != UTL_INVAL_SYMBOL; } + + // Gets at the symbol + operator UtlSymId_t() const { return m_Id; } + + // Gets the string associated with the symbol + const char* String() const; + + // Modules can choose to disable the static symbol table so to prevent + // accidental use of them. + static void DisableStaticSymbolTable(); + + // Methods with explicit locking mechanism. Only use for optimization reasons. + static void LockTableForRead(); + static void UnlockTableForRead(); + const char* StringNoLock() const; + + protected: + UtlSymId_t m_Id; + + // Initializes the symbol table + static void Initialize(); + + // returns the current symbol table + static CUtlSymbolTableMT* CurrTable(); + + // The standard global symbol table + static CUtlSymbolTableMT* s_pSymbolTable; + + static bool s_bAllowStaticSymbolTable; + + friend class CCleanupUtlSymbolTable; +}; + +//----------------------------------------------------------------------------- +// CUtlSymbolTable: +// description: +// This class defines a symbol table, which allows us to perform mappings +// of strings to symbols and back. The symbol class itself contains +// a static version of this class for creating global strings, but this +// class can also be instanced to create local symbol tables. +// +// This class stores the strings in a series of string pools. The first +// two bytes of each string are decorated with a hash to speed up +// comparisons. +//----------------------------------------------------------------------------- + +class CUtlSymbolTable { + public: + // constructor, destructor + CUtlSymbolTable(int growSize = 0, int initSize = 16, + bool caseInsensitive = false); + ~CUtlSymbolTable(); + + // Finds and/or creates a symbol based on the string + CUtlSymbol AddString(const char* pString); + + // Finds the symbol for pString + CUtlSymbol Find(const char* pString) const; + + // Look up the string associated with a particular symbol + const char* String(CUtlSymbol id) const; + + // Remove all symbols in the table. + void RemoveAll(); + + int GetNumStrings(void) const { return m_Lookup.Count(); } + + // We store one of these at the beginning of every string to speed + // up comparisons. + typedef unsigned short hashDecoration_t; + + protected: + class CStringPoolIndex { + public: + inline CStringPoolIndex() : m_iPool(0), m_iOffset(0) {} + + inline CStringPoolIndex(unsigned short iPool, unsigned short iOffset) + : m_iPool(iPool), m_iOffset(iOffset) {} + + inline bool operator==(const CStringPoolIndex& other) const { + return m_iPool == other.m_iPool && m_iOffset == other.m_iOffset; + } + + unsigned short m_iPool; // Index into m_StringPools. + unsigned short m_iOffset; // Index into the string pool. + }; + + class CLess { + public: + CLess(int = 0) {} // permits default initialization to NULL in CUtlRBTree + bool operator!() const { return false; } + bool operator()(const CStringPoolIndex& left, + const CStringPoolIndex& right) const; + }; + + // Stores the symbol lookup + class CTree : public CUtlRBTree { + public: + CTree(int growSize, int initSize) + : CUtlRBTree(growSize, + initSize) {} + friend class CUtlSymbolTable::CLess; // Needed to allow CLess to calculate + // pointer to symbol table + }; + + struct StringPool_t { + intp m_TotalLen; // How large is + int m_SpaceUsed; + char m_Data[1]; + }; + + CTree m_Lookup; + + bool m_bInsensitive; + mutable unsigned short m_nUserSearchStringHash; + mutable const char* m_pUserSearchString; + + // stores the string data + CUtlVector m_StringPools; + + private: + intp FindPoolWithSpace(intp len) const; + const char* StringFromIndex(const CStringPoolIndex& index) const; + const char* DecoratedStringFromIndex(const CStringPoolIndex& index) const; + + friend class CLess; + friend class CSymbolHash; +}; + +class CUtlSymbolTableMT : public CUtlSymbolTable { + public: + CUtlSymbolTableMT(int growSize = 0, int initSize = 32, + bool caseInsensitive = false) + : CUtlSymbolTable(growSize, initSize, caseInsensitive) {} + + CUtlSymbol AddString(const char* pString) { + m_lock.LockForWrite(); + CUtlSymbol result = CUtlSymbolTable::AddString(pString); + m_lock.UnlockWrite(); + return result; + } + + CUtlSymbol Find(const char* pString) const { + m_lock.LockForWrite(); + CUtlSymbol result = CUtlSymbolTable::Find(pString); + m_lock.UnlockWrite(); + return result; + } + + const char* String(CUtlSymbol id) const { + m_lock.LockForRead(); + const char* pszResult = CUtlSymbolTable::String(id); + m_lock.UnlockRead(); + return pszResult; + } + + const char* StringNoLock(CUtlSymbol id) const { + return CUtlSymbolTable::String(id); + } + + void LockForRead() { m_lock.LockForRead(); } + + void UnlockForRead() { m_lock.UnlockRead(); } + + private: +#ifdef WIN32 + mutable CThreadSpinRWLock m_lock; +#else + mutable CThreadRWLock m_lock; +#endif +}; + +// This class defines a symbol table of individual filenames, stored more +// efficiently than a standard symbol table. Internally filenames are +// broken up into file and path entries, and a file handle class allows +// convenient access to these. + +// The handle is a CUtlSymbol for the dirname and the same for the filename, the +// accessor +// copies them into a static char buffer for return. +typedef void* FileNameHandle_t; + +// Symbol table for more efficiently storing filenames by breaking paths and +// filenames apart. Refactored from Basefilesystem.h +class CUtlFilenameSymbolTable { + // Internal representation of a FileHandle_t + // If we get more than 64K filenames, we'll have to revisit... + // Right now CUtlSymbol is a short, so this packs into an int/void * pointer + // size... + struct FileNameHandleInternal_t { + FileNameHandleInternal_t() { + path = 0; + file = 0; + } + + // Part before the final '/' character + unsigned short path; + // Part after the final '/', including extension + unsigned short file; + }; + + public: + FileNameHandle_t FindOrAddFileName(const char* pFileName); + FileNameHandle_t FindFileName(const char* pFileName); + int PathIndex(const FileNameHandle_t& handle) { + return ((const FileNameHandleInternal_t*)&handle)->path; + } + bool String(const FileNameHandle_t& handle, char* buf, int buflen); + void RemoveAll(); + void SpewStrings(); + bool SaveToBuffer(CUtlBuffer& buffer); + bool RestoreFromBuffer(CUtlBuffer& buffer); + + private: + CCountedStringPool m_StringPool; + mutable CThreadSpinRWLock m_lock; +}; + +// This creates a simple class that includes the underlying CUtlSymbol +// as a private member and then instances a private symbol table to +// manage those symbols. Avoids the possibility of the code polluting the +// 'global'/default symbol table, while letting the code look like +// it's just using = and .String() to look at CUtlSymbol type objects +// +// NOTE: You can't pass these objects between .dlls in an interface (also true +// of CUtlSymbol of course) +// +#define DECLARE_PRIVATE_SYMBOLTYPE(typename) \ + class typename { \ + public: \ + typename(); \ + typename(const char* pStr); \ + typename& operator=(typename const& src); \ + bool operator==(typename const& src) const; \ + const char* String() const; \ + \ + private: \ + CUtlSymbol m_SymbolId; \ + }; + +// Put this in the .cpp file that uses the above typename +#define IMPLEMENT_PRIVATE_SYMBOLTYPE(typename) \ + static CUtlSymbolTable g_##typename##SymbolTable; \ + typename ::typename() { m_SymbolId = UTL_INVAL_SYMBOL; } \ + typename ::typename(const char* pStr) { \ + m_SymbolId = g_##typename##SymbolTable.AddString(pStr); \ + } \ + typename& typename ::operator=(typename const& src) { \ + m_SymbolId = src.m_SymbolId; \ + return *this; \ + } \ + bool typename ::operator==(typename const& src) const { \ + return (m_SymbolId == src.m_SymbolId); \ + } \ + const char* typename ::String() const { \ + return g_##typename##SymbolTable.String(m_SymbolId); \ + } + +#endif // VPC_TIER1_UTLSYMBOL_H_ diff --git a/public/tier1/utlvector.h b/public/tier1/utlvector.h new file mode 100644 index 0000000..fa52444 --- /dev/null +++ b/public/tier1/utlvector.h @@ -0,0 +1,1099 @@ +// Copyright Valve Corporation, All rights reserved. +// +// A growable array class that maintains a free list and keeps elements +// in the same location + +#ifndef VPC_TIER1_UTLVECTOR_H_ +#define VPC_TIER1_UTLVECTOR_H_ + +#include + +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" +#include "tier1/utlmemory.h" +#include "tier1/utlblockmemory.h" +#include "tier1/strtools.h" + +#define FOR_EACH_VEC(vecName, iteratorName) \ + for (intp iteratorName = 0; iteratorName < (vecName).Count(); iteratorName++) +#define FOR_EACH_VEC_BACK(vecName, iteratorName) \ + for (intp iteratorName = (vecName).Count() - 1; iteratorName >= 0; \ + iteratorName--) + +// A growable array class which doubles in size by default. It will always keep +// all elements consecutive in memory, and may move the elements around in +// memory (via a PvRealloc) when elements are inserted or removed. Clients +// should therefore refer to the elements of the vector by index (they should +// *never* maintain pointers to elements in the vector). +template > +class CUtlVector { + typedef A CAllocator; + + public: + typedef T ElemType_t; + + // constructor, destructor + CUtlVector(intp growSize = 0, intp initSize = 0); + CUtlVector(T *pMemory, intp allocationCount, intp numElements = 0); + ~CUtlVector(); + + // Copy the array. + CUtlVector &operator=(const CUtlVector &other); + + // element access + T &operator[](intp i); + const T &operator[](intp i) const; + T &Element(intp i); + const T &Element(intp i) const; + T &Head(); + const T &Head() const; + T &Tail(); + const T &Tail() const; + + // Gets the base address (can change when adding elements!) + T *Base() { return m_Memory.Base(); } + const T *Base() const { return m_Memory.Base(); } + + auto begin() noexcept { return Base(); } + auto end() noexcept { return Base() + Count(); } + auto begin() const noexcept { return Base(); } + auto end() const noexcept { return Base() + Count(); } + + // Returns the number of elements in the vector + intp Count() const; + + // Is element index valid? + bool IsValidIndex(intp i) const; + static intp InvalidIndex(); + + // Adds an element, uses default constructor + intp AddToHead(); + intp AddToTail(); + intp InsertBefore(intp elem); + intp InsertAfter(intp elem); + + // Adds an element, uses copy constructor + intp AddToHead(const T &src); + intp AddToTail(const T &src); + intp InsertBefore(intp elem, const T &src); + intp InsertAfter(intp elem, const T &src); + + // Adds multiple elements, uses default constructor + intp AddMultipleToHead(intp num); + intp AddMultipleToTail(intp num); + intp AddMultipleToTail(intp num, const T *pToCopy); + intp InsertMultipleBefore(intp elem, intp num); + intp InsertMultipleBefore(intp elem, intp num, const T *pToCopy); + intp InsertMultipleAfter(intp elem, intp num); + + // Calls RemoveAll() then AddMultipleToTail. + void SetSize(intp size); + void SetCount(intp count); + void SetCountNonDestructively( + intp count); // sets count by adding or removing elements to tail TODO: + // This should probably be the default behavior for SetCount + + // Calls SetSize and copies each element. + void CopyArray(const T *pArray, intp size); + + // Fast swap + void Swap(CUtlVector &vec); + + // Add the specified array to the tail. + intp AddVectorToTail(CUtlVector const &src); + + // Finds an element (element needs operator== defined) + intp Find(const T &src) const; + void FillWithValue(const T &src); + + bool HasElement(const T &src) const; + + // Makes sure we have enough memory allocated to store a requested # of + // elements + void EnsureCapacity(intp num); + + // Makes sure we have at least this many elements + void EnsureCount(intp num); + + // Element removal + void FastRemove(intp elem); // doesn't preserve order + void Remove(intp elem); // preserves order, shifts elements + bool FindAndRemove(const T &src); // removes first occurrence of src, + // preserves order, shifts elements + bool FindAndFastRemove( + const T &src); // removes first occurrence of src, doesn't preserve order + void RemoveMultiple(intp elem, intp num); // preserves order, shifts elements + void RemoveMultipleFromHead(intp num); // removes num elements from tail + void RemoveMultipleFromTail(intp num); // removes num elements from tail + void RemoveAll(); // doesn't deallocate memory + + // Memory deallocation + void Purge(); + + // Purges the list and calls delete on each element in it. + void PurgeAndDeleteElements(); + + // Compacts the vector to the number of elements actually in use + void Compact(); + + // Set the size by which it grows when it needs to allocate more memory. + void SetGrowSize(intp size) { m_Memory.SetGrowSize(size); } + + intp NumAllocated() + const; // Only use this if you really know what you're doing! + + void Sort(int(__cdecl *pfnCompare)(const T *, const T *)); + + // Call this to quickly sort non-contiguously allocated vectors + void InPlaceQuickSort(int(__cdecl *pfnCompare)(const T *, const T *)); + +#ifdef DBGFLAG_VALIDATE + void Validate(CValidator &validator, + char *pchName); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + + protected: + // Can't copy this unless we explicitly do it! + CUtlVector(CUtlVector const &vec) = delete; + + // Grows the vector + void GrowVector(intp num = 1); + + // Shifts elements.... + void ShiftElementsRight(intp elem, intp num = 1); + void ShiftElementsLeft(intp elem, intp num = 1); + + CAllocator m_Memory; + intp m_Size; + +#ifndef _X360 + // For easier access to the elements through the debugger + // it's in release builds so this can be used in libraries correctly + T *m_pElements; + + inline void ResetDbgInfo() { m_pElements = Base(); } +#else + inline void ResetDbgInfo() {} +#endif + + private: + void InPlaceQuickSort_r(int(__cdecl *pfnCompare)(const T *, const T *), + intp nLeft, intp nRight); +}; + +// this is kind of ugly, but until C++ gets templatized typedefs in C++0x, it's +// our only choice +template +class CUtlBlockVector : public CUtlVector> { + public: + CUtlBlockVector(intp growSize = 0, intp initSize = 0) + : CUtlVector>(growSize, initSize) {} +}; + +//----------------------------------------------------------------------------- +// The CUtlVectorMT class: +// An array class with spurious mutex protection. Nothing is actually protected +// unless you call Lock and Unlock. Also, the Mutex_t is actually not a type +// but a member which probably isn't used. +//----------------------------------------------------------------------------- + +template +class CUtlVectorMT : public BASE_UTLVECTOR, public MUTEX_TYPE { + typedef BASE_UTLVECTOR BaseClass; + + public: + // MUTEX_TYPE Mutex_t; + + // constructor, destructor + CUtlVectorMT(intp growSize = 0, intp initSize = 0) + : BaseClass(growSize, initSize) {} + CUtlVectorMT(typename BaseClass::ElemType_t *pMemory, intp numElements) + : BaseClass(pMemory, numElements) {} +}; + +//----------------------------------------------------------------------------- +// The CUtlVectorFixed class: +// A array class with a fixed allocation scheme +//----------------------------------------------------------------------------- +template +class CUtlVectorFixed : public CUtlVector> { + typedef CUtlVector> BaseClass; + + public: + // constructor, destructor + CUtlVectorFixed(intp growSize = 0, intp initSize = 0) + : BaseClass(growSize, initSize) {} + CUtlVectorFixed(T *pMemory, intp numElements) + : BaseClass(pMemory, numElements) {} +}; + +//----------------------------------------------------------------------------- +// The CUtlVectorFixedGrowable class: +// A array class with a fixed allocation scheme backed by a dynamic one +//----------------------------------------------------------------------------- +template +class CUtlVectorFixedGrowable + : public CUtlVector> { + typedef CUtlVector> BaseClass; + + public: + // constructor, destructor + CUtlVectorFixedGrowable(intp growSize = 0) : BaseClass(growSize, MAX_SIZE) {} +}; + +//----------------------------------------------------------------------------- +// The CUtlVectorConservative class: +// A array class with a conservative allocation scheme +//----------------------------------------------------------------------------- +template +class CUtlVectorConservative : public CUtlVector> { + typedef CUtlVector> BaseClass; + + public: + // constructor, destructor + CUtlVectorConservative(intp growSize = 0, intp initSize = 0) + : BaseClass(growSize, initSize) {} + CUtlVectorConservative(T *pMemory, intp numElements) + : BaseClass(pMemory, numElements) {} +}; + +//----------------------------------------------------------------------------- +// The CUtlVectorUltra Conservative class: +// A array class with a very conservative allocation scheme, with customizable +// allocator Especialy useful if you have a lot of vectors that are sparse, or +// if you're carefully packing holders of vectors +//----------------------------------------------------------------------------- +class CUtlVectorUltraConservativeAllocator { + public: + static void *Alloc(size_t nSize) { return malloc(nSize); } + + static void *Realloc(void *pMem, size_t nSize) { + return realloc(pMem, nSize); + } + + static void Free(void *pMem) { free(pMem); } + + static size_t GetSize(void *pMem) { return mallocsize(pMem); } +}; + +template +class CUtlVectorUltraConservative : private A { + public: + CUtlVectorUltraConservative() { m_pData = StaticData(); } + + ~CUtlVectorUltraConservative() { RemoveAll(); } + + intp Count() const { return m_pData->m_Size; } + + static intp InvalidIndex() { return -1; } + + inline bool IsValidIndex(intp i) const { return (i >= 0) && (i < Count()); } + + T &operator[](intp i) { + Assert(IsValidIndex(i)); + return m_pData->m_Elements[i]; + } + + const T &operator[](intp i) const { + Assert(IsValidIndex(i)); + return m_pData->m_Elements[i]; + } + + T &Element(intp i) { + Assert(IsValidIndex(i)); + return m_pData->m_Elements[i]; + } + + const T &Element(intp i) const { + Assert(IsValidIndex(i)); + return m_pData->m_Elements[i]; + } + + void EnsureCapacity(intp num) { + intp nCurCount = Count(); + if (num <= nCurCount) { + return; + } + if (m_pData == StaticData()) { + m_pData = (Data_t *)A::Alloc(sizeof(intp) + (num * sizeof(T))); + m_pData->m_Size = 0; + } else { + intp nNeeded = sizeof(intp) + (num * sizeof(T)); + intp nHave = A::GetSize(m_pData); + if (nNeeded > nHave) { + m_pData = (Data_t *)A::Realloc(m_pData, nNeeded); + } + } + } + + intp AddToTail(const T &src) { + intp iNew = Count(); + EnsureCapacity(Count() + 1); + m_pData->m_Elements[iNew] = src; + m_pData->m_Size++; + return iNew; + } + + void RemoveAll() { + if (Count()) { + for (intp i = m_pData->m_Size; --i >= 0;) { + Destruct(&m_pData->m_Elements[i]); + } + } + if (m_pData != StaticData()) { + A::Free(m_pData); + m_pData = StaticData(); + } + } + + void PurgeAndDeleteElements() { + if (m_pData != StaticData()) { + for (intp i = 0; i < m_pData->m_Size; i++) { + delete Element(i); + } + RemoveAll(); + } + } + + void FastRemove(intp elem) { + Assert(IsValidIndex(elem)); + + Destruct(&Element(elem)); + if (Count() > 0) { + if (elem != m_pData->m_Size - 1) + memcpy(&Element(elem), &Element(m_pData->m_Size - 1), sizeof(T)); + --m_pData->m_Size; + } + if (!m_pData->m_Size) { + A::Free(m_pData); + m_pData = StaticData(); + } + } + + void Remove(intp elem) { + Destruct(&Element(elem)); + ShiftElementsLeft(elem); + --m_pData->m_Size; + if (!m_pData->m_Size) { + A::Free(m_pData); + m_pData = StaticData(); + } + } + + intp Find(const T &src) const { + intp nCount = Count(); + for (intp i = 0; i < nCount; ++i) { + if (Element(i) == src) return i; + } + return -1; + } + + bool FindAndRemove(const T &src) { + intp elem = Find(src); + if (elem != -1) { + Remove(elem); + return true; + } + return false; + } + + bool FindAndFastRemove(const T &src) { + intp elem = Find(src); + if (elem != -1) { + FastRemove(elem); + return true; + } + return false; + } + + struct Data_t { + intp m_Size; + T *m_Elements; + }; + + Data_t *m_pData; + + private: + void ShiftElementsLeft(intp elem, intp num = 1) { + intp Size = Count(); + Assert(IsValidIndex(elem) || (Size == 0) || (num == 0)); + intp numToMove = Size - elem - num; + if ((numToMove > 0) && (num > 0)) { + V_memmove(&Element(elem), &Element(elem + num), numToMove * sizeof(T)); + +#ifdef _DEBUG + V_memset(&Element(Size - num), 0xDD, num * sizeof(T)); +#endif + } + } + + static Data_t *StaticData() { + static Data_t staticData; + Assert(staticData.m_Size == 0); + return &staticData; + } +}; + +//----------------------------------------------------------------------------- +// The CCopyableUtlVector class: +// A array class that allows copy construction (so you can nest a CUtlVector +// inside of another one of our containers) +// WARNING - this class lets you copy construct which can be an expensive +// operation if you don't carefully control when it happens +// Only use this when nesting a CUtlVector() inside of another one of our +// container classes (i.e a CUtlMap) +//----------------------------------------------------------------------------- +template +class CCopyableUtlVector : public CUtlVector> { + typedef CUtlVector> BaseClass; + + public: + CCopyableUtlVector(intp growSize = 0, intp initSize = 0) + : BaseClass(growSize, initSize) {} + CCopyableUtlVector(T *pMemory, intp numElements) + : BaseClass(pMemory, numElements) {} + virtual ~CCopyableUtlVector() {} + CCopyableUtlVector(CCopyableUtlVector const &vec) { + CopyArray(vec.Base(), vec.Count()); + } +}; + +// TODO (Ilya): It seems like all the functions in CUtlVector are simple enough +// that they should be inlined. + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +template +inline CUtlVector::CUtlVector(intp growSize, intp initSize) + : m_Memory(growSize, initSize), m_Size(0) { + ResetDbgInfo(); +} + +template +inline CUtlVector::CUtlVector(T *pMemory, intp allocationCount, + intp numElements) + : m_Memory(pMemory, allocationCount), m_Size(numElements) { + ResetDbgInfo(); +} + +template +inline CUtlVector::~CUtlVector() { + Purge(); +} + +template +inline CUtlVector &CUtlVector::operator=( + const CUtlVector &other) { + intp nCount = other.Count(); + SetSize(nCount); + for (intp i = 0; i < nCount; i++) { + (*this)[i] = other[i]; + } + return *this; +} + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template +inline T &CUtlVector::operator[](intp i) { + Assert(i < m_Size); + return m_Memory[i]; +} + +template +inline const T &CUtlVector::operator[](intp i) const { + Assert(i < m_Size); + return m_Memory[i]; +} + +template +inline T &CUtlVector::Element(intp i) { + Assert(i < m_Size); + return m_Memory[i]; +} + +template +inline const T &CUtlVector::Element(intp i) const { + Assert(i < m_Size); + return m_Memory[i]; +} + +template +inline T &CUtlVector::Head() { + Assert(m_Size > 0); + return m_Memory[0]; +} + +template +inline const T &CUtlVector::Head() const { + Assert(m_Size > 0); + return m_Memory[0]; +} + +template +inline T &CUtlVector::Tail() { + Assert(m_Size > 0); + return m_Memory[m_Size - 1]; +} + +template +inline const T &CUtlVector::Tail() const { + Assert(m_Size > 0); + return m_Memory[m_Size - 1]; +} + +//----------------------------------------------------------------------------- +// Count +//----------------------------------------------------------------------------- +template +inline intp CUtlVector::Count() const { + return m_Size; +} + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template +inline bool CUtlVector::IsValidIndex(intp i) const { + return (i >= 0) && (i < m_Size); +} + +//----------------------------------------------------------------------------- +// Returns in invalid index +//----------------------------------------------------------------------------- +template +inline intp CUtlVector::InvalidIndex() { + return -1; +} + +//----------------------------------------------------------------------------- +// Grows the vector +//----------------------------------------------------------------------------- +template +void CUtlVector::GrowVector(intp num) { + if (m_Size + num > m_Memory.NumAllocated()) { + MEM_ALLOC_CREDIT_CLASS(); + m_Memory.Grow(m_Size + num - m_Memory.NumAllocated()); + } + + m_Size += num; + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// Sorts the vector +//----------------------------------------------------------------------------- +template +void CUtlVector::Sort(int(__cdecl *pfnCompare)(const T *, const T *)) { + typedef int(__cdecl * QSortCompareFunc_t)(const void *, const void *); + if (Count() <= 1) return; + + if (Base()) { + qsort(Base(), Count(), sizeof(T), (QSortCompareFunc_t)(pfnCompare)); + } else { + Assert(0); + // this path is untested + // if you want to sort vectors that use a non-sequential memory allocator, + // you'll probably want to patch in a quicksort algorithm here + // I just threw in this bubble sort to have something just in case... + + for (intp i = m_Size - 1; i >= 0; --i) { + for (intp j = 1; j <= i; ++j) { + if (pfnCompare(&Element(j - 1), &Element(j)) < 0) { + V_swap(Element(j - 1), Element(j)); + } + } + } + } +} + +//---------------------------------------------------------------------------------------------- +// Private function that does the in-place quicksort for non-contiguously +// allocated vectors. +//---------------------------------------------------------------------------------------------- +template +void CUtlVector::InPlaceQuickSort_r(int(__cdecl *pfnCompare)(const T *, + const T *), + intp nLeft, intp nRight) { + intp nPivot; + intp nLeftIdx = nLeft; + intp nRightIdx = nRight; + + if (nRight - nLeft > 0) { + nPivot = (nLeft + nRight) / 2; + + while ((nLeftIdx <= nPivot) && (nRightIdx >= nPivot)) { + while ((pfnCompare(&Element(nLeftIdx), &Element(nPivot)) < 0) && + (nLeftIdx <= nPivot)) { + nLeftIdx++; + } + + while ((pfnCompare(&Element(nRightIdx), &Element(nPivot)) > 0) && + (nRightIdx >= nPivot)) { + nRightIdx--; + } + + V_swap(Element(nLeftIdx), Element(nRightIdx)); + + nLeftIdx++; + nRightIdx--; + + if ((nLeftIdx - 1) == nPivot) { + nPivot = nRightIdx = nRightIdx + 1; + } else if (nRightIdx + 1 == nPivot) { + nPivot = nLeftIdx = nLeftIdx - 1; + } + } + + InPlaceQuickSort_r(pfnCompare, nLeft, nPivot - 1); + InPlaceQuickSort_r(pfnCompare, nPivot + 1, nRight); + } +} + +//---------------------------------------------------------------------------------------------- +// Call this to quickly sort non-contiguously allocated vectors. Sort uses a +// slower bubble sort. +//---------------------------------------------------------------------------------------------- +template +void CUtlVector::InPlaceQuickSort(int(__cdecl *pfnCompare)(const T *, + const T *)) { + InPlaceQuickSort_r(pfnCompare, 0, Count() - 1); +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- +template +void CUtlVector::EnsureCapacity(intp num) { + MEM_ALLOC_CREDIT_CLASS(); + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + +//----------------------------------------------------------------------------- +// Makes sure we have at least this many elements +//----------------------------------------------------------------------------- +template +void CUtlVector::EnsureCount(intp num) { + if (Count() < num) { + AddMultipleToTail(num - Count()); + } +} + +//----------------------------------------------------------------------------- +// Shifts elements +//----------------------------------------------------------------------------- +template +void CUtlVector::ShiftElementsRight(intp elem, intp num) { + Assert(IsValidIndex(elem) || (m_Size == 0) || (num == 0)); + intp numToMove = m_Size - elem - num; + if ((numToMove > 0) && (num > 0)) + V_memmove(&Element(elem + num), &Element(elem), numToMove * sizeof(T)); +} + +template +void CUtlVector::ShiftElementsLeft(intp elem, intp num) { + Assert(IsValidIndex(elem) || (m_Size == 0) || (num == 0)); + intp numToMove = m_Size - elem - num; + if ((numToMove > 0) && (num > 0)) { + V_memmove(&Element(elem), &Element(elem + num), numToMove * sizeof(T)); + +#ifdef _DEBUG + V_memset(&Element(m_Size - num), 0xDD, num * sizeof(T)); +#endif + } +} + +//----------------------------------------------------------------------------- +// Adds an element, uses default constructor +//----------------------------------------------------------------------------- +template +inline intp CUtlVector::AddToHead() { + return InsertBefore(0); +} + +template +inline intp CUtlVector::AddToTail() { + return InsertBefore(m_Size); +} + +template +inline intp CUtlVector::InsertAfter(intp elem) { + return InsertBefore(elem + 1); +} + +template +intp CUtlVector::InsertBefore(intp elem) { + // Can insert at the end + Assert((elem == Count()) || IsValidIndex(elem)); + + GrowVector(); + ShiftElementsRight(elem); + Construct(&Element(elem)); + return elem; +} + +//----------------------------------------------------------------------------- +// Adds an element, uses copy constructor +//----------------------------------------------------------------------------- +template +inline intp CUtlVector::AddToHead(const T &src) { + // Can't insert something that's in the list... reallocation may hose us + Assert((Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()))); + return InsertBefore(0, src); +} + +template +inline intp CUtlVector::AddToTail(const T &src) { + // Can't insert something that's in the list... reallocation may hose us + Assert((Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()))); + return InsertBefore(m_Size, src); +} + +template +inline intp CUtlVector::InsertAfter(intp elem, const T &src) { + // Can't insert something that's in the list... reallocation may hose us + Assert((Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()))); + return InsertBefore(elem + 1, src); +} + +template +intp CUtlVector::InsertBefore(intp elem, const T &src) { + // Can't insert something that's in the list... reallocation may hose us + Assert((Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()))); + + // Can insert at the end + Assert((elem == Count()) || IsValidIndex(elem)); + + GrowVector(); + ShiftElementsRight(elem); + CopyConstruct(&Element(elem), src); + return elem; +} + +//----------------------------------------------------------------------------- +// Adds multiple elements, uses default constructor +//----------------------------------------------------------------------------- +template +inline intp CUtlVector::AddMultipleToHead(intp num) { + return InsertMultipleBefore(0, num); +} + +template +inline intp CUtlVector::AddMultipleToTail(intp num) { + return InsertMultipleBefore(m_Size, num); +} + +template +inline intp CUtlVector::AddMultipleToTail(intp num, const T *pToCopy) { + // Can't insert something that's in the list... reallocation may hose us + Assert((Base() == NULL) || !pToCopy || (pToCopy + num <= Base()) || + (pToCopy >= (Base() + Count()))); + + return InsertMultipleBefore(m_Size, num, pToCopy); +} + +template +intp CUtlVector::InsertMultipleAfter(intp elem, intp num) { + return InsertMultipleBefore(elem + 1, num); +} + +template +void CUtlVector::SetCount(intp count) { + RemoveAll(); + AddMultipleToTail(count); +} + +template +inline void CUtlVector::SetSize(intp size) { + SetCount(size); +} + +template +void CUtlVector::SetCountNonDestructively(intp count) { + intp delta = count - m_Size; + if (delta > 0) + AddMultipleToTail(delta); + else if (delta < 0) + RemoveMultipleFromTail(-delta); +} + +template +void CUtlVector::CopyArray(const T *pArray, intp size) { + // Can't insert something that's in the list... reallocation may hose us + Assert((Base() == NULL) || !pArray || (Base() >= (pArray + size)) || + (pArray >= (Base() + Count()))); + + SetSize(size); + for (intp i = 0; i < size; i++) { + (*this)[i] = pArray[i]; + } +} + +template +void CUtlVector::Swap(CUtlVector &vec) { + m_Memory.Swap(vec.m_Memory); + V_swap(m_Size, vec.m_Size); +#ifndef _X360 + V_swap(m_pElements, vec.m_pElements); +#endif +} + +template +intp CUtlVector::AddVectorToTail(CUtlVector const &src) { + Assert(&src != this); + + intp base = Count(); + + // Make space. + intp nSrcCount = src.Count(); + EnsureCapacity(base + nSrcCount); + + // Copy the elements. + m_Size += nSrcCount; + for (intp i = 0; i < nSrcCount; i++) { + CopyConstruct(&Element(base + i), src[i]); + } + return base; +} + +template +inline intp CUtlVector::InsertMultipleBefore(intp elem, intp num) { + if (num == 0) return elem; + + // Can insert at the end + Assert((elem == Count()) || IsValidIndex(elem)); + + GrowVector(num); + ShiftElementsRight(elem, num); + + // Invoke default constructors + for (intp i = 0; i < num; ++i) { + Construct(&Element(elem + i)); + } + + return elem; +} + +template +inline intp CUtlVector::InsertMultipleBefore(intp elem, intp num, + const T *pToInsert) { + if (num == 0) return elem; + + // Can insert at the end + Assert((elem == Count()) || IsValidIndex(elem)); + + GrowVector(num); + ShiftElementsRight(elem, num); + + // Invoke default constructors + if (!pToInsert) { + for (intp i = 0; i < num; ++i) { + Construct(&Element(elem + i)); + } + } else { + for (intp i = 0; i < num; i++) { + CopyConstruct(&Element(elem + i), pToInsert[i]); + } + } + + return elem; +} + +//----------------------------------------------------------------------------- +// Finds an element (element needs operator== defined) +//----------------------------------------------------------------------------- +template +intp CUtlVector::Find(const T &src) const { + for (intp i = 0; i < Count(); ++i) { + if (Element(i) == src) return i; + } + return -1; +} + +template +void CUtlVector::FillWithValue(const T &src) { + for (intp i = 0; i < Count(); i++) { + Element(i) = src; + } +} + +template +bool CUtlVector::HasElement(const T &src) const { + return (Find(src) >= 0); +} + +//----------------------------------------------------------------------------- +// Element removal +//----------------------------------------------------------------------------- +template +void CUtlVector::FastRemove(intp elem) { + Assert(IsValidIndex(elem)); + + Destruct(&Element(elem)); + if (m_Size > 0) { + if (elem != m_Size - 1) + memcpy(&Element(elem), &Element(m_Size - 1), sizeof(T)); + --m_Size; + } +} + +template +void CUtlVector::Remove(intp elem) { + Destruct(&Element(elem)); + ShiftElementsLeft(elem); + --m_Size; +} + +template +bool CUtlVector::FindAndRemove(const T &src) { + intp elem = Find(src); + if (elem != -1) { + Remove(elem); + return true; + } + return false; +} + +template +bool CUtlVector::FindAndFastRemove(const T &src) { + intp elem = Find(src); + if (elem != -1) { + FastRemove(elem); + return true; + } + return false; +} + +template +void CUtlVector::RemoveMultiple(intp elem, intp num) { + Assert(elem >= 0); + Assert(elem + num <= Count()); + + for (intp i = elem + num; --i >= elem;) Destruct(&Element(i)); + + ShiftElementsLeft(elem, num); + m_Size -= num; +} + +template +void CUtlVector::RemoveMultipleFromHead(intp num) { + Assert(num <= Count()); + + for (intp i = num; --i >= 0;) Destruct(&Element(i)); + + ShiftElementsLeft(0, num); + m_Size -= num; +} + +template +void CUtlVector::RemoveMultipleFromTail(intp num) { + Assert(num <= Count()); + + for (intp i = m_Size - num; i < m_Size; i++) Destruct(&Element(i)); + + m_Size -= num; +} + +template +void CUtlVector::RemoveAll() { + for (intp i = m_Size; --i >= 0;) { + Destruct(&Element(i)); + } + + m_Size = 0; +} + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- + +template +inline void CUtlVector::Purge() { + RemoveAll(); + m_Memory.Purge(); + ResetDbgInfo(); +} + +template +inline void CUtlVector::PurgeAndDeleteElements() { + for (intp i = 0; i < m_Size; i++) { + delete Element(i); + } + Purge(); +} + +template +inline void CUtlVector::Compact() { + m_Memory.Purge(m_Size); +} + +template +inline intp CUtlVector::NumAllocated() const { + return m_Memory.NumAllocated(); +} + +//----------------------------------------------------------------------------- +// Data and memory validation +//----------------------------------------------------------------------------- +#ifdef DBGFLAG_VALIDATE +template +void CUtlVector::Validate(CValidator &validator, char *pchName) { + validator.Push(typeid(*this).name(), this, pchName); + + m_Memory.Validate(validator, "m_Memory"); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + +// A vector class for storing pointers, so that the elements pointed to by the +// pointers are deleted on exit. +template +class CUtlVectorAutoPurge : public CUtlVector> { + public: + ~CUtlVectorAutoPurge(void) { this->PurgeAndDeleteElements(); } +}; + +// easy string list class with dynamically allocated strings. For use with +// V_SplitString, etc. Frees the dynamic strings in destructor. +class CUtlStringList : public CUtlVectorAutoPurge { + public: + void CopyAndAddToTail( + char const *pString) // clone the string and add to the end + { + char *pNewStr = new char[1 + strlen(pString)]; + V_strcpy(pNewStr, pString); + AddToTail(pNewStr); + } + + static int __cdecl SortFunc(char *const *sz1, char *const *sz2) { + return strcmp(*sz1, *sz2); + } +}; + +// placing it here a few days before Cert to minimize disruption to the +// rest of codebase +class CSplitString : public CUtlVector> { + public: + CSplitString(const char *pString, const char *pSeparator); + CSplitString(const char *pString, const char **pSeparators, intp nSeparators); + ~CSplitString(); + // + // NOTE: If you want to make Construct() public and implement Purge() here, + // you'll have to free m_szBuffer there + // + private: + void Construct(const char *pString, const char **pSeparators, + intp nSeparators); + void PurgeAndDeleteElements(); + + private: + // a copy of original string, with '\0' instead of separators + char *m_szBuffer; +}; + +#endif // VPC_TIER1_UTLVECTOR_H_ diff --git a/public/tier2/tier2.h b/public/tier2/tier2.h new file mode 100644 index 0000000..dc75e50 --- /dev/null +++ b/public/tier2/tier2.h @@ -0,0 +1,88 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A higher level link library for general use in the game and tools. + +#ifndef VPC_TIER2_TIER2_H_ +#define VPC_TIER2_TIER2_H_ + +#include "tier1/tier1.h" + +// Call this to connect to/disconnect from all tier 2 libraries. +// It's up to the caller to check the globals it cares about to see if ones are +// missing +void ConnectTier2Libraries(CreateInterfaceFn *pFactoryList, int nFactoryCount); +void DisconnectTier2Libraries(); + +// Call this to get the file system set up to stdio for utilities, etc: +void InitDefaultFileSystem(); +void ShutdownDefaultFileSystem(); + +// for simple utilities using valve libraries, call the entry point below in +// main(). It will init a filesystem for you, init mathlib, and create the +// command line. Note that this function may modify argc/argv because it filters +// out arguments (like -allowdebug). +void InitCommandLineProgram(int &argc, char **&argv); + +// Helper empty implementation of an IAppSystem for tier2 libraries +template +class CTier2AppSystem : public CTier1AppSystem { + typedef CTier1AppSystem BaseClass; + + public: + virtual bool Connect(CreateInterfaceFn factory) { + if (!BaseClass::Connect(factory)) return false; + + ConnectTier2Libraries(&factory, 1); + return true; + } + + virtual InitReturnVal_t Init() { + InitReturnVal_t nRetVal = BaseClass::Init(); + if (nRetVal != INIT_OK) return nRetVal; + + return INIT_OK; + } + + virtual AppSystemTier_t GetTier() { return APP_SYSTEM_TIER2; } + + virtual void Shutdown() { BaseClass::Shutdown(); } + + virtual void Disconnect() { + DisconnectTier2Libraries(); + BaseClass::Disconnect(); + } +}; + +// Distance fade information +enum FadeMode_t { + // These map directly to cpu_level, and g_aFadeData contains settings for each + // given cpu_level. The exception is 'FADE_MODE_LEVEL', which refers to + // level-specific values in the map entity. + FADE_MODE_NONE = 0, + FADE_MODE_LOW, + FADE_MODE_MED, + FADE_MODE_HIGH, + FADE_MODE_360, + FADE_MODE_LEVEL, + + FADE_MODE_COUNT, +}; + +struct FadeData_t { + // Size (height in pixels) above which objects start to fade in + float m_flPixelMin; + // Size (height in pixels) above which objects are fully faded in + float m_flPixelMax; + // Reference screen res w.r.t which the above pixel values were chosen + float m_flWidth; + // Scale factor applied before entity distance-based fade is calculated + float m_flFadeDistScale; +}; + +// see tier2.cpp for data! +extern FadeData_t g_aFadeData[FADE_MODE_COUNT]; + +// Used by the resource system for fast resource frame counter +extern uint32 g_nResourceFrameCount; + +#endif // VPC_TIER2_TIER2_H_ diff --git a/public/unitlib/unitlib.h b/public/unitlib/unitlib.h new file mode 100644 index 0000000..076c23f --- /dev/null +++ b/public/unitlib/unitlib.h @@ -0,0 +1,238 @@ +// Copyright Valve Corporation, All rights reserved.// + +#ifndef VPC_UNITLIB_UNITLIB_H_ +#define VPC_UNITLIB_UNITLIB_H_ + +#include "tier0/platform.h" + +// Usage model for the UnitTest library +// +// The general methodology here is that clients are expected to create unit +// test DLLs that statically link to the unit test DLL and which implement +// tests of various libraries. The unit test application will dynamically +// load all DLLs in a directory, which causes them to install their test cases +// into the unit test system. The application then runs through all tests and +// executes them all, displaying the results. +// +// *** NOTE: The test suites are compiled in both debug and release builds, +// even though it's expected to only be useful in debug builds. This is because +// I couldn't come up with a good way of disabling the code in release builds. +// (The only options I could come up with would still compile in the +// functions, just not install them into the unit test library, or would make +// it so that you couldn't step through the unit test code). +// +// Even though this is the case, there's no reason not to add test cases +// directly into your shipping DLLs, as long as you surround the code with +// #ifdef _DEBUG. To error check a project to make sure it's not compiling +// in unit tests in Release build, just don't link in unitlib.lib in +// Release. You can of course also put your test suites into separate DLLs. +// +// All tests inherit from the ITestCase interface. There are two major +// kinds of tests; the first is a single test case meant to run a piece +// of code and check its results match expected values using the Assert macros. +// The second kind is a test suite which is simply a list of other tests. +// +// The following classes and macros are used to easily create unit test +// cases and suites: +// +// Use DEFINE_TESTSUITE to define a particular test suite, and DEFINE_TESTCASE +// to add as many test cases as you like to that test suite, as follows: +// +// DEFINE_TESTSUITE( VectorTestSuite ) +// +// DEFINE_TESTCASE( VectorAdditionTest, VectorTestSuite ) +// { +// .. test code here .. +// } +// +// Note that the definition of the test suite can occur in a different file +// as the test case. A link error will occur if the test suite to which a +// test case is added has not been defined. +// +// To create a test case that is not part of a suite, use... +// +// DEFINE_TESTCASE_NOSUITE( VectorAdditionTest ) +// { +// .. test code here .. +// } +// +// You can also create a suite which is a child of another suite using +// +// DEFINE_SUBSUITE( VectorTestSuite, MathTestSuite ) + +// dll export stuff +#ifdef TIER0_DLL_EXPORT +#define UNITLIB_DLL_EXPORT +#endif + +#ifdef UNITLIB_DLL_EXPORT +#define UNITLIB_INTERFACE DLL_EXPORT +#define UNITLIB_CLASS_INTERFACE DLL_CLASS_EXPORT +#define UNITLIB_GLOBAL_INTERFACE DLL_GLOBAL_EXPORT +#else +#define UNITLIB_INTERFACE DLL_IMPORT +#define UNITLIB_CLASS_INTERFACE DLL_CLASS_IMPORT +#define UNITLIB_GLOBAL_INTERFACE DLL_GLOBAL_IMPORT +#endif + +//----------------------------------------------------------------------------- +// All unit test libraries can be asked for a unit test +// AppSystem to perform connection +//----------------------------------------------------------------------------- +#define UNITTEST_INTERFACE_VERSION "UnitTestV001" + +//----------------------------------------------------------------------------- +// +// NOTE: All classes and interfaces below you shouldn't use directly. +// Use the DEFINE_TESTSUITE and DEFINE_TESTCASE macros instead. +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Test case + suite interface +//----------------------------------------------------------------------------- +class ITestCase { + public: + // This returns the test name + virtual char const* GetName() = 0; + + // This runs the test + virtual void RunTest() = 0; +}; + +class ITestSuite : public ITestCase { + public: + // Add a test to the suite... + virtual void AddTest(ITestCase* pTest) = 0; +}; + +//----------------------------------------------------------------------------- +// This is the main function exported by the unit test library used by +// unit test DLLs to install their test cases into a list to be run +//----------------------------------------------------------------------------- +UNITLIB_INTERFACE void UnitTestInstallTestCase(ITestCase* pTest); + +//----------------------------------------------------------------------------- +// These are the methods used by the unit test running program to run all tests +//----------------------------------------------------------------------------- +UNITLIB_INTERFACE int UnitTestCount(); +UNITLIB_INTERFACE ITestCase* GetUnitTest(int i); + +//----------------------------------------------------------------------------- +// Helper for unit test DLLs to expose IAppSystems +//----------------------------------------------------------------------------- +#define USE_UNITTEST_APPSYSTEM(_className) \ + static _className s_UnitTest##_className; \ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(_className, IAppSystem, \ + UNITTEST_INTERFACE_VERSION, \ + s_UnitTest##_className); + +//----------------------------------------------------------------------------- +// Base class for test cases +//----------------------------------------------------------------------------- +class UNITLIB_CLASS_INTERFACE CTestCase : public ITestCase { + public: + CTestCase(char const* pName, ITestSuite* pParent = 0); + ~CTestCase(); + + // Returns the test name + char const* GetName(); + + private: + char* m_pName; +}; + +//----------------------------------------------------------------------------- +// Test suite class +//----------------------------------------------------------------------------- +class UNITLIB_CLASS_INTERFACE CTestSuite : public ITestSuite { + public: + CTestSuite(char const* pName, ITestSuite* pParent = 0); + ~CTestSuite(); + + // This runs the test + void RunTest(); + + // Add a test to the suite... + void AddTest(ITestCase* pTest); + + // Returns the test name + char const* GetName(); + + protected: + int m_TestCount; + ITestCase** m_ppTestCases; + char* m_pName; +}; + +#define TESTSUITE_CLASS(_suite) \ + class CTS##_suite : public CTestSuite { \ + public: \ + CTS##_suite(); \ + }; + +#define TESTSUITE_ACCESSOR(_suite) \ + CTS##_suite* GetTS##_suite() { \ + static CTS##_suite s_TS##_suite; \ + return &s_TS##_suite; \ + } + +#define FWD_DECLARE_TESTSUITE(_suite) \ + class CTS##_suite; \ + CTS##_suite* GetTS##_suite(); + +#define DEFINE_TESTSUITE(_suite) \ + TESTSUITE_CLASS(_suite) \ + TESTSUITE_ACCESSOR(_suite) \ + CTS##_suite::CTS##_suite() : CTestSuite(#_suite) {} + +#define DEFINE_SUBSUITE(_suite, _parent) \ + TESTSUITE_CLASS(_suite) \ + TESTSUITE_ACCESSOR(_suite) \ + FWD_DECLARE_TESTSUITE(_parent) \ + CTS##_suite::CTS##_suite() : CTestSuite(#_suite, GetTS##_parent()) {} + +#define TESTCASE_CLASS(_case) \ + class CTC##_case : public CTestCase { \ + public: \ + CTC##_case(); \ + void RunTest(); \ + }; + +#define DEFINE_TESTCASE_NOSUITE(_case) \ + TESTCASE_CLASS(_case) \ + CTC##_case::CTC##_case() : CTestCase(#_case) {} \ + \ + CTC##_case s_TC##_case; \ + \ + void CTC##_case ::RunTest() + +#define DEFINE_TESTCASE(_case, _suite) \ + TESTCASE_CLASS(_case) \ + FWD_DECLARE_TESTSUITE(_suite) \ + CTC##_case::CTC##_case() : CTestCase(#_case, GetTS##_suite()) {} \ + \ + CTC##_case s_TC##_case; \ + \ + void CTC##_case ::RunTest() + +#define _Shipping_AssertMsg(_exp, _msg, _executeExp, _bFatal) \ + do { \ + if (!(_exp)) { \ + LoggingResponse_t ret = \ + Log_Assert("%s (%d) : %s\n", __TFILE__, __LINE__, _msg); \ + _executeExp; \ + if (ret == LR_DEBUGGER) { \ + if (!ShouldUseNewAssertDialog() || \ + DoNewAssertDialog(__TFILE__, __LINE__, _msg)) \ + DebuggerBreak(); \ + if (_bFatal) _ExitOnFatalAssert(__TFILE__, __LINE__); \ + } \ + } \ + } while (0) + +#define Shipping_Assert(_exp) \ + _Shipping_AssertMsg(_exp, _T("Assertion Failed: ") _T(#_exp), ((void)0), \ + false) + +#endif // VPC_UNITLIB_UNITLIB_H_ diff --git a/public/vstdlib/cvar.h b/public/vstdlib/cvar.h new file mode 100644 index 0000000..c9b3670 --- /dev/null +++ b/public/vstdlib/cvar.h @@ -0,0 +1,12 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_VSTDLIB_CVAR_H_ +#define VPC_VSTDLIB_CVAR_H_ + +#include "vstdlib/vstdlib.h" +#include "icvar.h" + +// Returns a CVar dictionary for tool usage +VSTDLIB_INTERFACE CreateInterfaceFn VStdLib_GetICVarFactory(); + +#endif // VPC_VSTDLIB_CVAR_H_ diff --git a/public/vstdlib/ikeyvaluessystem.h b/public/vstdlib/ikeyvaluessystem.h new file mode 100644 index 0000000..458111e --- /dev/null +++ b/public/vstdlib/ikeyvaluessystem.h @@ -0,0 +1,54 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_VSTDLIB_IKEYVALUESSYSTEM_H_ +#define VPC_VSTDLIB_IKEYVALUESSYSTEM_H_ + +#include "vstdlib/vstdlib.h" + +// handle to a KeyValues key name symbol +using HKeySymbol = int; + +#define INVALID_KEY_SYMBOL (-1) + +// Purpose: Interface to shared data repository for KeyValues (included in +// vgui_controls.lib) allows for central data storage point of KeyValues symbol +// table +class IKeyValuesSystem { + public: + // registers the size of the KeyValues in the specified instance + // so it can build a properly sized memory pool for the KeyValues objects + // the sizes will usually never differ but this is for versioning safety + virtual void RegisterSizeofKeyValues(size_t size) = 0; + + // allocates/frees a KeyValues object from the shared mempool + virtual void *AllocKeyValuesMemory(size_t size) = 0; + virtual void FreeKeyValuesMemory(void *pMem) = 0; + + // symbol table access (used for key names) + virtual HKeySymbol GetSymbolForString(const char *name, + bool bCreate = true) = 0; + virtual const char *GetStringForSymbol(HKeySymbol symbol) = 0; + + // for debugging, adds KeyValues record into global list so we can track + // memory leaks + virtual void AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name) = 0; + virtual void RemoveKeyValuesFromMemoryLeakList(void *pMem) = 0; + + // set/get a value for keyvalues resolution symbol + // e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables + // [$LOWVIOLENCE] + virtual void SetKeyValuesExpressionSymbol(const char *name, bool bValue) = 0; + virtual bool GetKeyValuesExpressionSymbol(const char *name) = 0; + + // symbol table access from code with case-preserving requirements (used for + // key names) + virtual HKeySymbol GetSymbolForStringCaseSensitive( + HKeySymbol &hCaseInsensitiveSymbol, const char *name, + bool bCreate = true) = 0; +}; + +VSTDLIB_INTERFACE IKeyValuesSystem *KeyValuesSystem(); + +// #define KEYVALUESSYSTEM_INTERFACE_VERSION "KeyValuesSystem002" + +#endif // VPC_VSTDLIB_IKEYVALUESSYSTEM_H_ diff --git a/public/vstdlib/pch_vstdlib.h b/public/vstdlib/pch_vstdlib.h new file mode 100644 index 0000000..0c9945b --- /dev/null +++ b/public/vstdlib/pch_vstdlib.h @@ -0,0 +1,34 @@ +// Copyright Valve Corporation, All rights reserved. + +// First include standard libraries +#include + +#include +#include +#include +#include + +// Next, include public +#include "tier0/basetypes.h" +#include "tier0/dbg.h" +#include "tier0/valobject.h" + +// Next, include vstdlib +#include "vstdlib/vstdlib.h" +#include "tier1/strtools.h" +#include "vstdlib/random.h" +#include "tier1/keyvalues.h" +#include "tier1/utlmemory.h" +#include "tier1/utlrbtree.h" +#include "tier1/utlvector.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlmultilist.h" +#include "tier1/utlsymbol.h" +#include "tier0/icommandline.h" +#include "tier1/netadr.h" +#include "tier1/mempool.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlstring.h" +#include "tier1/utlmap.h" + +#include "tier0/memdbgon.h" diff --git a/public/vstdlib/random.h b/public/vstdlib/random.h new file mode 100644 index 0000000..a16a6be --- /dev/null +++ b/public/vstdlib/random.h @@ -0,0 +1,90 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Random number generator + +#ifndef VSTDLIB_RANDOM_H +#define VSTDLIB_RANDOM_H + +#include "vstdlib/vstdlib.h" +#include "tier0/basetypes.h" +#include "tier0/threadtools.h" +#include "tier1/interface.h" + +#define NTAB 32 + +// A generator of uniformly distributed random numbers +class IUniformRandomStream { + public: + // Sets the seed of the random number generator + virtual void SetSeed(int iSeed) = 0; + + // Generates random numbers + virtual float RandomFloat(float flMinVal = 0.0f, float flMaxVal = 1.0f) = 0; + virtual int RandomInt(int iMinVal, int iMaxVal) = 0; + virtual float RandomFloatExp(float flMinVal = 0.0f, float flMaxVal = 1.0f, + float flExponent = 1.0f) = 0; +}; + +// The standard generator of uniformly distributed random numbers +class VSTDLIB_CLASS CUniformRandomStream : public IUniformRandomStream { + public: + CUniformRandomStream(); + + // Sets the seed of the random number generator + virtual void SetSeed(int iSeed); + + // Generates random numbers + virtual float RandomFloat(float flMinVal = 0.0f, float flMaxVal = 1.0f); + virtual int RandomInt(int iMinVal, int iMaxVal); + virtual float RandomFloatExp(float flMinVal = 0.0f, float flMaxVal = 1.0f, + float flExponent = 1.0f); + + private: + int GenerateRandomNumber(); + + int m_idum; + int m_iy; + int m_iv[NTAB]; + + CThreadFastMutex m_mutex; +}; + +// A generator of gaussian distributed random numbers +class VSTDLIB_CLASS CGaussianRandomStream { + public: + // Passing in NULL will cause the gaussian stream to use the + // installed global random number generator + CGaussianRandomStream(IUniformRandomStream *pUniformStream = NULL); + + // Attaches to a random uniform stream + void AttachToStream(IUniformRandomStream *pUniformStream = NULL); + + // Generates random numbers + float RandomFloat(float flMean = 0.0f, float flStdDev = 1.0f); + + private: + IUniformRandomStream *m_pUniformStream; + bool m_bHaveValue; + float m_flRandomValue; + + CThreadFastMutex m_mutex; +}; + +// A couple of convenience functions to access the library's global uniform +// stream +VSTDLIB_INTERFACE void RandomSeed(int iSeed); +VSTDLIB_INTERFACE float RandomFloat(float flMinVal = 0.0f, + float flMaxVal = 1.0f); +VSTDLIB_INTERFACE float RandomFloatExp(float flMinVal = 0.0f, + float flMaxVal = 1.0f, + float flExponent = 1.0f); +VSTDLIB_INTERFACE int RandomInt(int iMinVal, int iMaxVal); +VSTDLIB_INTERFACE float RandomGaussianFloat(float flMean = 0.0f, + float flStdDev = 1.0f); + +// Installs a global random number generator, which will affect the Random +// functions above +VSTDLIB_INTERFACE void InstallUniformRandomStream( + IUniformRandomStream *pStream); + +#endif // VSTDLIB_RANDOM_H diff --git a/public/vstdlib/vstdlib.h b/public/vstdlib/vstdlib.h new file mode 100644 index 0000000..c397439 --- /dev/null +++ b/public/vstdlib/vstdlib.h @@ -0,0 +1,28 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_VSTDLIB_VSTDLIB_H_ +#define VPC_VSTDLIB_VSTDLIB_H_ + +#include "tier0/platform.h" + +// dll export stuff +#ifdef STATIC_VSTDLIB +#define VSTDLIB_INTERFACE +#define VSTDLIB_OVERLOAD +#define VSTDLIB_CLASS +#define VSTDLIB_GLOBAL +#else +#ifdef VSTDLIB_DLL_EXPORT +#define VSTDLIB_INTERFACE DLL_EXPORT +#define VSTDLIB_OVERLOAD DLL_GLOBAL_EXPORT +#define VSTDLIB_CLASS DLL_CLASS_EXPORT +#define VSTDLIB_GLOBAL DLL_GLOBAL_EXPORT +#else +#define VSTDLIB_INTERFACE DLL_IMPORT +#define VSTDLIB_OVERLOAD DLL_GLOBAL_IMPORT +#define VSTDLIB_CLASS DLL_CLASS_IMPORT +#define VSTDLIB_GLOBAL DLL_GLOBAL_IMPORT +#endif +#endif + +#endif // VPC_VSTDLIB_VSTDLIB_H_ diff --git a/public/vstdlib/vstrtools.h b/public/vstdlib/vstrtools.h new file mode 100644 index 0000000..db13b11 --- /dev/null +++ b/public/vstdlib/vstrtools.h @@ -0,0 +1,250 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Functions for UCS/UTF/Unicode string operations. These functions are in +// vstdlib instead of tier1, because on PS/3 they need to load and initialize a +// system module, which is more frugal to do from a single place rather than +// multiple times in different PRX'es. The functions themselves aren't supposed +// to be called frequently enough for the DLL/PRX boundary marshalling, if any, +// to have any measureable impact on performance. + +#ifndef VPC_VSTDLIB_VSTRTOOLS_H_ +#define VPC_VSTDLIB_VSTRTOOLS_H_ + +#include "tier0/platform.h" +#include "tier0/basetypes.h" +#include "tier1/strtools.h" + +#ifdef STATIC_VSTDLIB +#define VSTRTOOLS_INTERFACE +#else +#ifdef VSTDLIB_DLL_EXPORT +#define VSTRTOOLS_INTERFACE DLL_EXPORT +#else +#define VSTRTOOLS_INTERFACE DLL_IMPORT +#endif +#endif + +// conversion functions wchar_t <-> char, returning the number of characters +// converted +VSTRTOOLS_INTERFACE int V_UTF8ToUnicode(const char *pUTF8, wchar_t *pwchDest, + int cubDestSizeInBytes); +VSTRTOOLS_INTERFACE int V_UnicodeToUTF8(const wchar_t *pUnicode, char *pUTF8, + int cubDestSizeInBytes); +VSTRTOOLS_INTERFACE intp V_UCS2ToUnicode(const ucs2 *pUCS2, wchar_t *pUnicode, + intp cubDestSizeInBytes); +VSTRTOOLS_INTERFACE int V_UCS2ToUTF8(const ucs2 *pUCS2, char *pUTF8, + int cubDestSizeInBytes); +VSTRTOOLS_INTERFACE int V_UnicodeToUCS2(const wchar_t *pUnicode, + int cubSrcInBytes, char *pUCS2, + int cubDestSizeInBytes); +VSTRTOOLS_INTERFACE int V_UTF8ToUCS2(const char *pUTF8, int cubSrcInBytes, + ucs2 *pUCS2, int cubDestSizeInBytes); + +// copy at most n bytes into destination, will not corrupt utf-8 multi-byte +// sequences +VSTRTOOLS_INTERFACE void *V_UTF8_strncpy(char *pDest, const char *pSrc, + size_t nMaxBytes); + +// +// This utility class is for performing UTF-8 <-> UTF-16 conversion. +// It is intended for use with function/method parameters. +// +// For example, you can call +// FunctionTakingUTF16( CStrAutoEncode( utf8_string ).ToWString() ) +// or +// FunctionTakingUTF8( CStrAutoEncode( utf16_string ).ToString() ) +// +// The converted string is allocated off the heap, and destroyed when +// the object goes out of scope. +// +// if the string cannot be converted, NULL is returned. +// +// This class doesn't have any conversion operators; the intention is +// to encourage the developer to get used to having to think about which +// encoding is desired. +// +class CStrAutoEncode { + public: + // ctor + explicit CStrAutoEncode(const char *pch) { + m_pch = pch; + m_pwch = NULL; +#if !defined(WIN32) && !defined(_WIN32) + m_pucs2 = NULL; + m_bCreatedUCS2 = false; +#endif + m_bCreatedUTF16 = false; + } + + // ctor + explicit CStrAutoEncode(const wchar_t *pwch) { + m_pch = NULL; + m_pwch = pwch; +#if !defined(WIN32) && !defined(_WIN32) + m_pucs2 = NULL; + m_bCreatedUCS2 = false; +#endif + m_bCreatedUTF16 = true; + } + +#if !defined(WIN32) && !defined(_WINDOWS) && !defined(_WIN32) && !defined(_PS3) + explicit CStrAutoEncode(const ucs2 *pwch) { + m_pch = NULL; + m_pwch = NULL; + m_pucs2 = pwch; + m_bCreatedUCS2 = true; + m_bCreatedUTF16 = false; + } +#endif + + // returns the UTF-8 string, converting on the fly. + const char *ToString() { + PopulateUTF8(); + return m_pch; + } + + // returns the UTF-8 string - a writable pointer. + // only use this if you don't want to call const_cast + // yourself. We need this for cases like CreateProcess. + char *ToStringWritable() { + PopulateUTF8(); + return const_cast(m_pch); + } + + // returns the UTF-16 string, converting on the fly. + const wchar_t *ToWString() { + PopulateUTF16(); + return m_pwch; + } + +#if !defined(WIN32) && !defined(_WIN32) + // returns the UTF-16 string, converting on the fly. + const ucs2 *ToUCS2String() { + PopulateUCS2(); + return m_pucs2; + } +#endif + + // returns the UTF-16 string - a writable pointer. + // only use this if you don't want to call const_cast + // yourself. We need this for cases like CreateProcess. + wchar_t *ToWStringWritable() { + PopulateUTF16(); + return const_cast(m_pwch); + } + + // dtor + ~CStrAutoEncode() { + // if we're "native unicode" then the UTF-8 string is something we + // allocated, and vice versa. + if (m_bCreatedUTF16) { + delete[] m_pch; + } else { + delete[] m_pwch; + } +#if !defined(WIN32) && !defined(_WIN32) + if (!m_bCreatedUCS2 && m_pucs2) delete[] m_pucs2; +#endif + } + + private: + // ensure we have done any conversion work required to farm out a + // UTF-8 encoded string. + // + // We perform two heap allocs here; the first one is the worst-case + // (four bytes per Unicode code point). This is usually quite pessimistic, + // so we perform a second allocation that's just the size we need. + void PopulateUTF8() { + if (!m_bCreatedUTF16) return; // no work to do + if (m_pwch == NULL) return; // don't have a UTF-16 string to convert + if (m_pch != NULL) + return; // already been converted to UTF-8; no work to do + + // each Unicode code point can expand to as many as four bytes in UTF-8; we + // also need to leave room for the terminating NUL. + uint32 cbMax = 4 * static_cast(V_wcslen(m_pwch)) + 1; + char *pchTemp = new char[cbMax]; + if (V_UnicodeToUTF8(m_pwch, pchTemp, cbMax)) { + uint32 cchAlloc = static_cast(V_strlen(pchTemp)) + 1; + char *pchHeap = new char[cchAlloc]; + V_strncpy(pchHeap, pchTemp, cchAlloc); + delete[] pchTemp; + m_pch = pchHeap; + } else { + // do nothing, and leave the UTF-8 string NULL + delete[] pchTemp; + } + } + + // ensure we have done any conversion work required to farm out a + // UTF-16 encoded string. + // + // We perform two heap allocs here; the first one is the worst-case + // (one code point per UTF-8 byte). This is sometimes pessimistic, + // so we perform a second allocation that's just the size we need. + void PopulateUTF16() { + if (m_bCreatedUTF16) return; // no work to do + if (m_pch == NULL) return; // no UTF-8 string to convert + if (m_pwch != NULL) + return; // already been converted to UTF-16; no work to do + + uint32 cchMax = static_cast(V_strlen(m_pch)) + 1; + wchar_t *pwchTemp = new wchar_t[cchMax]; + if (V_UTF8ToUnicode(m_pch, pwchTemp, cchMax * sizeof(wchar_t))) { + uint32 cchAlloc = static_cast(V_wcslen(pwchTemp)) + 1; + wchar_t *pwchHeap = new wchar_t[cchAlloc]; + V_wcsncpy(pwchHeap, pwchTemp, cchAlloc * sizeof(wchar_t)); + delete[] pwchTemp; + m_pwch = pwchHeap; + } else { + // do nothing, and leave the UTF-16 string NULL + delete[] pwchTemp; + } + } + +#if !defined(WIN32) && !defined(_WIN32) + // ensure we have done any conversion work required to farm out a + // UTF-16 encoded string. + // + // We perform two heap allocs here; the first one is the worst-case + // (one code point per UTF-8 byte). This is sometimes pessimistic, + // so we perform a second allocation that's just the size we need. + void PopulateUCS2() { + if (m_bCreatedUCS2) return; + if (m_pch == NULL) return; // no UTF-8 string to convert + if (m_pucs2 != NULL) + return; // already been converted to UTF-16; no work to do + + uint32 cchMax = static_cast(V_strlen(m_pch)) + 1; + ucs2 *pwchTemp = new ucs2[cchMax]; + if (V_UTF8ToUCS2(m_pch, cchMax, pwchTemp, cchMax * sizeof(ucs2))) { + uint32 cchAlloc = cchMax; + ucs2 *pwchHeap = new ucs2[cchAlloc]; + memcpy(pwchHeap, pwchTemp, cchAlloc * sizeof(ucs2)); + delete[] pwchTemp; + m_pucs2 = pwchHeap; + } else { + // do nothing, and leave the UTF-16 string NULL + delete[] pwchTemp; + } + } +#endif + + // one of these pointers is an owned pointer; whichever + // one is the encoding OTHER than the one we were initialized + // with is the pointer we've allocated and must free. + const char *m_pch; + const wchar_t *m_pwch; +#if !defined(WIN32) && !defined(_WIN32) + const ucs2 *m_pucs2; + bool m_bCreatedUCS2; +#endif + // "created as UTF-16", means our owned string is the UTF-8 string not the + // UTF-16 one. + bool m_bCreatedUTF16; +}; + +#define V_UTF8ToUnicode V_UTF8ToUnicode +#define V_UnicodeToUTF8 V_UnicodeToUTF8 + +#endif // VPC_VSTDLIB_VSTRTOOLS_H_ \ No newline at end of file diff --git a/public/winlite.h b/public/winlite.h new file mode 100644 index 0000000..4c848bd --- /dev/null +++ b/public/winlite.h @@ -0,0 +1,25 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_WINLITE_H_ +#define VPC_WINLITE_H_ + +#ifdef _WIN32 +// +// Prevent tons of unused windows definitions +// +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME + +#ifndef _X360 +#include +#include +#endif + +#undef PostMessage + +#endif // WIN32 + +#endif // VPC_WINLITE_H_ diff --git a/tier0/ValveETWProviderEvents.h b/tier0/ValveETWProviderEvents.h new file mode 100644 index 0000000..c1a575e --- /dev/null +++ b/tier0/ValveETWProviderEvents.h @@ -0,0 +1,1700 @@ +//**********************************************************************` +//* This is an include file generated by Message Compiler. *` +//* *` +//* Copyright (c) Microsoft Corporation. All Rights Reserved. *` +//**********************************************************************` +#pragma once +#include +#include +#include "evntprov.h" +// +// Initial Defs +// +#if !defined(ETW_INLINE) +#define ETW_INLINE DECLSPEC_NOINLINE __inline +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION +#if !defined(McGenDebug) +#define McGenDebug(a,b) +#endif + + +#if !defined(MCGEN_TRACE_CONTEXT_DEF) +#define MCGEN_TRACE_CONTEXT_DEF +typedef struct _MCGEN_TRACE_CONTEXT +{ + TRACEHANDLE RegistrationHandle; + TRACEHANDLE Logger; + ULONGLONG MatchAnyKeyword; + ULONGLONG MatchAllKeyword; + ULONG Flags; + ULONG IsEnabled; + UCHAR Level; + UCHAR Reserve; +} MCGEN_TRACE_CONTEXT, *PMCGEN_TRACE_CONTEXT; +#endif + +#if !defined(MCGEN_EVENT_ENABLED_DEF) +#define MCGEN_EVENT_ENABLED_DEF +FORCEINLINE +BOOLEAN +McGenEventEnabled( + __in PMCGEN_TRACE_CONTEXT EnableInfo, + __in PCEVENT_DESCRIPTOR EventDescriptor + ) +{ + // + // Check if the event Level is lower than the level at which + // the channel is enabled. + // If the event Level is 0 or the channel is enabled at level 0, + // all levels are enabled. + // + + if ((EventDescriptor->Level <= EnableInfo->Level) || // This also covers the case of Level == 0. + (EnableInfo->Level == 0)) { + + // + // Check if Keyword is enabled + // + + if ((EventDescriptor->Keyword == (ULONGLONG)0) || + ((EventDescriptor->Keyword & EnableInfo->MatchAnyKeyword) && + ((EventDescriptor->Keyword & EnableInfo->MatchAllKeyword) == EnableInfo->MatchAllKeyword))) { + return TRUE; + } + } + + return FALSE; + +} +#endif + + +// +// EnableCheckMacro +// +#ifndef MCGEN_ENABLE_CHECK +#define MCGEN_ENABLE_CHECK(Context, Descriptor) (Context.IsEnabled && McGenEventEnabled(&Context, &Descriptor)) +#endif + +#if !defined(MCGEN_CONTROL_CALLBACK) +#define MCGEN_CONTROL_CALLBACK + +DECLSPEC_NOINLINE __inline +VOID +__stdcall +McGenControlCallbackV2( + __in LPCGUID SourceId, + __in ULONG ControlCode, + __in UCHAR Level, + __in ULONGLONG MatchAnyKeyword, + __in ULONGLONG MatchAllKeyword, + __in_opt PEVENT_FILTER_DESCRIPTOR FilterData, + __inout_opt PVOID CallbackContext + ) +/*++ + +Routine Description: + + This is the notification callback for Vista. + +Arguments: + + SourceId - The GUID that identifies the session that enabled the provider. + + ControlCode - The parameter indicates whether the provider + is being enabled or disabled. + + Level - The level at which the event is enabled. + + MatchAnyKeyword - The bitmask of keywords that the provider uses to + determine the category of events that it writes. + + MatchAllKeyword - This bitmask additionally restricts the category + of events that the provider writes. + + FilterData - The provider-defined data. + + CallbackContext - The context of the callback that is defined when the provider + called EtwRegister to register itself. + +Remarks: + + ETW calls this function to notify provider of enable/disable + +--*/ +{ + PMCGEN_TRACE_CONTEXT Ctx = (PMCGEN_TRACE_CONTEXT)CallbackContext; +#ifndef MCGEN_PRIVATE_ENABLE_CALLBACK_V2 + UNREFERENCED_PARAMETER(SourceId); + UNREFERENCED_PARAMETER(FilterData); +#endif + + if (Ctx == NULL) { + return; + } + + switch (ControlCode) { + + case EVENT_CONTROL_CODE_ENABLE_PROVIDER: + Ctx->Level = Level; + Ctx->MatchAnyKeyword = MatchAnyKeyword; + Ctx->MatchAllKeyword = MatchAllKeyword; + Ctx->IsEnabled = EVENT_CONTROL_CODE_ENABLE_PROVIDER; + break; + + case EVENT_CONTROL_CODE_DISABLE_PROVIDER: + Ctx->IsEnabled = EVENT_CONTROL_CODE_DISABLE_PROVIDER; + Ctx->Level = 0; + Ctx->MatchAnyKeyword = 0; + Ctx->MatchAllKeyword = 0; + break; + + default: + break; + } + +#ifdef MCGEN_PRIVATE_ENABLE_CALLBACK_V2 + // + // Call user defined callback + // + MCGEN_PRIVATE_ENABLE_CALLBACK_V2( + SourceId, + ControlCode, + Level, + MatchAnyKeyword, + MatchAllKeyword, + FilterData, + CallbackContext + ); +#endif + + return; +} + +#endif +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION +//+ +// Provider Valve-Main Event Count 14 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_MAIN = {0x3fa9201e, 0x73b0, 0x43fe, {0x98, 0x21, 0x7e, 0x14, 0x53, 0x59, 0xbc, 0x6f}}; + +// +// Opcodes +// +#define _BeginOpcode 0xa +#define _EndOpcode 0xb +#define _StepOpcode 0xc +#define _MarkOpcode 0xd +#define _MarkOpcode1F 0xe +#define _MarkOpcode2F 0xf +#define _MarkOpcode3F 0x10 +#define _MarkOpcode4F 0x11 +#define _MarkOpcode1I 0x12 +#define _MarkOpcode2I 0x13 +#define _MarkOpcode3I 0x14 +#define _MarkOpcode4I 0x15 +#define _MarkOpcode1S 0x16 +#define _MarkOpcode2S 0x17 +#define _InformationOpcode 0x18 + +// +// Tasks +// +#define Block_Task 0x1 +EXTERN_C __declspec(selectany) const GUID BlockId = {0xf15f363a, 0x49fd, 0x4de3, {0x96, 0x7c, 0x17, 0x32, 0x46, 0x49, 0x45, 0xff}}; +#define ThreadID_Task 0x2 +EXTERN_C __declspec(selectany) const GUID ThreadIDId = {0xf15f363a, 0x493d, 0x4dea, {0x96, 0x7c, 0x11, 0x23, 0x46, 0x49, 0x45, 0xff}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Start = {0x64, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define Start_value 0x64 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Stop = {0x65, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define Stop_value 0x65 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark = {0x66, 0x0, 0x0, 0x0, 0xd, 0x1, 0x0}; +#define Mark_value 0x66 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark1F = {0x67, 0x0, 0x0, 0x0, 0xe, 0x1, 0x0}; +#define Mark1F_value 0x67 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark2F = {0x68, 0x0, 0x0, 0x0, 0xf, 0x1, 0x0}; +#define Mark2F_value 0x68 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark3F = {0x69, 0x0, 0x0, 0x0, 0x10, 0x1, 0x0}; +#define Mark3F_value 0x69 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark4F = {0x6a, 0x0, 0x0, 0x0, 0x11, 0x1, 0x0}; +#define Mark4F_value 0x6a +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark1I = {0x6b, 0x0, 0x0, 0x0, 0x12, 0x1, 0x0}; +#define Mark1I_value 0x6b +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark2I = {0x6c, 0x0, 0x0, 0x0, 0x13, 0x1, 0x0}; +#define Mark2I_value 0x6c +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark3I = {0x6d, 0x0, 0x0, 0x0, 0x14, 0x1, 0x0}; +#define Mark3I_value 0x6d +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark4I = {0x6e, 0x0, 0x0, 0x0, 0x15, 0x1, 0x0}; +#define Mark4I_value 0x6e +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark1S = {0x6f, 0x0, 0x0, 0x0, 0x16, 0x1, 0x0}; +#define Mark1S_value 0x6f +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark2S = {0x70, 0x0, 0x0, 0x0, 0x17, 0x1, 0x0}; +#define Mark2S_value 0x70 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Thread_ID = {0x71, 0x0, 0x0, 0x4, 0x18, 0x2, 0x0}; +#define Thread_ID_value 0x71 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_MainHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_MAIN_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_Main +#define EventRegisterValve_Main() McGenEventRegister(&VALVE_MAIN, McGenControlCallbackV2, &VALVE_MAIN_Context, &Valve_MainHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_Main +#define EventUnregisterValve_Main() McGenEventUnregister(&Valve_MainHandle) +#endif + +// +// Event Macro for Start +// +#define EventWriteStart(Description, Depth)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Start) ?\ + Template_sd(Valve_MainHandle, &Start, Description, Depth)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Stop +// +#define EventWriteStop(Description, Depth, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Stop) ?\ + Template_sdf(Valve_MainHandle, &Stop, Description, Depth, Duration__ms_)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark +// +#define EventWriteMark(Description)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark) ?\ + Template_s(Valve_MainHandle, &Mark, Description)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark1F +// +#define EventWriteMark1F(Description, Data_1)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark1F) ?\ + Template_sf(Valve_MainHandle, &Mark1F, Description, Data_1)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark2F +// +#define EventWriteMark2F(Description, Data_1, Data_2)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark2F) ?\ + Template_sff(Valve_MainHandle, &Mark2F, Description, Data_1, Data_2)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark3F +// +#define EventWriteMark3F(Description, Data_1, Data_2, Data_3)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark3F) ?\ + Template_sfff(Valve_MainHandle, &Mark3F, Description, Data_1, Data_2, Data_3)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark4F +// +#define EventWriteMark4F(Description, Data_1, Data_2, Data_3, Data_4)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark4F) ?\ + Template_sffff(Valve_MainHandle, &Mark4F, Description, Data_1, Data_2, Data_3, Data_4)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark1I +// +#define EventWriteMark1I(Description, Data_1)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark1I) ?\ + Template_sd(Valve_MainHandle, &Mark1I, Description, Data_1)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark2I +// +#define EventWriteMark2I(Description, Data_1, Data_2)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark2I) ?\ + Template_sdd(Valve_MainHandle, &Mark2I, Description, Data_1, Data_2)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark3I +// +#define EventWriteMark3I(Description, Data_1, Data_2, Data_3)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark3I) ?\ + Template_sddd(Valve_MainHandle, &Mark3I, Description, Data_1, Data_2, Data_3)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark4I +// +#define EventWriteMark4I(Description, Data_1, Data_2, Data_3, Data_4)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark4I) ?\ + Template_sdddd(Valve_MainHandle, &Mark4I, Description, Data_1, Data_2, Data_3, Data_4)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark1S +// +#define EventWriteMark1S(Description, Data_1)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark1S) ?\ + Template_ss(Valve_MainHandle, &Mark1S, Description, Data_1)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark2S +// +#define EventWriteMark2S(Description, Data_1, Data_2)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark2S) ?\ + Template_sss(Valve_MainHandle, &Mark2S, Description, Data_1, Data_2)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Thread_ID +// +#define EventWriteThread_ID(ThreadID, ThreadName)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Thread_ID) ?\ + Template_ds(Valve_MainHandle, &Thread_ID, ThreadID, ThreadName)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-FrameRate Event Count 2 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_FRAMERATE = {0x47a9201e, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xbc, 0x6f}}; + +// +// Opcodes +// +#define _RenderFrameMarkOpcode 0xa +#define _SimFrameMarkOpcode 0xb + +// +// Tasks +// +#define Frame_Task 0x1 +EXTERN_C __declspec(selectany) const GUID FrameId = {0xf15f363a, 0x49fd, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x45, 0xff}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR RenderFrameMark = {0xc8, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define RenderFrameMark_value 0xc8 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR SimFrameMark = {0xc9, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define SimFrameMark_value 0xc9 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_FrameRateHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_FRAMERATE_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_FrameRate +#define EventRegisterValve_FrameRate() McGenEventRegister(&VALVE_FRAMERATE, McGenControlCallbackV2, &VALVE_FRAMERATE_Context, &Valve_FrameRateHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_FrameRate +#define EventUnregisterValve_FrameRate() McGenEventUnregister(&Valve_FrameRateHandle) +#endif + +// +// Event Macro for RenderFrameMark +// +#define EventWriteRenderFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_FRAMERATE_Context, RenderFrameMark) ?\ + Template_df(Valve_FrameRateHandle, &RenderFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +// +// Event Macro for SimFrameMark +// +#define EventWriteSimFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_FRAMERATE_Context, SimFrameMark) ?\ + Template_df(Valve_FrameRateHandle, &SimFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-ServerFrameRate Event Count 2 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_SERVERFRAMERATE = {0x58a9201e, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xbc, 0x70}}; + +// +// Opcodes +// +#define _ServerRenderFrameMarkOpcode 0xa +#define _ServerSimFrameMarkOpcode 0xb + +// +// Tasks +// +#define Frame_Task 0x1 +EXTERN_C __declspec(selectany) const GUID ServerFrameId = {0x025f363a, 0x49fd, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x45, 0x00}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ServerRenderFrameMark = {0x12c, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define ServerRenderFrameMark_value 0x12c +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ServerSimFrameMark = {0x12d, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define ServerSimFrameMark_value 0x12d + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_ServerFrameRateHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_SERVERFRAMERATE_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_ServerFrameRate +#define EventRegisterValve_ServerFrameRate() McGenEventRegister(&VALVE_SERVERFRAMERATE, McGenControlCallbackV2, &VALVE_SERVERFRAMERATE_Context, &Valve_ServerFrameRateHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_ServerFrameRate +#define EventUnregisterValve_ServerFrameRate() McGenEventUnregister(&Valve_ServerFrameRateHandle) +#endif + +// +// Event Macro for ServerRenderFrameMark +// +#define EventWriteServerRenderFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_SERVERFRAMERATE_Context, ServerRenderFrameMark) ?\ + Template_df(Valve_ServerFrameRateHandle, &ServerRenderFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +// +// Event Macro for ServerSimFrameMark +// +#define EventWriteServerSimFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_SERVERFRAMERATE_Context, ServerSimFrameMark) ?\ + Template_df(Valve_ServerFrameRateHandle, &ServerSimFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-Input Event Count 5 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_INPUT = {0x1432afee, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xb4, 0x33}}; + +// +// Opcodes +// +#define _MouseDownOpcode 0xa +#define _MouseUpOpcode 0xb +#define _KeyDownOpcode 0xc +#define _MouseMoveOpcode 0xd +#define _MouseWheelOpcode 0xe + +// +// Tasks +// +#define Mouse_Task 0x1 +EXTERN_C __declspec(selectany) const GUID MouseId = {0x363a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x44, 0x33}}; +#define Keyboard_Task 0x2 +EXTERN_C __declspec(selectany) const GUID KeyboardId = {0x123a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0xbe, 0xad}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_down = {0x190, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define Mouse_down_value 0x190 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_up = {0x191, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define Mouse_up_value 0x191 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Key_down = {0x192, 0x0, 0x0, 0x0, 0xc, 0x2, 0x0}; +#define Key_down_value 0x192 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_Move = {0x193, 0x0, 0x0, 0x0, 0xd, 0x1, 0x0}; +#define Mouse_Move_value 0x193 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_Wheel = {0x194, 0x0, 0x0, 0x0, 0xe, 0x1, 0x0}; +#define Mouse_Wheel_value 0x194 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_InputHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_INPUT_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_Input +#define EventRegisterValve_Input() McGenEventRegister(&VALVE_INPUT, McGenControlCallbackV2, &VALVE_INPUT_Context, &Valve_InputHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_Input +#define EventUnregisterValve_Input() McGenEventUnregister(&Valve_InputHandle) +#endif + +// +// Event Macro for Mouse_down +// +#define EventWriteMouse_down(x, y, Button_Type)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_down) ?\ + Template_ddd(Valve_InputHandle, &Mouse_down, x, y, Button_Type)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mouse_up +// +#define EventWriteMouse_up(x, y, Button_Type)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_up) ?\ + Template_ddd(Valve_InputHandle, &Mouse_up, x, y, Button_Type)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Key_down +// +#define EventWriteKey_down(Character, Scan_Code, Virtual_Code)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Key_down) ?\ + Template_sdd(Valve_InputHandle, &Key_down, Character, Scan_Code, Virtual_Code)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mouse_Move +// +#define EventWriteMouse_Move(x, y)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_Move) ?\ + Template_dd(Valve_InputHandle, &Mouse_Move, x, y)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mouse_Wheel +// +#define EventWriteMouse_Wheel(x, y, wheelDelta)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_Wheel) ?\ + Template_ddd(Valve_InputHandle, &Mouse_Wheel, x, y, wheelDelta)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-Network Event Count 3 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_NETWORK = {0x4372afee, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xb5, 0x19}}; + +// +// Opcodes +// +#define _SendOpcode 0xa +#define _ThrottledOpcode 0xb +#define _ReadOpcode 0xc + +// +// Tasks +// +#define Transmit_Task 0x1 +EXTERN_C __declspec(selectany) const GUID TransmitId = {0x932a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x49, 0x01}}; +#define Network_Task 0x2 +EXTERN_C __declspec(selectany) const GUID ThrottledId = {0xa32a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x49, 0x02}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR SendPacket = {0x1f4, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define SendPacket_value 0x1f4 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Throttled = {0x1f5, 0x0, 0x0, 0x0, 0xb, 0x2, 0x0}; +#define Throttled_value 0x1f5 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ReadPacket = {0x1f6, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0}; +#define ReadPacket_value 0x1f6 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_NetworkHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_NETWORK_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_Network +#define EventRegisterValve_Network() McGenEventRegister(&VALVE_NETWORK, McGenControlCallbackV2, &VALVE_NETWORK_Context, &Valve_NetworkHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_Network +#define EventUnregisterValve_Network() McGenEventUnregister(&Valve_NetworkHandle) +#endif + +// +// Event Macro for SendPacket +// +#define EventWriteSendPacket(To, WireSize, outSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + MCGEN_ENABLE_CHECK(VALVE_NETWORK_Context, SendPacket) ?\ + Template_sdddd(Valve_NetworkHandle, &SendPacket, To, WireSize, outSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Throttled +// +#define EventWriteThrottled()\ + MCGEN_ENABLE_CHECK(VALVE_NETWORK_Context, Throttled) ?\ + TemplateEventDescriptor(Valve_NetworkHandle, &Throttled)\ + : ERROR_SUCCESS\ + +// +// Event Macro for ReadPacket +// +#define EventWriteReadPacket(From, WireSize, inSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + MCGEN_ENABLE_CHECK(VALVE_NETWORK_Context, ReadPacket) ?\ + Template_sdddd(Valve_NetworkHandle, &ReadPacket, From, WireSize, inSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Template Functions +// +// +//Template from manifest : T_Start +// +#ifndef Template_sd_def +#define Template_sd_def +ETW_INLINE +ULONG +Template_sd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Depth + ) +{ +#define ARGUMENT_COUNT_sd 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Depth, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sd, EventData); +} +#endif + +// +//Template from manifest : T_End +// +#ifndef Template_sdf_def +#define Template_sdf_def +ETW_INLINE +ULONG +Template_sdf( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Depth, + __in const float Duration__ms_ + ) +{ +#define ARGUMENT_COUNT_sdf 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sdf]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Depth, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Duration__ms_, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sdf, EventData); +} +#endif + +// +//Template from manifest : T_Mark +// +#ifndef Template_s_def +#define Template_s_def +ETW_INLINE +ULONG +Template_s( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description + ) +{ +#define ARGUMENT_COUNT_s 1 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_s]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_s, EventData); +} +#endif + +// +//Template from manifest : T_Mark1F +// +#ifndef Template_sf_def +#define Template_sf_def +ETW_INLINE +ULONG +Template_sf( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1 + ) +{ +#define ARGUMENT_COUNT_sf 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sf]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sf, EventData); +} +#endif + +// +//Template from manifest : T_Mark2F +// +#ifndef Template_sff_def +#define Template_sff_def +ETW_INLINE +ULONG +Template_sff( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1, + __in const float Data_2 + ) +{ +#define ARGUMENT_COUNT_sff 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sff]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sff, EventData); +} +#endif + +// +//Template from manifest : T_Mark3F +// +#ifndef Template_sfff_def +#define Template_sfff_def +ETW_INLINE +ULONG +Template_sfff( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1, + __in const float Data_2, + __in const float Data_3 + ) +{ +#define ARGUMENT_COUNT_sfff 4 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sfff]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const float) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sfff, EventData); +} +#endif + +// +//Template from manifest : T_Mark4F +// +#ifndef Template_sffff_def +#define Template_sffff_def +ETW_INLINE +ULONG +Template_sffff( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1, + __in const float Data_2, + __in const float Data_3, + __in const float Data_4 + ) +{ +#define ARGUMENT_COUNT_sffff 5 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sffff]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const float) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const float) ); + + EventDataDescCreate(&EventData[4], &Data_4, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sffff, EventData); +} +#endif + +// +//Template from manifest : T_Mark2I +// +#ifndef Template_sdd_def +#define Template_sdd_def +ETW_INLINE +ULONG +Template_sdd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Data_1, + __in const signed int Data_2 + ) +{ +#define ARGUMENT_COUNT_sdd 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sdd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sdd, EventData); +} +#endif + +// +//Template from manifest : T_Mark3I +// +#ifndef Template_sddd_def +#define Template_sddd_def +ETW_INLINE +ULONG +Template_sddd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Data_1, + __in const signed int Data_2, + __in const signed int Data_3 + ) +{ +#define ARGUMENT_COUNT_sddd 4 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sddd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sddd, EventData); +} +#endif + +// +//Template from manifest : T_Mark4I +// +#ifndef Template_sdddd_def +#define Template_sdddd_def +ETW_INLINE +ULONG +Template_sdddd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Data_1, + __in const signed int Data_2, + __in const signed int Data_3, + __in const signed int Data_4 + ) +{ +#define ARGUMENT_COUNT_sdddd 5 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sdddd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[4], &Data_4, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sdddd, EventData); +} +#endif + +// +//Template from manifest : T_Mark1S +// +#ifndef Template_ss_def +#define Template_ss_def +ETW_INLINE +ULONG +Template_ss( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in_opt LPCSTR Data_1 + ) +{ +#define ARGUMENT_COUNT_ss 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_ss]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], + (Data_1 != NULL) ? Data_1 : "NULL", + (Data_1 != NULL) ? (ULONG)((strlen(Data_1) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_ss, EventData); +} +#endif + +// +//Template from manifest : T_Mark2S +// +#ifndef Template_sss_def +#define Template_sss_def +ETW_INLINE +ULONG +Template_sss( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in_opt LPCSTR Data_1, + __in_opt LPCSTR Data_2 + ) +{ +#define ARGUMENT_COUNT_sss 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sss]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], + (Data_1 != NULL) ? Data_1 : "NULL", + (Data_1 != NULL) ? (ULONG)((strlen(Data_1) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[2], + (Data_2 != NULL) ? Data_2 : "NULL", + (Data_2 != NULL) ? (ULONG)((strlen(Data_2) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sss, EventData); +} +#endif + +// +//Template from manifest : T_ThreadID +// +#ifndef Template_ds_def +#define Template_ds_def +ETW_INLINE +ULONG +Template_ds( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int ThreadID, + __in_opt LPCSTR ThreadName + ) +{ +#define ARGUMENT_COUNT_ds 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_ds]; + + EventDataDescCreate(&EventData[0], &ThreadID, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], + (ThreadName != NULL) ? ThreadName : "NULL", + (ThreadName != NULL) ? (ULONG)((strlen(ThreadName) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_ds, EventData); +} +#endif + +// +//Template from manifest : T_FrameMark +// +#ifndef Template_df_def +#define Template_df_def +ETW_INLINE +ULONG +Template_df( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int Frame_number, + __in const float Duration__ms_ + ) +{ +#define ARGUMENT_COUNT_df 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_df]; + + EventDataDescCreate(&EventData[0], &Frame_number, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], &Duration__ms_, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_df, EventData); +} +#endif + +// +//Template from manifest : T_MouseClick +// +#ifndef Template_ddd_def +#define Template_ddd_def +ETW_INLINE +ULONG +Template_ddd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int x, + __in const signed int y, + __in const signed int Button_Type + ) +{ +#define ARGUMENT_COUNT_ddd 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_ddd]; + + EventDataDescCreate(&EventData[0], &x, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], &y, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Button_Type, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_ddd, EventData); +} +#endif + +// +//Template from manifest : T_MouseMove +// +#ifndef Template_dd_def +#define Template_dd_def +ETW_INLINE +ULONG +Template_dd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int x, + __in const signed int y + ) +{ +#define ARGUMENT_COUNT_dd 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_dd]; + + EventDataDescCreate(&EventData[0], &x, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], &y, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_dd, EventData); +} +#endif + +// +//Template from manifest : T_Throttled +// +#ifndef TemplateEventDescriptor_def +#define TemplateEventDescriptor_def + + +ETW_INLINE +ULONG +TemplateEventDescriptor( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor + ) +{ + return EventWrite(RegHandle, Descriptor, 0, NULL); +} +#endif + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +#if defined(__cplusplus) +}; +#endif + diff --git a/tier0/assert_dialog.cpp b/tier0/assert_dialog.cpp new file mode 100644 index 0000000..82fe2b1 --- /dev/null +++ b/tier0/assert_dialog.cpp @@ -0,0 +1,507 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier0/platform.h" + +#include "tier0/valve_off.h" + +#ifdef _X360 +#include "xbox/xbox_console.h" +#include "xbox/xbox_vxconsole.h" +#elif defined(_PS3) +#include "ps3/ps3_console.h" +#elif defined(_WIN32) +#include "winlite.h" +#elif POSIX +char *GetCommandLine(); +#endif + +#include "resource.h" + +#include "tier0/valve_on.h" + +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +struct CDialogInitInfo { + const tchar *m_pFilename; + int m_iLine; + const tchar *m_pExpression; +}; + +struct CAssertDisable { + tchar m_Filename[512]; + + // If these are not -1, then this CAssertDisable only disables asserts on + // lines between these values (inclusive). + int m_LineMin; + int m_LineMax; + + // Decremented each time we hit this assert and ignore it, until it's 0. + // Then the CAssertDisable is removed. + // If this is -1, then we always ignore this assert. + int m_nIgnoreTimes; + + CAssertDisable *m_pNext; +}; + +#ifdef _WIN32 +static HINSTANCE g_hTier0Instance{nullptr}; +#endif + +static bool g_bAssertsEnabled = true; + +static CAssertDisable *g_pAssertDisables = nullptr; + +#if (defined(_WIN32) && !defined(_X360)) +static int g_iLastLineRange = 5; +static int g_nLastIgnoreNumTimes = 1; +#endif +#if defined(_X360) || defined(_PS3) +static int g_VXConsoleAssertReturnValue = -1; +#endif + +// Set to true if they want to break in the debugger. +static bool g_bBreak = false; + +static CDialogInitInfo g_Info; + +static bool g_bDisableAsserts = false; + +// Internal functions. + +#if defined(_WIN32) && !defined(STATIC_TIER0) +BOOL WINAPI DllMain(HINSTANCE dll, // handle to the DLL module + DWORD, // reason for calling function + LPVOID // reserved +) { + g_hTier0Instance = dll; + return true; +} +#endif + +static bool IsDebugBreakEnabled() { + static bool bResult = + (_tcsstr(Plat_GetCommandLine(), _T("-debugbreak")) != nullptr); + return bResult; +} + +static bool AssertStack() { + static bool bResult = + (_tcsstr(Plat_GetCommandLine(), _T("-assertstack")) != nullptr); + return bResult; +} + +static bool AreAssertsDisabled() { + static bool bResult = + (_tcsstr(Plat_GetCommandLine(), _T("-noassert")) != nullptr); + return bResult || g_bDisableAsserts; +} + +static bool AllAssertOnce() { + static bool bResult = + (_tcsstr(Plat_GetCommandLine(), _T("-assertonce")) != nullptr); + return bResult; +} + +static bool AreAssertsEnabledInFileLine(const tchar *pFilename, int iLine) { + CAssertDisable **pPrev = &g_pAssertDisables; + CAssertDisable *pNext; + + for (CAssertDisable *pCur = g_pAssertDisables; pCur; pCur = pNext) { + pNext = pCur->m_pNext; + + if (_tcsicmp(pFilename, pCur->m_Filename) == 0) { + // Are asserts disabled in the whole file? + bool bAssertsEnabled = true; + if (pCur->m_LineMin == -1 && pCur->m_LineMax == -1) + bAssertsEnabled = false; + + // Are asserts disabled on the specified line? + if (iLine >= pCur->m_LineMin && iLine <= pCur->m_LineMax) + bAssertsEnabled = false; + + if (!bAssertsEnabled) { + // If this assert is only disabled for the next N times, then + // countdown.. + if (pCur->m_nIgnoreTimes > 0) { + --pCur->m_nIgnoreTimes; + if (pCur->m_nIgnoreTimes == 0) { + // Remove this one from the list. + *pPrev = pNext; + delete pCur; + continue; + } + } + + return false; + } + } + + pPrev = &pCur->m_pNext; + } + + return true; +} + +CAssertDisable *CreateNewAssertDisable(const tchar *pFilename) { + CAssertDisable *pDisable = new CAssertDisable; + pDisable->m_pNext = g_pAssertDisables; + g_pAssertDisables = pDisable; + + pDisable->m_LineMin = pDisable->m_LineMax = -1; + pDisable->m_nIgnoreTimes = -1; + + _tcsncpy(pDisable->m_Filename, pFilename, sizeof(pDisable->m_Filename) - 1); + pDisable->m_Filename[sizeof(pDisable->m_Filename) - 1] = '\0'; + + return pDisable; +} + +void IgnoreAssertsInCurrentFile() { + CreateNewAssertDisable(g_Info.m_pFilename); +} + +CAssertDisable *IgnoreAssertsNearby(int nRange) { + CAssertDisable *pDisable = CreateNewAssertDisable(g_Info.m_pFilename); + pDisable->m_LineMin = g_Info.m_iLine - nRange; + pDisable->m_LineMax = g_Info.m_iLine + nRange; + return pDisable; +} + +#if defined(_WIN32) && !defined(_X360) +INT_PTR CALLBACK AssertDialogProc( + HWND hDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + [[maybe_unused]] LPARAM lParam // second message parameter +) { + switch (uMsg) { + case WM_INITDIALOG: { +#ifdef TCHAR_IS_WCHAR + SetDlgItemTextW(hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression); + SetDlgItemTextW(hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename); +#else + SetDlgItemText(hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression); + SetDlgItemText(hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename); +#endif + SetDlgItemInt(hDlg, IDC_LINE_CONTROL, g_Info.m_iLine, false); + SetDlgItemInt(hDlg, IDC_IGNORE_NUMLINES, g_iLastLineRange, false); + SetDlgItemInt(hDlg, IDC_IGNORE_NUMTIMES, g_nLastIgnoreNumTimes, false); + + // Center the dialog. + RECT rcDlg, rcDesktop; + GetWindowRect(hDlg, &rcDlg); + GetWindowRect(GetDesktopWindow(), &rcDesktop); + SetWindowPos( + hDlg, HWND_TOP, + ((rcDesktop.right - rcDesktop.left) - (rcDlg.right - rcDlg.left)) / 2, + ((rcDesktop.bottom - rcDesktop.top) - (rcDlg.bottom - rcDlg.top)) / 2, + 0, 0, SWP_NOSIZE); + } + return true; + + case WM_COMMAND: { + switch (LOWORD(wParam)) { + case IDC_IGNORE_FILE: { + IgnoreAssertsInCurrentFile(); + EndDialog(hDlg, 0); + return true; + } + + // Ignore this assert N times. + case IDC_IGNORE_THIS: { + BOOL bTranslated = false; + UINT value = + GetDlgItemInt(hDlg, IDC_IGNORE_NUMTIMES, &bTranslated, false); + if (bTranslated && value > 1) { + CAssertDisable *pDisable = IgnoreAssertsNearby(0); + pDisable->m_nIgnoreTimes = value - 1; + g_nLastIgnoreNumTimes = value; + } + + EndDialog(hDlg, 0); + return true; + } + + // Always ignore this assert. + case IDC_IGNORE_ALWAYS: { + IgnoreAssertsNearby(0); + EndDialog(hDlg, 0); + return true; + } + + case IDC_IGNORE_NEARBY: { + BOOL bTranslated = false; + UINT value = + GetDlgItemInt(hDlg, IDC_IGNORE_NUMLINES, &bTranslated, false); + if (!bTranslated || value < 1) return true; + + IgnoreAssertsNearby(value); + EndDialog(hDlg, 0); + return true; + } + + case IDC_IGNORE_ALL: { + g_bAssertsEnabled = false; + EndDialog(hDlg, 0); + return true; + } + + case IDC_BREAK: { + g_bBreak = true; + EndDialog(hDlg, 0); + return true; + } + } + + case WM_KEYDOWN: { + // Escape? + if (wParam == 2) { + // Ignore this assert. + EndDialog(hDlg, 0); + return true; + } + } + } + return true; + } + + return FALSE; +} + +static HWND g_hBestParentWindow; + +static BOOL CALLBACK ParentWindowEnumProc( + HWND hWnd, // handle to parent window + LPARAM lParam // application-defined value +) { + if (IsWindowVisible(hWnd)) { + DWORD procID; + GetWindowThreadProcessId(hWnd, &procID); + + if (procID == (DWORD)lParam) { + g_hBestParentWindow = hWnd; + return FALSE; // don't iterate any more. + } + } + return TRUE; +} + +static HWND FindLikelyParentWindow() { + // Enumerate top-level windows and take the first visible one with our + // processID. + g_hBestParentWindow = nullptr; + EnumWindows(ParentWindowEnumProc, GetCurrentProcessId()); + return g_hBestParentWindow; +} +#endif + +// Interface functions. + +// provides access to the global that turns asserts on and off +PLATFORM_INTERFACE bool AreAllAssertsDisabled() { return !g_bAssertsEnabled; } + +PLATFORM_INTERFACE void SetAllAssertsDisabled(bool bAssertsDisabled) { + g_bAssertsEnabled = !bAssertsDisabled; +} + +PLATFORM_INTERFACE bool ShouldUseNewAssertDialog() { + static bool bMPIWorker = + (_tcsstr(Plat_GetCommandLine(), _T("-mpi_worker")) != NULL); + if (bMPIWorker) { + return false; + } + +#ifdef DBGFLAG_ASSERTDLG + return true; // always show an assert dialog +#else + // only show an assert dialog if the process is being debugged + return Plat_IsInDebugSession(); +#endif // DBGFLAG_ASSERTDLG +} + +PLATFORM_INTERFACE bool DoNewAssertDialog(const tchar *pFilename, int line, + const tchar *pExpression) { + LOCAL_THREAD_LOCK(); + + if (AreAssertsDisabled()) return false; + + // If they have the old mode enabled (always break immediately), then just + // break right into the debugger like we used to do. + if (IsDebugBreakEnabled()) return true; + + // Have ALL Asserts been disabled? + if (!g_bAssertsEnabled) return false; + + // Has this specific Assert been disabled? + if (!AreAssertsEnabledInFileLine(pFilename, line)) return false; + + // Now create the dialog. + g_Info.m_pFilename = pFilename; + g_Info.m_iLine = line; + g_Info.m_pExpression = pExpression; + + if (AssertStack()) { + IgnoreAssertsNearby(0); + // @TODO: add-back callstack spew support + Warning( + "%s (%d) : Assertion callstack...(NOT IMPLEMENTED IN NEW LOGGING " + "SYSTEM.)\n", + pFilename, line); + // Warning_SpewCallStack( 10, "%s (%d) : Assertion callstack...\n", + // pFilename, line ); + return false; + } + + if (AllAssertOnce()) { + IgnoreAssertsNearby(0); + } + + g_bBreak = false; + +#if defined(_X360) + + char cmdString[XBX_MAX_RCMDLENGTH]; + + // Before calling VXConsole, init the global variable that receives the result + g_VXConsoleAssertReturnValue = -1; + + // Message VXConsole to pop up a PC-side Assert dialog + _snprintf(cmdString, sizeof(cmdString), + "Assert() 0x%.8x File: %s\tLine: %d\t%s", + &g_VXConsoleAssertReturnValue, pFilename, line, pExpression); + XBX_SendRemoteCommand(cmdString, false); + + // We sent a synchronous message, so g_xbx_dbgVXConsoleAssertReturnValue + // should have been overwritten by now + if (g_VXConsoleAssertReturnValue == -1) { + // VXConsole isn't connected/running - default to the old behaviour (break) + g_bBreak = true; + } else { + // Respond to what the user selected + switch (g_VXConsoleAssertReturnValue) { + case ASSERT_ACTION_IGNORE_FILE: + IgnoreAssertsInCurrentFile(); + break; + case ASSERT_ACTION_IGNORE_THIS: + // Ignore this Assert once + break; + case ASSERT_ACTION_BREAK: + // Break on this Assert + g_bBreak = true; + break; + case ASSERT_ACTION_IGNORE_ALL: + // Ignore all Asserts from now on + g_bAssertsEnabled = false; + break; + case ASSERT_ACTION_IGNORE_ALWAYS: + // Ignore this Assert from now on + IgnoreAssertsNearby(0); + break; + case ASSERT_ACTION_OTHER: + default: + // Error... just break + XBX_Error( + "DoNewAssertDialog: invalid Assert response returned from " + "VXConsole - breaking to debugger"); + g_bBreak = true; + break; + } + } +#elif defined(_PS3) + // There are a few ways to handle this sort of assert behavior with the PS3 / + // Target Manager API. One is to use a DebuggerBreak per usual, and then + // SNProcessContinue in the TMAPI to make the game resume after a breakpoint. + // (You can use snIsDebuggerPresent() to determine if the debugger is + // attached, although really it doesn't matter here.) This doesn't work + // because the DebuggerBreak() is actually an interrupt op, and so Continue() + // won't continue past it -- you need to do that from inside the ProDG + // debugger itself. Another is to wait on a mutex here and then trip it from + // the TMAPI, but there isn't a clean way to trip sync primitives from TMAPI. + // Another way is to suspend the thread here and have TMAPI resume it. + // The simplest way is to spin-wait on a shared variable that you expect the + // TMAPI to poke into memory. I'm trying that. + + char cmdString[XBX_MAX_RCMDLENGTH]; + + // Before calling VXConsole, init the global variable that receives the result + g_VXConsoleAssertReturnValue = -1; + + // Message VXConsole to pop up a PC-side Assert dialog + _snprintf(cmdString, sizeof(cmdString), + "Assert() 0x%.8x File: %s\tLine: %d\t%s", + &g_VXConsoleAssertReturnValue, pFilename, line, pExpression); + XBX_SendRemoteCommand(cmdString, false); + + if (g_pValvePS3Console->IsConsoleConnected()) { + // DebuggerBreak(); + + while (g_VXConsoleAssertReturnValue == -1) { + ThreadSleep(1000); + } + + // assume that the VX has poked the return value + // Respond to what the user selected + switch (g_VXConsoleAssertReturnValue) { + case ASSERT_ACTION_IGNORE_FILE: + IgnoreAssertsInCurrentFile(); + break; + case ASSERT_ACTION_IGNORE_THIS: + // Ignore this Assert once + break; + case ASSERT_ACTION_BREAK: + // Break on this Assert + g_bBreak = true; + break; + case ASSERT_ACTION_IGNORE_ALL: + // Ignore all Asserts from now on + g_bAssertsEnabled = false; + break; + case ASSERT_ACTION_IGNORE_ALWAYS: + // Ignore this Assert from now on + IgnoreAssertsNearby(0); + break; + case ASSERT_ACTION_OTHER: + default: + // nothing. + break; + } + } else if (g_pValvePS3Console->IsDebuggerPresent()) { + g_bBreak = true; + } else { + // ignore the assert + } + +#elif defined(POSIX) + + fprintf(stderr, "%s %i %s\n", pFilename, line, pExpression); + if (getenv("RAISE_ON_ASSERT")) { + DebuggerBreak(); + g_bBreak = true; + } + +#elif defined(_WIN32) + + if (!g_hTier0Instance || !ThreadInMainThread()) { + int result = MessageBox(NULL, pExpression, "Assertion Failed", + MB_SYSTEMMODAL | MB_CANCELTRYCONTINUE); + + if (result == IDCANCEL) { + IgnoreAssertsNearby(0); + } else if (result == IDCONTINUE) { + g_bBreak = true; + } + } else { + HWND hParentWindow = FindLikelyParentWindow(); + + DialogBox(g_hTier0Instance, MAKEINTRESOURCE(IDD_ASSERT_DIALOG), + hParentWindow, AssertDialogProc); + } + +#endif + + return g_bBreak; +} diff --git a/tier0/commandline.cpp b/tier0/commandline.cpp new file mode 100644 index 0000000..4af0511 --- /dev/null +++ b/tier0/commandline.cpp @@ -0,0 +1,568 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" +#include "tier0/icommandline.h" + +#include "tier0/dbg.h" +#include "tier0_strtools.h" +#include "tier1/strtools.h" // this is included for the definition of V_isspace() + +#ifdef PLATFORM_POSIX +#include +#define _MAX_PATH PATH_MAX +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static const int MAX_PARAMETER_LEN = 128; + +//----------------------------------------------------------------------------- +// Purpose: Implements ICommandLine +//----------------------------------------------------------------------------- +class CCommandLine : public ICommandLine { + public: + // Construction + CCommandLine(); + virtual ~CCommandLine(); + + // Implements ICommandLine + virtual void CreateCmdLine(const char *commandline); + virtual void CreateCmdLine(int argc, char **argv); + virtual const char *GetCmdLine(void) const; + virtual const char *CheckParm(const char *psz, + const char **ppszValue = 0) const; + + virtual void RemoveParm(const char *parm); + virtual void AppendParm(const char *pszParm, const char *pszValues); + + virtual int ParmCount() const; + virtual int FindParm(const char *psz) const; + virtual const char *GetParm(int nIndex) const; + + virtual const char *ParmValue(const char *psz, + const char *pDefaultVal = nullptr) const; + virtual int ParmValue(const char *psz, int nDefaultVal) const; + virtual float ParmValue(const char *psz, float flDefaultVal) const; + virtual void SetParm(int nIndex, char const *pParm); + + private: + enum { + MAX_PARAMETER_LEN = 128, + MAX_PARAMETERS = 256, + }; + + // When the commandline contains @name, it reads the parameters from that file + void LoadParametersFromFile(const char *&pSrc, char *&pDst, intp maxDestLen, + bool bInQuotes); + + // Parse command line... + void ParseCommandLine(); + + // Frees the command line arguments + void CleanUpParms(); + + // Adds an argument.. + void AddArgument(const char *pFirst, const char *pLast); + + // Copy of actual command line + char *m_pszCmdLine; + + // Pointers to each argument... + int m_nParmCount; + char *m_ppParms[MAX_PARAMETERS]; +}; + +//----------------------------------------------------------------------------- +// Instance singleton and expose interface to rest of code +//----------------------------------------------------------------------------- +static CCommandLine g_CmdLine; +ICommandLine *CommandLine() { return &g_CmdLine; } + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommandLine::CCommandLine(void) { + m_pszCmdLine = nullptr; + m_nParmCount = 0; + m_ppParms[0] = nullptr; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommandLine::~CCommandLine(void) { + CleanUpParms(); + delete[] m_pszCmdLine; +} + +//----------------------------------------------------------------------------- +// Read commandline from file instead... +//----------------------------------------------------------------------------- +void CCommandLine::LoadParametersFromFile(const char *&pSrc, char *&pDst, + intp maxDestLen, bool bInQuotes) { + // Suck out the file name + char szFileName[MAX_PATH]; + char *pOut; + char *pDestStart = pDst; + + if (maxDestLen < 3) return; + + // Skip the @ sign + pSrc++; + + pOut = szFileName; + + char terminatingChar = ' '; + if (bInQuotes) terminatingChar = '\"'; + + while (*pSrc && *pSrc != terminatingChar) { + *pOut++ = *pSrc++; + if ((pOut - szFileName) >= (MAX_PATH - 1)) break; + } + + *pOut = '\0'; + + // Skip the space after the file name + if (*pSrc) pSrc++; + + // Now read in parameters from file + FILE *fp = fopen(szFileName, "r"); + if (fp) { + int c = fgetc(fp); + while (c != EOF) { + // Turn return characters into spaces + if (c == '\n') c = ' '; + + *pDst++ = static_cast(c); + + // Don't go past the end, and allow for our terminating space character + // AND a terminating null character. + if ((pDst - pDestStart) >= (maxDestLen - 2)) break; + + // Get the next character, if there are more + c = fgetc(fp); + } + + // Add a terminating space character + *pDst++ = ' '; + + fclose(fp); + } else { + fprintf(stderr, "Parameter file '%s' not found, skipping...", szFileName); + } +} + +//----------------------------------------------------------------------------- +// Creates a command line from the arguments passed in +//----------------------------------------------------------------------------- +void CCommandLine::CreateCmdLine(int argc, char **argv) { + char cmdline[2048]; + cmdline[0] = 0; + for (int i = 0; i < argc; ++i) { + strncat(cmdline, "\"", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, argv[i], sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, "\"", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, " ", sizeof(cmdline) - strlen(cmdline) - 1); + } + cmdline[sizeof(cmdline) - 1] = 0; + + CreateCmdLine(cmdline); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a command line from the passed in string +// Note that if you pass in a @filename, then the routine will read settings +// from a file instead of the command line +//----------------------------------------------------------------------------- +void CCommandLine::CreateCmdLine(const char *commandline) { + if (m_pszCmdLine) { + delete[] m_pszCmdLine; + } + + char szFull[4096]; + szFull[0] = '\0'; + + char *pDst = szFull; + const char *pSrc = commandline; + + bool bInQuotes = false; + const char *pInQuotesStart = 0; + while (*pSrc) { + // Is this an unslashed quote? + if (*pSrc == '"') { + if (pSrc == commandline || (pSrc[-1] != '/' && pSrc[-1] != '\\')) { + bInQuotes = !bInQuotes; + pInQuotesStart = pSrc + 1; + } + } + + if (*pSrc == '@') { + if (pSrc == commandline || (!bInQuotes && V_isspace(pSrc[-1])) || + (bInQuotes && pSrc == pInQuotesStart)) { + LoadParametersFromFile(pSrc, pDst, sizeof(szFull) - (pDst - szFull), + bInQuotes); + if (bInQuotes) { + // Back up over the opening quote which has already been copied to + // pDst. Otherwise we end up with an orphaned single quote which + // causes later parsing problems. + --pDst; + Assert(*pDst == '\"'); + } + // The opening quote, if any, is now gone. + bInQuotes = false; + continue; + } + } + + // Don't go past the end. + if ((pDst - szFull) >= (sizeof(szFull) - 1)) break; + + *pDst++ = *pSrc++; + } + + *pDst = '\0'; + + size_t len = strlen(szFull) + 1; + m_pszCmdLine = new char[len]; + memcpy(m_pszCmdLine, szFull, len); + +#if defined(PLATFORM_PS3) + Plat_SetCommandLine(m_pszCmdLine); +#endif + + ParseCommandLine(); +} + +//----------------------------------------------------------------------------- +// Finds a string in another string with a case insensitive test +//----------------------------------------------------------------------------- +static char *_stristr(char *pStr, const char *pSearch) { + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) return 0; + + char *pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) { + // Skip over non-matches + if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch)) { + // Check for match + char const *pMatch = pLetter + 1; + char const *pTest = pSearch + 1; + while (*pTest != 0) { + // We've run off the end; don't bother. + if (*pMatch == 0) return 0; + + if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest)) + break; + + ++pMatch; + ++pTest; + } + + // Found a match! + if (*pTest == 0) return pLetter; + } + + ++pLetter; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove specified string ( and any args attached to it ) from command +// line Input : *pszParm - +//----------------------------------------------------------------------------- +void CCommandLine::RemoveParm(const char *pszParm) { + if (!m_pszCmdLine) return; + + // Search for first occurrence of pszParm + char *p, *found; + char *pnextparam; + intp n; + size_t curlen; + + p = m_pszCmdLine; + while (*p) { + curlen = strlen(p); + + found = _stristr(p, pszParm); + if (!found) break; + + pnextparam = found + 1; + bool bHadQuote = false; + if (found > m_pszCmdLine && found[-1] == '\"') bHadQuote = true; + + while (pnextparam && *pnextparam && (*pnextparam != ' ') && + (*pnextparam != '\"')) + pnextparam++; + + if (pnextparam && + (static_cast(pnextparam - found) > strlen(pszParm))) { + p = pnextparam; + continue; + } + + while (pnextparam && *pnextparam && (*pnextparam != '-') && + (*pnextparam != '+')) + pnextparam++; + + if (bHadQuote) { + found--; + } + + if (pnextparam && *pnextparam) { + // We are either at the end of the string, or at the next param. Just + // chop out the current param. + n = curlen - (pnextparam - p); // # of characters after this param. + memmove(found, pnextparam, n); + + found[n] = '\0'; + } else { + // Clear out rest of string. + n = pnextparam - found; + memset(found, 0, n); + } + } + + // Strip and trailing ' ' characters left over. + while (1) { + intp len = strlen(m_pszCmdLine); + if (len == 0 || m_pszCmdLine[len - 1] != ' ') break; + + m_pszCmdLine[len - 1] = '\0'; + } + + ParseCommandLine(); +} + +//----------------------------------------------------------------------------- +// Purpose: Append parameter and argument values to command line +// Input : *pszParm - +// *pszValues - +//----------------------------------------------------------------------------- +void CCommandLine::AppendParm(const char *pszParm, const char *pszValues) { + intp nNewLength = 0; + char *pCmdString; + + nNewLength = strlen(pszParm); // Parameter. + if (pszValues) + nNewLength += strlen(pszValues) + 1; // Values + leading space character. + nNewLength++; // Terminal 0; + + if (!m_pszCmdLine) { + m_pszCmdLine = new char[nNewLength]; + strcpy(m_pszCmdLine, pszParm); + if (pszValues) { + strcat(m_pszCmdLine, " "); + strcat(m_pszCmdLine, pszValues); + } + + ParseCommandLine(); + return; + } + + // Remove any remnants from the current Cmd Line. + RemoveParm(pszParm); + + nNewLength += strlen(m_pszCmdLine) + 1 + 1; + + pCmdString = new char[nNewLength]; + memset(pCmdString, 0, nNewLength); + + strcpy(pCmdString, m_pszCmdLine); // Copy old command line. + strcat(pCmdString, " "); // Put in a space + strcat(pCmdString, pszParm); + if (pszValues) { + strcat(pCmdString, " "); + strcat(pCmdString, pszValues); + } + + // Kill off the old one + delete[] m_pszCmdLine; + + // Point at the new command line. + m_pszCmdLine = pCmdString; + + ParseCommandLine(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return current command line +// Output : const char +//----------------------------------------------------------------------------- +const char *CCommandLine::GetCmdLine(void) const { return m_pszCmdLine; } + +//----------------------------------------------------------------------------- +// Purpose: Search for the parameter in the current commandline +// Input : *psz - +// **ppszValue - +// Output : char +//----------------------------------------------------------------------------- +const char *CCommandLine::CheckParm(const char *psz, + const char **ppszValue) const { + if (ppszValue) *ppszValue = nullptr; + + int i = FindParm(psz); + if (i == 0) return nullptr; + + if (ppszValue) { + if ((i + 1) >= m_nParmCount) { + *ppszValue = nullptr; + } else { + *ppszValue = m_ppParms[i + 1]; + } + } + + return m_ppParms[i]; +} + +//----------------------------------------------------------------------------- +// Adds an argument.. +//----------------------------------------------------------------------------- +void CCommandLine::AddArgument(const char *pFirst, const char *pLast) { + if (pLast == pFirst) return; + + if (m_nParmCount >= MAX_PARAMETERS) + Error("CCommandLine::AddArgument: exceeded %d parameters", MAX_PARAMETERS); + + size_t nLen = (pLast - pFirst) + 1; + m_ppParms[m_nParmCount] = new char[nLen]; + memcpy(m_ppParms[m_nParmCount], pFirst, nLen - 1); + m_ppParms[m_nParmCount][nLen - 1] = 0; + + ++m_nParmCount; +} + +//----------------------------------------------------------------------------- +// Parse command line... +//----------------------------------------------------------------------------- +void CCommandLine::ParseCommandLine() { + CleanUpParms(); + if (!m_pszCmdLine) return; + + const char *pChar = m_pszCmdLine; + while (*pChar && V_isspace(*pChar)) { + ++pChar; + } + + bool bInQuotes = false; + const char *pFirstLetter = nullptr; + for (; *pChar; ++pChar) { + if (bInQuotes) { + if (*pChar != '\"') continue; + + AddArgument(pFirstLetter, pChar); + pFirstLetter = nullptr; + bInQuotes = false; + continue; + } + + // Haven't started a word yet... + if (!pFirstLetter) { + if (*pChar == '\"') { + bInQuotes = true; + pFirstLetter = pChar + 1; + continue; + } + + if (V_isspace(*pChar)) continue; + + pFirstLetter = pChar; + continue; + } + + // Here, we're in the middle of a word. Look for the end of it. + if (V_isspace(*pChar)) { + AddArgument(pFirstLetter, pChar); + pFirstLetter = nullptr; + } + } + + if (pFirstLetter) { + AddArgument(pFirstLetter, pChar); + } +} + +//----------------------------------------------------------------------------- +// Individual command line arguments +//----------------------------------------------------------------------------- +void CCommandLine::CleanUpParms() { + for (int i = 0; i < m_nParmCount; ++i) { + delete[] m_ppParms[i]; + m_ppParms[i] = nullptr; + } + m_nParmCount = 0; +} + +//----------------------------------------------------------------------------- +// Returns individual command line arguments +//----------------------------------------------------------------------------- +int CCommandLine::ParmCount() const { return m_nParmCount; } + +int CCommandLine::FindParm(const char *psz) const { + // Start at 1 so as to not search the exe name + for (int i = 1; i < m_nParmCount; ++i) { + if (!V_tier0_stricmp(psz, m_ppParms[i])) return i; + } + return 0; +} + +const char *CCommandLine::GetParm(int nIndex) const { + Assert((nIndex >= 0) && (nIndex < m_nParmCount)); + if ((nIndex < 0) || (nIndex >= m_nParmCount)) return ""; + return m_ppParms[nIndex]; +} +void CCommandLine::SetParm(int nIndex, char const *pParm) { + if (pParm) { + Assert((nIndex >= 0) && (nIndex < m_nParmCount)); + if ((nIndex >= 0) && (nIndex < m_nParmCount)) { + if (m_ppParms[nIndex]) delete[] m_ppParms[nIndex]; + m_ppParms[nIndex] = _strdup(pParm); + } + } +} + +//----------------------------------------------------------------------------- +// Returns the argument after the one specified, or the default if not found +//----------------------------------------------------------------------------- +const char *CCommandLine::ParmValue(const char *psz, + const char *pDefaultVal) const { + int nIndex = FindParm(psz); + if ((nIndex == 0) || (nIndex == m_nParmCount - 1)) return pDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with + // '+' or '-' + if (m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+') + return pDefaultVal; + + return m_ppParms[nIndex + 1]; +} + +int CCommandLine::ParmValue(const char *psz, int nDefaultVal) const { + int nIndex = FindParm(psz); + if ((nIndex == 0) || (nIndex == m_nParmCount - 1)) return nDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with + // '+' or '-' + if (m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+') + return nDefaultVal; + + return atoi(m_ppParms[nIndex + 1]); +} + +float CCommandLine::ParmValue(const char *psz, float flDefaultVal) const { + int nIndex = FindParm(psz); + if ((nIndex == 0) || (nIndex == m_nParmCount - 1)) return flDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with + // '+' or '-' + if (m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+') + return flDefaultVal; + + return strtof(m_ppParms[nIndex + 1], nullptr); +} diff --git a/tier0/cpu.cpp b/tier0/cpu.cpp new file mode 100644 index 0000000..4c9cadd --- /dev/null +++ b/tier0/cpu.cpp @@ -0,0 +1,479 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#include "cputopology.h" +#elif defined(PLATFORM_OSX) +#include +#endif + +#ifndef _PS3 +#include "tier0_strtools.h" +#endif + +#ifdef PLATFORM_WINDOWS_PC +#include +#endif + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +const tchar* GetProcessorVendorId(); + +static bool cpuid(uint32 function, uint32& out_eax, uint32& out_ebx, + uint32& out_ecx, uint32& out_edx) { + int info[4] = {0}; + +#if defined(_X360) || defined(_PS3) + return false; +#elif defined(__clang__) || defined(__GNUC__) +#if defined(_M_X64) || defined(__amd64__) + asm volatile( + "movq\t%%rbx, %%rsi\n\t" + "cpuid\n\t" + "xchgq\t%%rbx, %%rsi\n\t" + : "=a"(info[0]), "=S"(info[1]), "=c"(info[2]), "=d"(info[3]) + : "a"(function)); +#else +#error "Please add cpuid support for your arhitecture." +#endif // defined(_M_X64) || defined(__amd64__) +#elif defined(_MSC_VER) + __cpuid(info, function); +#endif + + out_eax = info[0]; + out_ebx = info[1]; + out_ecx = info[2]; + out_edx = info[3]; + + return true; +} + +static bool CheckMMXTechnology() { +#if defined(_X360) || defined(_PS3) + return true; +#else + uint32 eax, ebx, edx, unused; + if (!cpuid(1, eax, ebx, unused, edx)) return false; + + return (edx & 0x800000) != 0; +#endif +} + +static bool CheckSSETechnology() { +#if defined(_X360) || defined(_PS3) + return true; +#else + // Win98 is not supported, so just check SSE support. + uint32 eax, ebx, edx, unused; + if (!cpuid(1, eax, ebx, unused, edx)) { + return false; + } + + return (edx & 0x2000000L) != 0; +#endif +} + +static bool CheckSSE2Technology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + uint32 eax, ebx, edx, unused; + if (!cpuid(1, eax, ebx, unused, edx)) return false; + + return (edx & 0x04000000) != 0; +#endif +} + +bool CheckSSE3Technology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + uint32 eax, ebx, edx, ecx; + if (!cpuid(1, eax, ebx, ecx, edx)) return false; + + return (ecx & 0x00000001) != 0; // bit 1 of ECX +#endif +} + +bool CheckSSSE3Technology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + // SSSE 3 is implemented by both Intel and AMD + // detection is done the same way for both vendors + uint32 eax, ebx, edx, ecx; + if (!cpuid(1, eax, ebx, ecx, edx)) return false; + + return (ecx & (1 << 9)) != 0; // bit 9 of ECX +#endif +} + +bool CheckSSE41Technology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + // SSE 4.1 is implemented by both Intel and AMD + // detection is done the same way for both vendors + uint32 eax, ebx, edx, ecx; + if (!cpuid(1, eax, ebx, ecx, edx)) return false; + + return (ecx & (1 << 19)) != 0; // bit 19 of ECX +#endif +} + +bool CheckSSE42Technology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + // SSE 4.2 is implemented by both Intel and AMD + // detection is done the same way for both vendors + uint32 eax, ebx, edx, ecx; + if (!cpuid(1, eax, ebx, ecx, edx)) return false; + + return (ecx & (1 << 20)) != 0; // bit 20 of ECX +#endif +} + +bool CheckSSE4aTechnology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + // SSE 4a is an AMD-only feature + const char* pchVendor = GetProcessorVendorId(); + if (0 != V_tier0_stricmp(pchVendor, "AuthenticAMD")) return false; + + uint32 eax, ebx, edx, ecx; + if (!cpuid(0x80000001, eax, ebx, ecx, edx)) return false; + + return (ecx & (1 << 6)) != 0; // bit 6 of ECX +#endif +} + +static bool Check3DNowTechnology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + uint32 eax, unused; + if (!cpuid(0x80000000, eax, unused, unused, unused)) return false; + + if (eax > 0x80000000L) { + if (!cpuid(0x80000001, unused, unused, unused, eax)) return false; + + return (eax & 1 << 31) != 0; + } + return false; +#endif +} + +static bool CheckCMOVTechnology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + uint32 eax, ebx, edx, unused; + if (!cpuid(1, eax, ebx, unused, edx)) return false; + + return (edx & (1 << 15)) != 0; +#endif +} + +static bool CheckFCMOVTechnology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + uint32 eax, ebx, edx, unused; + if (!cpuid(1, eax, ebx, unused, edx)) return false; + + return (edx & (1 << 16)) != 0; +#endif +} + +static bool CheckRDTSCTechnology() { +#if defined(_X360) || defined(_PS3) + return false; +#else + uint32 eax, ebx, edx, unused; + if (!cpuid(1, eax, ebx, unused, edx)) return false; + + return (edx & 0x10) != 0; +#endif +} + +// Return the Processor's vendor identification string, or "Generic_x86" if it +// doesn't exist on this CPU +const tchar* GetProcessorVendorId() { +#if defined(_X360) || defined(_PS3) + return "PPC"; +#else + uint32 unused, regs[3]; + + static tchar VendorID[13]; + memset(VendorID, 0, sizeof(VendorID)); + + if (!cpuid(0, unused, regs[0], regs[2], regs[1])) { + if (IsPC()) { + _tcscpy(VendorID, _T( "Generic_x86" )); + } else if (IsX360()) { + _tcscpy(VendorID, _T( "PowerPC" )); + } + } else { + memcpy(VendorID + 0, &(regs[0]), sizeof(regs[0])); + memcpy(VendorID + 4, &(regs[1]), sizeof(regs[1])); + memcpy(VendorID + 8, &(regs[2]), sizeof(regs[2])); + } + + return VendorID; +#endif +} + +// See Intel?64 and IA-32 Architectures Developer's Manual: Vol. 2A +// https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html +int LogicalProcessorsPerCore() { +#if defined(_X360) || defined(_PS3) || defined(LINUX) + return 2; // +#elif defined(_WIN32) + uint32 nMaxStandardFnSupported, nVendorId[3]; + if (!cpuid(0, nMaxStandardFnSupported, nVendorId[0], nVendorId[2], + nVendorId[1])) { + return 1; + } + + uint32 nFn1_Eax, nFn1_Ebx, nFn1_Ecx, nFn1_Edx; + if (!cpuid(1, nFn1_Eax, nFn1_Ebx, nFn1_Ecx, nFn1_Edx)) { + return 1; + } + + enum CpuidFnMasks { + HTT = 0x10000000, // Fn0000_0001 EDX[28] + LogicalProcessorCount = 0x00FF0000, // Fn0000_0001 EBX[23:16] + ApicId = 0xFF000000, // Fn0000_0001 EBX[31:24] + NC_Intel = 0xFC000000, // Fn0000_0004 EAX[31:26] + NC_Amd = 0x000000FF, // Fn8000_0008 ECX[7:0] + CmpLegacy_Amd = 0x00000002, // Fn8000_0001 ECX[1] + ApicIdCoreIdSize_Amd = 0x0000F000 // Fn8000_0008 ECX[15:12] + }; + + // Determine if hardware threading is enabled. + if (nFn1_Edx & HTT) { + // Determine the total number of logical processors per package. + int nLogProcsPerPkg = (nFn1_Ebx & LogicalProcessorCount) >> 16; + int nCoresPerPkg = 1; + + if (((nFn1_Ebx >> 16) & 0xFF) <= + 1) // Has Hyper-Threading OR Core Multi-Processing not been enabled ? + { + // NOTE: This is only tested on Intel CPUs; I don't know if it's true on + // AMD, as I have no HT AMD to test on + return 1; // HT was turned off, for all intents and purposes in our + // engine it means one logical CPU per core + } + + // Determine the total number of cores per package. This info + // is extracted differently dependending on the cpu vendor. + if (nVendorId[0] == 'uneG' && nVendorId[1] == 'Ieni' && + nVendorId[2] == 'letn') // GenuineIntel + { + if (nMaxStandardFnSupported >= 4) { + uint32 nFn4_Eax, nFn4_Ebx, nFn4_Ecx, nFn4_Edx; + if (cpuid(4, nFn4_Eax, nFn4_Ebx, nFn4_Ecx, nFn4_Edx)) { + nCoresPerPkg = ((nFn4_Eax & NC_Intel) >> 26) + 1; + } + } + // as the DirectX CoreDetection sample goes, the logic is that on + // old processors where the functions aren't supported, we assume one core + // per package, multiple logical processors per package I suspect this may + // be wrong, especially for AMD processors. + return nLogProcsPerPkg / nCoresPerPkg; + } +#if 0 // To make as concervative change as possible now, I'll skip AMD + // hyperthread detection + else + { + if( nVendorId[0] == 'htuA' && nVendorId[1] == 'itne' && nVendorId[2] == 'DMAc' ) // AuthenticAMD + { + uint32 nFnx8_Eax, nFnx8_Ebx, nFnx8_Ecx, nFnx8_Edx ; + if( cpuid( 0x80000008, nFnx8_Eax, nFnx8_Ebx, nFnx8_Ecx, nFnx8_Edx ) ) + { + // AMD reports the msb width of the CORE_ID bit field of the APIC ID + // in ApicIdCoreIdSize_Amd. The maximum value represented by the msb + // width is the theoretical number of cores the processor can support + // and not the actual number of current cores, which is how the msb width + // of the CORE_ID bit field has been traditionally determined. If the + // ApicIdCoreIdSize_Amd value is zero, then you use the traditional method + // to determine the CORE_ID msb width. + DWORD msbWidth = nFnx8_Ecx & ApicIdCoreIdSize_Amd; + if( msbWidth ) + { + // Set nCoresPerPkg to the maximum theortical number of cores + // the processor package can support (2 ^ width) so the APIC + // extractor object can be configured to extract the proper + // values from an APIC. + nCoresPerPkg = 1 << ( msbWidth >> 12 ); + } + else + { + // Set nCoresPerPkg to the actual number of cores being reported + // by the CPUID instruction. + nCoresPerPkg = ( nFnx8_Ecx & NC_Amd ) + 1; + } + } + } + // as the DirectX CoreDetection sample goes, the logic is that on old processors where + // the functions aren't supported, we assume one core per package, multiple logical processors per package + // I suspect this may be wrong, especially for AMD processors. + return nLogProcsPerPkg / nCoresPerPkg; + } +#endif + } + return 1; +#endif +} + +// Measure the processor clock speed by sampling the cycle count, waiting +// for some fraction of a second, then measuring the elapsed number of cycles. +static int64 CalculateClockSpeed() { +#if defined(_X360) || defined(_PS3) + // Xbox360 and PS3 have the same clock speed and share a lot of + // characteristics on PPU + return 3200000000LL; +#else +#if defined(_WIN32) + LARGE_INTEGER waitTime, startCount, curCount; + CCycleCount start, end; + + // Take 1/32 of a second for the measurement. + QueryPerformanceFrequency(&waitTime); + int scale = 5; + waitTime.QuadPart >>= scale; + + QueryPerformanceCounter(&startCount); + start.Sample(); + do { + QueryPerformanceCounter(&curCount); + } while (curCount.QuadPart - startCount.QuadPart < waitTime.QuadPart); + end.Sample(); + + return (end.m_Int64 - start.m_Int64) << scale; +#elif defined(POSIX) + uint64 CalculateCPUFreq(); // from cpu_linux.cpp + int64 freq = (int64)CalculateCPUFreq(); + if (freq == 0) // couldn't calculate clock speed + { + Error("Unable to determine CPU Frequency\n"); + } + return freq; +#else +#error "Please implement Clock Speed function for this platform" +#endif +#endif +} + +static CPUInformation s_cpuInformation; + +const CPUInformation& GetCPUInformation() { + CPUInformation& pi = s_cpuInformation; + // Has the structure already been initialized and filled out? + if (pi.m_Size == sizeof(pi)) return pi; + + // Redundant, but just in case the user somehow messes with the size. + memset(&pi, 0x0, sizeof(pi)); + // Fill out the structure, and return it: + pi.m_Size = sizeof(pi); + // Grab the processor frequency: + pi.m_Speed = CalculateClockSpeed(); + + // Get the logical and physical processor counts: + +#if defined(_X360) + pi.m_nPhysicalProcessors = 3; + pi.m_nLogicalProcessors = 6; +#elif defined(_PS3) + pi.m_nPhysicalProcessors = 1; + pi.m_nLogicalProcessors = 2; +#elif defined(_WIN32) && !defined(_X360) + SYSTEM_INFO si = {0}; + GetNativeSystemInfo(&si); + + pi.m_nLogicalProcessors = + static_cast(clamp(si.dwNumberOfProcessors, 1U, 255U)); + + CpuTopology topo; + pi.m_nPhysicalProcessors = + static_cast(clamp(topo.NumberOfSystemCores(), 1U, 255U)); + + // Make sure I always report at least one, when running WinXP with the /ONECPU + // switch, it likes to report 0 processors for some reason. + if (pi.m_nPhysicalProcessors == 0 && pi.m_nLogicalProcessors == 0) { + Assert( !"Sergiy: apparently I didn't fix some CPU detection code completely. Let me know and I'll do my best to fix it soon." ); + pi.m_nPhysicalProcessors = 1; + pi.m_nLogicalProcessors = 1; + } +#elif defined(LINUX) + pi.m_nLogicalProcessors = 0; + pi.m_nPhysicalProcessors = 0; + const int k_cMaxProcessors = 256; + bool rgbProcessors[k_cMaxProcessors]; + memset(rgbProcessors, 0, sizeof(rgbProcessors)); + int cMaxCoreId = 0; + + FILE* fpCpuInfo = fopen("/proc/cpuinfo", "r"); + if (fpCpuInfo) { + char rgchLine[256]; + while (fgets(rgchLine, sizeof(rgchLine), fpCpuInfo)) { + if (!strncasecmp(rgchLine, "processor", strlen("processor"))) { + pi.m_nLogicalProcessors++; + } + if (!strncasecmp(rgchLine, "core id", strlen("core id"))) { + char* pchValue = strchr(rgchLine, ':'); + cMaxCoreId = MAX(cMaxCoreId, atoi(pchValue + 1)); + } + if (!strncasecmp(rgchLine, "physical id", strlen("physical id"))) { + // it seems (based on survey data) that we can see + // processor N (N > 0) when it's the only processor in + // the system. so keep track of each processor + char* pchValue = strchr(rgchLine, ':'); + int cPhysicalId = atoi(pchValue + 1); + if (cPhysicalId < k_cMaxProcessors) rgbProcessors[cPhysicalId] = true; + } + } + fclose(fpCpuInfo); + for (int i = 0; i < k_cMaxProcessors; i++) + if (rgbProcessors[i]) pi.m_nPhysicalProcessors++; + pi.m_nPhysicalProcessors *= (cMaxCoreId + 1); + } else { + pi.m_nLogicalProcessors = 1; + pi.m_nPhysicalProcessors = 1; + Assert(!"couldn't read cpu information from /proc/cpuinfo"); + } + +#elif defined(OSX) + int mib[2], num_cpu = 1; + size_t len; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(num_cpu); + sysctl(mib, 2, &num_cpu, &len, NULL, 0); + pi.m_nPhysicalProcessors = num_cpu; + pi.m_nLogicalProcessors = num_cpu; +#endif + + // Determine Processor Features: + pi.m_bRDTSC = CheckRDTSCTechnology(); + pi.m_bCMOV = CheckCMOVTechnology(); + pi.m_bFCMOV = CheckFCMOVTechnology(); + pi.m_bMMX = CheckMMXTechnology(); + pi.m_bSSE = CheckSSETechnology(); + pi.m_bSSE2 = CheckSSE2Technology(); + pi.m_bSSE3 = CheckSSE3Technology(); + pi.m_bSSSE3 = CheckSSSE3Technology(); + pi.m_bSSE4a = CheckSSE4aTechnology(); + pi.m_bSSE41 = CheckSSE41Technology(); + pi.m_bSSE42 = CheckSSE42Technology(); + pi.m_b3DNow = Check3DNowTechnology(); + pi.m_szProcessorID = (tchar*)GetProcessorVendorId(); + pi.m_bHT = pi.m_nPhysicalProcessors < pi.m_nLogicalProcessors; + + return pi; +} diff --git a/tier0/cpu_posix.cpp b/tier0/cpu_posix.cpp new file mode 100644 index 0000000..4dfd0ab --- /dev/null +++ b/tier0/cpu_posix.cpp @@ -0,0 +1,129 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: determine CPU speed under linux + +#include +#include +#include +#include + +#include +#include +#include + +#include "tier0/platform.h" + +#define rdtsc(x) __asm__ __volatile__("rdtsc" : "=A"(x)) + +class TimeVal { + public: + TimeVal() {} + TimeVal &operator=(const TimeVal &val) { m_TimeVal = val.m_TimeVal; } + inline double operator-(const TimeVal &left) { + uint64 left_us = + (uint64)left.m_TimeVal.tv_sec * 1000000 + left.m_TimeVal.tv_usec; + uint64 right_us = (uint64)m_TimeVal.tv_sec * 1000000 + m_TimeVal.tv_usec; + uint64 diff_us = left_us - right_us; + return diff_us / 1000000; + } + + timeval m_TimeVal; +}; + +// Compute the positive difference between two 64 bit numbers. +static inline uint64 diff(uint64 v1, uint64 v2) { + uint64 d = v1 - v2; + if (d >= 0) + return d; + else + return -d; +} + +#ifdef OSX +uint64 GetCPUFreqFromPROC() { + int mib[2] = {CTL_HW, HW_CPU_FREQ}; + uint64 frequency = 0; + size_t len = sizeof(frequency); + + if (sysctl(mib, 2, &frequency, &len, NULL, 0) == -1) return 0; + return frequency; +} +#else +uint64 GetCPUFreqFromPROC() { + double mhz = 0; + char line[1024], *s, search_str[] = "cpu MHz"; + FILE *fp; + + /* open proc/cpuinfo */ + if ((fp = fopen("/proc/cpuinfo", "r")) == NULL) { + return 0; + } + + /* ignore all lines until we reach MHz information */ + while (fgets(line, 1024, fp) != NULL) { + if (strstr(line, search_str) != NULL) { + /* ignore all characters in line up to : */ + for (s = line; *s && (*s != ':'); ++s) + ; + /* get MHz number */ + if (*s && (sscanf(s + 1, "%lf", &mhz) == 1)) break; + } + } + + if (fp != NULL) fclose(fp); + + return (uint64)(mhz * 1000000); +} +#endif + +uint64 CalculateCPUFreq() { +#ifdef LINUX + char const *pFreq = getenv("CPU_MHZ"); + if (pFreq) { + uint64 retVal = 1000000; + return retVal * atoi(pFreq); + } +#endif + + // Compute the period. Loop until we get 3 consecutive periods that + // are the same to within a small error. The error is chosen + // to be +/- 0.02% on a P-200. + const uint64 error = 40000; + const int max_iterations = 600; + int count; + uint64 period, period1 = error * 2, period2 = 0, period3 = 0; + + for (count = 0; count < max_iterations; count++) { + TimeVal start_time, end_time; + uint64 start_tsc, end_tsc; + gettimeofday(&start_time.m_TimeVal, 0); + rdtsc(start_tsc); + usleep(5000); // sleep for 5 msec + gettimeofday(&end_time.m_TimeVal, 0); + rdtsc(end_tsc); + + period3 = (end_tsc - start_tsc) / (end_time - start_time); + + if (diff(period1, period2) <= error && diff(period2, period3) <= error && + diff(period1, period3) <= error) + break; + + period1 = period2; + period2 = period3; + } + + if (count == max_iterations) { + return GetCPUFreqFromPROC(); // fall back to /proc + } + + // Set the period to the average period measured. + period = (period1 + period2 + period3) / 3; + + // Some Pentiums have broken TSCs that increment very + // slowly or unevenly. + if (period < 10000000) { + return GetCPUFreqFromPROC(); // fall back to /proc + } + + return period; +} diff --git a/tier0/cputopology.cpp b/tier0/cputopology.cpp new file mode 100644 index 0000000..2505257 --- /dev/null +++ b/tier0/cputopology.cpp @@ -0,0 +1,834 @@ +// CpuTopology.cpp +// +// CpuTopology class implementation. +// +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "pch_tier0.h" + +#if defined(_WIN32) && !defined(_X360) && !defined(_PS3) +#include "cputopology.h" + +#include +#include + +#undef malloc +#undef free + +// Name: ICpuTopology +// Desc: Specifies the interface that each class that provides an implementation +// for extracting cpu topology must conform to. This is the Implementor +// class in the traditional Bridge Pattern. +class ICpuTopology { + public: + virtual ~ICpuTopology() {} + virtual BOOL IsDefaultImpl() const = 0; + virtual DWORD NumberOfProcessCores() const = 0; + virtual DWORD NumberOfSystemCores() const = 0; + virtual DWORD_PTR CoreAffinityMask(DWORD coreIdx) const = 0; +}; + +namespace { +/////////////////////////////////////////////////////////////////////////////////// +// Local Class Definitions +/////////////////////////////////////////////////////////////////////////////////// + +//--------------------------------------------------------------------------------- +// Name: DefaultImpl +// Desc: Provides a default implementation for the ICpuTopology interface when +// GetLogicalProcessorInformation and CPUID are not supported for whatever +// reason. This is a ConcreteImplementor class in the traditional Bridge +// Pattern. +//--------------------------------------------------------------------------------- +class DefaultImpl : public ICpuTopology { + public: + //----------------------------------------------------------------------------- + // DefaultImpl::IsDefaultImpl + //----------------------------------------------------------------------------- + /*virtual*/ BOOL IsDefaultImpl() const { return TRUE; } + + //----------------------------------------------------------------------------- + // DefaultImpl::NumberOfProcessCores + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfProcessCores() const { return 1; } + + //----------------------------------------------------------------------------- + // DefaultImpl::IsNumberOfSystemCores + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfSystemCores() const { return 1; } + + //----------------------------------------------------------------------------- + // DefaultImpl::CoreAffinityMask + //----------------------------------------------------------------------------- + /*virtual*/ DWORD_PTR CoreAffinityMask(DWORD coreIdx) const { + DWORD_PTR coreAffinity = 0; + if (1 == coreIdx) { + DWORD_PTR dwSystemAffinity; + GetProcessAffinityMask(GetCurrentProcess(), &coreAffinity, + &dwSystemAffinity); + } + return coreAffinity; + } +}; + +//--------------------------------------------------------------------------------- +// Name: GlpiImpl +// Desc: Provides the GetLogicalProcessorInformation implementation for the +// ICpuTopology interface. This is a ConcreteImplementor class in the +// traditional Bridge Pattern. +//--------------------------------------------------------------------------------- +class GlpiImpl : public ICpuTopology { + public: + //----------------------------------------------------------------------------- + // Name: GlpiImpl::GlpiImpl + // Desc: Initializes the internal structures/data with information retrieved + // from a call to GetLogicalProcessorInformation. + //----------------------------------------------------------------------------- + GlpiImpl() : m_pSlpi(NULL), m_nItems(0) { + _ASSERT(IsSupported()); + + GlpiFnPtr pGlpi = GetGlpiFn_(); + _ASSERT(pGlpi); + + DWORD cbBuffer = 0; + pGlpi(0, &cbBuffer); + + m_pSlpi = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION*)malloc(cbBuffer); + pGlpi(m_pSlpi, &cbBuffer); + m_nItems = cbBuffer / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::~GlpiImpl + //----------------------------------------------------------------------------- + /*virtual*/ ~GlpiImpl() { + free(m_pSlpi); + m_pSlpi = 0; + m_nItems = 0; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::IsDefaultImpl + //----------------------------------------------------------------------------- + /*virtual*/ BOOL IsDefaultImpl() const { return FALSE; } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::NumberOfProcessCores + // Desc: Gets the total number of physical processor cores available to the + // current process. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfProcessCores() const { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinity, + &dwSystemAffinity); + + DWORD nCores = 0; + for (DWORD i = 0; i < m_nItems; ++i) { + if ((RelationProcessorCore == m_pSlpi[i].Relationship) && + (m_pSlpi[i].ProcessorMask & dwProcessAffinity)) { + ++nCores; + } + } + return nCores; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::NumberOfSystemCores + // Desc: Gets the total number of physical processor cores enabled on the + // system. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfSystemCores() const { + DWORD nCores = 0; + for (DWORD i = 0; i < m_nItems; ++i) { + if (RelationProcessorCore == m_pSlpi[i].Relationship) ++nCores; + } + return nCores; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::CoreAffinityMask + // Desc: Gets an affinity mask that corresponds to the requested processor + // core. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD_PTR CoreAffinityMask(DWORD coreIdx) const { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinity, + &dwSystemAffinity); + + for (DWORD i = 0; i < m_nItems; ++i) { + if (RelationProcessorCore == m_pSlpi[i].Relationship) { + if (!coreIdx--) { + return m_pSlpi[i].ProcessorMask & dwProcessAffinity; + } + } + } + return 0; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::IsSupported + //----------------------------------------------------------------------------- + static BOOL IsSupported() { return NULL != GetGlpiFn_(); } + + private: + // GetLogicalProcessorInformation function pointer + typedef BOOL(WINAPI* GlpiFnPtr)(SYSTEM_LOGICAL_PROCESSOR_INFORMATION*, + PDWORD); + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::VerifyGlpiFn_ + // Desc: Gets a pointer to the GetLogicalProcessorInformation function only if + // it is supported on the current platform. + // GetLogicalProcessorInformation is supported on Windows Server 2003 + // and XP64, however there is a bug with the implementation. Therefore, + // only GetLogicalProcessorInformation on Windows Vista is supported in + // this sample. + //----------------------------------------------------------------------------- + static GlpiFnPtr VerifyGlpiFn_() { + // VerifyVersionInfo function pointer + typedef BOOL(WINAPI * VviFnPtr)(LPOSVERSIONINFOEX, DWORD, DWORDLONG); + + HMODULE hMod = GetModuleHandle(TEXT("kernel32")); +#ifdef _UNICODE + VviFnPtr pVvi = (VviFnPtr)GetProcAddress(hMod, "VerifyVersionInfoW"); +#else + VviFnPtr pVvi = (VviFnPtr)GetProcAddress(hMod, "VerifyVersionInfoA"); +#endif + GlpiFnPtr pGlpi = NULL; + + if (pVvi) { + // VerSetConditionMask function pointer + typedef ULONGLONG(WINAPI * VscmFnPtr)(ULONGLONG, DWORD, BYTE); + + VscmFnPtr pVscm = (VscmFnPtr)GetProcAddress(hMod, "VerSetConditionMask"); + + _ASSERT(pVscm); + + // Check for Windows Vista + OSVERSIONINFOEX osvi = {sizeof(OSVERSIONINFOEX)}; + osvi.dwMajorVersion = 6; + osvi.dwMinorVersion = 0; + osvi.wServicePackMajor = 0; + osvi.wServicePackMinor = 0; + + ULONGLONG dwlMask = 0; + dwlMask = pVscm(dwlMask, VER_MAJORVERSION, VER_GREATER_EQUAL); + dwlMask = pVscm(dwlMask, VER_MINORVERSION, VER_GREATER_EQUAL); + dwlMask = pVscm(dwlMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + dwlMask = pVscm(dwlMask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (pVvi(&osvi, + VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | + VER_SERVICEPACKMINOR, + dwlMask)) { + pGlpi = + (GlpiFnPtr)GetProcAddress(hMod, "GetLogicalProcessorInformation"); + _ASSERT(pGlpi); + } + } + + return pGlpi; + } + + //----------------------------------------------------------------------------- + // Name: GlpiImpl::GetGlpiFn_ + // Desc: Gets a cached pointer to the GetLogicalProcessorInformation function. + //----------------------------------------------------------------------------- + static GlpiFnPtr GetGlpiFn_() { + static GlpiFnPtr pGlpi = VerifyGlpiFn_(); + return pGlpi; + } + + // Private Members + SYSTEM_LOGICAL_PROCESSOR_INFORMATION* m_pSlpi; + DWORD m_nItems; +}; + +//--------------------------------------------------------------------------------- +// Name: ApicExtractor +// Desc: A utility class that provides an interface for decoding a processor +// APIC ID. An APIC ID is an 8-bit identifier given to each logical +// processor on system boot and can be retrieved by the CPUID instruction. +// Each APIC ID is composed of a PACKAGE_ID, CORE_ID and SMT_ID that +// describe the relationship of a logical processor within the processor +// topology of the system. +//--------------------------------------------------------------------------------- +class ApicExtractor { + public: + //----------------------------------------------------------------------------- + // Name: ApicExtractor::ApicExtractor + //----------------------------------------------------------------------------- + ApicExtractor(DWORD nLogProcsPerPkg = 1, DWORD nCoresPerPkg = 1) { + SetPackageTopology(nLogProcsPerPkg, nCoresPerPkg); + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::SmtId + //----------------------------------------------------------------------------- + BYTE SmtId(BYTE apicId) const { return apicId & m_smtIdMask.mask; } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::CoreId + //----------------------------------------------------------------------------- + BYTE CoreId(BYTE apicId) const { + return (apicId & m_coreIdMask.mask) >> m_smtIdMask.width; + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::PackageId + //----------------------------------------------------------------------------- + BYTE PackageId(BYTE apicId) const { + return (apicId & m_pkgIdMask.mask) >> + (m_smtIdMask.width + m_coreIdMask.width); + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::PackageCoreId + //----------------------------------------------------------------------------- + BYTE PackageCoreId(BYTE apicId) const { + return (apicId & (m_pkgIdMask.mask | m_coreIdMask.mask)) >> + m_smtIdMask.width; + } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::GetLogProcsPerPkg + //----------------------------------------------------------------------------- + DWORD GetLogProcsPerPkg() const { return m_nLogProcsPerPkg; } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::GetCoresPerPkg + //----------------------------------------------------------------------------- + DWORD GetCoresPerPkg() const { return m_nCoresPerPkg; } + + //----------------------------------------------------------------------------- + // Name: ApicExtractor::SetPackageTopology + // Desc: You should call SetPackageTopology with the number of logical + // processors per package and number of cores per package before calling + // the sub id accessors (SmtId(), CoreId(), PackageId(), + // PackageCoreId()) as this information is required to effectively + // decode an APIC ID into its sub parts. + //----------------------------------------------------------------------------- + void SetPackageTopology(DWORD nLogProcsPerPkg, DWORD nCoresPerPkg) { + m_nLogProcsPerPkg = (BYTE)nLogProcsPerPkg; + m_nCoresPerPkg = (BYTE)nCoresPerPkg; + + // fix for Phenom x3 and similar CPUs - it reports 3 logical processors per + // package, and 4 cores per package so one core is probably just disabled + // for yield, but it causes a bug in GetMaskWidth that propagates + if (m_nCoresPerPkg > m_nLogProcsPerPkg) { + m_nCoresPerPkg = m_nLogProcsPerPkg; + } + + m_smtIdMask.width = GetMaskWidth_(m_nLogProcsPerPkg / m_nCoresPerPkg); + m_coreIdMask.width = GetMaskWidth_(m_nCoresPerPkg); + m_pkgIdMask.width = 8 - (m_smtIdMask.width + m_coreIdMask.width); + + m_pkgIdMask.mask = (BYTE)(0xFF << (m_smtIdMask.width + m_coreIdMask.width)); + m_coreIdMask.mask = (BYTE)((0xFF << m_smtIdMask.width) ^ m_pkgIdMask.mask); + m_smtIdMask.mask = (BYTE) ~(0xFF << m_smtIdMask.width); + } + + private: + //----------------------------------------------------------------------------- + // Name: ApicExtractor::GetMaskWidth_ + // Desc: Gets the width of a sub id bit field in an APIC ID. The width of a + // sub id (CORE_ID, SMT_ID) is only wide enough to support the maximum + // number of ids that needs to be represented in the topology. + //----------------------------------------------------------------------------- + static BYTE GetMaskWidth_(BYTE maxIds) { + --maxIds; + + // find index of msb + BYTE msbIdx = 8; + BYTE msbMask = 0x80; + while (msbMask && !(msbMask & maxIds)) { + --msbIdx; + msbMask >>= 1; + } + return msbIdx; + } + + struct IdMask { + BYTE width; + BYTE mask; + }; + + // Private Members + BYTE m_nLogProcsPerPkg; + BYTE m_nCoresPerPkg; + IdMask m_smtIdMask; + IdMask m_coreIdMask; + IdMask m_pkgIdMask; +}; + +//--------------------------------------------------------------------------------- +// Name: Cpuid +// Desc: A utility class that wraps the functionality of the CPUID instruction. +// Call the Call() method with the desired CPUID function, and use the +// register accessors to retrieve the register values. +//--------------------------------------------------------------------------------- +class Cpuid { + public: + // FnSet values are used to indicate a CPUID function set. + enum FnSet { Std = 0x00000000, Ext = 0x80000000 }; + + //----------------------------------------------------------------------------- + // Name: Cpuid::Cpuid + //----------------------------------------------------------------------------- + Cpuid() : m_eax(0), m_ebx(0), m_ecx(0), m_edx(0) {} + + // Register accessors + DWORD Eax() const { return m_eax; } + DWORD Ebx() const { return m_ebx; } + DWORD Ecx() const { return m_ecx; } + DWORD Edx() const { return m_edx; } + + //----------------------------------------------------------------------------- + // Name: Cpuid::Call + // Desc: Calls the CPUID instruction with the specified function. Returns + // TRUE + // if the CPUID function was supported, FALSE if it wasn't. + //----------------------------------------------------------------------------- + BOOL Call(FnSet fnSet, DWORD fn) { + if (IsFnSupported(fnSet, fn)) { + UncheckedCall_(fnSet, fn); + return true; + } + return false; + } + + //----------------------------------------------------------------------------- + // Name: Cpuid::IsVendor + // Desc: Compares a string with the vendor string encoded in the CPUID + // instruction. + //----------------------------------------------------------------------------- + static BOOL IsVendor(const char* strVendor) { + // Cache the vendor string + static const Cpuid cpu(Std); + return cpu.Ebx() == *reinterpret_cast(strVendor) && + cpu.Ecx() == *reinterpret_cast(strVendor + 8) && + cpu.Edx() == *reinterpret_cast(strVendor + 4); + } + + //----------------------------------------------------------------------------- + // Name: Cpuid::IsFnSupported + // Desc: Checks to see if a CPUID function is supported. Different processors + // support different functions. This method is automatically called + // from the Call() method, so you don't need to call it beforehand. + //----------------------------------------------------------------------------- + static BOOL IsFnSupported(FnSet fnSet, DWORD fn) { + // Cache the maximum supported standard function + static const DWORD MaxStdFn = Cpuid(Std).Eax(); + // Cache the maximum supported extended function + static const DWORD MaxExtFn = Cpuid(Ext).Eax(); + + bool ret = false; + switch (fnSet) { + case Std: + ret = (fn <= MaxStdFn); + break; + case Ext: + ret = (fn <= MaxExtFn); + break; + default: + _ASSERT(0); // should never get here + break; + } + return ret; + } + + private: + //----------------------------------------------------------------------------- + // Name: Cpuid::Cpuid + // Desc: This constructor is private and is only used to set a Cpuid object to + // initial values retrieved from CPUID functions 0x00000000 and + // 0x80000000. Good for caching values from the CPUID instruction that + // are not variable, like the encoded vendor string and the maximum + // supported CPUID function values. + //----------------------------------------------------------------------------- + explicit Cpuid(FnSet fnSet) { UncheckedCall_(fnSet, 0); } + + //----------------------------------------------------------------------------- + // Name: Cpuid::UncheckedCall_ + // Desc: Calls the CPUID instruction without checking for CPUID function + // support. + //----------------------------------------------------------------------------- + void UncheckedCall_(FnSet fnSet, DWORD fn) { + m_eax = fnSet | fn; + m_ecx = 0; + + int reg[4]; + __cpuidex(reg, m_eax, m_ecx); + + m_eax = reg[0]; + m_ebx = reg[1]; + m_ecx = reg[2]; + m_edx = reg[3]; + } + + // Private Members + DWORD m_eax; + DWORD m_ebx; + DWORD m_ecx; + DWORD m_edx; +}; + +//--------------------------------------------------------------------------------- +// Name: CpuidImpl +// Desc: Provides the CPUID instruction implementation for the ICpuTopology +// interface. This is a ConcreteImplementor class in the traditional +// Bridge Pattern. +//--------------------------------------------------------------------------------- +class CpuidImpl : public ICpuTopology { + public: + // CpuidFnMasks are used when extracting bit-encoded information retrieved + // from the CPUID instruction + enum CpuidFnMasks { + HTT = 0x10000000, // Fn0000_0001 EDX[28] + LogicalProcessorCount = 0x00FF0000, // Fn0000_0001 EBX[23:16] + ApicId = 0xFF000000, // Fn0000_0001 EBX[31:24] + NC_Intel = 0xFC000000, // Fn0000_0004 EAX[31:26] + NC_Amd = 0x000000FF, // Fn8000_0008 ECX[7:0] + CmpLegacy_Amd = 0x00000002, // Fn8000_0001 ECX[1] + ApicIdCoreIdSize_Amd = 0x0000F000 // Fn8000_0008 ECX[15:12] + }; + + enum { MaxLogicalProcessors = sizeof(DWORD_PTR) * 8 }; + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::CpuidImpl + // Desc: Initializes internal structures/data with information retrieved from + // calling the CPUID instruction. + //----------------------------------------------------------------------------- + CpuidImpl() : m_nItems(0) { + _ASSERT(IsSupported()); + + DWORD nLogProcsPerPkg = 1; + DWORD nCoresPerPkg = 1; + + Cpuid cpu; + + // Determine if hardware threading is enabled. + cpu.Call(Cpuid::Std, 1); + if (cpu.Edx() & HTT) { + // Determine the total number of logical processors per package. + nLogProcsPerPkg = (cpu.Ebx() & LogicalProcessorCount) >> 16; + + // Determine the total number of cores per package. This info + // is extracted differently dependending on the cpu vendor. + if (Cpuid::IsVendor(GenuineIntel)) { + if (cpu.Call(Cpuid::Std, 4)) { + nCoresPerPkg = ((cpu.Eax() & NC_Intel) >> 26) + 1; + } + } else { + _ASSERT(Cpuid::IsVendor(AuthenticAMD)); + if (cpu.Call(Cpuid::Ext, 8)) { + // AMD reports the msb width of the CORE_ID bit field of the APIC ID + // in ApicIdCoreIdSize_Amd. The maximum value represented by the msb + // width is the theoretical number of cores the processor can support + // and not the actual number of current cores, which is how the msb + // width of the CORE_ID bit field has been traditionally determined. + // If the ApicIdCoreIdSize_Amd value is zero, then you use the + // traditional method to determine the CORE_ID msb width. + DWORD msbWidth = cpu.Ecx() & ApicIdCoreIdSize_Amd; + if (msbWidth) { + // Set nCoresPerPkg to the maximum theortical number of cores + // the processor package can support (2 ^ width) so the APIC + // extractor object can be configured to extract the proper + // values from an APIC. + nCoresPerPkg = 1 << (msbWidth >> 12); + } else { + // Set nCoresPerPkg to the actual number of cores being reported + // by the CPUID instruction. + nCoresPerPkg = (cpu.Ecx() & NC_Amd) + 1; + } + } + } + } + + // Configure the APIC extractor object with the information it needs to + // be able to decode the APIC. + m_apicExtractor.SetPackageTopology(nLogProcsPerPkg, nCoresPerPkg); + + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + HANDLE hProcess = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + GetProcessAffinityMask(hProcess, &dwProcessAffinity, &dwSystemAffinity); + if (1 == dwSystemAffinity) { + // Since we only have 1 logical processor present on the system, we + // can explicitly set a single APIC ID to zero. + _ASSERT(1 == nLogProcsPerPkg); + m_apicIds[m_nItems++] = 0; + } else { + // Set the process affinity to the system affinity if they are not + // equal so that all logical processors can be accounted for. + if (dwProcessAffinity != dwSystemAffinity) { + SetProcessAffinityMask(hProcess, dwSystemAffinity); + } + + // Call cpuid on each active logical processor in the system affinity. + DWORD_PTR dwPrevThreadAffinity = 0; + for (DWORD_PTR dwThreadAffinity = 1; + dwThreadAffinity && dwThreadAffinity <= dwSystemAffinity; + dwThreadAffinity <<= 1) { + if (dwSystemAffinity & dwThreadAffinity) { + if (0 == dwPrevThreadAffinity) { + // Save the previous thread affinity so we can return + // the executing thread affinity back to this state. + _ASSERT(0 == m_nItems); + dwPrevThreadAffinity = + SetThreadAffinityMask(hThread, dwThreadAffinity); + } else { + _ASSERT(m_nItems > 0); + SetThreadAffinityMask(hThread, dwThreadAffinity); + } + + // Allow the thread to switch to masked logical processor. + Sleep(0); + + // Store the APIC ID + cpu.Call(Cpuid::Std, 1); + m_apicIds[m_nItems++] = (BYTE)((cpu.Ebx() & ApicId) >> 24); + } + } + + // Restore the previous process and thread affinity state. + SetProcessAffinityMask(hProcess, dwProcessAffinity); + SetThreadAffinityMask(hThread, dwPrevThreadAffinity); + Sleep(0); + } + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::IsDefaultImpl + //----------------------------------------------------------------------------- + /*virtual*/ BOOL IsDefaultImpl() const { return FALSE; } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::NumberOfProcessCores + // Desc: Gets the number of processor cores available to the current process. + // The total accounts for cores that may have been masked out by process + // affinity. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfProcessCores() const { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinity, + &dwSystemAffinity); + + BYTE pkgCoreIds[MaxLogicalProcessors] = {0}; + DWORD nPkgCoreIds = 0; + + for (DWORD i = 0; i < m_nItems; ++i) { + if (dwProcessAffinity & ((DWORD_PTR)1 << i)) { + AddUniquePkgCoreId_(i, pkgCoreIds, nPkgCoreIds); + } + } + return nPkgCoreIds; + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::NumberOfSystemCores + // Desc: Gets the number of processor cores on the system. + //----------------------------------------------------------------------------- + /*virtual*/ DWORD NumberOfSystemCores() const { + BYTE pkgCoreIds[MaxLogicalProcessors] = {0}; + DWORD nPkgCoreIds = 0; + for (DWORD i = 0; i < m_nItems; ++i) { + AddUniquePkgCoreId_(i, pkgCoreIds, nPkgCoreIds); + } + return nPkgCoreIds; + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::CoreAffinityMask + // Desc: Gets an affinity mask that corresponds to a specific processor core. + // coreIdx must be less than the total number of processor cores + // recognized by the operating system (NumberOfSystemCores()). + //----------------------------------------------------------------------------- + /*virtual*/ DWORD_PTR CoreAffinityMask(DWORD coreIdx) const { + BYTE pkgCoreIds[MaxLogicalProcessors] = {0}; + DWORD nPkgCoreIds = 0; + for (DWORD i = 0; i < m_nItems; ++i) { + AddUniquePkgCoreId_(i, pkgCoreIds, nPkgCoreIds); + } + + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinity, + &dwSystemAffinity); + + DWORD_PTR coreAffinity = 0; + if (coreIdx < nPkgCoreIds) { + for (DWORD i = 0; i < m_nItems; ++i) { + if (m_apicExtractor.PackageCoreId(m_apicIds[i]) == + pkgCoreIds[coreIdx]) { + coreAffinity |= (dwProcessAffinity & ((DWORD_PTR)1 << i)); + } + } + } + return coreAffinity; + } + + //----------------------------------------------------------------------------- + // Name: CpuidImpl::IsSupported + // Desc: Indicates if a CpuidImpl object is supported on this platform. + // Support is only granted on Intel and AMD platforms where the current + // calling process has security rights to query process affinity and + // change it if the process and system affinity differ. CpuidImpl is + // also not supported if thread affinity cannot be set on systems with + // more than 1 logical processor. + //----------------------------------------------------------------------------- + static BOOL IsSupported() { + BOOL bSupported = + Cpuid::IsVendor(GenuineIntel) || Cpuid::IsVendor(AuthenticAMD); + + if (bSupported) { + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + HANDLE hProcess = GetCurrentProcess(); + + // Query process affinity mask + bSupported = GetProcessAffinityMask(hProcess, &dwProcessAffinity, + &dwSystemAffinity); + if (bSupported) { + if (dwProcessAffinity != dwSystemAffinity) { + // The process and system affinities differ. Attempt to set + // the process affinity to the system affinity. + bSupported = SetProcessAffinityMask(hProcess, dwSystemAffinity); + if (bSupported) { + // Restore previous process affinity + bSupported = SetProcessAffinityMask(hProcess, dwProcessAffinity); + } + } + + if (bSupported && (dwSystemAffinity > 1)) { + // Attempt to set the thread affinity + HANDLE hThread = GetCurrentThread(); + DWORD_PTR dwThreadAffinity = + SetThreadAffinityMask(hThread, dwProcessAffinity); + if (dwThreadAffinity) { + // Restore the previous thread affinity + bSupported = 0 != SetThreadAffinityMask(hThread, dwThreadAffinity); + } else { + bSupported = FALSE; + } + } + } + } + return bSupported; + } + + private: + //----------------------------------------------------------------------------- + // Name: CpuidImpl::AddUniquePkgCoreId_ + // Desc: Adds the package/core id extracted from the APIC ID at m_apicIds[idx] + // in the if the package/core id is unique to the pkgCoreIds array. + // nPkgCore is an in/out parm that will reflect the total number of + // items in pkgCoreIds array. It will be incrememted if a unique + // package/core id is found and added. + //----------------------------------------------------------------------------- + void AddUniquePkgCoreId_(DWORD idx, BYTE* pkgCoreIds, + DWORD& nPkgCoreIds) const { + _ASSERT(idx < m_nItems); + _ASSERT(NULL != pkgCoreIds); + + DWORD j; + for (j = 0; j < nPkgCoreIds; ++j) { + if (pkgCoreIds[j] == m_apicExtractor.PackageCoreId(m_apicIds[idx])) break; + } + if (j == nPkgCoreIds) { + pkgCoreIds[j] = m_apicExtractor.PackageCoreId(m_apicIds[idx]); + ++nPkgCoreIds; + } + } + + // Private Members + BYTE m_apicIds[MaxLogicalProcessors]; + BYTE m_nItems; + ApicExtractor m_apicExtractor; + + // Supported Vendor Strings + static const char GenuineIntel[]; + static const char AuthenticAMD[]; +}; + +// Static initialization of vendor strings +const char CpuidImpl::GenuineIntel[] = "GenuineIntel"; +const char CpuidImpl::AuthenticAMD[] = "AuthenticAMD"; + +} // namespace + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::CpuTopology +// Desc: Initializes this object with the appropriately supported cpu topology +// implementation object. +//------------------------------------------------------------------------------------- +CpuTopology::CpuTopology(BOOL bForceCpuid) : m_pImpl(NULL) { + ForceCpuid(bForceCpuid); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::~CpuTopology +//------------------------------------------------------------------------------------- +CpuTopology::~CpuTopology() { Destroy_(); } + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::NumberOfProcessCores +// Desc: Gets the total number of physical processor cores available to the +// current +// process. +//------------------------------------------------------------------------------------- +DWORD CpuTopology::NumberOfProcessCores() const { + return m_pImpl->NumberOfProcessCores(); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::NumberOfSystemCores +// Desc: Gets the total number of physical processor cores enabled on the +// system. +//------------------------------------------------------------------------------------- +DWORD CpuTopology::NumberOfSystemCores() const { + return m_pImpl->NumberOfSystemCores(); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::CoreAffinityMask +// Desc: Gets an affinity mask that corresponds to the requested processor core. +//------------------------------------------------------------------------------------- +DWORD_PTR CpuTopology::CoreAffinityMask(DWORD coreIdx) const { + return m_pImpl->CoreAffinityMask(coreIdx); +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::IsDefaultImpl +// Desc: Returns TRUE if m_pImpl is a DefaultImpl object, FALSE if not. Used to +// indicate whether or not the prescribed methods (CPUID or +// GetLogicalProcessorInformation) are supported on the system. +//------------------------------------------------------------------------------------- +BOOL CpuTopology::IsDefaultImpl() const { return m_pImpl->IsDefaultImpl(); } + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::ForceCpuid +// Desc: Constructs a cpu topology object. If bForce is FALSE, then a GlpiImpl +// object +// is first attempted, then CpuidImpl, then finally DefaultImpl. If +// bForce is TRUE, then GlpiImpl is never attempted. +//------------------------------------------------------------------------------------- +void CpuTopology::ForceCpuid(BOOL bForce) { + Destroy_(); + + if (!bForce && GlpiImpl::IsSupported()) { + m_pImpl = new GlpiImpl(); + } else if (CpuidImpl::IsSupported()) { + m_pImpl = new CpuidImpl(); + } else { + m_pImpl = new DefaultImpl(); + } +} + +//------------------------------------------------------------------------------------- +// Name: CpuTopology::Destroy_ +//------------------------------------------------------------------------------------- +void CpuTopology::Destroy_() { + delete m_pImpl; + m_pImpl = NULL; +} +#endif \ No newline at end of file diff --git a/tier0/cputopology.h b/tier0/cputopology.h new file mode 100644 index 0000000..56319ba --- /dev/null +++ b/tier0/cputopology.h @@ -0,0 +1,36 @@ +// CpuTopology.h +// +// CpuTopology class declaration. +// +// Copyright (c) Microsoft Corporation. All rights reserved. + +#ifndef VPC_TIER0_CPU_TOPOLOGY_H_ +#define VPC_TIER0_CPU_TOPOLOGY_H_ + +#include "winlite.h" + +class ICpuTopology; + +// Name: CpuTopology +// Desc: This class constructs a supported cpu topology implementation object on +// initialization and forwards calls to it. This is the Abstraction class +// in the traditional Bridge Pattern. +class CpuTopology { + public: + CpuTopology(BOOL bForceCpuid = FALSE); + ~CpuTopology(); + + BOOL IsDefaultImpl() const; + DWORD NumberOfProcessCores() const; + DWORD NumberOfSystemCores() const; + DWORD_PTR CoreAffinityMask(DWORD coreIdx) const; + + void ForceCpuid(BOOL bForce); + + private: + void Destroy_(); + + ICpuTopology* m_pImpl; +}; + +#endif // VPC_TIER0_CPU_TOPOLOGY_H_ diff --git a/tier0/dbg.cpp b/tier0/dbg.cpp new file mode 100644 index 0000000..e032870 --- /dev/null +++ b/tier0/dbg.cpp @@ -0,0 +1,574 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier0/platform.h" + +#if defined(PLATFORM_WINDOWS_PC) +#define WIN_32_LEAN_AND_MEAN +#include "winlite.h" // Currently needed for IsBadReadPtr and IsBadWritePtr +#pragma comment(lib, "user32.lib") // For MessageBox +#endif + +#include "tier0/minidump.h" +#include "tier0/stacktools.h" + +#include "color.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" + +#if defined(_X360) +#include "xbox/xbox_console.h" +#endif + +#include "tier0/etwprof.h" + +#ifndef STEAM +#define PvRealloc realloc +#define PvAlloc malloc +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) +#pragma optimize("g", \ + off) // variable argument functions seem to screw up stack + // walking unless this optimization is disabled +#endif + +DEFINE_LOGGING_CHANNEL_NO_TAGS(LOG_LOADING, "LOADING"); + +//----------------------------------------------------------------------------- +// Stack attachment management +//----------------------------------------------------------------------------- +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) + +static bool s_bCallStacksWithAllWarnings = + false; // if true, attach a call stack to every SPEW_WARNING message. + // Warning()/DevWarning()/... +static int s_iWarningMaxCallStackLength = 5; +#define AutomaticWarningCallStackLength() \ + (s_bCallStacksWithAllWarnings ? s_iWarningMaxCallStackLength : 0) + +void _Warning_AlwaysSpewCallStack_Enable(bool bEnable) { + s_bCallStacksWithAllWarnings = bEnable; +} + +void _Warning_AlwaysSpewCallStack_Length(int iMaxCallStackLength) { + s_iWarningMaxCallStackLength = iMaxCallStackLength; +} + +static bool s_bCallStacksWithAllErrors = + false; // if true, attach a call stack to every SPEW_ERROR message. Mostly + // just Error() +static int s_iErrorMaxCallStackLength = + 20; // default to higher output with an error since we're quitting anyways +#define AutomaticErrorCallStackLength() \ + (s_bCallStacksWithAllErrors ? s_iErrorMaxCallStackLength : 0) + +void _Error_AlwaysSpewCallStack_Enable(bool bEnable) { + s_bCallStacksWithAllErrors = bEnable; +} + +void _Error_AlwaysSpewCallStack_Length(int iMaxCallStackLength) { + s_iErrorMaxCallStackLength = iMaxCallStackLength; +} + +#else //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +#define AutomaticWarningCallStackLength() 0 +#define AutomaticErrorCallStackLength() 0 + +void _Warning_AlwaysSpewCallStack_Enable(bool bEnable) {} + +void _Warning_AlwaysSpewCallStack_Length(int iMaxCallStackLength) {} + +void _Error_AlwaysSpewCallStack_Enable(bool bEnable) {} + +void _Error_AlwaysSpewCallStack_Length(int iMaxCallStackLength) {} + +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +void _ExitOnFatalAssert(const tchar *pFile, int line) { + Log_Msg(LOG_ASSERT, + _T("Fatal assert failed: %s, line %d. Application exiting.\n"), + pFile, line); + + // only write out minidumps if we're not in the debugger + if (!Plat_IsInDebugSession()) { + WriteMiniDump(); + } + + Log_Msg(LOG_DEVELOPER, _T("_ExitOnFatalAssert\n")); + Plat_ExitProcess(EXIT_FAILURE); +} + +//----------------------------------------------------------------------------- +// Templates to assist in validating pointers: +//----------------------------------------------------------------------------- +PLATFORM_INTERFACE void _AssertValidReadPtr(void *ptr, int count /* = 1*/) { +#if defined(_WIN32) && !defined(_X360) + Assert(!IsBadReadPtr(ptr, count)); +#else + Assert(!count || ptr); +#endif +} + +PLATFORM_INTERFACE void _AssertValidWritePtr(void *ptr, int count /* = 1*/) { +#if defined(_WIN32) && !defined(_X360) + Assert(!IsBadWritePtr(ptr, count)); +#else + Assert(!count || ptr); +#endif +} + +PLATFORM_INTERFACE void _AssertValidReadWritePtr(void *ptr, + int count /* = 1*/) { +#if defined(_WIN32) && !defined(_X360) + Assert(!(IsBadWritePtr(ptr, count) || IsBadReadPtr(ptr, count))); +#else + Assert(!count || ptr); +#endif +} + +PLATFORM_INTERFACE void _AssertValidStringPtr(const tchar *ptr, + int maxchar /* = 0xFFFFFF */) { +#if defined(_WIN32) && !defined(_X360) +#ifdef TCHAR_IS_CHAR + Assert(!IsBadStringPtr(ptr, maxchar)); +#else + Assert(!IsBadStringPtrW(ptr, maxchar)); +#endif +#else + Assert(ptr); +#endif +} + +void AppendCallStackToLogMessage(tchar *formattedMessage, int iMessageLength, + int iAppendCallStackLength) { +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) +#if defined(TCHAR_IS_CHAR) // I'm horrible with unicode and I don't plan on + // testing this with wide characters just yet + if (iAppendCallStackLength > 0) { + int iExistingMessageLength = (int)strlen( + formattedMessage); // no V_strlen in tier 0, plus we're only compiling + // this for windows and 360. Seems safe + formattedMessage += iExistingMessageLength; + iMessageLength -= iExistingMessageLength; + + if (iMessageLength <= 32) return; // no room for anything useful + + // append directly to the spew message + if ((iExistingMessageLength > 0) && (formattedMessage[-1] == '\n')) { + --formattedMessage; + ++iMessageLength; + } + + // append preface + int iAppendedLength = + _snprintf(formattedMessage, iMessageLength, _T("\nCall Stack:\n\t")); + + void **CallStackBuffer = + (void **)stackalloc(iAppendCallStackLength * sizeof(void *)); + int iCount = GetCallStack(CallStackBuffer, iAppendCallStackLength, 2); + if (TranslateStackInfo(CallStackBuffer, iCount, + formattedMessage + iAppendedLength, + iMessageLength - iAppendedLength, _T("\n\t")) == 0) { + // failure + formattedMessage[0] = + '\0'; // this is pointing at where we wrote "\nCall Stack:\n\t" + } else { + iAppendedLength += (int)strlen( + formattedMessage + + iAppendedLength); // no V_strlen in tier 0, plus we're only compiling + // this for windows and 360. Seems safe + + if (iAppendedLength < iMessageLength) { + formattedMessage[iAppendedLength] = '\n'; // Add another newline. + ++iAppendedLength; + + formattedMessage[iAppendedLength] = '\0'; + } + } + } +#else + AssertMsg(false, "Fixme"); +#endif +#endif +} + +// Forward declare for internal use only. +CLoggingSystem *GetGlobalLoggingSystem(); + +#define Log_LegacyHelperColor_Stack(Channel, Severity, Color, MessageFormat, \ + AppendCallStackLength) \ + do { \ + CLoggingSystem *pLoggingSystem = GetGlobalLoggingSystem(); \ + if (pLoggingSystem->IsChannelEnabled(Channel, Severity)) { \ + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; \ + va_list args; \ + va_start(args, MessageFormat); \ + Tier0Internal_vsntprintf(formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, \ + MessageFormat, args); \ + va_end(args); \ + AppendCallStackToLogMessage(formattedMessage, \ + MAX_LOGGING_MESSAGE_LENGTH, \ + AppendCallStackLength); \ + pLoggingSystem->LogDirect(Channel, Severity, Color, formattedMessage); \ + } \ + } while (0) + +#define Log_LegacyHelperColor(Channel, Severity, Color, MessageFormat) \ + Log_LegacyHelperColor_Stack(Channel, Severity, Color, MessageFormat, 0) + +#define Log_LegacyHelper_Stack(Channel, Severity, MessageFormat, \ + AppendCallStackLength) \ + Log_LegacyHelperColor_Stack(Channel, Severity, \ + pLoggingSystem->GetChannelColor(Channel), \ + MessageFormat, AppendCallStackLength) +#define Log_LegacyHelper(Channel, Severity, MessageFormat) \ + Log_LegacyHelperColor(Channel, Severity, \ + pLoggingSystem->GetChannelColor(Channel), \ + MessageFormat) + +void Msg(const tchar *pMsgFormat, ...) { + Log_LegacyHelper(LOG_GENERAL, LS_MESSAGE, pMsgFormat); +} + +void Warning(const tchar *pMsgFormat, ...) { + Log_LegacyHelper_Stack(LOG_GENERAL, LS_WARNING, pMsgFormat, + AutomaticWarningCallStackLength()); +} + +void Warning_SpewCallStack(int iMaxCallStackLength, const tchar *pMsgFormat, + ...) { + Log_LegacyHelper_Stack(LOG_GENERAL, LS_WARNING, pMsgFormat, + iMaxCallStackLength); +} + +void Error(const tchar *pMsgFormat, ...) { + Log_LegacyHelper_Stack(LOG_GENERAL, LS_ERROR, pMsgFormat, + AutomaticErrorCallStackLength()); +} + +void Error_SpewCallStack(int iMaxCallStackLength, const tchar *pMsgFormat, + ...) { + Log_LegacyHelper_Stack(LOG_GENERAL, LS_ERROR, pMsgFormat, + iMaxCallStackLength); +} + +//----------------------------------------------------------------------------- +// A couple of super-common dynamic spew messages, here for convenience +// These looked at the "developer" group, print if it's level 1 or higher +//----------------------------------------------------------------------------- +void DevMsg(int level, const tchar *pMsgFormat, ...) { + LoggingChannelID_t channel = + level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER; + Log_LegacyHelper(channel, LS_MESSAGE, pMsgFormat); +} + +void DevWarning(int level, const tchar *pMsgFormat, ...) { + LoggingChannelID_t channel = + level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER; + Log_LegacyHelper(channel, LS_WARNING, pMsgFormat); +} + +void DevMsg(const tchar *pMsgFormat, ...) { + Log_LegacyHelper(LOG_DEVELOPER, LS_MESSAGE, pMsgFormat); +} + +void DevWarning(const tchar *pMsgFormat, ...) { + Log_LegacyHelper(LOG_DEVELOPER, LS_WARNING, pMsgFormat); +} + +void ConColorMsg(const Color &clr, const tchar *pMsgFormat, ...) { + Log_LegacyHelperColor(LOG_CONSOLE, LS_MESSAGE, clr, pMsgFormat); +} + +void ConMsg(const tchar *pMsgFormat, ...) { + Log_LegacyHelper(LOG_CONSOLE, LS_MESSAGE, pMsgFormat); +} + +void ConDMsg(const tchar *pMsgFormat, ...) { + Log_LegacyHelper(LOG_DEVELOPER_CONSOLE, LS_MESSAGE, pMsgFormat); +} + +// If we don't have a function from math.h, then it doesn't link certain +// floating-point functions in and printfs with %f cause runtime errors in the C +// libraries. +PLATFORM_INTERFACE float CrackSmokingCompiler(float a) { + return (float)fabs(a); +} + +void *Plat_SimpleLog(const tchar *file, int line) { + FILE *f = _tfopen(_T("simple.log"), _T("at+")); + if (f) { + _ftprintf(f, _T("%s:%i\n"), file, line); + fclose(f); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: For debugging startup times, etc. +// Input : *fmt - +// ... - +//----------------------------------------------------------------------------- +void COM_TimestampedLog(char const *fmt, ...) { + static double s_LastStamp = 0.0; + static bool s_bShouldLog = false; + static bool s_bShouldLogToConsole = false; + static bool s_bShouldLogToETW = false; + static bool s_bChecked = false; + static bool s_bFirstWrite = false; + + if (!s_bChecked) { + s_bShouldLog = (CommandLine()->CheckParm("-profile")) ? true : false; + s_bShouldLogToConsole = + (CommandLine()->ParmValue("-profile", 0.0f) != 0.0f) ? true : false; + + s_bShouldLogToETW = + (CommandLine()->CheckParm("-etwprofile")) ? true : false; + if (s_bShouldLogToETW) { + s_bShouldLog = true; + } + + s_bChecked = true; + } + if (!s_bShouldLog) { + return; + } + + char string[1024]; + va_list argptr; + va_start(argptr, fmt); + Tier0Internal_vsnprintf(string, sizeof(string), fmt, argptr); + va_end(argptr); + + double curStamp = Plat_FloatTime(); + +#if defined(_X360) + XBX_rTimeStampLog(curStamp, string); +#elif defined(_PS3) + Log_Warning(LOG_LOADING, "%8.4f / %8.4f: %s\n", curStamp, + curStamp - s_LastStamp, string); +#endif + + if (IsPC()) { + // If ETW profiling is enabled then do it only. + if (s_bShouldLogToETW) { + ETWMark(string); + } else { + if (!s_bFirstWrite) { + _unlink("timestamped.log"); + s_bFirstWrite = true; + } + + FILE *fp = fopen("timestamped.log", "at+"); + if (fp) { + fprintf(fp, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, + string); + fclose(fp); + } + } + + if (s_bShouldLogToConsole) { + Msg("%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string); + } + } + + s_LastStamp = curStamp; +} + +#ifdef IS_WINDOWS_PC + +class CHardwareBreakPoint { + public: + enum EOpCode { + BRK_SET = 0, + BRK_UNSET, + }; + + CHardwareBreakPoint() { + m_pvAddress = 0; + m_hThread = 0; + m_eType = BREAKPOINT_EXECUTE; + m_eSize = BREAKPOINT_SIZE_1; + m_hThreadEvent = 0; + m_nRegister = 0; + m_eOperation = BRK_SET; + m_bSuccess = false; + } + + const void *m_pvAddress; + HANDLE m_hThread; + EHardwareBreakpointType m_eType; + EHardwareBreakpointSize m_eSize; + HANDLE m_hThreadEvent; + int m_nRegister; + EOpCode m_eOperation; + bool m_bSuccess; + + static void SetBits(DWORD_PTR &dw, int lowBit, int bits, int newValue); + static DWORD WINAPI ThreadProc(LPVOID lpParameter); +}; + +void CHardwareBreakPoint::SetBits(DWORD_PTR &dw, int lowBit, int bits, + int newValue) { + DWORD_PTR mask = (1 << bits) - 1; + dw = (dw & ~(mask << lowBit)) | (newValue << lowBit); +} + +DWORD WINAPI CHardwareBreakPoint::ThreadProc(LPVOID lpParameter) { + CHardwareBreakPoint *h = reinterpret_cast(lpParameter); + SuspendThread(h->m_hThread); //-V720 + + // Get current context + CONTEXT ct = {0}; + ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; + GetThreadContext(h->m_hThread, &ct); + + int FlagBit = 0; + + bool Dr0Busy = false; + bool Dr1Busy = false; + bool Dr2Busy = false; + bool Dr3Busy = false; + if (ct.Dr7 & 1) Dr0Busy = true; + if (ct.Dr7 & 4) Dr1Busy = true; + if (ct.Dr7 & 16) Dr2Busy = true; + if (ct.Dr7 & 64) Dr3Busy = true; + + if (h->m_eOperation == CHardwareBreakPoint::BRK_UNSET) { + // Remove + if (h->m_nRegister == 0) { + FlagBit = 0; + ct.Dr0 = 0; + Dr0Busy = false; + } + if (h->m_nRegister == 1) { + FlagBit = 2; + ct.Dr1 = 0; + Dr1Busy = false; + } + if (h->m_nRegister == 2) { + FlagBit = 4; + ct.Dr2 = 0; + Dr2Busy = false; + } + if (h->m_nRegister == 3) { + FlagBit = 6; + ct.Dr3 = 0; + Dr3Busy = false; + } + ct.Dr7 &= ~(1 << FlagBit); + } else { + if (!Dr0Busy) { + h->m_nRegister = 0; + ct.Dr0 = (DWORD_PTR)h->m_pvAddress; + Dr0Busy = true; + } else if (!Dr1Busy) { + h->m_nRegister = 1; + ct.Dr1 = (DWORD_PTR)h->m_pvAddress; + Dr1Busy = true; + } else if (!Dr2Busy) { + h->m_nRegister = 2; + ct.Dr2 = (DWORD_PTR)h->m_pvAddress; + Dr2Busy = true; + } else if (!Dr3Busy) { + h->m_nRegister = 3; + ct.Dr3 = (DWORD_PTR)h->m_pvAddress; + Dr3Busy = true; + } else { + h->m_bSuccess = false; + ResumeThread(h->m_hThread); + SetEvent(h->m_hThreadEvent); + return 0; + } + + ct.Dr6 = 0; + int st = 0; + if (h->m_eType == BREAKPOINT_EXECUTE) st = 0; + if (h->m_eType == BREAKPOINT_READWRITE) st = 3; + if (h->m_eType == BREAKPOINT_WRITE) st = 1; + + int le = 0; + if (h->m_eSize == BREAKPOINT_SIZE_1) le = 0; + if (h->m_eSize == BREAKPOINT_SIZE_2) le = 1; + if (h->m_eSize == BREAKPOINT_SIZE_4) le = 3; + if (h->m_eSize == BREAKPOINT_SIZE_8) le = 2; + + SetBits(ct.Dr7, 16 + h->m_nRegister * 4, 2, st); + SetBits(ct.Dr7, 18 + h->m_nRegister * 4, 2, le); + SetBits(ct.Dr7, h->m_nRegister * 2, 1, 1); + } + + ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; + SetThreadContext(h->m_hThread, &ct); + + ResumeThread(h->m_hThread); + h->m_bSuccess = true; + SetEvent(h->m_hThreadEvent); + return 0; +} + +HardwareBreakpointHandle_t SetHardwareBreakpoint(EHardwareBreakpointType eType, + EHardwareBreakpointSize eSize, + const void *pvLocation) { + CHardwareBreakPoint *h = new CHardwareBreakPoint(); + h->m_pvAddress = pvLocation; + h->m_eSize = eSize; + h->m_eType = eType; + HANDLE hThread = GetCurrentThread(); + h->m_hThread = hThread; + + if (hThread == GetCurrentThread()) { + DWORD nThreadId = GetCurrentThreadId(); + h->m_hThread = OpenThread(THREAD_ALL_ACCESS, 0, nThreadId); + } + + h->m_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + h->m_eOperation = CHardwareBreakPoint::BRK_SET; // Set Break + CreateThread(0, 0, CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0, 0); + WaitForSingleObject(h->m_hThreadEvent, INFINITE); + CloseHandle(h->m_hThreadEvent); + h->m_hThreadEvent = 0; + if (hThread == GetCurrentThread()) { + CloseHandle(h->m_hThread); + } + h->m_hThread = hThread; + if (!h->m_bSuccess) { + delete h; + return (HardwareBreakpointHandle_t)0; + } + return (HardwareBreakpointHandle_t)h; +} + +bool ClearHardwareBreakpoint(HardwareBreakpointHandle_t handle) { + CHardwareBreakPoint *h = reinterpret_cast(handle); + if (!h) { + return false; + } + + bool bOpened = false; + if (h->m_hThread == GetCurrentThread()) { + DWORD nThreadId = GetCurrentThreadId(); + h->m_hThread = OpenThread(THREAD_ALL_ACCESS, 0, nThreadId); + bOpened = true; + } + + h->m_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + h->m_eOperation = CHardwareBreakPoint::BRK_UNSET; // Remove Break + CreateThread(0, 0, CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0, 0); + WaitForSingleObject(h->m_hThreadEvent, INFINITE); + CloseHandle(h->m_hThreadEvent); + h->m_hThreadEvent = 0; + if (bOpened) { + CloseHandle(h->m_hThread); + } + delete h; + return true; +} + +#endif // IS_WINDOWS_PC diff --git a/tier0/dlmalloc/malloc-2.8.3.h b/tier0/dlmalloc/malloc-2.8.3.h new file mode 100644 index 0000000..c707177 --- /dev/null +++ b/tier0/dlmalloc/malloc-2.8.3.h @@ -0,0 +1,545 @@ +/* + Default header file for malloc-2.8.x, written by Doug Lea + and released to the public domain, as explained at + http://creativecommons.org/licenses/publicdomain. + + last update: Mon Aug 15 08:55:52 2005 Doug Lea (dl at gee) + + This header is for ANSI C/C++ only. You can set any of + the following #defines before including: + + * If USE_DL_PREFIX is defined, it is assumed that malloc.c + was also compiled with this option, so all routines + have names starting with "dl". + + * If HAVE_USR_INCLUDE_MALLOC_H is defined, it is assumed that this + file will be #included after . this is needed only if + your system defines a struct mallinfo that is incompatible with the + standard one declared here. Otherwise, you can include this file + INSTEAD of your system system . At least on ANSI, all + declarations should be compatible with system versions + + * If MSPACES is defined, declarations for mspace versions are included. +*/ + +#ifndef MALLOC_280_H +#define MALLOC_280_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* for size_t */ + +#define USE_DL_PREFIX + +#if !ONLY_MSPACES + +#ifndef USE_DL_PREFIX +#define dlcalloc calloc +#define dlfree free +#define dlmalloc malloc +#define dlmemalign memalign +#define dlrealloc realloc +#define dlvalloc valloc +#define dlpvalloc pvalloc +#define dlmallinfo mallinfo +#define dlmallopt mallopt +#define dlmalloc_trim malloc_trim +#define dlmalloc_stats malloc_stats +#define dlmalloc_usable_size malloc_usable_size +#define dlmalloc_footprint malloc_footprint +#define dlindependent_calloc independent_calloc +#define dlindependent_comalloc independent_comalloc +#endif /* USE_DL_PREFIX */ + + +/* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. +*/ +void* dlmalloc(size_t); + +/* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cuase the current program to abort. +*/ +void dlfree(void*); + +/* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. +*/ +void* dlcalloc(size_t, size_t); + +/* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. +*/ + +void* dlrealloc(void*, size_t); + +/* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. +*/ +void* dlmemalign(size_t, size_t); + +/* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. +*/ +void* dlvalloc(size_t); + +/* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt: + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (-1U disables trimming) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) +*/ +int dlmallopt(int, int); + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + + +/* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. +*/ +size_t dlmalloc_footprint(void); + +#if !NO_MALLINFO +/* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. +*/ +#ifndef HAVE_USR_INCLUDE_MALLOC_H +#ifndef _MALLOC_H +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; +#endif /* _MALLOC_H */ +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ + +struct mallinfo dlmallinfo(void); +#endif /* NO_MALLINFO */ + +/* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use regular calloc and assign pointers into this + space to represent elements. (In this case though, you cannot + independently free elements.) + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } +*/ +void** dlindependent_calloc(size_t, size_t, void**); + +/* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use a single regular malloc, and assign pointers at + particular offsets in the aggregate space. (In this case though, you + cannot independently free elements.) + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. +*/ +void** dlindependent_comalloc(size_t, size_t*, void**); + + +/* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ +void* dlpvalloc(size_t); + +/* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. +*/ +int dlmalloc_trim(size_t); + +/* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); +*/ +size_t dlmalloc_usable_size(void*); + +/* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. +*/ +void dlmalloc_stats(void); + +#endif /* !ONLY_MSPACES */ + +#if MSPACES + +/* + mspace is an opaque type representing an independent + region of space that supports mspace_malloc, etc. +*/ +typedef void* mspace; + +void *global_mspace(); +void *determine_mspace(void *mem); + +/* + create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). +*/ +mspace create_mspace(size_t capacity, int locked); + +/* + destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. +*/ +size_t destroy_mspace(mspace msp); + +/* + create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. +*/ +mspace create_mspace_with_base(void* base, size_t capacity, int locked); + +/* + mspace_malloc behaves as malloc, but operates within + the given space. +*/ +void* mspace_malloc(mspace msp, size_t bytes); + +/* + mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_free is not actually needed. + free may be called instead of mspace_free because freed chunks from + any space are handled by their originating spaces. +*/ +void mspace_free(mspace msp, void* mem); + +/* + mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_realloc is not actually + needed. realloc may be called instead of mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. +*/ +void* mspace_realloc(mspace msp, void* mem, size_t newsize); + +/* + mspace_calloc behaves as calloc, but operates within + the given space. +*/ +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + +/* + mspace_memalign behaves as memalign, but operates within + the given space. +*/ +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes); + +/* + mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. +*/ +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + +/* + mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. +*/ +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + +/* + mspace_footprint() returns the number of bytes obtained from the + system for this space. +*/ +size_t mspace_footprint(mspace msp); + +/* + mspace_max_footprint() returns the peak number of bytes obtained from the + system for this space. +*/ +size_t mspace_max_footprint(mspace msp); + + +#if !NO_MALLINFO +/* + mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. +*/ +struct mallinfo mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + +/* + mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. +*/ +void mspace_malloc_stats(mspace msp); + +/* + mspace_trim behaves as malloc_trim, but + operates within the given space. +*/ +int mspace_trim(mspace msp, size_t pad); + +/* + An alias for malloc_usable_size. +*/ +size_t mspace_usable_size(void *mem); + +/* + An alias for mallopt. +*/ +int mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif /* MALLOC_280_H */ diff --git a/tier0/dlmalloc/malloc.cpp b/tier0/dlmalloc/malloc.cpp new file mode 100644 index 0000000..2187aa0 --- /dev/null +++ b/tier0/dlmalloc/malloc.cpp @@ -0,0 +1,6307 @@ +/* +Copyright 2023 Doug Lea + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +* Version 2.8.6 Wed Aug 29 06:57:58 2012 Doug Lea + Re-licensed 25 Sep 2023 with MIT-0 replacing obsolete CC0 + See https://opensource.org/license/mit-0/ + +* Quickstart + + This library is all in one file to simplify the most common usage: + ftp it, compile it (-O3), and link it into another program. All of + the compile-time options default to reasonable values for use on + most platforms. You might later want to step through various + compile-time and dynamic tuning options. + + For convenience, an include file for code using this malloc is at: + ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.6.h + You don't really need this .h file unless you call functions not + defined in your system include files. The .h file contains only the + excerpts from this file needed for using this malloc on ANSI C/C++ + systems, so long as you haven't changed compile-time options about + naming and tuning parameters. If you do, then you can create your + own malloc.h that does include all settings by cutting at the point + indicated below. Note that you may already by default be using a C + library containing a malloc that is based on some version of this + malloc (for example in linux). You might still want to use the one + in this file to customize settings or to avoid overheads associated + with library versions. + +* Vital statistics: + + Supported pointer/size_t representation: 4 or 8 bytes + size_t MUST be an unsigned type of the same width as + pointers. (If you are using an ancient system that declares + size_t as a signed type, or need it to be a different width + than pointers, you can use a previous release of this malloc + (e.g. 2.7.2) supporting these.) + + Alignment: 8 bytes (minimum) + This suffices for nearly all current machines and C compilers. + However, you can define MALLOC_ALIGNMENT to be wider than this + if necessary (up to 128bytes), at the expense of using more space. + + Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes) + 8 or 16 bytes (if 8byte sizes) + Each malloced chunk has a hidden word of overhead holding size + and status information, and additional cross-check word + if FOOTERS is defined. + + Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead) + 8-byte ptrs: 32 bytes (including overhead) + + Even a request for zero bytes (i.e., malloc(0)) returns a + pointer to something of the minimum allocatable size. + The maximum overhead wastage (i.e., number of extra bytes + allocated than were requested in malloc) is less than or equal + to the minimum size, except for requests >= mmap_threshold that + are serviced via mmap(), where the worst case wastage is about + 32 bytes plus the remainder from a system page (the minimal + mmap unit); typically 4096 or 8192 bytes. + + Security: static-safe; optionally more or less + The "security" of malloc refers to the ability of malicious + code to accentuate the effects of errors (for example, freeing + space that is not currently malloc'ed or overwriting past the + ends of chunks) in code that calls malloc. This malloc + guarantees not to modify any memory locations below the base of + heap, i.e., static variables, even in the presence of usage + errors. The routines additionally detect most improper frees + and reallocs. All this holds as long as the static bookkeeping + for malloc itself is not corrupted by some other means. This + is only one aspect of security -- these checks do not, and + cannot, detect all possible programming errors. + + If FOOTERS is defined nonzero, then each allocated chunk + carries an additional check word to verify that it was malloced + from its space. These check words are the same within each + execution of a program using malloc, but differ across + executions, so externally crafted fake chunks cannot be + freed. This improves security by rejecting frees/reallocs that + could corrupt heap memory, in addition to the checks preventing + writes to statics that are always on. This may further improve + security at the expense of time and space overhead. (Note that + FOOTERS may also be worth using with MSPACES.) + + By default detected errors cause the program to abort (calling + "abort()"). You can override this to instead proceed past + errors by defining PROCEED_ON_ERROR. In this case, a bad free + has no effect, and a malloc that encounters a bad address + caused by user overwrites will ignore the bad address by + dropping pointers and indices to all known memory. This may + be appropriate for programs that should continue if at all + possible in the face of programming errors, although they may + run out of memory because dropped memory is never reclaimed. + + If you don't like either of these options, you can define + CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything + else. And if if you are sure that your program using malloc has + no errors or vulnerabilities, you can define INSECURE to 1, + which might (or might not) provide a small performance improvement. + + It is also possible to limit the maximum total allocatable + space, using malloc_set_footprint_limit. This is not + designed as a security feature in itself (calls to set limits + are not screened or privileged), but may be useful as one + aspect of a secure implementation. + + Thread-safety: NOT thread-safe unless USE_LOCKS defined non-zero + When USE_LOCKS is defined, each public call to malloc, free, + etc is surrounded with a lock. By default, this uses a plain + pthread mutex, win32 critical section, or a spin-lock if if + available for the platform and not disabled by setting + USE_SPIN_LOCKS=0. However, if USE_RECURSIVE_LOCKS is defined, + recursive versions are used instead (which are not required for + base functionality but may be needed in layered extensions). + Using a global lock is not especially fast, and can be a major + bottleneck. It is designed only to provide minimal protection + in concurrent environments, and to provide a basis for + extensions. If you are using malloc in a concurrent program, + consider instead using nedmalloc + (http://www.nedprod.com/programs/portable/nedmalloc/) or + ptmalloc (See http://www.malloc.de), which are derived from + versions of this malloc. + + System requirements: Any combination of MORECORE and/or MMAP/MUNMAP + This malloc can use unix sbrk or any emulation (invoked using + the CALL_MORECORE macro) and/or mmap/munmap or any emulation + (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system + memory. On most unix systems, it tends to work best if both + MORECORE and MMAP are enabled. On Win32, it uses emulations + based on VirtualAlloc. It also uses common C library functions + like memset. + + Compliance: I believe it is compliant with the Single Unix Specification + (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably + others as well. + +* Overview of algorithms + + This is not the fastest, most space-conserving, most portable, or + most tunable malloc ever written. However it is among the fastest + while also being among the most space-conserving, portable and + tunable. Consistent balance across these factors results in a good + general-purpose allocator for malloc-intensive programs. + + In most ways, this malloc is a best-fit allocator. Generally, it + chooses the best-fitting existing chunk for a request, with ties + broken in approximately least-recently-used order. (This strategy + normally maintains low fragmentation.) However, for requests less + than 256bytes, it deviates from best-fit when there is not an + exactly fitting available chunk by preferring to use space adjacent + to that used for the previous small request, as well as by breaking + ties in approximately most-recently-used order. (These enhance + locality of series of small allocations.) And for very large requests + (>= 256Kb by default), it relies on system memory mapping + facilities, if supported. (This helps avoid carrying around and + possibly fragmenting memory used only for large chunks.) + + All operations (except malloc_stats and mallinfo) have execution + times that are bounded by a constant factor of the number of bits in + a size_t, not counting any clearing in calloc or copying in realloc, + or actions surrounding MORECORE and MMAP that have times + proportional to the number of non-contiguous regions returned by + system allocation routines, which is often just 1. In real-time + applications, you can optionally suppress segment traversals using + NO_SEGMENT_TRAVERSAL, which assures bounded execution even when + system allocators return non-contiguous spaces, at the typical + expense of carrying around more memory and increased fragmentation. + + The implementation is not very modular and seriously overuses + macros. Perhaps someday all C compilers will do as good a job + inlining modular code as can now be done by brute-force expansion, + but now, enough of them seem not to. + + Some compilers issue a lot of warnings about code that is + dead/unreachable only on some platforms, and also about intentional + uses of negation on unsigned types. All known cases of each can be + ignored. + + For a longer but out of date high-level description, see + http://gee.cs.oswego.edu/dl/html/malloc.html + +* MSPACES + If MSPACES is defined, then in addition to malloc, free, etc., + this file also defines mspace_malloc, mspace_free, etc. These + are versions of malloc routines that take an "mspace" argument + obtained using create_mspace, to control all internal bookkeeping. + If ONLY_MSPACES is defined, only these versions are compiled. + So if you would like to use this allocator for only some allocations, + and your system malloc for others, you can compile with + ONLY_MSPACES and then do something like... + static mspace mymspace = create_mspace(0,0); // for example + #define mymalloc(bytes) mspace_malloc(mymspace, bytes) + + (Note: If you only need one instance of an mspace, you can instead + use "USE_DL_PREFIX" to relabel the global malloc.) + + You can similarly create thread-local allocators by storing + mspaces as thread-locals. For example: + static __thread mspace tlms = 0; + void* tlmalloc(size_t bytes) { + if (tlms == 0) tlms = create_mspace(0, 0); + return mspace_malloc(tlms, bytes); + } + void tlfree(void* mem) { mspace_free(tlms, mem); } + + Unless FOOTERS is defined, each mspace is completely independent. + You cannot allocate from one and free to another (although + conformance is only weakly checked, so usage errors are not always + caught). If FOOTERS is defined, then each chunk carries around a tag + indicating its originating mspace, and frees are directed to their + originating spaces. Normally, this requires use of locks. + + ------------------------- Compile-time options --------------------------- + +Be careful in setting #define values for numerical constants of type +size_t. On some systems, literal values are not automatically extended +to size_t precision unless they are explicitly casted. You can also +use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below. + +WIN32 default: defined if _WIN32 defined + Defining WIN32 sets up defaults for MS environment and compilers. + Otherwise defaults are for unix. Beware that there seem to be some + cases where this malloc might not be a pure drop-in replacement for + Win32 malloc: Random-looking failures from Win32 GDI API's (eg; + SetDIBits()) may be due to bugs in some video driver implementations + when pixel buffers are malloc()ed, and the region spans more than + one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb) + default granularity, pixel buffers may straddle virtual allocation + regions more often than when using the Microsoft allocator. You can + avoid this by using VirtualAlloc() and VirtualFree() for all pixel + buffers rather than using malloc(). If this is not possible, + recompile this malloc with a larger DEFAULT_GRANULARITY. Note: + in cases where MSC and gcc (cygwin) are known to differ on WIN32, + conditions use _MSC_VER to distinguish them. + +DLMALLOC_EXPORT default: extern + Defines how public APIs are declared. If you want to export via a + Windows DLL, you might define this as + #define DLMALLOC_EXPORT extern __declspec(dllexport) + If you want a POSIX ELF shared object, you might use + #define DLMALLOC_EXPORT extern __attribute__((visibility("default"))) + +MALLOC_ALIGNMENT default: (size_t)(2 * sizeof(void *)) + Controls the minimum alignment for malloc'ed chunks. It must be a + power of two and at least 8, even on machines for which smaller + alignments would suffice. It may be defined as larger than this + though. Note however that code and data structures are optimized for + the case of 8-byte alignment. + +MSPACES default: 0 (false) + If true, compile in support for independent allocation spaces. + This is only supported if HAVE_MMAP is true. + +ONLY_MSPACES default: 0 (false) + If true, only compile in mspace versions, not regular versions. + +USE_LOCKS default: 0 (false) + Causes each call to each public routine to be surrounded with + pthread or WIN32 mutex lock/unlock. (If set true, this can be + overridden on a per-mspace basis for mspace versions.) If set to a + non-zero value other than 1, locks are used, but their + implementation is left out, so lock functions must be supplied manually, + as described below. + +USE_SPIN_LOCKS default: 1 iff USE_LOCKS and spin locks available + If true, uses custom spin locks for locking. This is currently + supported only gcc >= 4.1, older gccs on x86 platforms, and recent + MS compilers. Otherwise, posix locks or win32 critical sections are + used. + +USE_RECURSIVE_LOCKS default: not defined + If defined nonzero, uses recursive (aka reentrant) locks, otherwise + uses plain mutexes. This is not required for malloc proper, but may + be needed for layered allocators such as nedmalloc. + +LOCK_AT_FORK default: not defined + If defined nonzero, performs pthread_atfork upon initialization + to initialize child lock while holding parent lock. The implementation + assumes that pthread locks (not custom locks) are being used. In other + cases, you may need to customize the implementation. + +FOOTERS default: 0 + If true, provide extra checking and dispatching by placing + information in the footers of allocated chunks. This adds + space and time overhead. + +INSECURE default: 0 + If true, omit checks for usage errors and heap space overwrites. + +USE_DL_PREFIX default: NOT defined + Causes compiler to prefix all public routines with the string 'dl'. + This can be useful when you only want to use this malloc in one part + of a program, using your regular system malloc elsewhere. + +MALLOC_INSPECT_ALL default: NOT defined + If defined, compiles malloc_inspect_all and mspace_inspect_all, that + perform traversal of all heap space. Unless access to these + functions is otherwise restricted, you probably do not want to + include them in secure implementations. + +ABORT default: defined as abort() + Defines how to abort on failed checks. On most systems, a failed + check cannot die with an "assert" or even print an informative + message, because the underlying print routines in turn call malloc, + which will fail again. Generally, the best policy is to simply call + abort(). It's not very useful to do more than this because many + errors due to overwriting will show up as address faults (null, odd + addresses etc) rather than malloc-triggered checks, so will also + abort. Also, most compilers know that abort() does not return, so + can better optimize code conditionally calling it. + +PROCEED_ON_ERROR default: defined as 0 (false) + Controls whether detected bad addresses cause them to bypassed + rather than aborting. If set, detected bad arguments to free and + realloc are ignored. And all bookkeeping information is zeroed out + upon a detected overwrite of freed heap space, thus losing the + ability to ever return it from malloc again, but enabling the + application to proceed. If PROCEED_ON_ERROR is defined, the + static variable malloc_corruption_error_count is compiled in + and can be examined to see if errors have occurred. This option + generates slower code than the default abort policy. + +DEBUG default: NOT defined + The DEBUG setting is mainly intended for people trying to modify + this code or diagnose problems when porting to new platforms. + However, it may also be able to better isolate user errors than just + using runtime checks. The assertions in the check routines spell + out in more detail the assumptions and invariants underlying the + algorithms. The checking is fairly extensive, and will slow down + execution noticeably. Calling malloc_stats or mallinfo with DEBUG + set will attempt to check every non-mmapped allocated and free chunk + in the course of computing the summaries. + +ABORT_ON_ASSERT_FAILURE default: defined as 1 (true) + Debugging assertion failures can be nearly impossible if your + version of the assert macro causes malloc to be called, which will + lead to a cascade of further failures, blowing the runtime stack. + ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(), + which will usually make debugging easier. + +MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32 + The action to take before "return 0" when malloc fails to be able to + return memory because there is none available. + +HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES + True if this system supports sbrk or an emulation of it. + +MORECORE default: sbrk + The name of the sbrk-style system routine to call to obtain more + memory. See below for guidance on writing custom MORECORE + functions. The type of the argument to sbrk/MORECORE varies across + systems. It cannot be size_t, because it supports negative + arguments, so it is normally the signed type of the same width as + size_t (sometimes declared as "intptr_t"). It doesn't much matter + though. Internally, we only call it with arguments less than half + the max value of a size_t, which should work across all reasonable + possibilities, although sometimes generating compiler warnings. + +MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE + If true, take advantage of fact that consecutive calls to MORECORE + with positive arguments always return contiguous increasing + addresses. This is true of unix sbrk. It does not hurt too much to + set it true anyway, since malloc copes with non-contiguities. + Setting it false when definitely non-contiguous saves time + and possibly wasted space it would take to discover this though. + +MORECORE_CANNOT_TRIM default: NOT defined + True if MORECORE cannot release space back to the system when given + negative arguments. This is generally necessary only if you are + using a hand-crafted MORECORE function that cannot handle negative + arguments. + +NO_SEGMENT_TRAVERSAL default: 0 + If non-zero, suppresses traversals of memory segments + returned by either MORECORE or CALL_MMAP. This disables + merging of segments that are contiguous, and selectively + releasing them to the OS if unused, but bounds execution times. + +HAVE_MMAP default: 1 (true) + True if this system supports mmap or an emulation of it. If so, and + HAVE_MORECORE is not true, MMAP is used for all system + allocation. If set and HAVE_MORECORE is true as well, MMAP is + primarily used to directly allocate very large blocks. It is also + used as a backup strategy in cases where MORECORE fails to provide + space from system. Note: A single call to MUNMAP is assumed to be + able to unmap memory that may have be allocated using multiple calls + to MMAP, so long as they are adjacent. + +HAVE_MREMAP default: 1 on linux, else 0 + If true realloc() uses mremap() to re-allocate large blocks and + extend or shrink allocation spaces. + +MMAP_CLEARS default: 1 except on WINCE. + True if mmap clears memory so calloc doesn't need to. This is true + for standard unix mmap using /dev/zero and on WIN32 except for WINCE. + +USE_BUILTIN_FFS default: 0 (i.e., not used) + Causes malloc to use the builtin ffs() function to compute indices. + Some compilers may recognize and intrinsify ffs to be faster than the + supplied C version. Also, the case of x86 using gcc is special-cased + to an asm instruction, so is already as fast as it can be, and so + this setting has no effect. Similarly for Win32 under recent MS compilers. + (On most x86s, the asm version is only slightly faster than the C version.) + +malloc_getpagesize default: derive from system includes, or 4096. + The system page size. To the extent possible, this malloc manages + memory from the system in page-size units. This may be (and + usually is) a function rather than a constant. This is ignored + if WIN32, where page size is determined using getSystemInfo during + initialization. + +USE_DEV_RANDOM default: 0 (i.e., not used) + Causes malloc to use /dev/random to initialize secure magic seed for + stamping footers. Otherwise, the current time is used. + +NO_MALLINFO default: 0 + If defined, don't compile "mallinfo". This can be a simple way + of dealing with mismatches between system declarations and + those in this file. + +MALLINFO_FIELD_TYPE default: size_t + The type of the fields in the mallinfo struct. This was originally + defined as "int" in SVID etc, but is more usefully defined as + size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set + +NO_MALLOC_STATS default: 0 + If defined, don't compile "malloc_stats". This avoids calls to + fprintf and bringing in stdio dependencies you might not want. + +REALLOC_ZERO_BYTES_FREES default: not defined + This should be set if a call to realloc with zero bytes should + be the same as a call to free. Some people think it should. Otherwise, + since this malloc returns a unique pointer for malloc(0), so does + realloc(p, 0). + +LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H +LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H +LACKS_STDLIB_H LACKS_SCHED_H LACKS_TIME_H default: NOT defined unless on WIN32 + Define these if your system does not have these header files. + You might need to manually insert some of the declarations they provide. + +DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, + system_info.dwAllocationGranularity in WIN32, + otherwise 64K. + Also settable using mallopt(M_GRANULARITY, x) + The unit for allocating and deallocating memory from the system. On + most systems with contiguous MORECORE, there is no reason to + make this more than a page. However, systems with MMAP tend to + either require or encourage larger granularities. You can increase + this value to prevent system allocation functions to be called so + often, especially if they are slow. The value must be at least one + page and must be a power of two. Setting to 0 causes initialization + to either page size or win32 region size. (Note: In previous + versions of malloc, the equivalent of this option was called + "TOP_PAD") + +DEFAULT_TRIM_THRESHOLD default: 2MB + Also settable using mallopt(M_TRIM_THRESHOLD, x) + The maximum amount of unused top-most memory to keep before + releasing via malloc_trim in free(). Automatic trimming is mainly + useful in long-lived programs using contiguous MORECORE. Because + trimming via sbrk can be slow on some systems, and can sometimes be + wasteful (in cases where programs immediately afterward allocate + more large chunks) the value should be high enough so that your + overall system performance would improve by releasing this much + memory. As a rough guide, you might set to a value close to the + average size of a process (program) running on your system. + Releasing this much memory would allow such a process to run in + memory. Generally, it is worth tuning trim thresholds when a + program undergoes phases where several large chunks are allocated + and released in ways that can reuse each other's storage, perhaps + mixed with phases where there are no such chunks at all. The trim + value must be greater than page size to have any useful effect. To + disable trimming completely, you can set to MAX_SIZE_T. Note that the trick + some people use of mallocing a huge space and then freeing it at + program startup, in an attempt to reserve system memory, doesn't + have the intended effect under automatic trimming, since that memory + will immediately be returned to the system. + +DEFAULT_MMAP_THRESHOLD default: 256K + Also settable using mallopt(M_MMAP_THRESHOLD, x) + The request size threshold for using MMAP to directly service a + request. Requests of at least this size that cannot be allocated + using already-existing space will be serviced via mmap. (If enough + normal freed space already exists it is used instead.) Using mmap + segregates relatively large chunks of memory so that they can be + individually obtained and released from the host system. A request + serviced through mmap is never reused by any other request (at least + not directly; the system may just so happen to remap successive + requests to the same locations). Segregating space in this way has + the benefits that: Mmapped space can always be individually released + back to the system, which helps keep the system level memory demands + of a long-lived program low. Also, mapped memory doesn't become + `locked' between other chunks, as can happen with normally allocated + chunks, which means that even trimming via malloc_trim would not + release them. However, it has the disadvantage that the space + cannot be reclaimed, consolidated, and then used to service later + requests, as happens with normal chunks. The advantages of mmap + nearly always outweigh disadvantages for "large" chunks, but the + value of "large" may vary across systems. The default is an + empirically derived value that works well in most systems. You can + disable mmap by setting to MAX_SIZE_T. + +MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP + The number of consolidated frees between checks to release + unused segments when freeing. When using non-contiguous segments, + especially with multiple mspaces, checking only for topmost space + doesn't always suffice to trigger trimming. To compensate for this, + free() will, with a period of MAX_RELEASE_CHECK_RATE (or the + current number of segments, if greater) try to release unused + segments to the OS when freeing chunks that result in + consolidation. The best value for this parameter is a compromise + between slowing down frees with relatively costly checks that + rarely trigger versus holding on to unused memory. To effectively + disable, set to MAX_SIZE_T. This may lead to a very slight speed + improvement at the expense of carrying around more memory. +*/ + +//----------------------------------------------------------------------------- +// Valve specific defines [5/22/2009 tom] +#define USE_DL_PREFIX +#define USE_SPIN_LOCKS 2 +#define MALLOC_ALIGNMENT ((size_t)16U) +#define MSPACES 1 +#define FOOTERS 1 // for mspace retrieval support + +#include "tier0/threadtools.h" +#define ABORT \ + do { \ + DebuggerBreakIfDebugging(); \ + *(volatile int*)0xb = 0; \ + } while (0) +//----------------------------------------------------------------------------- + +/* Version identifier to allow people to support multiple versions */ +#ifndef DLMALLOC_VERSION +#define DLMALLOC_VERSION 20806 +#endif /* DLMALLOC_VERSION */ + +#ifndef DLMALLOC_EXPORT +#define DLMALLOC_EXPORT extern +#endif + +#ifndef WIN32 +#ifdef _WIN32 +#define WIN32 1 +#endif /* _WIN32 */ +#ifdef _WIN32_WCE +#define LACKS_FCNTL_H +#define WIN32 1 +#endif /* _WIN32_WCE */ +#endif /* WIN32 */ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#define HAVE_MMAP 1 +#define HAVE_MORECORE 0 +#define LACKS_UNISTD_H +#define LACKS_SYS_PARAM_H +#define LACKS_SYS_MMAN_H +#define LACKS_STRING_H +#define LACKS_STRINGS_H +#define LACKS_SYS_TYPES_H +#define LACKS_ERRNO_H +#define LACKS_SCHED_H +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef MMAP_CLEARS +#ifdef _WIN32_WCE /* WINCE reportedly does not clear */ +#define MMAP_CLEARS 0 +#else +#define MMAP_CLEARS 1 +#endif /* _WIN32_WCE */ +#endif /*MMAP_CLEARS */ +#endif /* WIN32 */ + +#if defined(DARWIN) || defined(_DARWIN) +/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */ +#ifndef HAVE_MORECORE +#define HAVE_MORECORE 0 +#define HAVE_MMAP 1 +/* OSX allocators provide 16 byte alignment */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)16U) +#endif +#endif /* HAVE_MORECORE */ +#endif /* DARWIN */ + +#ifndef LACKS_SYS_TYPES_H +#include /* For size_t */ +#endif /* LACKS_SYS_TYPES_H */ + +/* The maximum possible size_t value has all bits set */ +#define MAX_SIZE_T (~(size_t)0) + +#ifndef USE_LOCKS /* ensure true if spin or recursive locks set */ +#define USE_LOCKS ((defined(USE_SPIN_LOCKS) && USE_SPIN_LOCKS != 0) || \ + (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0)) +#endif /* USE_LOCKS */ + +#if USE_LOCKS /* Spin locks for gcc >= 4.1, older gcc on x86, MSC >= 1310 */ +#if ((defined(__GNUC__) && \ + ((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \ + defined(__i386__) || defined(__x86_64__))) || \ + (defined(_MSC_VER) && _MSC_VER>=1310)) +#ifndef USE_SPIN_LOCKS +#define USE_SPIN_LOCKS 1 +#endif /* USE_SPIN_LOCKS */ +#elif USE_SPIN_LOCKS +#error "USE_SPIN_LOCKS defined without implementation" +#endif /* ... locks available... */ +#elif !defined(USE_SPIN_LOCKS) +#define USE_SPIN_LOCKS 0 +#endif /* USE_LOCKS */ + +#ifndef ONLY_MSPACES +#define ONLY_MSPACES 0 +#endif /* ONLY_MSPACES */ +#ifndef MSPACES +#if ONLY_MSPACES +#define MSPACES 1 +#else /* ONLY_MSPACES */ +#define MSPACES 0 +#endif /* ONLY_MSPACES */ +#endif /* MSPACES */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)(2 * sizeof(void *))) +#endif /* MALLOC_ALIGNMENT */ +#ifndef FOOTERS +#define FOOTERS 0 +#endif /* FOOTERS */ +#ifndef ABORT +#define ABORT abort() +#endif /* ABORT */ +#ifndef ABORT_ON_ASSERT_FAILURE +#define ABORT_ON_ASSERT_FAILURE 1 +#endif /* ABORT_ON_ASSERT_FAILURE */ +#ifndef PROCEED_ON_ERROR +#define PROCEED_ON_ERROR 0 +#endif /* PROCEED_ON_ERROR */ + +#ifndef INSECURE +#define INSECURE 0 +#endif /* INSECURE */ +#ifndef MALLOC_INSPECT_ALL +#define MALLOC_INSPECT_ALL 0 +#endif /* MALLOC_INSPECT_ALL */ +#ifndef HAVE_MMAP +#define HAVE_MMAP 1 +#endif /* HAVE_MMAP */ +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 1 +#endif /* MMAP_CLEARS */ +#ifndef HAVE_MREMAP +#ifdef linux +#define HAVE_MREMAP 1 +#define _GNU_SOURCE /* Turns on mremap() definition */ +#else /* linux */ +#define HAVE_MREMAP 0 +#endif /* linux */ +#endif /* HAVE_MREMAP */ +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION errno = ENOMEM; +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef HAVE_MORECORE +#if ONLY_MSPACES +#define HAVE_MORECORE 0 +#else /* ONLY_MSPACES */ +#define HAVE_MORECORE 1 +#endif /* ONLY_MSPACES */ +#endif /* HAVE_MORECORE */ +#if !HAVE_MORECORE +#define MORECORE_CONTIGUOUS 0 +#else /* !HAVE_MORECORE */ +#define MORECORE_DEFAULT sbrk +#ifndef MORECORE_CONTIGUOUS +#define MORECORE_CONTIGUOUS 1 +#endif /* MORECORE_CONTIGUOUS */ +#endif /* HAVE_MORECORE */ +#ifndef DEFAULT_GRANULARITY +#if (MORECORE_CONTIGUOUS || defined(WIN32)) +#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ +#else /* MORECORE_CONTIGUOUS */ +#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) +#endif /* MORECORE_CONTIGUOUS */ +#endif /* DEFAULT_GRANULARITY */ +#ifndef DEFAULT_TRIM_THRESHOLD +#ifndef MORECORE_CANNOT_TRIM +#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) +#else /* MORECORE_CANNOT_TRIM */ +#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T +#endif /* MORECORE_CANNOT_TRIM */ +#endif /* DEFAULT_TRIM_THRESHOLD */ +#ifndef DEFAULT_MMAP_THRESHOLD +#if HAVE_MMAP +#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U) +#else /* HAVE_MMAP */ +#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* DEFAULT_MMAP_THRESHOLD */ +#ifndef MAX_RELEASE_CHECK_RATE +#if HAVE_MMAP +#define MAX_RELEASE_CHECK_RATE 4095 +#else +#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* MAX_RELEASE_CHECK_RATE */ +#ifndef USE_BUILTIN_FFS +#define USE_BUILTIN_FFS 0 +#endif /* USE_BUILTIN_FFS */ +#ifndef USE_DEV_RANDOM +#define USE_DEV_RANDOM 0 +#endif /* USE_DEV_RANDOM */ +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif /* NO_MALLINFO */ +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ +#ifndef NO_MALLOC_STATS +#define NO_MALLOC_STATS 0 +#endif /* NO_MALLOC_STATS */ +#ifndef NO_SEGMENT_TRAVERSAL +#define NO_SEGMENT_TRAVERSAL 0 +#endif /* NO_SEGMENT_TRAVERSAL */ + +/* + mallopt tuning options. SVID/XPG defines four standard parameter + numbers for mallopt, normally defined in malloc.h. None of these + are used in this malloc, so setting them has no effect. But this + malloc does support the following options. +*/ + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + +/* ------------------------ Mallinfo declarations ------------------------ */ + +#if !NO_MALLINFO +/* + This version of malloc supports the standard SVID/XPG mallinfo + routine that returns a struct containing usage properties and + statistics. It should work on any system that has a + /usr/include/malloc.h defining struct mallinfo. The main + declaration needed is the mallinfo struct that is returned (by-copy) + by mallinfo(). The malloinfo struct contains a bunch of fields that + are not even meaningful in this version of malloc. These fields are + are instead filled by mallinfo() with other numbers that might be of + interest. + + HAVE_USR_INCLUDE_MALLOC_H should be set if you have a + /usr/include/malloc.h file that includes a declaration of struct + mallinfo. If so, it is included; else a compliant version is + declared below. These must be precisely the same for mallinfo() to + work. The original SVID version of this struct, defined on most + systems with mallinfo, declares all fields as ints. But some others + define as unsigned long. If your system defines the fields using a + type of different width than listed here, you MUST #include your + system version and #define HAVE_USR_INCLUDE_MALLOC_H. +*/ + +/* #define HAVE_USR_INCLUDE_MALLOC_H */ + +#ifdef HAVE_USR_INCLUDE_MALLOC_H +#include "/usr/include/malloc.h" +#else /* HAVE_USR_INCLUDE_MALLOC_H */ +#ifndef STRUCT_MALLINFO_DECLARED +/* HP-UX (and others?) redefines mallinfo unless _STRUCT_MALLINFO is defined */ +#define _STRUCT_MALLINFO +#define STRUCT_MALLINFO_DECLARED 1 +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; +#endif /* STRUCT_MALLINFO_DECLARED */ +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ +#endif /* NO_MALLINFO */ + +/* + Try to persuade compilers to inline. The most critical functions for + inlining are defined as macros, so these aren't used for them. +*/ + +#ifndef FORCEINLINE + #if defined(__GNUC__) +#define FORCEINLINE __inline __attribute__ ((always_inline)) + #elif defined(_MSC_VER) + #define FORCEINLINE __forceinline + #endif +#endif +#ifndef NOINLINE + #if defined(__GNUC__) + #define NOINLINE __attribute__ ((noinline)) + #elif defined(_MSC_VER) + #define NOINLINE __declspec(noinline) + #else + #define NOINLINE + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#ifndef FORCEINLINE + #define FORCEINLINE inline +#endif +#endif /* __cplusplus */ +#ifndef FORCEINLINE + #define FORCEINLINE +#endif + +#if !ONLY_MSPACES + +/* ------------------- Declarations of public routines ------------------- */ + +#ifndef USE_DL_PREFIX +#define dlcalloc calloc +#define dlfree free +#define dlmalloc malloc +#define dlmemalign memalign +#define dlposix_memalign posix_memalign +#define dlrealloc realloc +#define dlrealloc_in_place realloc_in_place +#define dlvalloc valloc +#define dlpvalloc pvalloc +#define dlmallinfo mallinfo +#define dlmallopt mallopt +#define dlmalloc_trim malloc_trim +#define dlmalloc_stats malloc_stats +#define dlmalloc_usable_size malloc_usable_size +#define dlmalloc_footprint malloc_footprint +#define dlmalloc_max_footprint malloc_max_footprint +#define dlmalloc_footprint_limit malloc_footprint_limit +#define dlmalloc_set_footprint_limit malloc_set_footprint_limit +#define dlmalloc_inspect_all malloc_inspect_all +#define dlindependent_calloc independent_calloc +#define dlindependent_comalloc independent_comalloc +#define dlbulk_free bulk_free +#endif /* USE_DL_PREFIX */ + +/* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. +*/ +DLMALLOC_EXPORT void* dlmalloc(size_t); + +/* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cause the current program to abort. +*/ +DLMALLOC_EXPORT void dlfree(void*); + +/* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. +*/ +DLMALLOC_EXPORT void* dlcalloc(size_t, size_t); + +/* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. +*/ +DLMALLOC_EXPORT void* dlrealloc(void*, size_t); + +/* + realloc_in_place(void* p, size_t n) + Resizes the space allocated for p to size n, only if this can be + done without moving p (i.e., only if there is adjacent space + available if n is greater than p's current allocated size, or n is + less than or equal to p's size). This may be used instead of plain + realloc if an alternative allocation strategy is needed upon failure + to expand space; for example, reallocation of a buffer that must be + memory-aligned or cleared. You can use realloc_in_place to trigger + these alternatives only when needed. + + Returns p if successful; otherwise null. +*/ +DLMALLOC_EXPORT void* dlrealloc_in_place(void*, size_t); + +/* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. +*/ +DLMALLOC_EXPORT void* dlmemalign(size_t, size_t); + +/* + int posix_memalign(void** pp, size_t alignment, size_t n); + Allocates a chunk of n bytes, aligned in accord with the alignment + argument. Differs from memalign only in that it (1) assigns the + allocated memory to *pp rather than returning it, (2) fails and + returns EINVAL if the alignment is not a power of two (3) fails and + returns ENOMEM if memory cannot be allocated. +*/ +DLMALLOC_EXPORT int dlposix_memalign(void**, size_t, size_t); + +/* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. +*/ +DLMALLOC_EXPORT void* dlvalloc(size_t); + +/* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. To workaround the fact that mallopt is specified to use int, + not size_t parameters, the value -1 is specially treated as the + maximum unsigned size_t value. + + SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt. See below for details. Briefly, supported + parameters are as follows (listed defaults are for "typical" + configurations). + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (-1 disables) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) +*/ +DLMALLOC_EXPORT int dlmallopt(int, int); + +/* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. +*/ +DLMALLOC_EXPORT size_t dlmalloc_footprint(void); + +/* + malloc_max_footprint(); + Returns the maximum number of bytes obtained from the system. This + value will be greater than current footprint if deallocated space + has been reclaimed by the system. The peak number of bytes allocated + by malloc, realloc etc., is less than this value. Unlike mallinfo, + this function returns only a precomputed result, so can be called + frequently to monitor memory consumption. Even if locks are + otherwise defined, this function does not use them, so results might + not be up to date. +*/ +DLMALLOC_EXPORT size_t dlmalloc_max_footprint(void); + +/* + malloc_footprint_limit(); + Returns the number of bytes that the heap is allowed to obtain from + the system, returning the last value returned by + malloc_set_footprint_limit, or the maximum size_t value if + never set. The returned value reflects a permission. There is no + guarantee that this number of bytes can actually be obtained from + the system. +*/ +DLMALLOC_EXPORT size_t dlmalloc_footprint_limit(); + +/* + malloc_set_footprint_limit(); + Sets the maximum number of bytes to obtain from the system, causing + failure returns from malloc and related functions upon attempts to + exceed this value. The argument value may be subject to page + rounding to an enforceable limit; this actual value is returned. + Using an argument of the maximum possible size_t effectively + disables checks. If the argument is less than or equal to the + current malloc_footprint, then all future allocations that require + additional system memory will fail. However, invocation cannot + retroactively deallocate existing used memory. +*/ +DLMALLOC_EXPORT size_t dlmalloc_set_footprint_limit(size_t bytes); + +#if MALLOC_INSPECT_ALL +/* + malloc_inspect_all(void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg); + Traverses the heap and calls the given handler for each managed + region, skipping all bytes that are (or may be) used for bookkeeping + purposes. Traversal does not include include chunks that have been + directly memory mapped. Each reported region begins at the start + address, and continues up to but not including the end address. The + first used_bytes of the region contain allocated data. If + used_bytes is zero, the region is unallocated. The handler is + invoked with the given callback argument. If locks are defined, they + are held during the entire traversal. It is a bad idea to invoke + other malloc functions from within the handler. + + For example, to count the number of in-use chunks with size greater + than 1000, you could write: + static int count = 0; + void count_chunks(void* start, void* end, size_t used, void* arg) { + if (used >= 1000) ++count; + } + then: + malloc_inspect_all(count_chunks, NULL); + + malloc_inspect_all is compiled only if MALLOC_INSPECT_ALL is defined. +*/ +DLMALLOC_EXPORT void dlmalloc_inspect_all(void(*handler)(void*, void *, size_t, void*), + void* arg); + +#endif /* MALLOC_INSPECT_ALL */ + +#if !NO_MALLINFO +/* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. +*/ +DLMALLOC_EXPORT struct mallinfo dlmallinfo(void); +#endif /* NO_MALLINFO */ + +/* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be freed when it is no longer needed. This can be + done all at once using bulk_free. + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } +*/ +DLMALLOC_EXPORT void** dlindependent_calloc(size_t, size_t, void**); + +/* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be freed when it is no longer needed. This can be + done all at once using bulk_free. + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. +*/ +DLMALLOC_EXPORT void** dlindependent_comalloc(size_t, size_t*, void**); + +/* + bulk_free(void* array[], size_t n_elements) + Frees and clears (sets to null) each non-null pointer in the given + array. This is likely to be faster than freeing them one-by-one. + If footers are used, pointers that have been allocated in different + mspaces are not freed or cleared, and the count of all such pointers + is returned. For large arrays of pointers with poor locality, it + may be worthwhile to sort this array before calling bulk_free. +*/ +DLMALLOC_EXPORT size_t dlbulk_free(void**, size_t n_elements); + +/* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ +DLMALLOC_EXPORT void* dlpvalloc(size_t); + +/* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. +*/ +DLMALLOC_EXPORT int dlmalloc_trim(size_t); + +/* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. +*/ +DLMALLOC_EXPORT void dlmalloc_stats(void); + +/* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); +*/ +size_t dlmalloc_usable_size(void*); + +#endif /* ONLY_MSPACES */ + +#if MSPACES + +/* + mspace is an opaque type representing an independent + region of space that supports mspace_malloc, etc. +*/ +typedef void* mspace; + +/* + create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). +*/ +DLMALLOC_EXPORT mspace create_mspace(size_t capacity, int locked); + +/* + destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. +*/ +DLMALLOC_EXPORT size_t destroy_mspace(mspace msp); + +/* + create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. +*/ +DLMALLOC_EXPORT mspace create_mspace_with_base(void* base, size_t capacity, int locked); + +/* + mspace_track_large_chunks controls whether requests for large chunks + are allocated in their own untracked mmapped regions, separate from + others in this mspace. By default large chunks are not tracked, + which reduces fragmentation. However, such chunks are not + necessarily released to the system upon destroy_mspace. Enabling + tracking by setting to true may increase fragmentation, but avoids + leakage when relying on destroy_mspace to release all memory + allocated using this space. The function returns the previous + setting. +*/ +DLMALLOC_EXPORT int mspace_track_large_chunks(mspace msp, int enable); + + +/* + mspace_malloc behaves as malloc, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_malloc(mspace msp, size_t bytes); + +/* + mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_free is not actually needed. + free may be called instead of mspace_free because freed chunks from + any space are handled by their originating spaces. +*/ +DLMALLOC_EXPORT void mspace_free(mspace msp, void* mem); + +/* + mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_realloc is not actually + needed. realloc may be called instead of mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. +*/ +DLMALLOC_EXPORT void* mspace_realloc(mspace msp, void* mem, size_t newsize); + +/* + mspace_calloc behaves as calloc, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + +/* + mspace_memalign behaves as memalign, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_memalign(mspace msp, size_t alignment, size_t bytes); + +/* + mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. +*/ +DLMALLOC_EXPORT void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + +/* + mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. +*/ +DLMALLOC_EXPORT void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + +/* + mspace_footprint() returns the number of bytes obtained from the + system for this space. +*/ +DLMALLOC_EXPORT size_t mspace_footprint(mspace msp); + +/* + mspace_max_footprint() returns the peak number of bytes obtained from the + system for this space. +*/ +DLMALLOC_EXPORT size_t mspace_max_footprint(mspace msp); + + +#if !NO_MALLINFO +/* + mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. +*/ +DLMALLOC_EXPORT struct mallinfo mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + +/* + malloc_usable_size(void* p) behaves the same as malloc_usable_size; +*/ +DLMALLOC_EXPORT size_t mspace_usable_size(const void* mem); + +/* + mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. +*/ +DLMALLOC_EXPORT void mspace_malloc_stats(mspace msp); + +/* + mspace_trim behaves as malloc_trim, but + operates within the given space. +*/ +DLMALLOC_EXPORT int mspace_trim(mspace msp, size_t pad); + +/* + An alias for mallopt. +*/ +DLMALLOC_EXPORT int mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif /* __cplusplus */ + +/* + ======================================================================== + To make a fully customizable malloc.h header file, cut everything + above this line, put into file malloc.h, edit to suit, and #include it + on the next line, as well as in programs that use this malloc. + ======================================================================== +*/ + +/* #include "malloc.h" */ + +/*------------------------------ internal #includes ---------------------- */ + +#ifdef _MSC_VER +#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ +#endif /* _MSC_VER */ +#if !NO_MALLOC_STATS +#include /* for printing in malloc_stats */ +#endif /* NO_MALLOC_STATS */ +#ifndef LACKS_ERRNO_H +#include /* for MALLOC_FAILURE_ACTION */ +#endif /* LACKS_ERRNO_H */ +#ifdef DEBUG +#if ABORT_ON_ASSERT_FAILURE +#undef assert +#define assert(x) if(!(x)) ABORT +#else /* ABORT_ON_ASSERT_FAILURE */ +#include +#endif /* ABORT_ON_ASSERT_FAILURE */ +#else /* DEBUG */ +#ifndef assert +#define assert(x) +#endif +#define DEBUG 0 +#endif /* DEBUG */ +#if !defined(WIN32) && !defined(LACKS_TIME_H) +#include /* for magic initialization */ +#endif /* WIN32 */ +#ifndef LACKS_STDLIB_H +#include /* for abort() */ +#endif /* LACKS_STDLIB_H */ +#ifndef LACKS_STRING_H +#include /* for memset etc */ +#endif /* LACKS_STRING_H */ +#if USE_BUILTIN_FFS +#ifndef LACKS_STRINGS_H +#include /* for ffs */ +#endif /* LACKS_STRINGS_H */ +#endif /* USE_BUILTIN_FFS */ +#if HAVE_MMAP +#ifndef LACKS_SYS_MMAN_H +/* On some versions of linux, mremap decl in mman.h needs __USE_GNU set */ +#if (defined(linux) && !defined(__USE_GNU)) +#define __USE_GNU 1 +#include /* for mmap */ +#undef __USE_GNU +#else +#include /* for mmap */ +#endif /* linux */ +#endif /* LACKS_SYS_MMAN_H */ +#ifndef LACKS_FCNTL_H +#include +#endif /* LACKS_FCNTL_H */ +#endif /* HAVE_MMAP */ +#ifndef LACKS_UNISTD_H +#include /* for sbrk, sysconf */ +#else /* LACKS_UNISTD_H */ +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +extern void* sbrk(ptrdiff_t); +#endif /* FreeBSD etc */ +#endif /* LACKS_UNISTD_H */ + +/* Declarations for locking */ +#if USE_LOCKS +#ifndef WIN32 +#if defined (__SVR4) && defined (__sun) /* solaris */ +#include +#elif !defined(LACKS_SCHED_H) +#include +#endif /* solaris or LACKS_SCHED_H */ +#if (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0) || !USE_SPIN_LOCKS +#include +#endif /* USE_RECURSIVE_LOCKS ... */ +#elif defined(_MSC_VER) +#ifndef _M_AMD64 +/* These are already defined on AMD64 builds */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp); +LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _M_AMD64 */ +#pragma intrinsic (_InterlockedCompareExchange) +#pragma intrinsic (_InterlockedExchange) +#define interlockedcompareexchange _InterlockedCompareExchange +#define interlockedexchange _InterlockedExchange +#elif defined(WIN32) && defined(__GNUC__) +#define interlockedcompareexchange(a, b, c) __sync_val_compare_and_swap(a, c, b) +#define interlockedexchange __sync_lock_test_and_set +#endif /* Win32 */ +#else /* USE_LOCKS */ +#endif /* USE_LOCKS */ + +#ifndef LOCK_AT_FORK +#define LOCK_AT_FORK 0 +#endif + +/* Declarations for bit scanning on win32 */ +#if defined(_MSC_VER) && _MSC_VER>=1300 +#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +unsigned char _BitScanForward(unsigned long *index, unsigned long mask); +unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define BitScanForward _BitScanForward +#define BitScanReverse _BitScanReverse +#pragma intrinsic(_BitScanForward) +#pragma intrinsic(_BitScanReverse) +#endif /* BitScanForward */ +#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */ + +#ifndef WIN32 +#ifndef malloc_getpagesize +# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ +# ifndef _SC_PAGE_SIZE +# define _SC_PAGE_SIZE _SC_PAGESIZE +# endif +# endif +# ifdef _SC_PAGE_SIZE +# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) +# else +# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) + extern size_t getpagesize(); +# define malloc_getpagesize getpagesize() +# else +# ifdef WIN32 /* use supplied emulation of getpagesize */ +# define malloc_getpagesize getpagesize() +# else +# ifndef LACKS_SYS_PARAM_H +# include +# endif +# ifdef EXEC_PAGESIZE +# define malloc_getpagesize EXEC_PAGESIZE +# else +# ifdef NBPG +# ifndef CLSIZE +# define malloc_getpagesize NBPG +# else +# define malloc_getpagesize (NBPG * CLSIZE) +# endif +# else +# ifdef NBPC +# define malloc_getpagesize NBPC +# else +# ifdef PAGESIZE +# define malloc_getpagesize PAGESIZE +# else /* just guess */ +# define malloc_getpagesize ((size_t)4096U) +# endif +# endif +# endif +# endif +# endif +# endif +# endif +#endif +#endif + +/* ------------------- size_t and alignment properties -------------------- */ + +/* The byte and bit size of a size_t */ +#define SIZE_T_SIZE (sizeof(size_t)) +#define SIZE_T_BITSIZE (sizeof(size_t) << 3) + +/* Some constants coerced to size_t */ +/* Annoying but necessary to avoid errors on some platforms */ +#define SIZE_T_ZERO ((size_t)0) +#define SIZE_T_ONE ((size_t)1) +#define SIZE_T_TWO ((size_t)2) +#define SIZE_T_FOUR ((size_t)4) +#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1) +#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2) +#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES) +#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) + +/* The bit mask value corresponding to MALLOC_ALIGNMENT */ +#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) + +/* True if address a has acceptable alignment */ +#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0) + +/* the number of bytes to offset an address to align it */ +#define align_offset(A)\ + ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\ + ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK)) + +/* -------------------------- MMAP preliminaries ------------------------- */ + +/* + If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and + checks to fail so compiler optimizer can delete code rather than + using so many "#if"s. +*/ + + +/* MORECORE and MMAP must return MFAIL on failure */ +#define MFAIL ((void*)(MAX_SIZE_T)) +#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ + +#if HAVE_MMAP + +#ifndef WIN32 +#define MUNMAP_DEFAULT(a, s) munmap((a), (s)) +#define MMAP_PROT (PROT_READ|PROT_WRITE) +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +#define MAP_ANONYMOUS MAP_ANON +#endif /* MAP_ANON */ +#ifdef MAP_ANONYMOUS +#define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) +#define MMAP_DEFAULT(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0) +#else /* MAP_ANONYMOUS */ +/* + Nearly all versions of mmap support MAP_ANONYMOUS, so the following + is unlikely to be needed, but is supplied just in case. +*/ +#define MMAP_FLAGS (MAP_PRIVATE) +static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ +#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \ + (dev_zero_fd = open("/dev/zero", O_RDWR), \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) +#endif /* MAP_ANONYMOUS */ + +#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s) + +#else /* WIN32 */ + +/* Win32 MMAP via VirtualAlloc */ +static FORCEINLINE void* win32mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ +static FORCEINLINE void* win32direct_mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, + PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* This function supports releasing coalesed segments */ +static FORCEINLINE int win32munmap(void* ptr, size_t size) { + MEMORY_BASIC_INFORMATION minfo; + char* cptr = (char*)ptr; + while (size) { + if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) + return -1; + if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || + minfo.State != MEM_COMMIT || minfo.RegionSize > size) + return -1; + if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) + return -1; + cptr += minfo.RegionSize; + size -= minfo.RegionSize; + } + return 0; +} + +#define MMAP_DEFAULT(s) win32mmap(s) +#define MUNMAP_DEFAULT(a, s) win32munmap((a), (s)) +#define DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s) +#endif /* WIN32 */ +#endif /* HAVE_MMAP */ + +#if HAVE_MREMAP +#ifndef WIN32 +#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) +#endif /* WIN32 */ +#endif /* HAVE_MREMAP */ + +/** + * Define CALL_MORECORE + */ +#if HAVE_MORECORE + #ifdef MORECORE + #define CALL_MORECORE(S) MORECORE(S) + #else /* MORECORE */ + #define CALL_MORECORE(S) MORECORE_DEFAULT(S) + #endif /* MORECORE */ +#else /* HAVE_MORECORE */ + #define CALL_MORECORE(S) MFAIL +#endif /* HAVE_MORECORE */ + +/** + * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP + */ +#if HAVE_MMAP + #define USE_MMAP_BIT (SIZE_T_ONE) + + #ifdef MMAP + #define CALL_MMAP(s) MMAP(s) + #else /* MMAP */ + #define CALL_MMAP(s) MMAP_DEFAULT(s) + #endif /* MMAP */ + #ifdef MUNMAP + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) + #else /* MUNMAP */ + #define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s)) + #endif /* MUNMAP */ + #ifdef DIRECT_MMAP + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #else /* DIRECT_MMAP */ + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s) + #endif /* DIRECT_MMAP */ +#else /* HAVE_MMAP */ + #define USE_MMAP_BIT (SIZE_T_ZERO) + + #define MMAP(s) MFAIL + #define MUNMAP(a, s) (-1) + #define DIRECT_MMAP(s) MFAIL + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #define CALL_MMAP(s) MMAP(s) + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) +#endif /* HAVE_MMAP */ + +/** + * Define CALL_MREMAP + */ +#if HAVE_MMAP && HAVE_MREMAP + #ifdef MREMAP + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv)) + #else /* MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv)) + #endif /* MREMAP */ +#else /* HAVE_MMAP && HAVE_MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MFAIL +#endif /* HAVE_MMAP && HAVE_MREMAP */ + +/* mstate bit set if continguous morecore disabled or failed */ +#define USE_NONCONTIGUOUS_BIT (4U) + +/* segment bit set in create_mspace_with_base */ +#define EXTERN_BIT (8U) + + +/* --------------------------- Lock preliminaries ------------------------ */ + +/* + When locks are defined, there is one global lock, plus + one per-mspace lock. + + The global lock_ensures that mparams.magic and other unique + mparams values are initialized only once. It also protects + sequences of calls to MORECORE. In many cases sys_alloc requires + two calls, that should not be interleaved with calls by other + threads. This does not protect against direct calls to MORECORE + by other threads not using this lock, so there is still code to + cope the best we can on interference. + + Per-mspace locks surround calls to malloc, free, etc. + By default, locks are simple non-reentrant mutexes. + + Because lock-protected regions generally have bounded times, it is + OK to use the supplied simple spinlocks. Spinlocks are likely to + improve performance for lightly contended applications, but worsen + performance under heavy contention. + + If USE_LOCKS is > 1, the definitions of lock routines here are + bypassed, in which case you will need to define the type MLOCK_T, + and at least INITIAL_LOCK, DESTROY_LOCK, ACQUIRE_LOCK, RELEASE_LOCK + and TRY_LOCK. You must also declare a + static MLOCK_T malloc_global_mutex = { initialization values };. + +*/ + +#if !USE_LOCKS +#define USE_LOCK_BIT (0U) +#define INITIAL_LOCK(l) (0) +#define DESTROY_LOCK(l) (0) +#define ACQUIRE_MALLOC_GLOBAL_LOCK() +#define RELEASE_MALLOC_GLOBAL_LOCK() + +#else +#if USE_LOCKS > 1 +/* ----------------------- User-defined locks ------------------------ */ +/* Define your own lock implementation here */ +/* #define INITIAL_LOCK(lk) ... */ +/* #define DESTROY_LOCK(lk) ... */ +/* #define ACQUIRE_LOCK(lk) ... */ +/* #define RELEASE_LOCK(lk) ... */ +/* #define TRY_LOCK(lk) ... */ +/* static MLOCK_T malloc_global_mutex = ... */ + +#elif USE_SPIN_LOCKS + +/* First, define CAS_LOCK and CLEAR_LOCK on ints */ +/* Note CAS_LOCK defined to return 0 on success */ + +#if defined(__GNUC__)&& (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) +#define CAS_LOCK(sl) __sync_lock_test_and_set(sl, 1) +#define CLEAR_LOCK(sl) __sync_lock_release(sl) + +#elif (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) +/* Custom spin locks for older gcc on x86 */ +static FORCEINLINE int x86_cas_lock(int *sl) { + int ret; + int val = 1; + int cmp = 0; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (ret) + : "r" (val), "m" (*(sl)), "0"(cmp) + : "memory", "cc"); + return ret; +} + +static FORCEINLINE void x86_clear_lock(int* sl) { + assert(*sl != 0); + int prev = 0; + int ret; + __asm__ __volatile__ ("lock; xchgl %0, %1" + : "=r" (ret) + : "m" (*(sl)), "0"(prev) + : "memory"); +} + +#define CAS_LOCK(sl) x86_cas_lock(sl) +#define CLEAR_LOCK(sl) x86_clear_lock(sl) + +#else /* Win32 MSC */ +#define CAS_LOCK(sl) interlockedexchange(sl, (LONG)1) +#define CLEAR_LOCK(sl) interlockedexchange (sl, (LONG)0) + +#endif /* ... gcc spins locks ... */ + +/* How to yield for a spin lock */ +#define SPINS_PER_YIELD 63 +#if defined(_MSC_VER) +#define SLEEP_EX_DURATION 50 /* delay for yield/sleep */ +#define SPIN_LOCK_YIELD SleepEx(SLEEP_EX_DURATION, FALSE) +#elif defined (__SVR4) && defined (__sun) /* solaris */ +#define SPIN_LOCK_YIELD thr_yield(); +#elif !defined(LACKS_SCHED_H) +#define SPIN_LOCK_YIELD sched_yield(); +#else +#define SPIN_LOCK_YIELD +#endif /* ... yield ... */ + +#if !defined(USE_RECURSIVE_LOCKS) || USE_RECURSIVE_LOCKS == 0 +/* Plain spin locks use single word (embedded in malloc_states) */ +static int spin_acquire_lock(int *sl) { + int spins = 0; + while (*(volatile int *)sl != 0 || CAS_LOCK(sl)) { + if ((++spins & SPINS_PER_YIELD) == 0) { + SPIN_LOCK_YIELD; + } + } + return 0; +} + +#define MLOCK_T int +#define TRY_LOCK(sl) !CAS_LOCK(sl) +#define RELEASE_LOCK(sl) CLEAR_LOCK(sl) +#define ACQUIRE_LOCK(sl) (CAS_LOCK(sl)? spin_acquire_lock(sl) : 0) +#define INITIAL_LOCK(sl) (*sl = 0) +#define DESTROY_LOCK(sl) (0) +static MLOCK_T malloc_global_mutex = 0; + +#else /* USE_RECURSIVE_LOCKS */ +/* types for lock owners */ +#ifdef WIN32 +#define THREAD_ID_T DWORD +#define CURRENT_THREAD GetCurrentThreadId() +#define EQ_OWNER(X,Y) ((X) == (Y)) +#else +/* + Note: the following assume that pthread_t is a type that can be + initialized to (casted) zero. If this is not the case, you will need to + somehow redefine these or not use spin locks. +*/ +#define THREAD_ID_T pthread_t +#define CURRENT_THREAD pthread_self() +#define EQ_OWNER(X,Y) pthread_equal(X, Y) +#endif + +struct malloc_recursive_lock { + int sl; + unsigned int c; + THREAD_ID_T threadid; +}; + +#define MLOCK_T struct malloc_recursive_lock +static MLOCK_T malloc_global_mutex = { 0, 0, (THREAD_ID_T)0}; + +static FORCEINLINE void recursive_release_lock(MLOCK_T *lk) { + assert(lk->sl != 0); + if (--lk->c == 0) { + CLEAR_LOCK(&lk->sl); + } +} + +static FORCEINLINE int recursive_acquire_lock(MLOCK_T *lk) { + THREAD_ID_T mythreadid = CURRENT_THREAD; + int spins = 0; + for (;;) { + if (*((volatile int *)(&lk->sl)) == 0) { + if (!CAS_LOCK(&lk->sl)) { + lk->threadid = mythreadid; + lk->c = 1; + return 0; + } + } + else if (EQ_OWNER(lk->threadid, mythreadid)) { + ++lk->c; + return 0; + } + if ((++spins & SPINS_PER_YIELD) == 0) { + SPIN_LOCK_YIELD; + } + } +} + +static FORCEINLINE int recursive_try_lock(MLOCK_T *lk) { + THREAD_ID_T mythreadid = CURRENT_THREAD; + if (*((volatile int *)(&lk->sl)) == 0) { + if (!CAS_LOCK(&lk->sl)) { + lk->threadid = mythreadid; + lk->c = 1; + return 1; + } + } + else if (EQ_OWNER(lk->threadid, mythreadid)) { + ++lk->c; + return 1; + } + return 0; +} + +#define RELEASE_LOCK(lk) recursive_release_lock(lk) +#define TRY_LOCK(lk) recursive_try_lock(lk) +#define ACQUIRE_LOCK(lk) recursive_acquire_lock(lk) +#define INITIAL_LOCK(lk) ((lk)->threadid = (THREAD_ID_T)0, (lk)->sl = 0, (lk)->c = 0) +#define DESTROY_LOCK(lk) (0) +#endif /* USE_RECURSIVE_LOCKS */ + +#elif defined(WIN32) /* Win32 critical sections */ +#define MLOCK_T CRITICAL_SECTION +#define ACQUIRE_LOCK(lk) (EnterCriticalSection(lk), 0) +#define RELEASE_LOCK(lk) LeaveCriticalSection(lk) +#define TRY_LOCK(lk) TryEnterCriticalSection(lk) +#define INITIAL_LOCK(lk) (!InitializeCriticalSectionAndSpinCount((lk), 0x80000000|4000)) +#define DESTROY_LOCK(lk) (DeleteCriticalSection(lk), 0) +#define NEED_GLOBAL_LOCK_INIT + +static MLOCK_T malloc_global_mutex; +static volatile LONG malloc_global_mutex_status; + +/* Use spin loop to initialize global lock */ +static void init_malloc_global_mutex() { + for (;;) { + long stat = malloc_global_mutex_status; + if (stat > 0) + return; + /* transition to < 0 while initializing, then to > 0) */ + if (stat == 0 && + interlockedcompareexchange(&malloc_global_mutex_status, (LONG)-1, (LONG)0) == 0) { + InitializeCriticalSection(&malloc_global_mutex); + interlockedexchange(&malloc_global_mutex_status, (LONG)1); + return; + } + SleepEx(0, FALSE); + } +} + +#else /* pthreads-based locks */ +#define MLOCK_T pthread_mutex_t +#define ACQUIRE_LOCK(lk) pthread_mutex_lock(lk) +#define RELEASE_LOCK(lk) pthread_mutex_unlock(lk) +#define TRY_LOCK(lk) (!pthread_mutex_trylock(lk)) +#define INITIAL_LOCK(lk) pthread_init_lock(lk) +#define DESTROY_LOCK(lk) pthread_mutex_destroy(lk) + +#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0 && defined(linux) && !defined(PTHREAD_MUTEX_RECURSIVE) +/* Cope with old-style linux recursive lock initialization by adding */ +/* skipped internal declaration from pthread.h */ +extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr, + int __kind)); +#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP +#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y) +#endif /* USE_RECURSIVE_LOCKS ... */ + +static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int pthread_init_lock (MLOCK_T *lk) { + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) return 1; +#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0 + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1; +#endif + if (pthread_mutex_init(lk, &attr)) return 1; + if (pthread_mutexattr_destroy(&attr)) return 1; + return 0; +} + +#endif /* ... lock types ... */ + +/* Common code for all lock types */ +#define USE_LOCK_BIT (2U) + +#ifndef ACQUIRE_MALLOC_GLOBAL_LOCK +#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); +#endif + +#ifndef RELEASE_MALLOC_GLOBAL_LOCK +#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); +#endif + +#endif /* USE_LOCKS */ + +/* ----------------------- Chunk representations ------------------------ */ + +/* + (The following includes lightly edited explanations by Colin Plumb.) + + The malloc_chunk declaration below is misleading (but accurate and + necessary). It declares a "view" into memory allowing access to + necessary fields at known offsets from a given base. + + Chunks of memory are maintained using a `boundary tag' method as + originally described by Knuth. (See the paper by Paul Wilson + ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such + techniques.) Sizes of free chunks are stored both in the front of + each chunk and at the end. This makes consolidating fragmented + chunks into bigger chunks fast. The head fields also hold bits + representing whether chunks are free or in use. + + Here are some pictures to make it clearer. They are "exploded" to + show that the state of a chunk can be thought of as extending from + the high 31 bits of the head field of its header through the + prev_foot and PINUSE_BIT bit of the following chunk header. + + A chunk that's in use looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk (if P = 0) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 1| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + +- -+ + | | + +- -+ + | : + +- size - sizeof(size_t) available payload bytes -+ + : | + chunk-> +- -+ + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| + | Size of next chunk (may or may not be in use) | +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + And if it's free, it looks like this: + + chunk-> +- -+ + | User payload (must be in use, or we would have merged!) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 0| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Prev pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- size - sizeof(struct chunk) unused bytes -+ + : | + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| + | Size of next chunk (must be in use, or we would have merged)| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- User payload -+ + : | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| + +-+ + Note that since we always merge adjacent free chunks, the chunks + adjacent to a free chunk must be in use. + + Given a pointer to a chunk (which can be derived trivially from the + payload pointer) we can, in O(1) time, find out whether the adjacent + chunks are free, and if so, unlink them from the lists that they + are on and merge them with the current chunk. + + Chunks always begin on even word boundaries, so the mem portion + (which is returned to the user) is also on an even word boundary, and + thus at least double-word aligned. + + The P (PINUSE_BIT) bit, stored in the unused low-order bit of the + chunk size (which is always a multiple of two words), is an in-use + bit for the *previous* chunk. If that bit is *clear*, then the + word before the current chunk size contains the previous chunk + size, and can be used to find the front of the previous chunk. + The very first chunk allocated always has this bit set, preventing + access to non-existent (or non-owned) memory. If pinuse is set for + any given chunk, then you CANNOT determine the size of the + previous chunk, and might even get a memory addressing fault when + trying to do so. + + The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of + the chunk size redundantly records whether the current chunk is + inuse (unless the chunk is mmapped). This redundancy enables usage + checks within free and realloc, and reduces indirection when freeing + and consolidating chunks. + + Each freshly allocated chunk must have both cinuse and pinuse set. + That is, each allocated chunk borders either a previously allocated + and still in-use chunk, or the base of its memory arena. This is + ensured by making all allocations from the `lowest' part of any + found chunk. Further, no free chunk physically borders another one, + so each free chunk is known to be preceded and followed by either + inuse chunks or the ends of memory. + + Note that the `foot' of the current chunk is actually represented + as the prev_foot of the NEXT chunk. This makes it easier to + deal with alignments etc but can be very confusing when trying + to extend or adapt this code. + + The exceptions to all this are + + 1. The special chunk `top' is the top-most available chunk (i.e., + the one bordering the end of available memory). It is treated + specially. Top is never included in any bin, is used only if + no other chunk is available, and is released back to the + system if it is very large (see M_TRIM_THRESHOLD). In effect, + the top chunk is treated as larger (and thus less well + fitting) than any other available chunk. The top chunk + doesn't update its trailing size field since there is no next + contiguous chunk that would have to index off it. However, + space is still allocated for it (TOP_FOOT_SIZE) to enable + separation or merging when space is extended. + + 3. Chunks allocated via mmap, have both cinuse and pinuse bits + cleared in their head fields. Because they are allocated + one-by-one, each must carry its own prev_foot field, which is + also used to hold the offset this chunk has within its mmapped + region, which is needed to preserve alignment. Each mmapped + chunk is trailed by the first two fields of a fake next-chunk + for sake of usage checks. + +*/ + +struct malloc_chunk { + size_t prev_foot; /* Size of previous chunk (if free). */ + size_t head; /* Size and inuse bits. */ + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; +}; + +typedef struct malloc_chunk mchunk; +typedef struct malloc_chunk* mchunkptr; +typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */ +typedef unsigned int bindex_t; /* Described below */ +typedef unsigned int binmap_t; /* Described below */ +typedef unsigned int flag_t; /* The type of various bit flag sets */ + +/* ------------------- Chunks sizes and alignments ----------------------- */ + +#define MCHUNK_SIZE (sizeof(mchunk)) + +#if FOOTERS +#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +#else /* FOOTERS */ +#define CHUNK_OVERHEAD (SIZE_T_SIZE) +#endif /* FOOTERS */ + +/* MMapped chunks need a second word of overhead ... */ +#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +/* ... and additional padding for fake next-chunk at foot */ +#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES) + +/* The smallest size we can malloc is an aligned minimal chunk */ +#define MIN_CHUNK_SIZE\ + ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* conversion from malloc headers to user pointers, and back */ +#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES)) +#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES)) +/* chunk associated with aligned address A */ +#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +/* Bounds on request (not chunk) sizes. */ +#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2) +#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE) + +/* pad request bytes into a usable size */ +#define pad_request(req) \ + (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* pad request, checking for minimum (but not maximum) */ +#define request2size(req) \ + (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req)) + + +/* ------------------ Operations on head and foot fields ----------------- */ + +/* + The head field of a chunk is or'ed with PINUSE_BIT when previous + adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in + use, unless mmapped, in which case both bits are cleared. + + FLAG4_BIT is not used by this malloc, but might be useful in extensions. +*/ + +#define PINUSE_BIT (SIZE_T_ONE) +#define CINUSE_BIT (SIZE_T_TWO) +#define FLAG4_BIT (SIZE_T_FOUR) +#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT) +#define FLAG_BITS (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT) + +/* Head value for fenceposts */ +#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE) + +/* extraction of fields from head words */ +#define cinuse(p) ((p)->head & CINUSE_BIT) +#define pinuse(p) ((p)->head & PINUSE_BIT) +#define flag4inuse(p) ((p)->head & FLAG4_BIT) +#define is_inuse(p) (((p)->head & INUSE_BITS) != PINUSE_BIT) +#define is_mmapped(p) (((p)->head & INUSE_BITS) == 0) + +#define chunksize(p) ((p)->head & ~(FLAG_BITS)) + +#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) +#define set_flag4(p) ((p)->head |= FLAG4_BIT) +#define clear_flag4(p) ((p)->head &= ~FLAG4_BIT) + +/* Treat space at ptr +/- offset as a chunk */ +#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) +#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) + +/* Ptr to next or previous physical malloc_chunk. */ +#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS))) +#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) )) + +/* extract next chunk's pinuse bit */ +#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) + +/* Get/set size at footer */ +#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) +#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) + +/* Set size, pinuse bit, and foot */ +#define set_size_and_pinuse_of_free_chunk(p, s)\ + ((p)->head = (s|PINUSE_BIT), set_foot(p, s)) + +/* Set size, pinuse bit, foot, and clear next pinuse */ +#define set_free_with_pinuse(p, s, n)\ + (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) + +/* Get the internal overhead associated with chunk p */ +#define overhead_for(p)\ + (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) + +/* Return true if malloced space is not necessarily cleared */ +#if MMAP_CLEARS +#define calloc_must_clear(p) (!is_mmapped(p)) +#else /* MMAP_CLEARS */ +#define calloc_must_clear(p) (1) +#endif /* MMAP_CLEARS */ + +/* ---------------------- Overlaid data structures ----------------------- */ + +/* + When chunks are not in use, they are treated as nodes of either + lists or trees. + + "Small" chunks are stored in circular doubly-linked lists, and look + like this: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space (may be 0 bytes long) . + . . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Larger chunks are kept in a form of bitwise digital trees (aka + tries) keyed on chunksizes. Because malloc_tree_chunks are only for + free chunks greater than 256 bytes, their size doesn't impose any + constraints on user chunk sizes. Each node looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to left child (child[0]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to right child (child[1]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to parent | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | bin index of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Each tree holding treenodes is a tree of unique chunk sizes. Chunks + of the same size are arranged in a circularly-linked list, with only + the oldest chunk (the next to be used, in our FIFO ordering) + actually in the tree. (Tree members are distinguished by a non-null + parent pointer.) If a chunk with the same size an an existing node + is inserted, it is linked off the existing node using pointers that + work in the same way as fd/bk pointers of small chunks. + + Each tree contains a power of 2 sized range of chunk sizes (the + smallest is 0x100 <= x < 0x180), which is is divided in half at each + tree level, with the chunks in the smaller half of the range (0x100 + <= x < 0x140 for the top nose) in the left subtree and the larger + half (0x140 <= x < 0x180) in the right subtree. This is, of course, + done by inspecting individual bits. + + Using these rules, each node's left subtree contains all smaller + sizes than its right subtree. However, the node at the root of each + subtree has no particular ordering relationship to either. (The + dividing line between the subtree sizes is based on trie relation.) + If we remove the last chunk of a given size from the interior of the + tree, we need to replace it with a leaf node. The tree ordering + rules permit a node to be replaced by any leaf below it. + + The smallest chunk in a tree (a common operation in a best-fit + allocator) can be found by walking a path to the leftmost leaf in + the tree. Unlike a usual binary tree, where we follow left child + pointers until we reach a null, here we follow the right child + pointer any time the left one is null, until we reach a leaf with + both child pointers null. The smallest chunk in the tree will be + somewhere along that path. + + The worst case number of steps to add, find, or remove a node is + bounded by the number of bits differentiating chunks within + bins. Under current bin calculations, this ranges from 6 up to 21 + (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case + is of course much better. +*/ + +struct malloc_tree_chunk { + /* The first four fields must be compatible with malloc_chunk */ + size_t prev_foot; + size_t head; + struct malloc_tree_chunk* fd; + struct malloc_tree_chunk* bk; + + struct malloc_tree_chunk* child[2]; + struct malloc_tree_chunk* parent; + bindex_t index; +}; + +typedef struct malloc_tree_chunk tchunk; +typedef struct malloc_tree_chunk* tchunkptr; +typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */ + +/* A little helper macro for trees */ +#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1]) + +/* ----------------------------- Segments -------------------------------- */ + +/* + Each malloc space may include non-contiguous segments, held in a + list headed by an embedded malloc_segment record representing the + top-most space. Segments also include flags holding properties of + the space. Large chunks that are directly allocated by mmap are not + included in this list. They are instead independently created and + destroyed without otherwise keeping track of them. + + Segment management mainly comes into play for spaces allocated by + MMAP. Any call to MMAP might or might not return memory that is + adjacent to an existing segment. MORECORE normally contiguously + extends the current space, so this space is almost always adjacent, + which is simpler and faster to deal with. (This is why MORECORE is + used preferentially to MMAP when both are available -- see + sys_alloc.) When allocating using MMAP, we don't use any of the + hinting mechanisms (inconsistently) supported in various + implementations of unix mmap, or distinguish reserving from + committing memory. Instead, we just ask for space, and exploit + contiguity when we get it. It is probably possible to do + better than this on some systems, but no general scheme seems + to be significantly better. + + Management entails a simpler variant of the consolidation scheme + used for chunks to reduce fragmentation -- new adjacent memory is + normally prepended or appended to an existing segment. However, + there are limitations compared to chunk consolidation that mostly + reflect the fact that segment processing is relatively infrequent + (occurring only when getting memory from system) and that we + don't expect to have huge numbers of segments: + + * Segments are not indexed, so traversal requires linear scans. (It + would be possible to index these, but is not worth the extra + overhead and complexity for most programs on most platforms.) + * New segments are only appended to old ones when holding top-most + memory; if they cannot be prepended to others, they are held in + different segments. + + Except for the top-most segment of an mstate, each segment record + is kept at the tail of its segment. Segments are added by pushing + segment records onto the list headed by &mstate.seg for the + containing mstate. + + Segment flags control allocation/merge/deallocation policies: + * If EXTERN_BIT set, then we did not allocate this segment, + and so should not try to deallocate or merge with others. + (This currently holds only for the initial segment passed + into create_mspace_with_base.) + * If USE_MMAP_BIT set, the segment may be merged with + other surrounding mmapped segments and trimmed/de-allocated + using munmap. + * If neither bit is set, then the segment was obtained using + MORECORE so can be merged with surrounding MORECORE'd segments + and deallocated/trimmed using MORECORE with negative arguments. +*/ + +struct malloc_segment { + char* base; /* base address */ + size_t size; /* allocated size */ + struct malloc_segment* next; /* ptr to next segment */ + flag_t sflags; /* mmap and extern flag */ +}; + +#define is_mmapped_segment(S) ((S)->sflags & USE_MMAP_BIT) +#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT) + +typedef struct malloc_segment msegment; +typedef struct malloc_segment* msegmentptr; + +/* ---------------------------- malloc_state ----------------------------- */ + +/* + A malloc_state holds all of the bookkeeping for a space. + The main fields are: + + Top + The topmost chunk of the currently active segment. Its size is + cached in topsize. The actual size of topmost space is + topsize+TOP_FOOT_SIZE, which includes space reserved for adding + fenceposts and segment records if necessary when getting more + space from the system. The size at which to autotrim top is + cached from mparams in trim_check, except that it is disabled if + an autotrim fails. + + Designated victim (dv) + This is the preferred chunk for servicing small requests that + don't have exact fits. It is normally the chunk split off most + recently to service another small request. Its size is cached in + dvsize. The link fields of this chunk are not maintained since it + is not kept in a bin. + + SmallBins + An array of bin headers for free chunks. These bins hold chunks + with sizes less than MIN_LARGE_SIZE bytes. Each bin contains + chunks of all the same size, spaced 8 bytes apart. To simplify + use in double-linked lists, each bin header acts as a malloc_chunk + pointing to the real first node, if it exists (else pointing to + itself). This avoids special-casing for headers. But to avoid + waste, we allocate only the fd/bk pointers of bins, and then use + repositioning tricks to treat these as the fields of a chunk. + + TreeBins + Treebins are pointers to the roots of trees holding a range of + sizes. There are 2 equally spaced treebins for each power of two + from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything + larger. + + Bin maps + There is one bit map for small bins ("smallmap") and one for + treebins ("treemap). Each bin sets its bit when non-empty, and + clears the bit when empty. Bit operations are then used to avoid + bin-by-bin searching -- nearly all "search" is done without ever + looking at bins that won't be selected. The bit maps + conservatively use 32 bits per map word, even if on 64bit system. + For a good description of some of the bit-based techniques used + here, see Henry S. Warren Jr's book "Hacker's Delight" (and + supplement at http://hackersdelight.org/). Many of these are + intended to reduce the branchiness of paths through malloc etc, as + well as to reduce the number of memory locations read or written. + + Segments + A list of segments headed by an embedded malloc_segment record + representing the initial space. + + Address check support + The least_addr field is the least address ever obtained from + MORECORE or MMAP. Attempted frees and reallocs of any address less + than this are trapped (unless INSECURE is defined). + + Magic tag + A cross-check field that should always hold same value as mparams.magic. + + Max allowed footprint + The maximum allowed bytes to allocate from system (zero means no limit) + + Flags + Bits recording whether to use MMAP, locks, or contiguous MORECORE + + Statistics + Each space keeps track of current and maximum system memory + obtained via MORECORE or MMAP. + + Trim support + Fields holding the amount of unused topmost memory that should trigger + trimming, and a counter to force periodic scanning to release unused + non-topmost segments. + + Locking + If USE_LOCKS is defined, the "mutex" lock is acquired and released + around every public call using this mspace. + + Extension support + A void* pointer and a size_t field that can be used to help implement + extensions to this malloc. +*/ + +/* Bin types, widths and sizes */ +#define NSMALLBINS (32U) +#define NTREEBINS (32U) +#define SMALLBIN_SHIFT (3U) +#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT) +#define TREEBIN_SHIFT (8U) +#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT) +#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE) +#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD) + +struct malloc_state { + binmap_t smallmap; + binmap_t treemap; + size_t dvsize; + size_t topsize; + char* least_addr; + mchunkptr dv; + mchunkptr top; + size_t trim_check; + size_t release_checks; + size_t magic; + mchunkptr smallbins[(NSMALLBINS+1)*2]; + tbinptr treebins[NTREEBINS]; + size_t footprint; + size_t max_footprint; + size_t footprint_limit; /* zero means no limit */ + flag_t mflags; +#if USE_LOCKS + MLOCK_T mutex; /* locate lock among fields that rarely change */ +#endif /* USE_LOCKS */ + msegment seg; + void* extp; /* Unused but available for extensions */ + size_t exts; +}; + +typedef struct malloc_state* mstate; + +/* ------------- Global malloc_state and malloc_params ------------------- */ + +/* + malloc_params holds global properties, including those that can be + dynamically set using mallopt. There is a single instance, mparams, + initialized in init_mparams. Note that the non-zeroness of "magic" + also serves as an initialization flag. +*/ + +struct malloc_params { + size_t magic; + size_t page_size; + size_t granularity; + size_t mmap_threshold; + size_t trim_threshold; + flag_t default_mflags; +}; + +static struct malloc_params mparams; + +/* Ensure mparams initialized */ +#define ensure_initialization() (void)(mparams.magic != 0 || init_mparams()) + +#if !ONLY_MSPACES + +/* The global malloc_state used for all non-"mspace" calls */ +static struct malloc_state _gm_; +#define gm (&_gm_) +#define is_global(M) ((M) == &_gm_) + +#endif /* !ONLY_MSPACES */ + +#define is_initialized(M) ((M)->top != 0) + +/* -------------------------- system alloc setup ------------------------- */ + +/* Operations on mflags */ + +#define use_lock(M) ((M)->mflags & USE_LOCK_BIT) +#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT) +#if USE_LOCKS +#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT) +#else +#define disable_lock(M) +#endif + +#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) +#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) +#if HAVE_MMAP +#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) +#else +#define disable_mmap(M) +#endif + +#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) +#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) + +#define set_lock(M,L)\ + ((M)->mflags = (L)?\ + ((M)->mflags | USE_LOCK_BIT) :\ + ((M)->mflags & ~USE_LOCK_BIT)) + +/* page-align a size */ +#define page_align(S)\ + (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE)) + +/* granularity-align a size */ +#define granularity_align(S)\ + (((S) + (mparams.granularity - SIZE_T_ONE))\ + & ~(mparams.granularity - SIZE_T_ONE)) + + +/* For mmap, use granularity alignment on windows, else page-align */ +#ifdef WIN32 +#define mmap_align(S) granularity_align(S) +#else +#define mmap_align(S) page_align(S) +#endif + +/* For sys_alloc, enough padding to ensure can malloc request on success */ +#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT) + +#define is_page_aligned(S)\ + (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0) +#define is_granularity_aligned(S)\ + (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0) + +/* True if segment S holds address A */ +#define segment_holds(S, A)\ + ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) + +/* Return segment holding given address */ +static msegmentptr segment_holding(mstate m, char* addr) { + msegmentptr sp = &m->seg; + for (;;) { + if (addr >= sp->base && addr < sp->base + sp->size) + return sp; + if ((sp = sp->next) == 0) + return 0; + } +} + +/* Return true if segment contains a segment link */ +static int has_segment_link(mstate m, msegmentptr ss) { + msegmentptr sp = &m->seg; + for (;;) { + if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size) + return 1; + if ((sp = sp->next) == 0) + return 0; + } +} + +#ifndef MORECORE_CANNOT_TRIM +#define should_trim(M,s) ((s) > (M)->trim_check) +#else /* MORECORE_CANNOT_TRIM */ +#define should_trim(M,s) (0) +#endif /* MORECORE_CANNOT_TRIM */ + +/* + TOP_FOOT_SIZE is padding at the end of a segment, including space + that may be needed to place segment records and fenceposts when new + noncontiguous segments are added. +*/ +#define TOP_FOOT_SIZE\ + (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE) + + +/* ------------------------------- Hooks -------------------------------- */ + +/* + PREACTION should be defined to return 0 on success, and nonzero on + failure. If you are not using locking, you can redefine these to do + anything you like. +*/ + +#if USE_LOCKS +#define PREACTION(M) ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0) +#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); } +#else /* USE_LOCKS */ + +#ifndef PREACTION +#define PREACTION(M) (0) +#endif /* PREACTION */ + +#ifndef POSTACTION +#define POSTACTION(M) +#endif /* POSTACTION */ + +#endif /* USE_LOCKS */ + +/* + CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses. + USAGE_ERROR_ACTION is triggered on detected bad frees and + reallocs. The argument p is an address that might have triggered the + fault. It is ignored by the two predefined actions, but might be + useful in custom actions that try to help diagnose errors. +*/ + +#if PROCEED_ON_ERROR + +/* A count of the number of corruption errors causing resets */ +int malloc_corruption_error_count; + +/* default corruption action */ +static void reset_on_error(mstate m); + +#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m) +#define USAGE_ERROR_ACTION(m, p) + +#else /* PROCEED_ON_ERROR */ + +#ifndef CORRUPTION_ERROR_ACTION +#define CORRUPTION_ERROR_ACTION(m) ABORT +#endif /* CORRUPTION_ERROR_ACTION */ + +#ifndef USAGE_ERROR_ACTION +#define USAGE_ERROR_ACTION(m,p) ABORT +#endif /* USAGE_ERROR_ACTION */ + +#endif /* PROCEED_ON_ERROR */ + + +/* -------------------------- Debugging setup ---------------------------- */ + +#if ! DEBUG + +#define check_free_chunk(M,P) +#define check_inuse_chunk(M,P) +#define check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) +#define check_malloc_state(M) +#define check_top_chunk(M,P) + +#else /* DEBUG */ +#define check_free_chunk(M,P) do_check_free_chunk(M,P) +#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P) +#define check_top_chunk(M,P) do_check_top_chunk(M,P) +#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P) +#define check_malloc_state(M) do_check_malloc_state(M) + +static void do_check_any_chunk(mstate m, mchunkptr p); +static void do_check_top_chunk(mstate m, mchunkptr p); +static void do_check_mmapped_chunk(mstate m, mchunkptr p); +static void do_check_inuse_chunk(mstate m, mchunkptr p); +static void do_check_free_chunk(mstate m, mchunkptr p); +static void do_check_malloced_chunk(mstate m, void* mem, size_t s); +static void do_check_tree(mstate m, tchunkptr t); +static void do_check_treebin(mstate m, bindex_t i); +static void do_check_smallbin(mstate m, bindex_t i); +static void do_check_malloc_state(mstate m); +static int bin_find(mstate m, mchunkptr x); +static size_t traverse_and_check(mstate m); +#endif /* DEBUG */ + +/* ---------------------------- Indexing Bins ---------------------------- */ + +#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS) +#define small_index(s) (bindex_t)((s) >> SMALLBIN_SHIFT) +#define small_index2size(i) ((i) << SMALLBIN_SHIFT) +#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE)) + +/* addressing by index. See above about smallbin repositioning */ +#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1]))) +#define treebin_at(M,i) (&((M)->treebins[i])) + +/* assign tree index for size S to variable I. Use x86 asm if possible */ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_tree_index(S, I)\ +{\ + unsigned int X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = (unsigned) sizeof(X)*__CHAR_BIT__ - 1 - (unsigned) __builtin_clz(X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = _bit_scan_reverse (X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + _BitScanReverse((DWORD *) &K, (DWORD) X);\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#else /* GNUC */ +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int Y = (unsigned int)X;\ + unsigned int N = ((Y - 0x100) >> 16) & 8;\ + unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\ + N += K;\ + N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\ + K = 14 - N + ((Y <<= K) >> 15);\ + I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\ + }\ +} +#endif /* GNUC */ + +/* Bit representing maximum resolved size in a treebin at i */ +#define bit_for_tree_index(i) \ + (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2) + +/* Shift placing maximum resolved bit in a treebin at i as sign bit */ +#define leftshift_for_tree_index(i) \ + ((i == NTREEBINS-1)? 0 : \ + ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2))) + +/* The size of the smallest chunk held in bin with index i */ +#define minsize_for_tree_index(i) \ + ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \ + (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1))) + + +/* ------------------------ Operations on bin maps ----------------------- */ + +/* bit corresponding to given index */ +#define idx2bit(i) ((binmap_t)(1) << (i)) + +/* Mark/Clear bits with given index */ +#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i)) +#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i)) +#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i)) + +#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i)) +#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i)) +#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i)) + +/* isolate the least set bit of a bitmap */ +#define least_bit(x) ((x) & -(x)) + +/* mask with all bits to left of least bit of x on */ +#define left_bits(x) ((x<<1) | -(x<<1)) + +/* mask with all bits to left of or equal to least bit of x on */ +#define same_or_left_bits(x) ((x) | -(x)) + +/* index corresponding to given bit. Use x86 asm if possible */ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = __builtin_ctz(X); \ + I = (bindex_t)J;\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = _bit_scan_forward (X); \ + I = (bindex_t)J;\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + _BitScanForward((DWORD *) &J, X);\ + I = (bindex_t)J;\ +} + +#elif USE_BUILTIN_FFS +#define compute_bit2idx(X, I) I = ffs(X)-1 + +#else +#define compute_bit2idx(X, I)\ +{\ + unsigned int Y = X - 1;\ + unsigned int K = Y >> (16-4) & 16;\ + unsigned int N = K; Y >>= K;\ + N += K = Y >> (8-3) & 8; Y >>= K;\ + N += K = Y >> (4-2) & 4; Y >>= K;\ + N += K = Y >> (2-1) & 2; Y >>= K;\ + N += K = Y >> (1-0) & 1; Y >>= K;\ + I = (bindex_t)(N + Y);\ +} +#endif /* GNUC */ + + +/* ----------------------- Runtime Check Support ------------------------- */ + +/* + For security, the main invariant is that malloc/free/etc never + writes to a static address other than malloc_state, unless static + malloc_state itself has been corrupted, which cannot occur via + malloc (because of these checks). In essence this means that we + believe all pointers, sizes, maps etc held in malloc_state, but + check all of those linked or offsetted from other embedded data + structures. These checks are interspersed with main code in a way + that tends to minimize their run-time cost. + + When FOOTERS is defined, in addition to range checking, we also + verify footer fields of inuse chunks, which can be used guarantee + that the mstate controlling malloc/free is intact. This is a + streamlined version of the approach described by William Robertson + et al in "Run-time Detection of Heap-based Overflows" LISA'03 + http://www.usenix.org/events/lisa03/tech/robertson.html The footer + of an inuse chunk holds the xor of its mstate and a random seed, + that is checked upon calls to free() and realloc(). This is + (probabalistically) unguessable from outside the program, but can be + computed by any code successfully malloc'ing any chunk, so does not + itself provide protection against code that has already broken + security through some other means. Unlike Robertson et al, we + always dynamically check addresses of all offset chunks (previous, + next, etc). This turns out to be cheaper than relying on hashes. +*/ + +#if !INSECURE +/* Check if address a is at least as high as any from MORECORE or MMAP */ +#define ok_address(M, a) ((char*)(a) >= (M)->least_addr) +/* Check if address of next chunk n is higher than base chunk p */ +#define ok_next(p, n) ((char*)(p) < (char*)(n)) +/* Check if p has inuse status */ +#define ok_inuse(p) is_inuse(p) +/* Check if p has its pinuse bit on */ +#define ok_pinuse(p) pinuse(p) + +#else /* !INSECURE */ +#define ok_address(M, a) (1) +#define ok_next(b, n) (1) +#define ok_inuse(p) (1) +#define ok_pinuse(p) (1) +#endif /* !INSECURE */ + +#if (FOOTERS && !INSECURE) +/* Check if (alleged) mstate m has expected magic field */ +#define ok_magic(M) ((M)->magic == mparams.magic) +#else /* (FOOTERS && !INSECURE) */ +#define ok_magic(M) (1) +#endif /* (FOOTERS && !INSECURE) */ + +/* In gcc, use __builtin_expect to minimize impact of checks */ +#if !INSECURE +#if defined(__GNUC__) && __GNUC__ >= 3 +#define RTCHECK(e) __builtin_expect(e, 1) +#else /* GNUC */ +#define RTCHECK(e) (e) +#endif /* GNUC */ +#else /* !INSECURE */ +#define RTCHECK(e) (1) +#endif /* !INSECURE */ + +/* macros to set up inuse chunks with or without footers */ + +#if !FOOTERS + +#define mark_inuse_foot(M,p,s) + +/* Macros for setting head/foot of non-mmapped chunks */ + +/* Set cinuse bit and pinuse bit of next chunk */ +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set cinuse and pinuse of this chunk and pinuse of next chunk */ +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set size, cinuse and pinuse bit of this chunk */ +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT)) + +#else /* FOOTERS */ + +/* Set foot of inuse chunk to be xor of mstate and seed */ +#define mark_inuse_foot(M,p,s)\ + (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) + +#define get_mstate_for(p)\ + ((mstate)(((mchunkptr)((char*)(p) +\ + (chunksize(p))))->prev_foot ^ mparams.magic)) + +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \ + mark_inuse_foot(M,p,s)) + +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\ + mark_inuse_foot(M,p,s)) + +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + mark_inuse_foot(M, p, s)) + +#endif /* !FOOTERS */ + +/* ---------------------------- setting mparams -------------------------- */ + +#if LOCK_AT_FORK +static void pre_fork(void) { ACQUIRE_LOCK(&(gm)->mutex); } +static void post_fork_parent(void) { RELEASE_LOCK(&(gm)->mutex); } +static void post_fork_child(void) { INITIAL_LOCK(&(gm)->mutex); } +#endif /* LOCK_AT_FORK */ + +/* Initialize mparams */ +static int init_mparams(void) { +#ifdef NEED_GLOBAL_LOCK_INIT + if (malloc_global_mutex_status <= 0) + init_malloc_global_mutex(); +#endif + + ACQUIRE_MALLOC_GLOBAL_LOCK(); + if (mparams.magic == 0) { + size_t magic; + size_t psize; + size_t gsize; + +#ifndef WIN32 + psize = malloc_getpagesize; + gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize); +#else /* WIN32 */ + { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + psize = system_info.dwPageSize; + gsize = ((DEFAULT_GRANULARITY != 0)? + DEFAULT_GRANULARITY : system_info.dwAllocationGranularity); + } +#endif /* WIN32 */ + + /* Sanity-check configuration: + size_t must be unsigned and as wide as pointer type. + ints must be at least 4 bytes. + alignment must be at least 8. + Alignment, min chunk size, and page size must all be powers of 2. + */ + if ((sizeof(size_t) != sizeof(char*)) || + (MAX_SIZE_T < MIN_CHUNK_SIZE) || + (sizeof(int) < 4) || + (MALLOC_ALIGNMENT < (size_t)8U) || + ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) || + ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) || + ((gsize & (gsize-SIZE_T_ONE)) != 0) || + ((psize & (psize-SIZE_T_ONE)) != 0)) + ABORT; + mparams.granularity = gsize; + mparams.page_size = psize; + mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; + mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; +#if MORECORE_CONTIGUOUS + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; +#else /* MORECORE_CONTIGUOUS */ + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; +#endif /* MORECORE_CONTIGUOUS */ + +#if !ONLY_MSPACES + /* Set up lock for main malloc area */ + gm->mflags = mparams.default_mflags; + (void)INITIAL_LOCK(&gm->mutex); +#endif +#if LOCK_AT_FORK + pthread_atfork(&pre_fork, &post_fork_parent, &post_fork_child); +#endif + + { +#if USE_DEV_RANDOM + int fd; + unsigned char buf[sizeof(size_t)]; + /* Try to use /dev/urandom, else fall back on using time */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 && + read(fd, buf, sizeof(buf)) == sizeof(buf)) { + magic = *((size_t *) buf); + close(fd); + } + else +#endif /* USE_DEV_RANDOM */ +#ifdef WIN32 + magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U); +#elif defined(LACKS_TIME_H) + magic = (size_t)&magic ^ (size_t)0x55555555U; +#else + magic = (size_t)(time(0) ^ (size_t)0x55555555U); +#endif + magic |= (size_t)8U; /* ensure nonzero */ + magic &= ~(size_t)7U; /* improve chances of fault for bad values */ + /* Until memory modes commonly available, use volatile-write */ + (*(volatile size_t *)(&(mparams.magic))) = magic; + } + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; +} + +/* support for mallopt */ +static int change_mparam(int param_number, int value) { + size_t val; + ensure_initialization(); + val = (value == -1)? MAX_SIZE_T : (size_t)value; + switch(param_number) { + case M_TRIM_THRESHOLD: + mparams.trim_threshold = val; + return 1; + case M_GRANULARITY: + if (val >= mparams.page_size && ((val & (val-1)) == 0)) { + mparams.granularity = val; + return 1; + } + else + return 0; + case M_MMAP_THRESHOLD: + mparams.mmap_threshold = val; + return 1; + default: + return 0; + } +} + +#if DEBUG +/* ------------------------- Debugging Support --------------------------- */ + +/* Check properties of any chunk, whether free, inuse, mmapped etc */ +static void do_check_any_chunk(mstate m, mchunkptr p) { + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); +} + +/* Check properties of top chunk */ +static void do_check_top_chunk(mstate m, mchunkptr p) { + msegmentptr sp = segment_holding(m, (char*)p); + size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */ + assert(sp != 0); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(sz == m->topsize); + assert(sz > 0); + assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE); + assert(pinuse(p)); + assert(!pinuse(chunk_plus_offset(p, sz))); +} + +/* Check properties of (inuse) mmapped chunks */ +static void do_check_mmapped_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + size_t len = (sz + (p->prev_foot) + MMAP_FOOT_PAD); + assert(is_mmapped(p)); + assert(use_mmap(m)); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(!is_small(sz)); + assert((len & (mparams.page_size-SIZE_T_ONE)) == 0); + assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD); + assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0); +} + +/* Check properties of inuse chunks */ +static void do_check_inuse_chunk(mstate m, mchunkptr p) { + do_check_any_chunk(m, p); + assert(is_inuse(p)); + assert(next_pinuse(p)); + /* If not pinuse and not mmapped, previous chunk has OK offset */ + assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p); + if (is_mmapped(p)) + do_check_mmapped_chunk(m, p); +} + +/* Check properties of free chunks */ +static void do_check_free_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + mchunkptr next = chunk_plus_offset(p, sz); + do_check_any_chunk(m, p); + assert(!is_inuse(p)); + assert(!next_pinuse(p)); + assert (!is_mmapped(p)); + if (p != m->dv && p != m->top) { + if (sz >= MIN_CHUNK_SIZE) { + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(is_aligned(chunk2mem(p))); + assert(next->prev_foot == sz); + assert(pinuse(p)); + assert (next == m->top || is_inuse(next)); + assert(p->fd->bk == p); + assert(p->bk->fd == p); + } + else /* markers are always of size SIZE_T_SIZE */ + assert(sz == SIZE_T_SIZE); + } +} + +/* Check properties of malloced chunks at the point they are malloced */ +static void do_check_malloced_chunk(mstate m, void* mem, size_t s) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t sz = p->head & ~INUSE_BITS; + do_check_inuse_chunk(m, p); + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(sz >= MIN_CHUNK_SIZE); + assert(sz >= s); + /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */ + assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE)); + } +} + +/* Check a tree and its subtrees. */ +static void do_check_tree(mstate m, tchunkptr t) { + tchunkptr head = 0; + tchunkptr u = t; + bindex_t tindex = t->index; + size_t tsize = chunksize(t); + bindex_t idx; + compute_tree_index(tsize, idx); + assert(tindex == idx); + assert(tsize >= MIN_LARGE_SIZE); + assert(tsize >= minsize_for_tree_index(idx)); + assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1)))); + + do { /* traverse through chain of same-sized nodes */ + do_check_any_chunk(m, ((mchunkptr)u)); + assert(u->index == tindex); + assert(chunksize(u) == tsize); + assert(!is_inuse(u)); + assert(!next_pinuse(u)); + assert(u->fd->bk == u); + assert(u->bk->fd == u); + if (u->parent == 0) { + assert(u->child[0] == 0); + assert(u->child[1] == 0); + } + else { + assert(head == 0); /* only one node on chain has parent */ + head = u; + assert(u->parent != u); + assert (u->parent->child[0] == u || + u->parent->child[1] == u || + *((tbinptr*)(u->parent)) == u); + if (u->child[0] != 0) { + assert(u->child[0]->parent == u); + assert(u->child[0] != u); + do_check_tree(m, u->child[0]); + } + if (u->child[1] != 0) { + assert(u->child[1]->parent == u); + assert(u->child[1] != u); + do_check_tree(m, u->child[1]); + } + if (u->child[0] != 0 && u->child[1] != 0) { + assert(chunksize(u->child[0]) < chunksize(u->child[1])); + } + } + u = u->fd; + } while (u != t); + assert(head != 0); +} + +/* Check all the chunks in a treebin. */ +static void do_check_treebin(mstate m, bindex_t i) { + tbinptr* tb = treebin_at(m, i); + tchunkptr t = *tb; + int empty = (m->treemap & (1U << i)) == 0; + if (t == 0) + assert(empty); + if (!empty) + do_check_tree(m, t); +} + +/* Check all the chunks in a smallbin. */ +static void do_check_smallbin(mstate m, bindex_t i) { + sbinptr b = smallbin_at(m, i); + mchunkptr p = b->bk; + unsigned int empty = (m->smallmap & (1U << i)) == 0; + if (p == b) + assert(empty); + if (!empty) { + for (; p != b; p = p->bk) { + size_t size = chunksize(p); + mchunkptr q; + /* each chunk claims to be free */ + do_check_free_chunk(m, p); + /* chunk belongs in bin */ + assert(small_index(size) == i); + assert(p->bk == b || chunksize(p->bk) == chunksize(p)); + /* chunk is followed by an inuse chunk */ + q = next_chunk(p); + if (q->head != FENCEPOST_HEAD) + do_check_inuse_chunk(m, q); + } + } +} + +/* Find x in a bin. Used in other check functions. */ +static int bin_find(mstate m, mchunkptr x) { + size_t size = chunksize(x); + if (is_small(size)) { + bindex_t sidx = small_index(size); + sbinptr b = smallbin_at(m, sidx); + if (smallmap_is_marked(m, sidx)) { + mchunkptr p = b; + do { + if (p == x) + return 1; + } while ((p = p->fd) != b); + } + } + else { + bindex_t tidx; + compute_tree_index(size, tidx); + if (treemap_is_marked(m, tidx)) { + tchunkptr t = *treebin_at(m, tidx); + size_t sizebits = size << leftshift_for_tree_index(tidx); + while (t != 0 && chunksize(t) != size) { + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + sizebits <<= 1; + } + if (t != 0) { + tchunkptr u = t; + do { + if (u == (tchunkptr)x) + return 1; + } while ((u = u->fd) != t); + } + } + } + return 0; +} + +/* Traverse each chunk and check it; return total */ +static size_t traverse_and_check(mstate m) { + size_t sum = 0; + if (is_initialized(m)) { + msegmentptr s = &m->seg; + sum += m->topsize + TOP_FOOT_SIZE; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + mchunkptr lastq = 0; + assert(pinuse(q)); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + sum += chunksize(q); + if (is_inuse(q)) { + assert(!bin_find(m, q)); + do_check_inuse_chunk(m, q); + } + else { + assert(q == m->dv || bin_find(m, q)); + assert(lastq == 0 || is_inuse(lastq)); /* Not 2 consecutive free */ + do_check_free_chunk(m, q); + } + lastq = q; + q = next_chunk(q); + } + s = s->next; + } + } + return sum; +} + + +/* Check all properties of malloc_state. */ +static void do_check_malloc_state(mstate m) { + bindex_t i; + size_t total; + /* check bins */ + for (i = 0; i < NSMALLBINS; ++i) + do_check_smallbin(m, i); + for (i = 0; i < NTREEBINS; ++i) + do_check_treebin(m, i); + + if (m->dvsize != 0) { /* check dv chunk */ + do_check_any_chunk(m, m->dv); + assert(m->dvsize == chunksize(m->dv)); + assert(m->dvsize >= MIN_CHUNK_SIZE); + assert(bin_find(m, m->dv) == 0); + } + + if (m->top != 0) { /* check top chunk */ + do_check_top_chunk(m, m->top); + /*assert(m->topsize == chunksize(m->top)); redundant */ + assert(m->topsize > 0); + assert(bin_find(m, m->top) == 0); + } + + total = traverse_and_check(m); + assert(total <= m->footprint); + assert(m->footprint <= m->max_footprint); +} +#endif /* DEBUG */ + +/* ----------------------------- statistics ------------------------------ */ + +#if !NO_MALLINFO +static struct mallinfo internal_mallinfo(mstate m) { + struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + ensure_initialization(); + if (!PREACTION(m)) { + check_malloc_state(m); + if (is_initialized(m)) { + size_t nfree = SIZE_T_ONE; /* top always free */ + size_t mfree = m->topsize + TOP_FOOT_SIZE; + size_t sum = mfree; + msegmentptr s = &m->seg; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + size_t sz = chunksize(q); + sum += sz; + if (!is_inuse(q)) { + mfree += sz; + ++nfree; + } + q = next_chunk(q); + } + s = s->next; + } + + nm.arena = sum; + nm.ordblks = nfree; + nm.hblkhd = m->footprint - sum; + nm.usmblks = m->max_footprint; + nm.uordblks = m->footprint - mfree; + nm.fordblks = mfree; + nm.keepcost = m->topsize; + } + + POSTACTION(m); + } + return nm; +} +#endif /* !NO_MALLINFO */ + +#if !NO_MALLOC_STATS +static void internal_malloc_stats(mstate m) { + ensure_initialization(); + if (!PREACTION(m)) { + size_t maxfp = 0; + size_t fp = 0; + size_t used = 0; + check_malloc_state(m); + if (is_initialized(m)) { + msegmentptr s = &m->seg; + maxfp = m->max_footprint; + fp = m->footprint; + used = fp - (m->topsize + TOP_FOOT_SIZE); + + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + if (!is_inuse(q)) + used -= chunksize(q); + q = next_chunk(q); + } + s = s->next; + } + } + POSTACTION(m); /* drop lock */ + fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp)); + fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp)); + fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used)); + } +} +#endif /* NO_MALLOC_STATS */ + +/* ----------------------- Operations on smallbins ----------------------- */ + +/* + Various forms of linking and unlinking are defined as macros. Even + the ones for trees, which are very long but have very short typical + paths. This is ugly but reduces reliance on inlining support of + compilers. +*/ + +/* Link a free chunk into a smallbin */ +#define insert_small_chunk(M, P, S) {\ + bindex_t I = small_index(S);\ + mchunkptr B = smallbin_at(M, I);\ + mchunkptr F = B;\ + assert(S >= MIN_CHUNK_SIZE);\ + if (!smallmap_is_marked(M, I))\ + mark_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, B->fd)))\ + F = B->fd;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + B->fd = P;\ + F->bk = P;\ + P->fd = F;\ + P->bk = B;\ +} + +/* Unlink a chunk from a smallbin */ +#define unlink_small_chunk(M, P, S) {\ + mchunkptr F = P->fd;\ + mchunkptr B = P->bk;\ + bindex_t I = small_index(S);\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (RTCHECK(F == smallbin_at(M,I) || (ok_address(M, F) && F->bk == P))) { \ + if (B == F) {\ + clear_smallmap(M, I);\ + }\ + else if (RTCHECK(B == smallbin_at(M,I) ||\ + (ok_address(M, B) && B->fd == P))) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Unlink the first chunk from a smallbin */ +#define unlink_first_small_chunk(M, B, P, I) {\ + mchunkptr F = P->fd;\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (B == F) {\ + clear_smallmap(M, I);\ + }\ + else if (RTCHECK(ok_address(M, F) && F->bk == P)) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Replace dv node, binning the old one */ +/* Used only when dvsize known to be small */ +#define replace_dv(M, P, S) {\ + size_t DVS = M->dvsize;\ + assert(is_small(DVS));\ + if (DVS != 0) {\ + mchunkptr DV = M->dv;\ + insert_small_chunk(M, DV, DVS);\ + }\ + M->dvsize = S;\ + M->dv = P;\ +} + +/* ------------------------- Operations on trees ------------------------- */ + +/* Insert chunk into tree */ +#define insert_large_chunk(M, X, S) {\ + tbinptr* H;\ + bindex_t I;\ + compute_tree_index(S, I);\ + H = treebin_at(M, I);\ + X->index = I;\ + X->child[0] = X->child[1] = 0;\ + if (!treemap_is_marked(M, I)) {\ + mark_treemap(M, I);\ + *H = X;\ + X->parent = (tchunkptr)H;\ + X->fd = X->bk = X;\ + }\ + else {\ + tchunkptr T = *H;\ + size_t K = S << leftshift_for_tree_index(I);\ + for (;;) {\ + if (chunksize(T) != S) {\ + tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\ + K <<= 1;\ + if (*C != 0)\ + T = *C;\ + else if (RTCHECK(ok_address(M, C))) {\ + *C = X;\ + X->parent = T;\ + X->fd = X->bk = X;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + else {\ + tchunkptr F = T->fd;\ + if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\ + T->fd = F->bk = X;\ + X->fd = F;\ + X->bk = T;\ + X->parent = 0;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + }\ + }\ +} + +/* + Unlink steps: + + 1. If x is a chained node, unlink it from its same-sized fd/bk links + and choose its bk node as its replacement. + 2. If x was the last node of its size, but not a leaf node, it must + be replaced with a leaf node (not merely one with an open left or + right), to make sure that lefts and rights of descendents + correspond properly to bit masks. We use the rightmost descendent + of x. We could use any other leaf, but this is easy to locate and + tends to counteract removal of leftmosts elsewhere, and so keeps + paths shorter than minimally guaranteed. This doesn't loop much + because on average a node in a tree is near the bottom. + 3. If x is the base of a chain (i.e., has parent links) relink + x's parent and children to x's replacement (or null if none). +*/ + +#define unlink_large_chunk(M, X) {\ + tchunkptr XP = X->parent;\ + tchunkptr R;\ + if (X->bk != X) {\ + tchunkptr F = X->fd;\ + R = X->bk;\ + if (RTCHECK(ok_address(M, F) && F->bk == X && R->fd == X)) {\ + F->bk = R;\ + R->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + tchunkptr* RP;\ + if (((R = *(RP = &(X->child[1]))) != 0) ||\ + ((R = *(RP = &(X->child[0]))) != 0)) {\ + tchunkptr* CP;\ + while ((*(CP = &(R->child[1])) != 0) ||\ + (*(CP = &(R->child[0])) != 0)) {\ + R = *(RP = CP);\ + }\ + if (RTCHECK(ok_address(M, RP)))\ + *RP = 0;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + }\ + if (XP != 0) {\ + tbinptr* H = treebin_at(M, X->index);\ + if (X == *H) {\ + if ((*H = R) == 0) \ + clear_treemap(M, X->index);\ + }\ + else if (RTCHECK(ok_address(M, XP))) {\ + if (XP->child[0] == X) \ + XP->child[0] = R;\ + else \ + XP->child[1] = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + if (R != 0) {\ + if (RTCHECK(ok_address(M, R))) {\ + tchunkptr C0, C1;\ + R->parent = XP;\ + if ((C0 = X->child[0]) != 0) {\ + if (RTCHECK(ok_address(M, C0))) {\ + R->child[0] = C0;\ + C0->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + if ((C1 = X->child[1]) != 0) {\ + if (RTCHECK(ok_address(M, C1))) {\ + R->child[1] = C1;\ + C1->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ +} + +/* Relays to large vs small bin operations */ + +#define insert_chunk(M, P, S)\ + if (is_small(S)) insert_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); } + +#define unlink_chunk(M, P, S)\ + if (is_small(S)) unlink_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); } + + +/* Relays to internal calls to malloc/free from realloc, memalign etc */ + +#if ONLY_MSPACES +#define internal_malloc(m, b) mspace_malloc(m, b) +#define internal_free(m, mem) mspace_free(m,mem); +#else /* ONLY_MSPACES */ +#if MSPACES +#define internal_malloc(m, b)\ + ((m == gm)? dlmalloc(b) : mspace_malloc(m, b)) +#define internal_free(m, mem)\ + if (m == gm) dlfree(mem); else mspace_free(m,mem); +#else /* MSPACES */ +#define internal_malloc(m, b) dlmalloc(b) +#define internal_free(m, mem) dlfree(mem) +#endif /* MSPACES */ +#endif /* ONLY_MSPACES */ + +/* ----------------------- Direct-mmapping chunks ----------------------- */ + +/* + Directly mmapped chunks are set up with an offset to the start of + the mmapped region stored in the prev_foot field of the chunk. This + allows reconstruction of the required argument to MUNMAP when freed, + and also allows adjustment of the returned chunk to meet alignment + requirements (especially in memalign). +*/ + +/* Malloc using mmap */ +static void* mmap_alloc(mstate m, size_t nb) { + size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + if (m->footprint_limit != 0) { + size_t fp = m->footprint + mmsize; + if (fp <= m->footprint || fp > m->footprint_limit) + return 0; + } + if (mmsize > nb) { /* Check for wrap around 0 */ + char* mm = (char*)(CALL_DIRECT_MMAP(mmsize)); + if (mm != CMFAIL) { + size_t offset = align_offset(chunk2mem(mm)); + size_t psize = mmsize - offset - MMAP_FOOT_PAD; + mchunkptr p = (mchunkptr)(mm + offset); + p->prev_foot = offset; + p->head = psize; + mark_inuse_foot(m, p, psize); + chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0; + + if (m->least_addr == 0 || mm < m->least_addr) + m->least_addr = mm; + if ((m->footprint += mmsize) > m->max_footprint) + m->max_footprint = m->footprint; + assert(is_aligned(chunk2mem(p))); + check_mmapped_chunk(m, p); + return chunk2mem(p); + } + } + return 0; +} + +/* Realloc using mmap */ +static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb, int flags) { + size_t oldsize = chunksize(oldp); + (void)flags; /* placate people compiling -Wunused */ + if (is_small(nb)) /* Can't shrink mmap regions below small size */ + return 0; + /* Keep old chunk if big enough but not too big */ + if (oldsize >= nb + SIZE_T_SIZE && + (oldsize - nb) <= (mparams.granularity << 1)) + return oldp; + else { + size_t offset = oldp->prev_foot; + size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD; + size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + char* cp = (char*)CALL_MREMAP((char*)oldp - offset, + oldmmsize, newmmsize, flags); + if (cp != CMFAIL) { + mchunkptr newp = (mchunkptr)(cp + offset); + size_t psize = newmmsize - offset - MMAP_FOOT_PAD; + newp->head = psize; + mark_inuse_foot(m, newp, psize); + chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0; + + if (cp < m->least_addr) + m->least_addr = cp; + if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint) + m->max_footprint = m->footprint; + check_mmapped_chunk(m, newp); + return newp; + } + } + return 0; +} + + +/* -------------------------- mspace management -------------------------- */ + +/* Initialize top chunk and its size */ +static void init_top(mstate m, mchunkptr p, size_t psize) { + /* Ensure alignment */ + size_t offset = align_offset(chunk2mem(p)); + p = (mchunkptr)((char*)p + offset); + psize -= offset; + + m->top = p; + m->topsize = psize; + p->head = psize | PINUSE_BIT; + /* set size of fake trailing chunk holding overhead space only once */ + chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE; + m->trim_check = mparams.trim_threshold; /* reset on each update */ +} + +/* Initialize bins for a new mstate that is otherwise zeroed out */ +static void init_bins(mstate m) { + /* Establish circular links for smallbins */ + bindex_t i; + for (i = 0; i < NSMALLBINS; ++i) { + sbinptr bin = smallbin_at(m,i); + bin->fd = bin->bk = bin; + } +} + +#if PROCEED_ON_ERROR + +/* default corruption action */ +static void reset_on_error(mstate m) { + int i; + ++malloc_corruption_error_count; + /* Reinitialize fields to forget about all memory */ + m->smallmap = m->treemap = 0; + m->dvsize = m->topsize = 0; + m->seg.base = 0; + m->seg.size = 0; + m->seg.next = 0; + m->top = m->dv = 0; + for (i = 0; i < NTREEBINS; ++i) + *treebin_at(m, i) = 0; + init_bins(m); +} +#endif /* PROCEED_ON_ERROR */ + +/* Allocate chunk and prepend remainder with chunk in successor base. */ +static void* prepend_alloc(mstate m, char* newbase, char* oldbase, + size_t nb) { + mchunkptr p = align_as_chunk(newbase); + mchunkptr oldfirst = align_as_chunk(oldbase); + size_t psize = (char*)oldfirst - (char*)p; + mchunkptr q = chunk_plus_offset(p, nb); + size_t qsize = psize - nb; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + + assert((char*)oldfirst > (char*)q); + assert(pinuse(oldfirst)); + assert(qsize >= MIN_CHUNK_SIZE); + + /* consolidate remainder with first chunk of old base */ + if (oldfirst == m->top) { + size_t tsize = m->topsize += qsize; + m->top = q; + q->head = tsize | PINUSE_BIT; + check_top_chunk(m, q); + } + else if (oldfirst == m->dv) { + size_t dsize = m->dvsize += qsize; + m->dv = q; + set_size_and_pinuse_of_free_chunk(q, dsize); + } + else { + if (!is_inuse(oldfirst)) { + size_t nsize = chunksize(oldfirst); + unlink_chunk(m, oldfirst, nsize); + oldfirst = chunk_plus_offset(oldfirst, nsize); + qsize += nsize; + } + set_free_with_pinuse(q, qsize, oldfirst); + insert_chunk(m, q, qsize); + check_free_chunk(m, q); + } + + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); +} + +/* Add a segment to hold a new noncontiguous region */ +static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) { + /* Determine locations and sizes of segment, fenceposts, old top */ + char* old_top = (char*)m->top; + msegmentptr oldsp = segment_holding(m, old_top); + char* old_end = oldsp->base + oldsp->size; + size_t ssize = pad_request(sizeof(struct malloc_segment)); + char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + size_t offset = align_offset(chunk2mem(rawsp)); + char* asp = rawsp + offset; + char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; + mchunkptr sp = (mchunkptr)csp; + msegmentptr ss = (msegmentptr)(chunk2mem(sp)); + mchunkptr tnext = chunk_plus_offset(sp, ssize); + mchunkptr p = tnext; + int nfences = 0; + + /* reset top to new space */ + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + + /* Set up segment record */ + assert(is_aligned(ss)); + set_size_and_pinuse_of_inuse_chunk(m, sp, ssize); + *ss = m->seg; /* Push current record */ + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmapped; + m->seg.next = ss; + + /* Insert trailing fenceposts */ + for (;;) { + mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE); + p->head = FENCEPOST_HEAD; + ++nfences; + if ((char*)(&(nextp->head)) < old_end) + p = nextp; + else + break; + } + assert(nfences >= 2); + + /* Insert the rest of old top into a bin as an ordinary free chunk */ + if (csp != old_top) { + mchunkptr q = (mchunkptr)old_top; + size_t psize = csp - old_top; + mchunkptr tn = chunk_plus_offset(q, psize); + set_free_with_pinuse(q, psize, tn); + insert_chunk(m, q, psize); + } + + check_top_chunk(m, m->top); +} + +/* -------------------------- System allocation -------------------------- */ + +/* Get memory from system using MORECORE or MMAP */ +static void* sys_alloc(mstate m, size_t nb) { + char* tbase = CMFAIL; + size_t tsize = 0; + flag_t mmap_flag = 0; + size_t asize; /* allocation size */ + + ensure_initialization(); + + /* Directly map large chunks, but only if already initialized */ + if (use_mmap(m) && nb >= mparams.mmap_threshold && m->topsize != 0) { + void* mem = mmap_alloc(m, nb); + if (mem != 0) + return mem; + } + + asize = granularity_align(nb + SYS_ALLOC_PADDING); + if (asize <= nb) + return 0; /* wraparound */ + if (m->footprint_limit != 0) { + size_t fp = m->footprint + asize; + if (fp <= m->footprint || fp > m->footprint_limit) + return 0; + } + + /* + Try getting memory in any of three ways (in most-preferred to + least-preferred order): + 1. A call to MORECORE that can normally contiguously extend memory. + (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or + or main space is mmapped or a previous contiguous call failed) + 2. A call to MMAP new space (disabled if not HAVE_MMAP). + Note that under the default settings, if MORECORE is unable to + fulfill a request, and HAVE_MMAP is true, then mmap is + used as a noncontiguous system allocator. This is a useful backup + strategy for systems with holes in address spaces -- in this case + sbrk cannot contiguously expand the heap, but mmap may be able to + find space. + 3. A call to MORECORE that cannot usually contiguously extend memory. + (disabled if not HAVE_MORECORE) + + In all cases, we need to request enough bytes from system to ensure + we can malloc nb bytes upon success, so pad with enough space for + top_foot, plus alignment-pad to make sure we don't lose bytes if + not on boundary, and round this up to a granularity unit. + */ + + if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { + char* br = CMFAIL; + size_t ssize = asize; /* sbrk call size */ + msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); + ACQUIRE_MALLOC_GLOBAL_LOCK(); + + if (ss == 0) { /* First time through or recovery */ + char* base = (char*)CALL_MORECORE(0); + if (base != CMFAIL) { + size_t fp; + /* Adjust to end on a page boundary */ + if (!is_page_aligned(base)) + ssize += (page_align((size_t)base) - (size_t)base); + fp = m->footprint + ssize; /* recheck limits */ + if (ssize > nb && ssize < HALF_MAX_SIZE_T && + (m->footprint_limit == 0 || + (fp > m->footprint && fp <= m->footprint_limit)) && + (br = (char*)(CALL_MORECORE(ssize))) == base) { + tbase = base; + tsize = ssize; + } + } + } + else { + /* Subtract out existing available top space from MORECORE request. */ + ssize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING); + /* Use mem here only if it did continuously extend old space */ + if (ssize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(ssize))) == ss->base+ss->size) { + tbase = br; + tsize = ssize; + } + } + + if (tbase == CMFAIL) { /* Cope with partial failure */ + if (br != CMFAIL) { /* Try to use/extend the space we did get */ + if (ssize < HALF_MAX_SIZE_T && + ssize < nb + SYS_ALLOC_PADDING) { + size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - ssize); + if (esize < HALF_MAX_SIZE_T) { + char* end = (char*)CALL_MORECORE(esize); + if (end != CMFAIL) + ssize += esize; + else { /* Can't use; try to release */ + (void) CALL_MORECORE(-ssize); + br = CMFAIL; + } + } + } + } + if (br != CMFAIL) { /* Use the space we did get */ + tbase = br; + tsize = ssize; + } + else + disable_contiguous(m); /* Don't try contiguous path in the future */ + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + } + + if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ + char* mp = (char*)(CALL_MMAP(asize)); + if (mp != CMFAIL) { + tbase = mp; + tsize = asize; + mmap_flag = USE_MMAP_BIT; + } + } + + if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ + if (asize < HALF_MAX_SIZE_T) { + char* br = CMFAIL; + char* end = CMFAIL; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + br = (char*)(CALL_MORECORE(asize)); + end = (char*)(CALL_MORECORE(0)); + RELEASE_MALLOC_GLOBAL_LOCK(); + if (br != CMFAIL && end != CMFAIL && br < end) { + size_t ssize = end - br; + if (ssize > nb + TOP_FOOT_SIZE) { + tbase = br; + tsize = ssize; + } + } + } + } + + if (tbase != CMFAIL) { + + if ((m->footprint += tsize) > m->max_footprint) + m->max_footprint = m->footprint; + + if (!is_initialized(m)) { /* first-time initialization */ + if (m->least_addr == 0 || tbase < m->least_addr) + m->least_addr = tbase; + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmap_flag; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + init_bins(m); +#if !ONLY_MSPACES + if (is_global(m)) + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + else +#endif + { + /* Offset top by embedded malloc_state */ + mchunkptr mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE); + } + } + + else { + /* Try to merge with an existing segment */ + msegmentptr sp = &m->seg; + /* Only consider most recent segment if traversal suppressed */ + while (sp != 0 && tbase != sp->base + sp->size) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag && + segment_holds(sp, m->top)) { /* append */ + sp->size += tsize; + init_top(m, m->top, m->topsize + tsize); + } + else { + if (tbase < m->least_addr) + m->least_addr = tbase; + sp = &m->seg; + while (sp != 0 && sp->base != tbase + tsize) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag) { + char* oldbase = sp->base; + sp->base = tbase; + sp->size += tsize; + return prepend_alloc(m, tbase, oldbase, nb); + } + else + add_segment(m, tbase, tsize, mmap_flag); + } + } + + if (nb < m->topsize) { /* Allocate from new or extended top space */ + size_t rsize = m->topsize -= nb; + mchunkptr p = m->top; + mchunkptr r = m->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + check_top_chunk(m, m->top); + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); + } + } + + MALLOC_FAILURE_ACTION; + return 0; +} + +/* ----------------------- system deallocation -------------------------- */ + +/* Unmap and unlink any mmapped segments that don't contain used chunks */ +static size_t release_unused_segments(mstate m) { + size_t released = 0; + int nsegs = 0; + msegmentptr pred = &m->seg; + msegmentptr sp = pred->next; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + msegmentptr next = sp->next; + ++nsegs; + if (is_mmapped_segment(sp) && !is_extern_segment(sp)) { + mchunkptr p = align_as_chunk(base); + size_t psize = chunksize(p); + /* Can unmap if first chunk holds entire segment and not pinned */ + if (!is_inuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) { + tchunkptr tp = (tchunkptr)p; + assert(segment_holds(sp, (char*)sp)); + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + else { + unlink_large_chunk(m, tp); + } + if (CALL_MUNMAP(base, size) == 0) { + released += size; + m->footprint -= size; + /* unlink obsoleted record */ + sp = pred; + sp->next = next; + } + else { /* back out if cannot unmap */ + insert_large_chunk(m, tp, psize); + } + } + } + if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */ + break; + pred = sp; + sp = next; + } + /* Reset check counter */ + m->release_checks = (((size_t) nsegs > (size_t) MAX_RELEASE_CHECK_RATE)? + (size_t) nsegs : (size_t) MAX_RELEASE_CHECK_RATE); + return released; +} + +static int sys_trim(mstate m, size_t pad) { + size_t released = 0; + ensure_initialization(); + if (pad < MAX_REQUEST && is_initialized(m)) { + pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ + + if (m->topsize > pad) { + /* Shrink top space in granularity-size units, keeping at least one */ + size_t unit = mparams.granularity; + size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - + SIZE_T_ONE) * unit; + msegmentptr sp = segment_holding(m, (char*)m->top); + + if (!is_extern_segment(sp)) { + if (is_mmapped_segment(sp)) { + if (HAVE_MMAP && + sp->size >= extra && + !has_segment_link(m, sp)) { /* can't shrink if pinned */ + size_t newsize = sp->size - extra; + (void)newsize; /* placate people compiling -Wunused-variable */ + /* Prefer mremap, fall back to munmap */ + if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) || + (CALL_MUNMAP(sp->base + newsize, extra) == 0)) { + released = extra; + } + } + } + else if (HAVE_MORECORE) { + if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ + extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + { + /* Make sure end of memory is where we last set it. */ + char* old_br = (char*)(CALL_MORECORE(0)); + if (old_br == sp->base + sp->size) { + char* rel_br = (char*)(CALL_MORECORE(-extra)); + char* new_br = (char*)(CALL_MORECORE(0)); + if (rel_br != CMFAIL && new_br < old_br) + released = old_br - new_br; + } + } + RELEASE_MALLOC_GLOBAL_LOCK(); + } + } + + if (released != 0) { + sp->size -= released; + m->footprint -= released; + init_top(m, m->top, m->topsize - released); + check_top_chunk(m, m->top); + } + } + + /* Unmap any unused mmapped segments */ + if (HAVE_MMAP) + released += release_unused_segments(m); + + /* On failure, disable autotrim to avoid repeated failed future calls */ + if (released == 0 && m->topsize > m->trim_check) + m->trim_check = MAX_SIZE_T; + } + + return (released != 0)? 1 : 0; +} + +/* Consolidate and bin a chunk. Differs from exported versions + of free mainly in that the chunk need not be marked as inuse. +*/ +static void dispose_chunk(mstate m, mchunkptr p, size_t psize) { + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + mchunkptr prev; + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + m->footprint -= psize; + return; + } + prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(m, prev))) { /* consolidate backward */ + if (p != m->dv) { + unlink_chunk(m, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + m->dvsize = psize; + set_free_with_pinuse(p, psize, next); + return; + } + } + else { + CORRUPTION_ERROR_ACTION(m); + return; + } + } + if (RTCHECK(ok_address(m, next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == m->top) { + size_t tsize = m->topsize += psize; + m->top = p; + p->head = tsize | PINUSE_BIT; + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + return; + } + else if (next == m->dv) { + size_t dsize = m->dvsize += psize; + m->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + return; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(m, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == m->dv) { + m->dvsize = psize; + return; + } + } + } + else { + set_free_with_pinuse(p, psize, next); + } + insert_chunk(m, p, psize); + } + else { + CORRUPTION_ERROR_ACTION(m); + } +} + +/* ---------------------------- malloc --------------------------- */ + +/* allocate a large request from the best fitting chunk in a treebin */ +static void* tmalloc_large(mstate m, size_t nb) { + tchunkptr v = 0; + size_t rsize = -nb; /* Unsigned negation */ + tchunkptr t; + bindex_t idx; + compute_tree_index(nb, idx); + if ((t = *treebin_at(m, idx)) != 0) { + /* Traverse tree for this bin looking for node with size == nb */ + size_t sizebits = nb << leftshift_for_tree_index(idx); + tchunkptr rst = 0; /* The deepest untaken right subtree */ + for (;;) { + tchunkptr rt; + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + v = t; + if ((rsize = trem) == 0) + break; + } + rt = t->child[1]; + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + if (rt != 0 && rt != t) + rst = rt; + if (t == 0) { + t = rst; /* set t to least subtree holding sizes > nb */ + break; + } + sizebits <<= 1; + } + } + if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */ + binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap; + if (leftbits != 0) { + bindex_t i; + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + t = *treebin_at(m, i); + } + } + + while (t != 0) { /* find smallest of tree or subtree */ + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + t = leftmost_child(t); + } + + /* If dv is a better fit, return 0 so malloc will use it */ + if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { + if (RTCHECK(ok_address(m, v))) { /* split */ + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + insert_chunk(m, r, rsize); + } + return chunk2mem(v); + } + } + CORRUPTION_ERROR_ACTION(m); + } + return 0; +} + +/* allocate a small request from the best fitting chunk in a treebin */ +static void* tmalloc_small(mstate m, size_t nb) { + tchunkptr t, v; + size_t rsize; + bindex_t i; + binmap_t leastbit = least_bit(m->treemap); + compute_bit2idx(leastbit, i); + v = t = *treebin_at(m, i); + rsize = chunksize(t) - nb; + + while ((t = leftmost_child(t)) != 0) { + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + } + + if (RTCHECK(ok_address(m, v))) { + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(m, r, rsize); + } + return chunk2mem(v); + } + } + + CORRUPTION_ERROR_ACTION(m); + return 0; +} + +#if !ONLY_MSPACES + +void* dlmalloc(size_t bytes) { + /* + Basic algorithm: + If a small request (< 256 bytes minus per-chunk overhead): + 1. If one exists, use a remainderless chunk in associated smallbin. + (Remainderless means that there are too few excess bytes to + represent as a chunk.) + 2. If it is big enough, use the dv chunk, which is normally the + chunk adjacent to the one used for the most recent small request. + 3. If one exists, split the smallest available chunk in a bin, + saving remainder in dv. + 4. If it is big enough, use the top chunk. + 5. If available, get memory from system and use it + Otherwise, for a large request: + 1. Find the smallest available binned chunk that fits, and use it + if it is better fitting than dv chunk, splitting if necessary. + 2. If better fitting than any binned chunk, use the dv chunk. + 3. If it is big enough, use the top chunk. + 4. If request size >= mmap threshold, try to directly mmap this chunk. + 5. If available, get memory from system and use it + + The ugly goto's here ensure that postaction occurs along all paths. + */ + +#if USE_LOCKS + ensure_initialization(); /* initialize in sys_alloc if not using locks */ +#endif + + if (!PREACTION(gm)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = gm->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(gm, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(gm, b, p, idx); + set_inuse_and_pinuse(gm, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb > gm->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(gm, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(gm, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(gm, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(gm, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + + if (nb <= gm->dvsize) { + size_t rsize = gm->dvsize - nb; + mchunkptr p = gm->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = gm->dv = chunk_plus_offset(p, nb); + gm->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + } + else { /* exhaust dv */ + size_t dvs = gm->dvsize; + gm->dvsize = 0; + gm->dv = 0; + set_inuse_and_pinuse(gm, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb < gm->topsize) { /* Split top */ + size_t rsize = gm->topsize -= nb; + mchunkptr p = gm->top; + mchunkptr r = gm->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + mem = chunk2mem(p); + check_top_chunk(gm, gm->top); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + mem = sys_alloc(gm, nb); + + postaction: + POSTACTION(gm); + return mem; + } + + return 0; +} + +/* ---------------------------- free --------------------------- */ + +void dlfree(void* mem) { + /* + Consolidate freed chunks with preceeding or succeeding bordering + free chunks, if they exist, and then place in a bin. Intermixed + with special cases for top, dv, mmapped chunks, and usage errors. + */ + + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } +#else /* FOOTERS */ +#define fm gm +#endif /* FOOTERS */ + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +#if !FOOTERS +#undef fm +#endif /* FOOTERS */ +} + +void* dlcalloc(size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = dlmalloc(req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +#endif /* !ONLY_MSPACES */ + +/* ------------ Internal support for realloc, memalign, etc -------------- */ + +/* Try to realloc; only in-place unless can_move true */ +static mchunkptr try_realloc_chunk(mstate m, mchunkptr p, size_t nb, + int can_move) { + mchunkptr newp = 0; + size_t oldsize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, oldsize); + if (RTCHECK(ok_address(m, p) && ok_inuse(p) && + ok_next(p, next) && ok_pinuse(next))) { + if (is_mmapped(p)) { + newp = mmap_resize(m, p, nb, can_move); + } + else if (oldsize >= nb) { /* already big enough */ + size_t rsize = oldsize - nb; + if (rsize >= MIN_CHUNK_SIZE) { /* split off remainder */ + mchunkptr r = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, r, rsize); + dispose_chunk(m, r, rsize); + } + newp = p; + } + else if (next == m->top) { /* extend into top */ + if (oldsize + m->topsize > nb) { + size_t newsize = oldsize + m->topsize; + size_t newtopsize = newsize - nb; + mchunkptr newtop = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + newtop->head = newtopsize |PINUSE_BIT; + m->top = newtop; + m->topsize = newtopsize; + newp = p; + } + } + else if (next == m->dv) { /* extend into dv */ + size_t dvs = m->dvsize; + if (oldsize + dvs >= nb) { + size_t dsize = oldsize + dvs - nb; + if (dsize >= MIN_CHUNK_SIZE) { + mchunkptr r = chunk_plus_offset(p, nb); + mchunkptr n = chunk_plus_offset(r, dsize); + set_inuse(m, p, nb); + set_size_and_pinuse_of_free_chunk(r, dsize); + clear_pinuse(n); + m->dvsize = dsize; + m->dv = r; + } + else { /* exhaust dv */ + size_t newsize = oldsize + dvs; + set_inuse(m, p, newsize); + m->dvsize = 0; + m->dv = 0; + } + newp = p; + } + } + else if (!cinuse(next)) { /* extend into next free chunk */ + size_t nextsize = chunksize(next); + if (oldsize + nextsize >= nb) { + size_t rsize = oldsize + nextsize - nb; + unlink_chunk(m, next, nextsize); + if (rsize < MIN_CHUNK_SIZE) { + size_t newsize = oldsize + nextsize; + set_inuse(m, p, newsize); + } + else { + mchunkptr r = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, r, rsize); + dispose_chunk(m, r, rsize); + } + newp = p; + } + } + } + else { + USAGE_ERROR_ACTION(m, chunk2mem(p)); + } + return newp; +} + +static void* internal_memalign(mstate m, size_t alignment, size_t bytes) { + void* mem = 0; + if (alignment < MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */ + alignment = MIN_CHUNK_SIZE; + if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */ + size_t a = MALLOC_ALIGNMENT << 1; + while (a < alignment) a <<= 1; + alignment = a; + } + if (bytes >= MAX_REQUEST - alignment) { + if (m != 0) { /* Test isn't needed but avoids compiler warning */ + MALLOC_FAILURE_ACTION; + } + } + else { + size_t nb = request2size(bytes); + size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD; + mem = internal_malloc(m, req); + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (PREACTION(m)) + return 0; + if ((((size_t)(mem)) & (alignment - 1)) != 0) { /* misaligned */ + /* + Find an aligned spot inside chunk. Since we need to give + back leading space in a chunk of at least MIN_CHUNK_SIZE, if + the first calculation places us at a spot with less than + MIN_CHUNK_SIZE leader, we can move to the next aligned spot. + We've allocated enough total room so that this is always + possible. + */ + char* br = (char*)mem2chunk((size_t)(((size_t)((char*)mem + alignment - + SIZE_T_ONE)) & + -alignment)); + char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)? + br : br+alignment; + mchunkptr newp = (mchunkptr)pos; + size_t leadsize = pos - (char*)(p); + size_t newsize = chunksize(p) - leadsize; + + if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */ + newp->prev_foot = p->prev_foot + leadsize; + newp->head = newsize; + } + else { /* Otherwise, give back leader, use the rest */ + set_inuse(m, newp, newsize); + set_inuse(m, p, leadsize); + dispose_chunk(m, p, leadsize); + } + p = newp; + } + + /* Give back spare room at the end */ + if (!is_mmapped(p)) { + size_t size = chunksize(p); + if (size > nb + MIN_CHUNK_SIZE) { + size_t remainder_size = size - nb; + mchunkptr remainder = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, remainder, remainder_size); + dispose_chunk(m, remainder, remainder_size); + } + } + + mem = chunk2mem(p); + assert (chunksize(p) >= nb); + assert(((size_t)mem & (alignment - 1)) == 0); + check_inuse_chunk(m, p); + POSTACTION(m); + } + } + return mem; +} + +/* + Common support for independent_X routines, handling + all of the combinations that can result. + The opts arg has: + bit 0 set if all elements are same size (using sizes[0]) + bit 1 set if elements should be zeroed +*/ +static void** ialloc(mstate m, + size_t n_elements, + size_t* sizes, + int opts, + void* chunks[]) { + + size_t element_size; /* chunksize of each element, if all same */ + size_t contents_size; /* total size of elements */ + size_t array_size; /* request size of pointer array */ + void* mem; /* malloced aggregate space */ + mchunkptr p; /* corresponding chunk */ + size_t remainder_size; /* remaining bytes while splitting */ + void** marray; /* either "chunks" or malloced ptr array */ + mchunkptr array_chunk; /* chunk for malloced ptr array */ + flag_t was_enabled; /* to disable mmap */ + size_t size; + size_t i; + + ensure_initialization(); + /* compute array length, if needed */ + if (chunks != 0) { + if (n_elements == 0) + return chunks; /* nothing to do */ + marray = chunks; + array_size = 0; + } + else { + /* if empty req, must still return chunk representing empty array */ + if (n_elements == 0) + return (void**)internal_malloc(m, 0); + marray = 0; + array_size = request2size(n_elements * (sizeof(void*))); + } + + /* compute total element size */ + if (opts & 0x1) { /* all-same-size */ + element_size = request2size(*sizes); + contents_size = n_elements * element_size; + } + else { /* add up all the sizes */ + element_size = 0; + contents_size = 0; + for (i = 0; i != n_elements; ++i) + contents_size += request2size(sizes[i]); + } + + size = contents_size + array_size; + + /* + Allocate the aggregate chunk. First disable direct-mmapping so + malloc won't use it, since we would not be able to later + free/realloc space internal to a segregated mmap region. + */ + was_enabled = use_mmap(m); + disable_mmap(m); + mem = internal_malloc(m, size - CHUNK_OVERHEAD); + if (was_enabled) + enable_mmap(m); + if (mem == 0) + return 0; + + if (PREACTION(m)) return 0; + p = mem2chunk(mem); + remainder_size = chunksize(p); + + assert(!is_mmapped(p)); + + if (opts & 0x2) { /* optionally clear the elements */ + memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size); + } + + /* If not provided, allocate the pointer array as final part of chunk */ + if (marray == 0) { + size_t array_chunk_size; + array_chunk = chunk_plus_offset(p, contents_size); + array_chunk_size = remainder_size - contents_size; + marray = (void**) (chunk2mem(array_chunk)); + set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size); + remainder_size = contents_size; + } + + /* split out elements */ + for (i = 0; ; ++i) { + marray[i] = chunk2mem(p); + if (i != n_elements-1) { + if (element_size != 0) + size = element_size; + else + size = request2size(sizes[i]); + remainder_size -= size; + set_size_and_pinuse_of_inuse_chunk(m, p, size); + p = chunk_plus_offset(p, size); + } + else { /* the final element absorbs any overallocation slop */ + set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size); + break; + } + } + +#if DEBUG + if (marray != chunks) { + /* final element must have exactly exhausted chunk */ + if (element_size != 0) { + assert(remainder_size == element_size); + } + else { + assert(remainder_size == request2size(sizes[i])); + } + check_inuse_chunk(m, mem2chunk(marray)); + } + for (i = 0; i != n_elements; ++i) + check_inuse_chunk(m, mem2chunk(marray[i])); + +#endif /* DEBUG */ + + POSTACTION(m); + return marray; +} + +/* Try to free all pointers in the given array. + Note: this could be made faster, by delaying consolidation, + at the price of disabling some user integrity checks, We + still optimize some consolidations by combining adjacent + chunks before freeing, which will occur often if allocated + with ialloc or the array is sorted. +*/ +static size_t internal_bulk_free(mstate m, void* array[], size_t nelem) { + size_t unfreed = 0; + if (!PREACTION(m)) { + void** a; + void** fence = &(array[nelem]); + for (a = array; a != fence; ++a) { + void* mem = *a; + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t psize = chunksize(p); +#if FOOTERS + if (get_mstate_for(p) != m) { + ++unfreed; + continue; + } +#endif + check_inuse_chunk(m, p); + *a = 0; + if (RTCHECK(ok_address(m, p) && ok_inuse(p))) { + void ** b = a + 1; /* try to merge with next chunk */ + mchunkptr next = next_chunk(p); + if (b != fence && *b == chunk2mem(next)) { + size_t newsize = chunksize(next) + psize; + set_inuse(m, p, newsize); + *b = chunk2mem(p); + } + else + dispose_chunk(m, p, psize); + } + else { + CORRUPTION_ERROR_ACTION(m); + break; + } + } + } + if (should_trim(m, m->topsize)) + sys_trim(m, 0); + POSTACTION(m); + } + return unfreed; +} + +/* Traversal */ +#if MALLOC_INSPECT_ALL +static void internal_inspect_all(mstate m, + void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + if (is_initialized(m)) { + mchunkptr top = m->top; + msegmentptr s; + for (s = &m->seg; s != 0; s = s->next) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && q->head != FENCEPOST_HEAD) { + mchunkptr next = next_chunk(q); + size_t sz = chunksize(q); + size_t used; + void* start; + if (is_inuse(q)) { + used = sz - CHUNK_OVERHEAD; /* must not be mmapped */ + start = chunk2mem(q); + } + else { + used = 0; + if (is_small(sz)) { /* offset by possible bookkeeping */ + start = (void*)((char*)q + sizeof(struct malloc_chunk)); + } + else { + start = (void*)((char*)q + sizeof(struct malloc_tree_chunk)); + } + } + if (start < (void*)next) /* skip if all space is bookkeeping */ + handler(start, next, used, arg); + if (q == top) + break; + q = next; + } + } + } +} +#endif /* MALLOC_INSPECT_ALL */ + +/* ------------------ Exported realloc, memalign, etc -------------------- */ + +#if !ONLY_MSPACES + +void* dlrealloc(void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem == 0) { + mem = dlmalloc(bytes); + } + else if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } +#ifdef REALLOC_ZERO_BYTES_FREES + else if (bytes == 0) { + dlfree(oldmem); + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1); + POSTACTION(m); + if (newp != 0) { + check_inuse_chunk(m, newp); + mem = chunk2mem(newp); + } + else { + mem = internal_malloc(m, bytes); + if (mem != 0) { + size_t oc = chunksize(oldp) - overhead_for(oldp); + memcpy(mem, oldmem, (oc < bytes)? oc : bytes); + internal_free(m, oldmem); + } + } + } + } + return mem; +} + +void* dlrealloc_in_place(void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem != 0) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); + POSTACTION(m); + if (newp == oldp) { + check_inuse_chunk(m, newp); + mem = oldmem; + } + } + } + } + return mem; +} + +void* dlmemalign(size_t alignment, size_t bytes) { + if (alignment <= MALLOC_ALIGNMENT) { + return dlmalloc(bytes); + } + return internal_memalign(gm, alignment, bytes); +} + +int dlposix_memalign(void** pp, size_t alignment, size_t bytes) { + void* mem = 0; + if (alignment == MALLOC_ALIGNMENT) + mem = dlmalloc(bytes); + else { + size_t d = alignment / sizeof(void*); + size_t r = alignment % sizeof(void*); + if (r != 0 || d == 0 || (d & (d-SIZE_T_ONE)) != 0) + return EINVAL; + else if (bytes <= MAX_REQUEST - alignment) { + if (alignment < MIN_CHUNK_SIZE) + alignment = MIN_CHUNK_SIZE; + mem = internal_memalign(gm, alignment, bytes); + } + } + if (mem == 0) + return ENOMEM; + else { + *pp = mem; + return 0; + } +} + +void* dlvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, bytes); +} + +void* dlpvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); +} + +void** dlindependent_calloc(size_t n_elements, size_t elem_size, + void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + return ialloc(gm, n_elements, &sz, 3, chunks); +} + +void** dlindependent_comalloc(size_t n_elements, size_t sizes[], + void* chunks[]) { + return ialloc(gm, n_elements, sizes, 0, chunks); +} + +size_t dlbulk_free(void* array[], size_t nelem) { + return internal_bulk_free(gm, array, nelem); +} + +#if MALLOC_INSPECT_ALL +void dlmalloc_inspect_all(void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + ensure_initialization(); + if (!PREACTION(gm)) { + internal_inspect_all(gm, handler, arg); + POSTACTION(gm); + } +} +#endif /* MALLOC_INSPECT_ALL */ + +int dlmalloc_trim(size_t pad) { + int result = 0; + ensure_initialization(); + if (!PREACTION(gm)) { + result = sys_trim(gm, pad); + POSTACTION(gm); + } + return result; +} + +size_t dlmalloc_footprint(void) { + return gm->footprint; +} + +size_t dlmalloc_max_footprint(void) { + return gm->max_footprint; +} + +size_t dlmalloc_footprint_limit(void) { + size_t maf = gm->footprint_limit; + return maf == 0 ? MAX_SIZE_T : maf; +} + +size_t dlmalloc_set_footprint_limit(size_t bytes) { + size_t result; /* invert sense of 0 */ + if (bytes == 0) + result = granularity_align(1); /* Use minimal size */ + if (bytes == MAX_SIZE_T) + result = 0; /* disable */ + else + result = granularity_align(bytes); + return gm->footprint_limit = result; +} + +#if !NO_MALLINFO +struct mallinfo dlmallinfo(void) { + return internal_mallinfo(gm); +} +#endif /* NO_MALLINFO */ + +#if !NO_MALLOC_STATS +void dlmalloc_stats() { + internal_malloc_stats(gm); +} +#endif /* NO_MALLOC_STATS */ + +int dlmallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +size_t dlmalloc_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +#endif /* !ONLY_MSPACES */ + +/* ----------------------------- user mspaces ---------------------------- */ + +#if MSPACES + +static mstate init_user_mstate(char* tbase, size_t tsize) { + size_t msize = pad_request(sizeof(struct malloc_state)); + mchunkptr mn; + mchunkptr msp = align_as_chunk(tbase); + mstate m = (mstate)(chunk2mem(msp)); + memset(m, 0, msize); + (void)INITIAL_LOCK(&m->mutex); + msp->head = (msize|INUSE_BITS); + m->seg.base = m->least_addr = tbase; + m->seg.size = m->footprint = m->max_footprint = tsize; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + m->mflags = mparams.default_mflags; + m->extp = 0; + m->exts = 0; + disable_contiguous(m); + init_bins(m); + mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE); + check_top_chunk(m, m->top); + return m; +} + +mspace create_mspace(size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + size_t rs = ((capacity == 0)? mparams.granularity : + (capacity + TOP_FOOT_SIZE + msize)); + size_t tsize = granularity_align(rs); + char* tbase = (char*)(CALL_MMAP(tsize)); + if (tbase != CMFAIL) { + m = init_user_mstate(tbase, tsize); + m->seg.sflags = USE_MMAP_BIT; + set_lock(m, locked); + } + } + return (mspace)m; +} + +mspace create_mspace_with_base(void* base, size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity > msize + TOP_FOOT_SIZE && + capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + m = init_user_mstate((char*)base, capacity); + m->seg.sflags = EXTERN_BIT; + set_lock(m, locked); + } + return (mspace)m; +} + +int mspace_track_large_chunks(mspace msp, int enable) { + int ret = 0; + mstate ms = (mstate)msp; + if (!PREACTION(ms)) { + if (!use_mmap(ms)) { + ret = 1; + } + if (!enable) { + enable_mmap(ms); + } else { + disable_mmap(ms); + } + POSTACTION(ms); + } + return ret; +} + +size_t destroy_mspace(mspace msp) { + size_t freed = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + msegmentptr sp = &ms->seg; + (void)DESTROY_LOCK(&ms->mutex); /* destroy before unmapped */ + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + flag_t flag = sp->sflags; + (void)base; /* placate people compiling -Wunused-variable */ + sp = sp->next; + if ((flag & USE_MMAP_BIT) && !(flag & EXTERN_BIT) && + CALL_MUNMAP(base, size) == 0) + freed += size; + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return freed; +} + +/* + mspace versions of routines are near-clones of the global + versions. This is not so nice but better than the alternatives. +*/ + +void* mspace_malloc(mspace msp, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (!PREACTION(ms)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = ms->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(ms, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(ms, b, p, idx); + set_inuse_and_pinuse(ms, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb > ms->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(ms, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(ms, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(ms, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(ms, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + + if (nb <= ms->dvsize) { + size_t rsize = ms->dvsize - nb; + mchunkptr p = ms->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = ms->dv = chunk_plus_offset(p, nb); + ms->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + } + else { /* exhaust dv */ + size_t dvs = ms->dvsize; + ms->dvsize = 0; + ms->dv = 0; + set_inuse_and_pinuse(ms, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb < ms->topsize) { /* Split top */ + size_t rsize = ms->topsize -= nb; + mchunkptr p = ms->top; + mchunkptr r = ms->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + mem = chunk2mem(p); + check_top_chunk(ms, ms->top); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + mem = sys_alloc(ms, nb); + + postaction: + POSTACTION(ms); + return mem; + } + + return 0; +} + +void mspace_free(mspace msp, void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + (void)msp; /* placate people compiling -Wunused */ +#else /* FOOTERS */ + mstate fm = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +} + +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = internal_malloc(ms, req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem == 0) { + mem = mspace_malloc(msp, bytes); + } + else if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } +#ifdef REALLOC_ZERO_BYTES_FREES + else if (bytes == 0) { + mspace_free(msp, oldmem); + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = (mstate)msp; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1); + POSTACTION(m); + if (newp != 0) { + check_inuse_chunk(m, newp); + mem = chunk2mem(newp); + } + else { + mem = mspace_malloc(m, bytes); + if (mem != 0) { + size_t oc = chunksize(oldp) - overhead_for(oldp); + memcpy(mem, oldmem, (oc < bytes)? oc : bytes); + mspace_free(m, oldmem); + } + } + } + } + return mem; +} + +void* mspace_realloc_in_place(mspace msp, void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem != 0) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = (mstate)msp; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + (void)msp; /* placate people compiling -Wunused */ + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); + POSTACTION(m); + if (newp == oldp) { + check_inuse_chunk(m, newp); + mem = oldmem; + } + } + } + } + return mem; +} + +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (alignment <= MALLOC_ALIGNMENT) + return mspace_malloc(msp, bytes); + return internal_memalign(ms, alignment, bytes); +} + +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, &sz, 3, chunks); +} + +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, sizes, 0, chunks); +} + +size_t mspace_bulk_free(mspace msp, void* array[], size_t nelem) { + return internal_bulk_free((mstate)msp, array, nelem); +} + +#if MALLOC_INSPECT_ALL +void mspace_inspect_all(mspace msp, + void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + internal_inspect_all(ms, handler, arg); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} +#endif /* MALLOC_INSPECT_ALL */ + +int mspace_trim(mspace msp, size_t pad) { + int result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + result = sys_trim(ms, pad); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +#if !NO_MALLOC_STATS +void mspace_malloc_stats(mspace msp) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + internal_malloc_stats(ms); + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} +#endif /* NO_MALLOC_STATS */ + +size_t mspace_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_max_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->max_footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_footprint_limit(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + size_t maf = ms->footprint_limit; + result = (maf == 0) ? MAX_SIZE_T : maf; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_set_footprint_limit(mspace msp, size_t bytes) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (bytes == 0) + result = granularity_align(1); /* Use minimal size */ + if (bytes == MAX_SIZE_T) + result = 0; /* disable */ + else + result = granularity_align(bytes); + ms->footprint_limit = result; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +#if !NO_MALLINFO +struct mallinfo mspace_mallinfo(mspace msp) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + } + return internal_mallinfo(ms); +} +#endif /* NO_MALLINFO */ + +size_t mspace_usable_size(const void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +int mspace_mallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* MSPACES */ + + +/* -------------------- Alternative MORECORE functions ------------------- */ + +/* + Guidelines for creating a custom version of MORECORE: + + * For best performance, MORECORE should allocate in multiples of pagesize. + * MORECORE may allocate more memory than requested. (Or even less, + but this will usually result in a malloc failure.) + * MORECORE must not allocate memory when given argument zero, but + instead return one past the end address of memory from previous + nonzero call. + * For best performance, consecutive calls to MORECORE with positive + arguments should return increasing addresses, indicating that + space has been contiguously extended. + * Even though consecutive calls to MORECORE need not return contiguous + addresses, it must be OK for malloc'ed chunks to span multiple + regions in those cases where they do happen to be contiguous. + * MORECORE need not handle negative arguments -- it may instead + just return MFAIL when given negative arguments. + Negative arguments are always multiples of pagesize. MORECORE + must not misinterpret negative args as large positive unsigned + args. You can suppress all such calls from even occurring by defining + MORECORE_CANNOT_TRIM, + + As an example alternative MORECORE, here is a custom allocator + kindly contributed for pre-OSX macOS. It uses virtually but not + necessarily physically contiguous non-paged memory (locked in, + present and won't get swapped out). You can use it by uncommenting + this section, adding some #includes, and setting up the appropriate + defines above: + + #define MORECORE osMoreCore + + There is also a shutdown routine that should somehow be called for + cleanup upon program exit. + + #define MAX_POOL_ENTRIES 100 + #define MINIMUM_MORECORE_SIZE (64 * 1024U) + static int next_os_pool; + void *our_os_pools[MAX_POOL_ENTRIES]; + + void *osMoreCore(int size) + { + void *ptr = 0; + static void *sbrk_top = 0; + + if (size > 0) + { + if (size < MINIMUM_MORECORE_SIZE) + size = MINIMUM_MORECORE_SIZE; + if (CurrentExecutionLevel() == kTaskLevel) + ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); + if (ptr == 0) + { + return (void *) MFAIL; + } + // save ptrs so they can be freed during cleanup + our_os_pools[next_os_pool] = ptr; + next_os_pool++; + ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); + sbrk_top = (char *) ptr + size; + return ptr; + } + else if (size < 0) + { + // we don't currently support shrink behavior + return (void *) MFAIL; + } + else + { + return sbrk_top; + } + } + + // cleanup any allocated memory pools + // called as last thing before shutting down driver + + void osCleanupMem(void) + { + void **ptr; + + for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) + if (*ptr) + { + PoolDeallocate(*ptr); + *ptr = 0; + } + } + +*/ + + +/* ----------------------------------------------------------------------- +History: + v2.8.6 Wed Aug 29 06:57:58 2012 Doug Lea + * fix bad comparison in dlposix_memalign + * don't reuse adjusted asize in sys_alloc + * add LOCK_AT_FORK -- thanks to Kirill Artamonov for the suggestion + * reduce compiler warnings -- thanks to all who reported/suggested these + + v2.8.5 Sun May 22 10:26:02 2011 Doug Lea (dl at gee) + * Always perform unlink checks unless INSECURE + * Add posix_memalign. + * Improve realloc to expand in more cases; expose realloc_in_place. + Thanks to Peter Buhr for the suggestion. + * Add footprint_limit, inspect_all, bulk_free. Thanks + to Barry Hayes and others for the suggestions. + * Internal refactorings to avoid calls while holding locks + * Use non-reentrant locks by default. Thanks to Roland McGrath + for the suggestion. + * Small fixes to mspace_destroy, reset_on_error. + * Various configuration extensions/changes. Thanks + to all who contributed these. + + V2.8.4a Thu Apr 28 14:39:43 2011 (dl at gee.cs.oswego.edu) + * Update Creative Commons URL + + V2.8.4 Wed May 27 09:56:23 2009 Doug Lea (dl at gee) + * Use zeros instead of prev foot for is_mmapped + * Add mspace_track_large_chunks; thanks to Jean Brouwers + * Fix set_inuse in internal_realloc; thanks to Jean Brouwers + * Fix insufficient sys_alloc padding when using 16byte alignment + * Fix bad error check in mspace_footprint + * Adaptations for ptmalloc; thanks to Wolfram Gloger. + * Reentrant spin locks; thanks to Earl Chew and others + * Win32 improvements; thanks to Niall Douglas and Earl Chew + * Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options + * Extension hook in malloc_state + * Various small adjustments to reduce warnings on some compilers + * Various configuration extensions/changes for more platforms. Thanks + to all who contributed these. + + V2.8.3 Thu Sep 22 11:16:32 2005 Doug Lea (dl at gee) + * Add max_footprint functions + * Ensure all appropriate literals are size_t + * Fix conditional compilation problem for some #define settings + * Avoid concatenating segments with the one provided + in create_mspace_with_base + * Rename some variables to avoid compiler shadowing warnings + * Use explicit lock initialization. + * Better handling of sbrk interference. + * Simplify and fix segment insertion, trimming and mspace_destroy + * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x + * Thanks especially to Dennis Flanagan for help on these. + + V2.8.2 Sun Jun 12 16:01:10 2005 Doug Lea (dl at gee) + * Fix memalign brace error. + + V2.8.1 Wed Jun 8 16:11:46 2005 Doug Lea (dl at gee) + * Fix improper #endif nesting in C++ + * Add explicit casts needed for C++ + + V2.8.0 Mon May 30 14:09:02 2005 Doug Lea (dl at gee) + * Use trees for large bins + * Support mspaces + * Use segments to unify sbrk-based and mmap-based system allocation, + removing need for emulation on most platforms without sbrk. + * Default safety checks + * Optional footer checks. Thanks to William Robertson for the idea. + * Internal code refactoring + * Incorporate suggestions and platform-specific changes. + Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas, + Aaron Bachmann, Emery Berger, and others. + * Speed up non-fastbin processing enough to remove fastbins. + * Remove useless cfree() to avoid conflicts with other apps. + * Remove internal memcpy, memset. Compilers handle builtins better. + * Remove some options that no one ever used and rename others. + + V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) + * Fix malloc_state bitmap array misdeclaration + + V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) + * Allow tuning of FIRST_SORTED_BIN_SIZE + * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. + * Better detection and support for non-contiguousness of MORECORE. + Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger + * Bypass most of malloc if no frees. Thanks To Emery Berger. + * Fix freeing of old top non-contiguous chunk im sysmalloc. + * Raised default trim and map thresholds to 256K. + * Fix mmap-related #defines. Thanks to Lubos Lunak. + * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. + * Branch-free bin calculation + * Default trim and mmap thresholds now 256K. + + V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) + * Introduce independent_comalloc and independent_calloc. + Thanks to Michael Pachos for motivation and help. + * Make optional .h file available + * Allow > 2GB requests on 32bit systems. + * new WIN32 sbrk, mmap, munmap, lock code from . + Thanks also to Andreas Mueller , + and Anonymous. + * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for + helping test this.) + * memalign: check alignment arg + * realloc: don't try to shift chunks backwards, since this + leads to more fragmentation in some programs and doesn't + seem to help in any others. + * Collect all cases in malloc requiring system memory into sysmalloc + * Use mmap as backup to sbrk + * Place all internal state in malloc_state + * Introduce fastbins (although similar to 2.5.1) + * Many minor tunings and cosmetic improvements + * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK + * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS + Thanks to Tony E. Bennett and others. + * Include errno.h to support default failure action. + + V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) + * return null for negative arguments + * Added Several WIN32 cleanups from Martin C. Fong + * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' + (e.g. WIN32 platforms) + * Cleanup header file inclusion for WIN32 platforms + * Cleanup code to avoid Microsoft Visual C++ compiler complaints + * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing + memory allocation routines + * Set 'malloc_getpagesize' for WIN32 platforms (needs more work) + * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to + usage of 'assert' in non-WIN32 code + * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to + avoid infinite loop + * Always call 'fREe()' rather than 'free()' + + V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) + * Fixed ordering problem with boundary-stamping + + V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) + * Added pvalloc, as recommended by H.J. Liu + * Added 64bit pointer support mainly from Wolfram Gloger + * Added anonymously donated WIN32 sbrk emulation + * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen + * malloc_extend_top: fix mask error that caused wastage after + foreign sbrks + * Add linux mremap support code from HJ Liu + + V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) + * Integrated most documentation with the code. + * Add support for mmap, with help from + Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Use last_remainder in more cases. + * Pack bins using idea from colin@nyx10.cs.du.edu + * Use ordered bins instead of best-fit threshhold + * Eliminate block-local decls to simplify tracing and debugging. + * Support another case of realloc via move into top + * Fix error occuring when initial sbrk_base not word-aligned. + * Rely on page size for units instead of SBRK_UNIT to + avoid surprises about sbrk alignment conventions. + * Add mallinfo, mallopt. Thanks to Raymond Nijssen + (raymond@es.ele.tue.nl) for the suggestion. + * Add `pad' argument to malloc_trim and top_pad mallopt parameter. + * More precautions for cases where other routines call sbrk, + courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Added macros etc., allowing use in linux libc from + H.J. Lu (hjl@gnu.ai.mit.edu) + * Inverted this history list + + V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) + * Re-tuned and fixed to behave more nicely with V2.6.0 changes. + * Removed all preallocation code since under current scheme + the work required to undo bad preallocations exceeds + the work saved in good cases for most test programs. + * No longer use return list or unconsolidated bins since + no scheme using them consistently outperforms those that don't + given above changes. + * Use best fit for very large chunks to prevent some worst-cases. + * Added some support for debugging + + V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) + * Removed footers when chunks are in use. Thanks to + Paul Wilson (wilson@cs.texas.edu) for the suggestion. + + V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) + * Added malloc_trim, with help from Wolfram Gloger + (wmglo@Dent.MED.Uni-Muenchen.DE). + + V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) + + V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) + * realloc: try to expand in both directions + * malloc: swap order of clean-bin strategy; + * realloc: only conditionally expand backwards + * Try not to scavenge used bins + * Use bin counts as a guide to preallocation + * Occasionally bin return list chunks in first scan + * Add a few optimizations from colin@nyx10.cs.du.edu + + V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) + * faster bin computation & slightly different binning + * merged all consolidations to one part of malloc proper + (eliminating old malloc_find_space & malloc_clean_bin) + * Scan 2 returns chunks (not just 1) + * Propagate failure in realloc if malloc returns 0 + * Add stuff to allow compilation on non-ANSI compilers + from kpv@research.att.com + + V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) + * removed potential for odd address access in prev_chunk + * removed dependency on getpagesize.h + * misc cosmetics and a bit more internal documentation + * anticosmetics: mangled names in macros to evade debugger strangeness + * tested on sparc, hp-700, dec-mips, rs6000 + with gcc & native cc (hp, dec only) allowing + Detlefs & Zorn comparison study (in SIGPLAN Notices.) + + Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) + * Based loosely on libg++-1.2X malloc. (It retains some of the overall + structure of old version, but most details differ.) + +*/ \ No newline at end of file diff --git a/tier0/etwprof.cpp b/tier0/etwprof.cpp new file mode 100644 index 0000000..9d11444 --- /dev/null +++ b/tier0/etwprof.cpp @@ -0,0 +1,355 @@ +// Copyright Valve Corporation, All rights reserved. +// +// ETW (Event Tracing for Windows) profiling helpers. +// +// This allows easy insertion of Generic Event markers into ETW/xperf tracing +// which then aids in analyzing the traces and finding performance problems. + +#include "pch_tier0.h" +#include "tier0/etwprof.h" + +#include + +#ifdef ETW_MARKS_ENABLED + +// After building the DLL if it has never been registered on this machine or +// if the providers have changed you need to go: +// xcopy /y %vgame%\bin\tier0.dll %temp% +// wevtutil um %vgame%\..\src\tier0\ValveETWProvider.man +// wevtutil im %vgame%\..\src\tier0\ValveETWProvider.man + +#include "winlite.h" +// These are defined in evntrace.h but you need a Vista+ Windows +// SDK to have them available, so I define them here. +#define EVENT_CONTROL_CODE_DISABLE_PROVIDER 0 +#define EVENT_CONTROL_CODE_ENABLE_PROVIDER 1 +#define EVENT_CONTROL_CODE_CAPTURE_STATE 2 + +// EVNTAPI is used in evntprov.h which is included by ValveETWProviderEvents.h +// We define EVNTAPI without the DECLSPEC_IMPORT specifier so that +// we can implement these functions locally instead of using the import library, +// and can therefore still run on Windows XP. +#define EVNTAPI __stdcall +// Include the event register/write/unregister macros compiled from the manifest +// file. Note that this includes evntprov.h which requires a Vista+ Windows SDK +// which we don't currently have, so evntprov.h is checked in. +#include "ValveETWProviderEvents.h" + +// Typedefs for use with GetProcAddress +typedef ULONG(__stdcall *tEventRegister)(LPCGUID ProviderId, + PENABLECALLBACK EnableCallback, + PVOID CallbackContext, + PREGHANDLE RegHandle); +typedef ULONG(__stdcall *tEventWrite)(REGHANDLE RegHandle, + PCEVENT_DESCRIPTOR EventDescriptor, + ULONG UserDataCount, + PEVENT_DATA_DESCRIPTOR UserData); +typedef ULONG(__stdcall *tEventUnregister)(REGHANDLE RegHandle); + +// Helper class to dynamically load Advapi32.dll, find the ETW functions, +// register the providers if possible, and get the performance counter +// frequency. +class CETWRegister { + public: + CETWRegister() { + QueryPerformanceFrequency(&m_frequency); + + // Find Advapi32.dll. This should always succeed. + HMODULE pAdvapiDLL = LoadLibraryW(L"Advapi32.dll"); + if (pAdvapiDLL) { + // Try to find the ETW functions. This will fail on XP. + m_pEventRegister = + (tEventRegister)GetProcAddress(pAdvapiDLL, "EventRegister"); + m_pEventWrite = (tEventWrite)GetProcAddress(pAdvapiDLL, "EventWrite"); + m_pEventUnregister = + (tEventUnregister)GetProcAddress(pAdvapiDLL, "EventUnregister"); + + // Register two ETW providers. If registration fails then the event + // logging calls will fail. On XP these calls will do nothing. On Vista + // and above, if these providers have been enabled by xperf or logman then + // the VALVE_FRAMERATE_Context and VALVE_MAIN_Context globals will be + // modified like this: + // MatchAnyKeyword: 0xffffffffffffffff + // IsEnabled: 1 + // Level: 255 + // In other words, fully enabled. + + EventRegisterValve_FrameRate(); + EventRegisterValve_ServerFrameRate(); + EventRegisterValve_Main(); + EventRegisterValve_Input(); + EventRegisterValve_Network(); + + // Emit the thread ID for the main thread. This also indicates that + // the main provider is initialized. + EventWriteThread_ID(GetCurrentThreadId(), "Main thread"); + // Emit an input system event so we know that it is active. + EventWriteKey_down("Valve input provider initialized.", 0, 0); + } + } + ~CETWRegister() { + // Unregister our providers. + EventUnregisterValve_Network(); + EventUnregisterValve_Input(); + EventUnregisterValve_Main(); + EventUnregisterValve_ServerFrameRate(); + EventUnregisterValve_FrameRate(); + } + + tEventRegister m_pEventRegister; + tEventWrite m_pEventWrite; + tEventUnregister m_pEventUnregister; + + // QPC frequency + LARGE_INTEGER m_frequency; + +} g_ETWRegister; + +// Redirector function for EventRegister. Called by macros in +// ValveETWProviderEvents.h +ULONG EVNTAPI EventRegister(LPCGUID ProviderId, PENABLECALLBACK EnableCallback, + PVOID CallbackContext, PREGHANDLE RegHandle) { + if (g_ETWRegister.m_pEventRegister) + return g_ETWRegister.m_pEventRegister(ProviderId, EnableCallback, + CallbackContext, RegHandle); + + return 0; +} + +// Redirector function for EventWrite. Called by macros in +// ValveETWProviderEvents.h +ULONG EVNTAPI EventWrite(REGHANDLE RegHandle, + PCEVENT_DESCRIPTOR EventDescriptor, + ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData) { + if (g_ETWRegister.m_pEventWrite) + return g_ETWRegister.m_pEventWrite(RegHandle, EventDescriptor, + UserDataCount, UserData); + return 0; +} + +// Redirector function for EventUnregister. Called by macros in +// ValveETWProviderEvents.h +ULONG EVNTAPI EventUnregister(REGHANDLE RegHandle) { + if (g_ETWRegister.m_pEventUnregister) + return g_ETWRegister.m_pEventUnregister(RegHandle); + return 0; +} + +// Call QueryPerformanceCounter +static int64 GetQPCTime() { + LARGE_INTEGER time; + + QueryPerformanceCounter(&time); + return time.QuadPart; +} + +// Convert a QueryPerformanceCounter delta into milliseconds +static float QPCToMS(int64 nDelta) { + // Convert from a QPC delta to seconds. + float flSeconds = + (float)(nDelta / double(g_ETWRegister.m_frequency.QuadPart)); + + // Convert from seconds to milliseconds + return flSeconds * 1000; +} + +// Public functions for emitting ETW events. + +int64 ETWMark(const char *pMessage) { + int64 nTime = GetQPCTime(); + EventWriteMark(pMessage); + return nTime; +} + +int64 ETWMarkPrintf(PRINTF_FORMAT_STRING const char *pMessage, ...) { + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + if (!VALVE_MAIN_Context.IsEnabled) { + return 0; + } + + char buffer[1000]; + va_list args; + va_start(args, pMessage); + vsprintf_s(buffer, pMessage, args); + va_end(args); + + int64 nTime = GetQPCTime(); + EventWriteMark(buffer); + return nTime; +} + +void ETWMark1F(const char *pMessage, float data1) { + EventWriteMark1F(pMessage, data1); +} + +void ETWMark2F(const char *pMessage, float data1, float data2) { + EventWriteMark2F(pMessage, data1, data2); +} + +void ETWMark3F(const char *pMessage, float data1, float data2, float data3) { + EventWriteMark3F(pMessage, data1, data2, data3); +} + +void ETWMark4F(const char *pMessage, float data1, float data2, float data3, + float data4) { + EventWriteMark4F(pMessage, data1, data2, data3, data4); +} + +void ETWMark1I(const char *pMessage, int data1) { + EventWriteMark1I(pMessage, data1); +} + +void ETWMark2I(const char *pMessage, int data1, int data2) { + EventWriteMark2I(pMessage, data1, data2); +} + +void ETWMark3I(const char *pMessage, int data1, int data2, int data3) { + EventWriteMark3I(pMessage, data1, data2, data3); +} + +void ETWMark4I(const char *pMessage, int data1, int data2, int data3, + int data4) { + EventWriteMark4I(pMessage, data1, data2, data3, data4); +} + +void ETWMark1S(const char *pMessage, const char *data1) { + EventWriteMark1S(pMessage, data1); +} + +void ETWMark2S(const char *pMessage, const char *data1, const char *data2) { + EventWriteMark2S(pMessage, data1, data2); +} + +// Track the depth of ETW Begin/End pairs. This needs to be per-thread +// if we start emitting marks on multiple threads. Using __declspec(thread) +// has some problems on Windows XP, but since these ETW functions only work +// on Vista+ that doesn't matter. +static __declspec(thread) int s_nDepth; + +int64 ETWBegin(const char *pMessage) { + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + // In this case we also avoid the potentially unreliable TLS implementation + // (for dynamically loaded DLLs) on Windows XP. + if (!VALVE_MAIN_Context.IsEnabled) { + return 0; + } + + int64 nTime = GetQPCTime(); + EventWriteStart(pMessage, s_nDepth++); + return nTime; +} + +int64 ETWEnd(const char *pMessage, int64 nStartTime) { + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + // In this case we also avoid the potentially unreliable TLS implementation + // (for dynamically loaded DLLs) on Windows XP. + if (!VALVE_MAIN_Context.IsEnabled) { + return 0; + } + + int64 nTime = GetQPCTime(); + EventWriteStop(pMessage, --s_nDepth, QPCToMS(nTime - nStartTime)); + return nTime; +} + +static int s_nRenderFrameCount; + +int ETWGetRenderFrameNumber() { return s_nRenderFrameCount; } + +// Insert a render frame marker using the Valve-FrameRate provider. +// Automatically count the frame number and frame time. Since the frame count +// and elapsed time are tracked without paying attention to the bIsServerProcess +// flag the results will be 'unexpected' if bIsServerProcess changes value +// within a process. +void ETWRenderFrameMark(bool bIsServerProcess) { + static int64 s_lastFrameTime; + + int64 nCurrentFrameTime = GetQPCTime(); + float flElapsedFrameTime = 0.0f; + if (s_nRenderFrameCount) { + flElapsedFrameTime = QPCToMS(nCurrentFrameTime - s_lastFrameTime); + } + + if (bIsServerProcess) { + EventWriteServerRenderFrameMark(s_nRenderFrameCount, flElapsedFrameTime); + } else { + EventWriteRenderFrameMark(s_nRenderFrameCount, flElapsedFrameTime); + } + + ++s_nRenderFrameCount; + s_lastFrameTime = nCurrentFrameTime; +} + +// Insert a simulation frame marker using the Valve-FrameRate provider. +// Automatically count the frame number and frame time. Since the frame count +// and elapsed time are tracked without paying attention to the bIsServerProcess +// flag the results will be 'unexpected' if bIsServerProcess changes value +// within a process. +void ETWSimFrameMark(bool bIsServerProcess) { + static int s_nFrameCount; + static int64 s_lastFrameTime; + + int64 nCurrentFrameTime = GetQPCTime(); + float flElapsedFrameTime = 0.0f; + if (s_nFrameCount) { + flElapsedFrameTime = QPCToMS(nCurrentFrameTime - s_lastFrameTime); + } + + if (bIsServerProcess) { + EventWriteServerSimFrameMark(s_nFrameCount, flElapsedFrameTime); + } else { + EventWriteSimFrameMark(s_nFrameCount, flElapsedFrameTime); + } + + ++s_nFrameCount; + s_lastFrameTime = nCurrentFrameTime; +} + +void ETWMouseDown(int whichButton, int x, int y) { + EventWriteMouse_down(whichButton, x, y); +} + +void ETWMouseUp(int whichButton, int x, int y) { + EventWriteMouse_up(whichButton, x, y); +} + +void ETWKeyDown(int nScanCode, int nVirtualCode, const char *pChar) { + EventWriteKey_down(pChar, nScanCode, nVirtualCode); +} + +void ETWSendPacket(const char *pTo, int nWireSize, int nOutSequenceNR, + int nOutSequenceNrAck) { + static int s_nCumulativeWireSize; + s_nCumulativeWireSize += nWireSize; + + EventWriteSendPacket(pTo, nWireSize, nOutSequenceNR, nOutSequenceNrAck, + s_nCumulativeWireSize); +} + +void ETWThrottled() { EventWriteThrottled(); } + +void ETWReadPacket(const char *pFrom, int nWireSize, int nInSequenceNR, + int nOutSequenceNRAck) { + static int s_nCumulativeWireSize; + s_nCumulativeWireSize += nWireSize; + + EventWriteReadPacket(pFrom, nWireSize, nInSequenceNR, nOutSequenceNRAck, + s_nCumulativeWireSize); +} + +#endif // ETW_MARKS_ENABLED diff --git a/tier0/fasttimer.cpp b/tier0/fasttimer.cpp new file mode 100644 index 0000000..914c5fc --- /dev/null +++ b/tier0/fasttimer.cpp @@ -0,0 +1,17 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" + +#include "tier0/fasttimer.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +uint64 g_ClockSpeed; // Clocks/sec +unsigned long g_dwClockSpeed; +double g_ClockSpeedMicrosecondsMultiplier; +double g_ClockSpeedMillisecondsMultiplier; +double g_ClockSpeedSecondsMultiplier; + +// Constructor init the clock speed. +CClockSpeedInit g_ClockSpeedInit; diff --git a/tier0/logging.cpp b/tier0/logging.cpp new file mode 100644 index 0000000..38acaba --- /dev/null +++ b/tier0/logging.cpp @@ -0,0 +1,667 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Logging system definitions. + +#include "pch_tier0.h" +#include "logging.h" + +#include +#include "dbg.h" +#include "threadtools.h" +#include "tier0_strtools.h" // this is from tier1, but only included for inline definition of V_isspace + +#ifdef _PS3 +#include +#endif + +////////////////////////////////////////////////////////////////////////// +// Define commonly used channels here +////////////////////////////////////////////////////////////////////////// +DEFINE_LOGGING_CHANNEL_NO_TAGS(LOG_GENERAL, "General"); + +DEFINE_LOGGING_CHANNEL_NO_TAGS(LOG_ASSERT, "Assert"); + +// Corresponds to ConMsg/ConWarning/etc. with a level <= 1. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL(LOG_CONSOLE, "Console", LCF_CONSOLE_ONLY, LS_ERROR) + ; + ADD_LOGGING_CHANNEL_TAG("Console"); +END_DEFINE_LOGGING_CHANNEL(); + +// Corresponds to DevMsg/DevWarning/etc. with a level <= 1. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL(LOG_DEVELOPER, "Developer", LCF_CONSOLE_ONLY, + LS_ERROR) + ; + ADD_LOGGING_CHANNEL_TAG("Developer"); +END_DEFINE_LOGGING_CHANNEL(); + +// Corresponds to ConMsg/ConWarning/etc. with a level >= 2. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL(LOG_DEVELOPER_CONSOLE, "DeveloperConsole", + LCF_CONSOLE_ONLY, LS_ERROR) + ; + ADD_LOGGING_CHANNEL_TAG("DeveloperVerbose"); + ADD_LOGGING_CHANNEL_TAG("Console"); +END_DEFINE_LOGGING_CHANNEL(); + +// Corresponds to DevMsg/DevWarning/etc, with a level >= 2. +// Only errors are spewed by default. +BEGIN_DEFINE_LOGGING_CHANNEL(LOG_DEVELOPER_VERBOSE, "DeveloperVerbose", + LCF_CONSOLE_ONLY, LS_ERROR, + Color(192, 128, 192, 255)) + ; + ADD_LOGGING_CHANNEL_TAG("DeveloperVerbose"); +END_DEFINE_LOGGING_CHANNEL(); + +////////////////////////////////////////////////////////////////////////// +// Globals +////////////////////////////////////////////////////////////////////////// + +// The index of the logging state used by the current thread. This defaults to +// 0 across all threads, which indicates that the global listener set should be +// used (CLoggingSystem::m_nGlobalStateIndex). +// +// NOTE: +// Because our linux TLS implementation does not support embedding a thread +// local integer in a class, the logging system must use a global thread-local +// integer. This means that we can only have one instance of CLoggingSystem, +// although we could support additional instances if we are willing to lose +// support for thread-local spew handling. There is no other reason why this +// class must be a singleton, except for the fact that there's no reason to have +// more than one in existence. +bool g_bEnforceLoggingSystemSingleton = false; + +#ifdef _PS3 +#include "tls_ps3.h" +#else // _PS3 +CTHREADLOCALINT g_nThreadLocalStateIndex; +#endif // _PS3 + +////////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////////// + +CLoggingSystem *g_pGlobalLoggingSystem = NULL; + +// This function does not get inlined due to the static variable :( +CLoggingSystem *GetGlobalLoggingSystem_Internal() { + static CLoggingSystem globalLoggingSystem; + g_pGlobalLoggingSystem = &globalLoggingSystem; + return &globalLoggingSystem; +} + +// This function can get inlined +CLoggingSystem *GetGlobalLoggingSystem() { + return (g_pGlobalLoggingSystem == NULL) ? GetGlobalLoggingSystem_Internal() + : g_pGlobalLoggingSystem; +} + +CLoggingSystem::CLoggingSystem() + : m_nChannelCount(0), + m_nChannelTagCount(0), + m_nTagNamePoolIndex(0), + m_nGlobalStateIndex(0) { + Assert(!g_bEnforceLoggingSystemSingleton); + g_bEnforceLoggingSystemSingleton = true; +#if !defined(_PS3) && !defined(POSIX) && !defined(PLATFORM_WINDOWS) + // Due to uncertain constructor ordering (g_nThreadLocalStateIndex + // may not be constructed yet so TLS index may not be available yet) + // we cannot initialize the state index here without risking + // AppVerifier errors and undefined behavior. Luckily TlsAlloc values + // are guaranteed to be zero-initialized so we don't need to zero-init, + // this, and in fact we can't for all threads. + // TLS on PS3 is zero-initialized in global ELF section + // TLS is also not accessible at this point before PRX entry point runs + g_nThreadLocalStateIndex = 0; +#endif + + m_LoggingStates[0].m_nPreviousStackEntry = -1; + + m_LoggingStates[0].m_nListenerCount = 1; + m_LoggingStates[0].m_RegisteredListeners[0] = &m_DefaultLoggingListener; + m_LoggingStates[0].m_pLoggingResponse = &m_DefaultLoggingResponse; + + // Mark all other logging state blocks as unused. + for (int i = 1; i < MAX_LOGGING_STATE_COUNT; ++i) { + m_LoggingStates[i].m_nListenerCount = -1; + } + + m_pStateMutex = NULL; +} + +CLoggingSystem::~CLoggingSystem() { + g_bEnforceLoggingSystemSingleton = false; + delete m_pStateMutex; +} + +LoggingChannelID_t CLoggingSystem::RegisterLoggingChannel( + const char *pChannelName, RegisterTagsFunc registerTagsFunc, int flags, + LoggingSeverity_t severity, Color spewColor) { + if (m_nChannelCount >= MAX_LOGGING_CHANNEL_COUNT) { + // Out of logging channels... catastrophic fail! + Log_Error(LOG_GENERAL, "Out of logging channels.\n"); + Assert(0); + return INVALID_LOGGING_CHANNEL_ID; + } else { + // Channels can be multiply defined, in which case return the ID of the + // existing channel. + for (int i = 0; i < m_nChannelCount; ++i) { + if (V_tier0_stricmp(m_RegisteredChannels[i].m_Name, pChannelName) == 0) { + // OK to call the tag registration callback; duplicates will be culled + // away. This allows multiple people to register a logging channel, and + // the union of all tags will be registered. + if (registerTagsFunc != NULL) { + registerTagsFunc(); + } + + // If a logging channel is registered multiple times, only one of the + // registrations should specify flags/severity/color. + if (m_RegisteredChannels[i].m_Flags == 0 && + m_RegisteredChannels[i].m_MinimumSeverity == LS_MESSAGE && + m_RegisteredChannels[i].m_SpewColor == UNSPECIFIED_LOGGING_COLOR) { + m_RegisteredChannels[i].m_Flags = (LoggingChannelFlags_t)flags; + m_RegisteredChannels[i].m_MinimumSeverity = severity; + m_RegisteredChannels[i].m_SpewColor = spewColor; + } else { + AssertMsg(flags == 0 || flags == m_RegisteredChannels[i].m_Flags, + "Non-zero or mismatched flags specified in logging channel " + "re-registration!"); + AssertMsg(severity == LS_MESSAGE || + severity == m_RegisteredChannels[i].m_MinimumSeverity, + "Non-default or mismatched severity specified in logging " + "channel re-registration!"); + AssertMsg(spewColor == UNSPECIFIED_LOGGING_COLOR || + spewColor == m_RegisteredChannels[i].m_SpewColor, + "Non-default or mismatched color specified in logging " + "channel re-registration!"); + } + + return m_RegisteredChannels[i].m_ID; + } + } + + m_RegisteredChannels[m_nChannelCount].m_ID = m_nChannelCount; + m_RegisteredChannels[m_nChannelCount].m_Flags = + (LoggingChannelFlags_t)flags; + m_RegisteredChannels[m_nChannelCount].m_MinimumSeverity = severity; + m_RegisteredChannels[m_nChannelCount].m_SpewColor = spewColor; + strncpy(m_RegisteredChannels[m_nChannelCount].m_Name, pChannelName, + MAX_LOGGING_IDENTIFIER_LENGTH); + + if (registerTagsFunc != NULL) { + registerTagsFunc(); + } + return m_nChannelCount++; + } +} + +LoggingChannelID_t CLoggingSystem::FindChannel(const char *pChannelName) const { + for (int i = 0; i < m_nChannelCount; ++i) { + if (V_tier0_stricmp(m_RegisteredChannels[i].m_Name, pChannelName) == 0) { + return i; + } + } + + return INVALID_LOGGING_CHANNEL_ID; +} + +void CLoggingSystem::AddTagToCurrentChannel(const char *pTagName) { + // Add tags at the head of the tag-list of the most recently added channel. + LoggingChannel_t *pChannel = &m_RegisteredChannels[m_nChannelCount]; + + // First check for duplicates + if (pChannel->HasTag(pTagName)) { + return; + } + + LoggingTag_t *pTag = AllocTag(pTagName); + + pTag->m_pNextTag = pChannel->m_pFirstTag; + pChannel->m_pFirstTag = pTag; +} + +void CLoggingSystem::SetChannelSpewLevel(LoggingChannelID_t channelID, + LoggingSeverity_t minimumSeverity) { + GetChannel(channelID)->SetSpewLevel(minimumSeverity); +} + +void CLoggingSystem::SetChannelSpewLevelByName( + const char *pName, LoggingSeverity_t minimumSeverity) { + for (int i = 0; i < m_nChannelCount; ++i) { + if (V_tier0_stricmp(m_RegisteredChannels[i].m_Name, pName) == 0) { + m_RegisteredChannels[i].SetSpewLevel(minimumSeverity); + } + } +} + +void CLoggingSystem::SetChannelSpewLevelByTag( + const char *pTag, LoggingSeverity_t minimumSeverity) { + for (int i = 0; i < m_nChannelCount; ++i) { + if (m_RegisteredChannels[i].HasTag(pTag)) { + m_RegisteredChannels[i].SetSpewLevel(minimumSeverity); + } + } +} + +void CLoggingSystem::PushLoggingState(bool bThreadLocal, bool bClearState) { + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + + int nNewState = FindUnusedStateIndex(); + // Ensure we're not out of state blocks. + Assert(nNewState != -1); + + int nCurrentState = + bThreadLocal ? (int)g_nThreadLocalStateIndex : m_nGlobalStateIndex; + + if (bClearState) { + m_LoggingStates[nNewState].m_nListenerCount = 0; + m_LoggingStates[nNewState].m_pLoggingResponse = &m_DefaultLoggingResponse; + } else { + m_LoggingStates[nNewState] = m_LoggingStates[nCurrentState]; + } + + m_LoggingStates[nNewState].m_nPreviousStackEntry = nCurrentState; + + if (bThreadLocal) { + g_nThreadLocalStateIndex = nNewState; + } else { + m_nGlobalStateIndex = nNewState; + } + + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::PopLoggingState(bool bThreadLocal) { + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + + int nCurrentState = + bThreadLocal ? (int)g_nThreadLocalStateIndex : m_nGlobalStateIndex; + + // Shouldn't be less than 0 (implies error during Push()) or 0 (implies that + // Push() was never called) + Assert(nCurrentState > 0); + + // Mark the current state as unused. + m_LoggingStates[nCurrentState].m_nListenerCount = -1; + + if (bThreadLocal) { + g_nThreadLocalStateIndex = + m_LoggingStates[nCurrentState].m_nPreviousStackEntry; + } else { + m_nGlobalStateIndex = m_LoggingStates[nCurrentState].m_nPreviousStackEntry; + } + + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::RegisterLoggingListener(ILoggingListener *pListener) { + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + // Do not overflow m_RegisteredListeners. + if (pState->m_nListenerCount >= MAX_LOGGING_LISTENER_COUNT) { + // Out of logging listener slots... catastrophic fail! + Assert(0); + } else { + pState->m_RegisteredListeners[pState->m_nListenerCount] = pListener; + ++pState->m_nListenerCount; + } + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::UnregisterLoggingListener(ILoggingListener *pListener) { + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + for (int i = 0; i < pState->m_nListenerCount; ++i) { + if (pState->m_RegisteredListeners[i] == pListener) { + // Shuffle all the listeners ahead over these, and reduce the count. + for (int j = i; j < (pState->m_nListenerCount - 1); ++j) { + pState->m_RegisteredListeners[j] = pState->m_RegisteredListeners[j + 1]; + } + pState->m_nListenerCount--; + break; + } + } + m_pStateMutex->Unlock(); +} + +bool CLoggingSystem::IsListenerRegistered(ILoggingListener *pListener) { + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + const LoggingState_t *pState = GetCurrentState(); + bool bFound = false; + for (int i = 0; i < pState->m_nListenerCount; ++i) { + if (pState->m_RegisteredListeners[i] == pListener) { + bFound = true; + break; + } + } + m_pStateMutex->Unlock(); + return bFound; +} + +void CLoggingSystem::ResetCurrentLoggingState() { + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + pState->m_nListenerCount = 0; + pState->m_pLoggingResponse = &m_DefaultLoggingResponse; + m_pStateMutex->Unlock(); +} + +void CLoggingSystem::SetLoggingResponsePolicy( + ILoggingResponsePolicy *pLoggingResponse) { + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + LoggingState_t *pState = GetCurrentState(); + if (pLoggingResponse == NULL) { + pState->m_pLoggingResponse = &m_DefaultLoggingResponse; + } else { + pState->m_pLoggingResponse = pLoggingResponse; + } + m_pStateMutex->Unlock(); +} + +LoggingResponse_t CLoggingSystem::LogDirect(LoggingChannelID_t channelID, + LoggingSeverity_t severity, + Color color, + const tchar *pMessage) { + Assert(IsValidChannelID(channelID)); + if (!IsValidChannelID(channelID)) return LR_CONTINUE; + + LoggingContext_t context; + context.m_ChannelID = channelID; + context.m_Flags = m_RegisteredChannels[channelID].m_Flags; + context.m_Severity = severity; + context.m_Color = (color == UNSPECIFIED_LOGGING_COLOR) + ? m_RegisteredChannels[channelID].m_SpewColor + : color; + + // It is assumed that the mutex is reentrant safe on all platforms. + if (!m_pStateMutex) m_pStateMutex = new CThreadFastMutex(); + + m_pStateMutex->Lock(); + + LoggingState_t *pState = GetCurrentState(); + + for (int i = 0; i < pState->m_nListenerCount; ++i) { + pState->m_RegisteredListeners[i]->Log(&context, pMessage); + } + +#if defined(_PS3) && !defined(_CERT) + if (!pState->m_nListenerCount) { + unsigned int unBytesWritten; + sys_tty_write(SYS_TTYP15, pMessage, strlen(pMessage), &unBytesWritten); + } +#endif + + LoggingResponse_t response = pState->m_pLoggingResponse->OnLog(&context); + + m_pStateMutex->Unlock(); + + switch (response) { + case LR_DEBUGGER: + // Asserts put the debug break in the macro itself so the code breaks at + // the failure point. + if (severity != LS_ASSERT) { + DebuggerBreakIfDebugging(); + } + break; + + case LR_ABORT: + Log_Msg(LOG_DEVELOPER_VERBOSE, + "Exiting due to logging LR_ABORT request.\n"); + Plat_ExitProcess(EXIT_FAILURE); + break; + + case LR_CONTINUE: + break; + } + + return response; +} + +CLoggingSystem::LoggingChannel_t *CLoggingSystem::GetChannel( + LoggingChannelID_t channelID) { + Assert(IsValidChannelID(channelID)); + return &m_RegisteredChannels[channelID]; +} + +const CLoggingSystem::LoggingChannel_t *CLoggingSystem::GetChannel( + LoggingChannelID_t channelID) const { + Assert(IsValidChannelID(channelID)); + return &m_RegisteredChannels[channelID]; +} + +CLoggingSystem::LoggingState_t *CLoggingSystem::GetCurrentState() { + // Assume the caller grabbed the mutex. + int nState = g_nThreadLocalStateIndex; + if (nState != 0) { + Assert(nState > 0 && nState < MAX_LOGGING_STATE_COUNT); + return &m_LoggingStates[nState]; + } else { + Assert(m_nGlobalStateIndex >= 0 && + m_nGlobalStateIndex < MAX_LOGGING_STATE_COUNT); + return &m_LoggingStates[m_nGlobalStateIndex]; + } +} + +const CLoggingSystem::LoggingState_t *CLoggingSystem::GetCurrentState() const { + // Assume the caller grabbed the mutex. + int nState = g_nThreadLocalStateIndex; + if (nState != 0) { + Assert(nState > 0 && nState < MAX_LOGGING_STATE_COUNT); + return &m_LoggingStates[nState]; + } else { + Assert(m_nGlobalStateIndex >= 0 && + m_nGlobalStateIndex < MAX_LOGGING_STATE_COUNT); + return &m_LoggingStates[m_nGlobalStateIndex]; + } +} + +int CLoggingSystem::FindUnusedStateIndex() { + for (int i = 0; i < MAX_LOGGING_STATE_COUNT; ++i) { + if (m_LoggingStates[i].m_nListenerCount < 0) { + return i; + } + } + return -1; +} + +CLoggingSystem::LoggingTag_t *CLoggingSystem::AllocTag(const char *pTagName) { + Assert(m_nChannelTagCount < MAX_LOGGING_TAG_COUNT); + LoggingTag_t *pTag = &m_ChannelTags[m_nChannelTagCount++]; + + pTag->m_pNextTag = NULL; + pTag->m_pTagName = m_TagNamePool + m_nTagNamePoolIndex; + + // Copy string into pool. + size_t nTagLength = strlen(pTagName); + Assert(m_nTagNamePoolIndex + nTagLength + 1 <= + MAX_LOGGING_TAG_CHARACTER_COUNT); + strcpy(m_TagNamePool + m_nTagNamePoolIndex, pTagName); + m_nTagNamePoolIndex += (int)nTagLength + 1; + + return pTag; +} + +LoggingChannelID_t LoggingSystem_RegisterLoggingChannel( + const char *pName, RegisterTagsFunc registerTagsFunc, int flags, + LoggingSeverity_t severity, Color color) { + return GetGlobalLoggingSystem()->RegisterLoggingChannel( + pName, registerTagsFunc, flags, severity, color); +} + +void LoggingSystem_ResetCurrentLoggingState() { + GetGlobalLoggingSystem()->ResetCurrentLoggingState(); +} + +void LoggingSystem_RegisterLoggingListener(ILoggingListener *pListener) { + GetGlobalLoggingSystem()->RegisterLoggingListener(pListener); +} + +void LoggingSystem_UnregisterLoggingListener(ILoggingListener *pListener) { + GetGlobalLoggingSystem()->UnregisterLoggingListener(pListener); +} + +void LoggingSystem_SetLoggingResponsePolicy( + ILoggingResponsePolicy *pResponsePolicy) { + GetGlobalLoggingSystem()->SetLoggingResponsePolicy(pResponsePolicy); +} + +void LoggingSystem_PushLoggingState(bool bThreadLocal, bool bClearState) { + GetGlobalLoggingSystem()->PushLoggingState(bThreadLocal, bClearState); +} + +void LoggingSystem_PopLoggingState(bool bThreadLocal) { + GetGlobalLoggingSystem()->PopLoggingState(bThreadLocal); +} + +void LoggingSystem_AddTagToCurrentChannel(const char *pTagName) { + GetGlobalLoggingSystem()->AddTagToCurrentChannel(pTagName); +} + +LoggingChannelID_t LoggingSystem_FindChannel(const char *pChannelName) { + return GetGlobalLoggingSystem()->FindChannel(pChannelName); +} + +int LoggingSystem_GetChannelCount() { + return GetGlobalLoggingSystem()->GetChannelCount(); +} + +LoggingChannelID_t LoggingSystem_GetFirstChannelID() { + return (GetGlobalLoggingSystem()->GetChannelCount() > 0) + ? 0 + : INVALID_LOGGING_CHANNEL_ID; +} + +LoggingChannelID_t LoggingSystem_GetNextChannelID( + LoggingChannelID_t channelID) { + int nChannelCount = GetGlobalLoggingSystem()->GetChannelCount(); + int nNextChannel = channelID + 1; + return (nNextChannel < nChannelCount) ? nNextChannel + : INVALID_LOGGING_CHANNEL_ID; +} + +const CLoggingSystem::LoggingChannel_t *LoggingSystem_GetChannel( + LoggingChannelID_t channelIndex) { + return GetGlobalLoggingSystem()->GetChannel(channelIndex); +} + +bool LoggingSystem_HasTag(LoggingChannelID_t channelID, const char *pTag) { + return GetGlobalLoggingSystem()->HasTag(channelID, pTag); +} + +bool LoggingSystem_IsChannelEnabled(LoggingChannelID_t channelID, + LoggingSeverity_t severity) { + return GetGlobalLoggingSystem()->IsChannelEnabled(channelID, severity); +} + +void LoggingSystem_SetChannelSpewLevel(LoggingChannelID_t channelID, + LoggingSeverity_t minimumSeverity) { + GetGlobalLoggingSystem()->SetChannelSpewLevel(channelID, minimumSeverity); +} + +void LoggingSystem_SetChannelSpewLevelByName( + const char *pName, LoggingSeverity_t minimumSeverity) { + GetGlobalLoggingSystem()->SetChannelSpewLevelByName(pName, minimumSeverity); +} + +void LoggingSystem_SetChannelSpewLevelByTag(const char *pTag, + LoggingSeverity_t minimumSeverity) { + GetGlobalLoggingSystem()->SetChannelSpewLevelByTag(pTag, minimumSeverity); +} + +int32 LoggingSystem_GetChannelColor(LoggingChannelID_t channelID) { + return GetGlobalLoggingSystem()->GetChannelColor(channelID).GetRawColor(); +} + +void LoggingSystem_SetChannelColor(LoggingChannelID_t channelID, int color) { + Color c; + c.SetRawColor(color); + GetGlobalLoggingSystem()->SetChannelColor(channelID, c); +} + +LoggingChannelFlags_t LoggingSystem_GetChannelFlags( + LoggingChannelID_t channelID) { + return GetGlobalLoggingSystem()->GetChannelFlags(channelID); +} + +void LoggingSystem_SetChannelFlags(LoggingChannelID_t channelID, + LoggingChannelFlags_t flags) { + GetGlobalLoggingSystem()->SetChannelFlags(channelID, flags); +} + +LoggingResponse_t LoggingSystem_Log( + LoggingChannelID_t channelID, LoggingSeverity_t severity, + PRINTF_FORMAT_STRING const char *pMessageFormat, ...) { + if (!GetGlobalLoggingSystem()->IsChannelEnabled(channelID, severity)) + return LR_CONTINUE; + + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; + + va_list args; + va_start(args, pMessageFormat); + Tier0Internal_vsntprintf(formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, + pMessageFormat, args); + va_end(args); + + return GetGlobalLoggingSystem()->LogDirect( + channelID, severity, UNSPECIFIED_LOGGING_COLOR, formattedMessage); +} + +LoggingResponse_t LoggingSystem_Log( + LoggingChannelID_t channelID, LoggingSeverity_t severity, Color spewColor, + PRINTF_FORMAT_STRING const char *pMessageFormat, ...) { + if (!GetGlobalLoggingSystem()->IsChannelEnabled(channelID, severity)) + return LR_CONTINUE; + + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; + + va_list args; + va_start(args, pMessageFormat); + Tier0Internal_vsntprintf(formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, + pMessageFormat, args); + va_end(args); + + return GetGlobalLoggingSystem()->LogDirect(channelID, severity, spewColor, + formattedMessage); +} + +LoggingResponse_t LoggingSystem_LogDirect(LoggingChannelID_t channelID, + LoggingSeverity_t severity, + Color spewColor, + const char *pMessage) { + if (!GetGlobalLoggingSystem()->IsChannelEnabled(channelID, severity)) + return LR_CONTINUE; + return GetGlobalLoggingSystem()->LogDirect(channelID, severity, spewColor, + pMessage); +} + +LoggingResponse_t LoggingSystem_LogAssert( + PRINTF_FORMAT_STRING const char *pMessageFormat, ...) { + if (!GetGlobalLoggingSystem()->IsChannelEnabled(LOG_ASSERT, LS_ASSERT)) + return LR_CONTINUE; + + tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; + + va_list args; + va_start(args, pMessageFormat); + Tier0Internal_vsntprintf(formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, + pMessageFormat, args); + va_end(args); + + return GetGlobalLoggingSystem()->LogDirect( + LOG_ASSERT, LS_ASSERT, UNSPECIFIED_LOGGING_COLOR, formattedMessage); +} diff --git a/tier0/mem.cpp b/tier0/mem.cpp new file mode 100644 index 0000000..b2d56b9 --- /dev/null +++ b/tier0/mem.cpp @@ -0,0 +1,62 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Memory allocation! + +#include "pch_tier0.h" +#include "tier0/mem.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef STEAM +#define PvRealloc realloc +#define PvAlloc malloc +#define PvExpand _expand +#endif + +enum { MAX_STACK_DEPTH = 32 }; + +static uint8 *s_pBuf = NULL; +static int s_pBufStackDepth[MAX_STACK_DEPTH]; +static int s_nBufDepth = -1; +static int s_nBufCurSize = 0; +static int s_nBufAllocSize = 0; + +//----------------------------------------------------------------------------- +// Other DLL-exported methods for particular kinds of memory +//----------------------------------------------------------------------------- +void *MemAllocScratch(int nMemSize) { + // Minimally allocate 1M scratch + if (s_nBufAllocSize < s_nBufCurSize + nMemSize) { + s_nBufAllocSize = s_nBufCurSize + nMemSize; + if (s_nBufAllocSize < 2 * 1024) { + s_nBufAllocSize = 2 * 1024; + } + + if (s_pBuf) { + s_pBuf = (uint8 *)PvRealloc(s_pBuf, s_nBufAllocSize); + Assert(s_pBuf); + } else { + s_pBuf = (uint8 *)PvAlloc(s_nBufAllocSize); + } + } + + int nBase = s_nBufCurSize; + s_nBufCurSize += nMemSize; + ++s_nBufDepth; + Assert(s_nBufDepth < MAX_STACK_DEPTH); + s_pBufStackDepth[s_nBufDepth] = nMemSize; + + return &s_pBuf[nBase]; +} + +void MemFreeScratch() { + Assert(s_nBufDepth >= 0); + s_nBufCurSize -= s_pBufStackDepth[s_nBufDepth]; + --s_nBufDepth; +} + +#ifdef POSIX +void ZeroMemory(void *mem, size_t length) { memset(mem, 0x0, length); } +#endif diff --git a/tier0/mem_helpers.cpp b/tier0/mem_helpers.cpp new file mode 100644 index 0000000..0f28773 --- /dev/null +++ b/tier0/mem_helpers.cpp @@ -0,0 +1,156 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "mem_helpers.h" + +#include "tier0/platform.h" +#include "tier0/icommandline.h" +#include "tier0/dbg.h" +#include + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +// Needed for debugging +const char *g_pszModule = "tier0"; +bool g_bInitMemory = true; + +#if defined(PLATFORM_POSIX) || defined(PLATFORM_PS3) +void DoApplyMemoryInitializations(void *pMem, size_t nSize) {} + +size_t CalcHeapUsed() { return 0; } +#else + +unsigned long g_dwFeeFee = 0xffeeffee; + +// Generated by Mathematica. +unsigned char g_RandomValues[256] = { + 95, 126, 220, 71, 92, 179, 95, 219, 111, 150, 38, 155, 181, 62, 40, + 231, 238, 54, 47, 55, 186, 204, 64, 70, 118, 94, 107, 251, 199, 140, + 67, 87, 86, 127, 210, 41, 21, 90, 208, 24, 167, 204, 32, 254, 38, + 51, 9, 11, 38, 33, 188, 104, 0, 75, 119, 24, 122, 203, 24, 164, + 250, 224, 241, 182, 213, 201, 173, 67, 200, 255, 244, 227, 46, 219, 26, + 149, 218, 132, 120, 154, 227, 244, 106, 198, 109, 87, 150, 40, 16, 99, + 169, 193, 100, 156, 78, 171, 246, 47, 84, 119, 10, 52, 207, 171, 230, + 90, 90, 127, 180, 153, 68, 140, 62, 14, 87, 57, 208, 154, 116, 29, + 131, 177, 224, 187, 51, 148, 142, 245, 152, 230, 184, 117, 91, 146, 235, + 153, 35, 104, 187, 177, 215, 131, 17, 49, 211, 244, 60, 152, 103, 248, + 51, 224, 237, 240, 51, 30, 10, 233, 253, 106, 252, 73, 134, 136, 178, + 86, 228, 107, 77, 255, 85, 242, 204, 119, 102, 53, 209, 35, 123, 32, + 252, 210, 43, 12, 136, 167, 155, 210, 71, 254, 178, 172, 3, 230, 93, + 208, 196, 68, 235, 16, 106, 189, 201, 177, 85, 78, 206, 187, 48, 68, + 64, 190, 117, 236, 49, 174, 105, 63, 207, 70, 170, 93, 6, 110, 52, + 111, 169, 92, 247, 86, 10, 174, 207, 240, 104, 209, 81, 177, 123, 189, + 175, 212, 101, 219, 114, 243, 44, 91, 51, 139, 91, 57, 120, 41, 98, + 119}; + +unsigned long g_iCurRandomValueOffset = 0; + +void InitializeToFeeFee(void *pMem, size_t nSize) { + unsigned long *pCurDWord = (unsigned long *)pMem; + size_t nDWords = nSize >> 2; + while (nDWords) { + *pCurDWord = 0xffeeffee; + ++pCurDWord; + --nDWords; + } + + unsigned char *pCurChar = (unsigned char *)pCurDWord; + size_t nBytes = nSize & 3; + size_t iOffset = 0; + while (nBytes) { + *pCurChar = ((unsigned char *)&g_dwFeeFee)[iOffset]; + ++iOffset; + --nBytes; + ++pCurChar; + } +} + +void InitializeToRandom(void *pMem, size_t nSize) { + unsigned char *pOut = (unsigned char *)pMem; + for (size_t i = 0; i < nSize; i++) { + pOut[i] = g_RandomValues[(g_iCurRandomValueOffset & 255)]; + ++g_iCurRandomValueOffset; + } +} + +void DoApplyMemoryInitializations(void *pMem, size_t nSize) { + if (!pMem) return; + + // If they passed -noinitmemory on the command line, don't do anything here. + Assert(g_bInitMemory); + + // First time we get in here, remember all the settings. + static bool bDebuggerPresent = Plat_IsInDebugSession(); + static bool bCheckedCommandLine = false; + static bool bRandomizeMemory = false; + if (!bCheckedCommandLine) { + bCheckedCommandLine = true; + + // APS + char *pStr = (char *)Plat_GetCommandLineA(); + if (pStr) { + char tempStr[512]; + strncpy(tempStr, pStr, sizeof(tempStr) - 1); + tempStr[sizeof(tempStr) - 1] = 0; + _strupr(tempStr); + + if (strstr(tempStr, "-RANDOMIZEMEMORY")) bRandomizeMemory = true; + + if (strstr(tempStr, "-NOINITMEMORY")) g_bInitMemory = false; + } + } + + if (bRandomizeMemory) { + // They asked for it.. randomize all the memory. + InitializeToRandom(pMem, nSize); + } else { + if (bDebuggerPresent) { + // Ok, it's already set to 0xbaadf00d, but we want something that will + // make floating-point #'s NANs. + InitializeToFeeFee(pMem, nSize); + } else { +#if defined(_DEBUG) || defined(USE_LIGHT_MEM_DEBUG) +#ifdef LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH + extern bool g_bUsingLMD; + if (!g_bUsingLMD) { + return; + } +#endif + // Ok, it's already set to 0xcdcdcdcd, but we want something that will + // make floating-point #'s NANs. + InitializeToFeeFee(pMem, nSize); +#endif + } + } +} + +size_t CalcHeapUsed() { +#if defined(_X360) + return 0; +#else + _HEAPINFO hinfo; + int heapstatus; + intp nTotal; + + nTotal = 0; + hinfo._pentry = NULL; + while ((heapstatus = _heapwalk(&hinfo)) == _HEAPOK) { + nTotal += (hinfo._useflag == _USEDENTRY) ? hinfo._size : 0; + } + + switch (heapstatus) { + case _HEAPEMPTY: + case _HEAPEND: + // success + break; + + default: + // heap corrupted + nTotal = -1; + } + + return nTotal; +#endif +} + +#endif // not PLATFORM_POSIX diff --git a/tier0/mem_helpers.h b/tier0/mem_helpers.h new file mode 100644 index 0000000..7259c61 --- /dev/null +++ b/tier0/mem_helpers.h @@ -0,0 +1,33 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_MEM_HELPERS_H_ +#define VPC_TIER0_MEM_HELPERS_H_ + +// Normally, the runtime libraries like to mess with the memory returned by +// malloc(), which can create problems trying to repro bugs in debug builds or +// in the debugger. +// +// If the debugger is present, it initializes data to 0xbaadf00d, which makes +// floating point numbers come out to about 0.1. +// +// If the debugger is not present, and it's a debug build, then you get +// 0xcdcdcdcd, which is about 25 million. +// +// Otherwise, you get uninitialized memory. +// +// In here, we make sure the memory is either random garbage, or it's set to +// 0xffeeffee, which casts to a NAN. +extern bool g_bInitMemory; + +#define ApplyMemoryInitializations(pMem, nSize) \ + if (!g_bInitMemory) \ + ; \ + else { \ + DoApplyMemoryInitializations(pMem, nSize); \ + } + +void DoApplyMemoryInitializations(void *pMem, size_t nSize); + +size_t CalcHeapUsed(); + +#endif // VPC_TIER0_MEM_HELPERS_H_ diff --git a/tier0/mem_impl_type.h b/tier0/mem_impl_type.h new file mode 100644 index 0000000..1e5eb28 --- /dev/null +++ b/tier0/mem_impl_type.h @@ -0,0 +1,8 @@ +// Copyright Valve Corporation, All rights reserved. + +#if ((!defined(POSIX) || defined(_GAMECONSOLE)) && \ + (defined(_DEBUG) || defined(USE_MEM_DEBUG))) +#define MEM_IMPL_TYPE_DBG 1 +#else +#define MEM_IMPL_TYPE_STD 1 +#endif diff --git a/tier0/memdbg.cpp b/tier0/memdbg.cpp new file mode 100644 index 0000000..73a6ddd --- /dev/null +++ b/tier0/memdbg.cpp @@ -0,0 +1,2718 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Memory allocation! + +#include "pch_tier0.h" + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +#include +#include "tier0/dbg.h" +#if defined(USE_STACK_TRACES) +#include "tier0/stackstats.h" +#endif +#include "tier0/memalloc.h" +#include "tier0/fasttimer.h" +#include "mem_helpers.h" +#include "tier0_strtools.h" + +#ifdef PLATFORM_WINDOWS_PC +#include "winlite.h" +#include +#endif + +#ifdef OSX +#include +#include +#endif + +#include +#include +#include +#include "tier0/threadtools.h" + +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif + +#ifdef _PS3 +#include "sys/memory.h" +#include "tls_ps3.h" +#include "ps3/ps3_helpers.h" +#include "memoverride_ps3.h" +#endif + +#ifdef USE_LIGHT_MEM_DEBUG +#undef USE_MEM_DEBUG +#endif + +#if (!defined(POSIX) && (defined(_DEBUG) || defined(USE_MEM_DEBUG))) +#pragma message( \ + "USE_MEM_DEBUG is enabled in a release build. Don't check this in!") +#endif + +#include "mem_impl_type.h" + +#if MEM_IMPL_TYPE_DBG + +#if defined(_WIN32) && (!defined(_X360) && !defined(_WIN64)) +// be sure to disable frame pointer omission for all projects. "vpc /nofpo" when +// using stack traces #define USE_STACK_TRACES +// or: +//#define USE_STACK_TRACES_DETAILED +const size_t STACK_TRACE_LENGTH = 32; +#endif + +// prevent stupid bugs from checking one and not the other +#if defined(USE_STACK_TRACES_DETAILED) && !defined(USE_STACK_TRACES) +#define USE_STACK_TRACES // don't comment me. I'm a safety check +#endif + +#if defined(USE_STACK_TRACES) +#define SORT_STACK_TRACE_DESCRIPTION_DUMPS +#endif + +#if (defined(USE_STACK_TRACES)) && \ + !(defined(TIER0_FPO_DISABLED) || defined(_DEBUG)) +#error Stack traces will not work unless FPO is disabled for every function traced through. Rebuild everything with FPO disabled "vpc /nofpo" +#endif + +//----------------------------------------------------------------------------- + +#ifdef _PS3 +MemOverrideRawCrtFunctions_t *g_pMemOverrideRawCrtFns; +#define DebugAlloc (g_pMemOverrideRawCrtFns->pfn_malloc) +#define DebugFree (g_pMemOverrideRawCrtFns->pfn_free) +#elif defined(_X360) +#define DebugAlloc DmAllocatePool +#define DebugFree DmFreePool +#else +#define DebugAlloc malloc +#define DebugFree free +#endif + +#ifdef _WIN32 +int g_DefaultHeapFlags = + _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_ALLOC_MEM_DF); +#else +int g_DefaultHeapFlags = 0; +#endif // win32 + +#if defined(_MEMTEST) +static char s_szStatsMapName[32]; +static char s_szStatsComment[256]; +#endif + +#pragma optimize("", off) +//----------------------------------------------------------------------------- + +#if defined(USE_STACK_TRACES) + +bool GetModuleFromAddress(void *address, char *pResult, int iLength) { + return GetModuleNameFromAddress(address, pResult, iLength); +} + +bool GetCallerModule(char *pDest, int iLength) { + void *pCaller; + GetCallStack_Fast(&pCaller, 1, 2); + + return (pCaller != 0 && GetModuleFromAddress(pCaller, pDest, iLength)); +} + +// +// Note: StackDescribe function is non-reentrant: +// Reason: Stack description is stored in a static buffer. +// Solution: Passing caller-allocated buffers would allow the +// function to become reentrant, however the current only client +// (FindOrCreateFilename) is synchronized with a heap mutex, after retrieving +// stack description the heap memory will be allocated to copy the text. +// + +char *StackDescribe(void *const *ppAddresses, int nMaxAddresses) { + static char s_chStackDescription[32 * 1024]; + char *pchBuffer = s_chStackDescription; + +#if defined(SORT_STACK_TRACE_DESCRIPTION_DUMPS) // Assuming StackDescribe is + // called iteratively on a + // sorted set of stacks (as in + // DumpStackStats()). We can + // save work by skipping + // unchanged parts at the + // beginning of the string. + static void *LastCallStack[STACK_TRACE_LENGTH] = {NULL}; + static char *pEndPos[STACK_TRACE_LENGTH] = {NULL}; + bool bUseExistingString = true; +#else + s_chStackDescription[0] = 0; +#endif + + int k; + for (k = 0; k < nMaxAddresses; ++k) { + if (!ppAddresses[k]) break; + +#if defined(SORT_STACK_TRACE_DESCRIPTION_DUMPS) + if (bUseExistingString && (k < STACK_TRACE_LENGTH)) { + if (ppAddresses[k] == LastCallStack[k]) { + pchBuffer = pEndPos[k]; + continue; + } else { + // everything from here on is invalidated + bUseExistingString = false; + for (int clearEntries = k; clearEntries < STACK_TRACE_LENGTH; + ++clearEntries) // wipe out unused entries + { + LastCallStack[clearEntries] = NULL; + pEndPos[clearEntries] = NULL; + } + // fall through to existing code + + if (k == 0) + *pchBuffer = '\0'; + else + sprintf(pchBuffer, "<--"); + } + } +#endif + { + pchBuffer += strlen(pchBuffer); + + char szTemp[MAX_PATH]; + szTemp[0] = '\0'; + uint32 iLine = 0; + uint32 iLineDisplacement = 0; + uint64 iSymbolDisplacement = 0; + if (GetFileAndLineFromAddress(ppAddresses[k], szTemp, MAX_PATH, iLine, + &iLineDisplacement)) { + char const *pchFileName = szTemp + strlen(szTemp); + for (size_t numSlashesAllowed = 2; pchFileName > szTemp; + --pchFileName) { + if (*pchFileName == '\\') { + if (numSlashesAllowed--) + continue; + else + break; + } + } + sprintf(pchBuffer, iLineDisplacement ? "%s:%d+0x%I32X" : "%s:%d", + pchFileName, iLine, iLineDisplacement); + } else if (GetSymbolNameFromAddress(ppAddresses[k], szTemp, MAX_PATH, + &iSymbolDisplacement)) { + sprintf(pchBuffer, + (iSymbolDisplacement > 0 && !(iSymbolDisplacement >> 63)) + ? "%s+0x%llX" + : "%s", + szTemp, iSymbolDisplacement); + } else { + sprintf(pchBuffer, "#0x%08p", ppAddresses[k]); + } + + pchBuffer += strlen(pchBuffer); + sprintf(pchBuffer, "<--"); + +#if defined(SORT_STACK_TRACE_DESCRIPTION_DUMPS) + if (k < STACK_TRACE_LENGTH) { + LastCallStack[k] = ppAddresses[k]; + pEndPos[k] = pchBuffer; + } +#endif + } + } + *pchBuffer = 0; + +#if defined(SORT_STACK_TRACE_DESCRIPTION_DUMPS) + for (; k < STACK_TRACE_LENGTH; ++k) // wipe out unused entries + { + LastCallStack[k] = NULL; + pEndPos[k] = NULL; + } +#endif + + return s_chStackDescription; +} + +#else + +#define GetModuleFromAddress(address, pResult, iLength) ((*pResult = 0), 0) +#define GetCallerModule(pDest, iLength) false + +#endif + +//----------------------------------------------------------------------------- + +// NOTE: This exactly mirrors the dbg header in the MSDEV crt +// eventually when we write our own allocator, we can kill this +struct CrtDbgMemHeader_t { + unsigned char m_Reserved[8]; + const char *m_pFileName; + int m_nLineNumber; + unsigned char m_Reserved2[16]; +}; + +struct Sentinal_t { + DWORD value[4]; +}; + +Sentinal_t g_HeadSentinelAllocated = { + 0xeee1beef, + 0xeee1f00d, + 0xbd122969, + 0xbeefbeef, +}; + +Sentinal_t g_HeadSentinelFree = { + 0xdeadbeef, + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, +}; + +Sentinal_t g_TailSentinel = { + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, + 0xbaadf00d, +}; + +const byte g_FreeFill = 0xdd; + +enum DbgMemHeaderBlockType_t { BLOCKTYPE_FREE, BLOCKTYPE_ALLOCATED }; + +struct DbgMemHeader_t +#if !defined(_DEBUG) || defined(_PS3) + : CrtDbgMemHeader_t +#endif +{ + size_t nLogicalSize; +#if defined(USE_STACK_TRACES) + unsigned int nStatIndex; + byte reserved[16 - (sizeof(unsigned int) * + 2)]; // MS allocator always returns mem aligned on 16 + // bytes, which some of our code depends on +#else + byte reserved[16 - sizeof(unsigned int)]; // MS allocator always returns mem + // aligned on 16 bytes, which some + // of our code depends on +#endif + Sentinal_t sentinal; +}; + +const int g_nRecentFrees = (IsPC()) ? 8192 : 512; +DbgMemHeader_t **GetRecentFrees() { + static DbgMemHeader_t **g_pRecentFrees = + (DbgMemHeader_t **) +#ifdef _PS3 + g_pMemOverrideRawCrtFns->pfn_calloc +#else + calloc +#endif + (g_nRecentFrees, sizeof(DbgMemHeader_t *)); + return g_pRecentFrees; +} +uint32 volatile g_iNextFreeSlot; + +uint32 volatile g_break_BytesFree = 0xffffffff; + +void LMDReportInvalidBlock(DbgMemHeader_t *pHeader, const char *pszMessage) { + char szMsg[256]; + if (pHeader) { + sprintf(szMsg, "HEAP IS CORRUPT: %s (block 0x%zx, %zu bytes)\n", pszMessage, + (size_t)(((byte *)pHeader) + sizeof(DbgMemHeader_t)), + pHeader->nLogicalSize); + } else { + sprintf(szMsg, "HEAP IS CORRUPT: %s\n", pszMessage); + } + Assert(!"HEAP IS CORRUPT!"); + DebuggerBreak(); +} + +void LMDValidateBlock(DbgMemHeader_t *pHeader, bool bFreeList) { + if (memcmp(&pHeader->sentinal, + bFreeList ? &g_HeadSentinelFree : &g_HeadSentinelAllocated, + sizeof(Sentinal_t)) != 0) { + LMDReportInvalidBlock(pHeader, "Head sentinel corrupt"); + } + if (memcmp(((Sentinal_t *)((((byte *)pHeader) + sizeof(DbgMemHeader_t) + + pHeader->nLogicalSize))), + &g_TailSentinel, sizeof(Sentinal_t)) != 0) { + LMDReportInvalidBlock(pHeader, "Tail sentinel corrupt"); + } + if (bFreeList) { + byte *pCur = (byte *)pHeader + sizeof(DbgMemHeader_t); + byte *pLimit = pCur + pHeader->nLogicalSize; + while (pCur != pLimit) { + if (*pCur++ != g_FreeFill) { + LMDReportInvalidBlock(pHeader, "Write after free"); + } + } + } +} + +//----------------------------------------------------------------------------- + +#if defined(_DEBUG) && !defined(POSIX) +#define GetCrtDbgMemHeader(pMem) \ + ((CrtDbgMemHeader_t *)((DbgMemHeader_t *)pMem - 1) - 1) +#elif defined(OSX) +DbgMemHeader_t *GetCrtDbgMemHeader(void *pMem); +#else +#define GetCrtDbgMemHeader(pMem) ((DbgMemHeader_t *)(pMem)-1) +#endif + +#if defined(USE_STACK_TRACES) +#define GetAllocationStatIndex_Internal(pMem) \ + (((DbgMemHeader_t *)pMem - 1)->nStatIndex) +#endif + +#ifdef OSX +DbgMemHeader_t *GetCrtDbgMemHeader(void *pMem) { + size_t msize = malloc_size(pMem); + return (DbgMemHeader_t *)((char *)pMem + msize - sizeof(DbgMemHeader_t)); +} +#endif + +inline void *InternalMalloc(size_t nSize, const char *pFileName, int nLine) { +#if defined(POSIX) || defined(_PS3) + void *pAllocedMem = NULL; +#ifdef OSX + pAllocedMem = + malloc_zone_malloc(malloc_default_zone(), + nSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pAllocedMem); +#elif defined(_PS3) + pAllocedMem = (g_pMemOverrideRawCrtFns->pfn_malloc)( + nSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; + *((void **)pInternalMem->m_Reserved2) = pAllocedMem; +#else + pAllocedMem = malloc(nSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = nSize; + *((int *)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *((Sentinal_t *)(((byte *)pInternalMem) + sizeof(DbgMemHeader_t) + nSize)) = + g_TailSentinel; + LMDValidateBlock(pInternalMem, false); + +#ifdef OSX + return pAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem; +#if !defined(_DEBUG) + pInternalMem = (DbgMemHeader_t *)malloc(nSize + sizeof(DbgMemHeader_t)); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_malloc_dbg(nSize + sizeof(DbgMemHeader_t), + _NORMAL_BLOCK, pFileName, nLine); +#endif + + if (pInternalMem) { + pInternalMem->nLogicalSize = nSize; + return pInternalMem + 1; + } + + return NULL; +#endif // WIN32 +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +inline void *InternalMallocAligned(size_t nSize, size_t align, + const char *pFileName, int nLine) { +#if defined(POSIX) || defined(_PS3) + void *pAllocedMem = NULL; +#ifdef OSX + pAllocedMem = + malloc_zone_malloc(malloc_default_zone(), + nSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pAllocedMem); +#elif defined(_PS3) + size_t numWastedAlignPages = (sizeof(DbgMemHeader_t) / align); + if (align * numWastedAlignPages < sizeof(DbgMemHeader_t)) + ++numWastedAlignPages; + size_t nSizeRequired = + nSize + numWastedAlignPages * align + sizeof(Sentinal_t); + pAllocedMem = (g_pMemOverrideRawCrtFns->pfn_memalign)(align, nSizeRequired); + DbgMemHeader_t *pInternalMem = + GetCrtDbgMemHeader(((char *)pAllocedMem) + numWastedAlignPages * align); + *((void **)pInternalMem->m_Reserved2) = pAllocedMem; +#else + pAllocedMem = malloc(nSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = nSize; + *((int *)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *((Sentinal_t *)(((byte *)pInternalMem) + sizeof(DbgMemHeader_t) + nSize)) = + g_TailSentinel; + LMDValidateBlock(pInternalMem, false); + +#ifdef OSX + return pAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem; +#if !defined(_DEBUG) + pInternalMem = (DbgMemHeader_t *)malloc(nSize + sizeof(DbgMemHeader_t)); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_malloc_dbg(nSize + sizeof(DbgMemHeader_t), + _NORMAL_BLOCK, pFileName, nLine); +#endif + + pInternalMem->nLogicalSize = nSize; + return pInternalMem + 1; +#endif // WIN32 +} +#endif + +inline void *InternalRealloc(void *pMem, size_t nNewSize, const char *pFileName, + int nLine) { + if (!pMem) return InternalMalloc(nNewSize, pFileName, nLine); + +#ifdef POSIX + void *pNewAllocedMem = NULL; +#ifdef OSX + pNewAllocedMem = (DbgMemHeader_t *)malloc_zone_realloc( + malloc_default_zone(), pMem, + nNewSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pNewAllocedMem); +#elif defined(_PS3) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); + pNewAllocedMem = (DbgMemHeader_t *)(g_pMemOverrideRawCrtFns->pfn_realloc)( + *((void **)pInternalMem->m_Reserved2), + nNewSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; + *((void **)pInternalMem->m_Reserved2) = pNewAllocedMem; +#else + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); + pNewAllocedMem = (DbgMemHeader_t *)realloc( + pInternalMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = static_cast(nNewSize); + *((int *)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *((Sentinal_t *)(((byte *)pInternalMem) + sizeof(DbgMemHeader_t) + + nNewSize)) = g_TailSentinel; + LMDValidateBlock(pInternalMem, false); + +#ifdef OSX + return pNewAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#if !defined(_DEBUG) + pInternalMem = (DbgMemHeader_t *)realloc(pInternalMem, + nNewSize + sizeof(DbgMemHeader_t)); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_realloc_dbg( + pInternalMem, nNewSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, + nLine); +#endif + + if (pInternalMem) { + pInternalMem->nLogicalSize = nNewSize; + return pInternalMem + 1; + } + + return NULL; +#endif // WIN32 +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +inline void *InternalReallocAligned(void *pMem, size_t nNewSize, size_t align, + const char *pFileName, int nLine) { + if (!pMem) return InternalMallocAligned(nNewSize, align, pFileName, nLine); + +#ifdef POSIX + void *pNewAllocedMem = NULL; +#ifdef OSX + pNewAllocedMem = (DbgMemHeader_t *)malloc_zone_realloc( + malloc_default_zone(), pMem, + nNewSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pNewAllocedMem); +#elif defined(_PS3) + size_t numWastedAlignPages = (sizeof(DbgMemHeader_t) / align); + if (align * numWastedAlignPages < sizeof(DbgMemHeader_t)) + ++numWastedAlignPages; + size_t nSizeRequired = + nNewSize + numWastedAlignPages * align + sizeof(Sentinal_t); + + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); + pNewAllocedMem = + (DbgMemHeader_t *)(g_pMemOverrideRawCrtFns->pfn_reallocalign)( + *((void **)pInternalMem->m_Reserved2), nSizeRequired, align); + pInternalMem = GetCrtDbgMemHeader(((char *)pNewAllocedMem) + + numWastedAlignPages * align); + *((void **)pInternalMem->m_Reserved2) = pNewAllocedMem; +#else + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); + pNewAllocedMem = (DbgMemHeader_t *)realloc( + pInternalMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof(Sentinal_t)); + pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; +#endif + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = static_cast(nNewSize); + *((int *)pInternalMem->m_Reserved) = 0xf00df00d; + + pInternalMem->sentinal = g_HeadSentinelAllocated; + *((Sentinal_t *)(((byte *)pInternalMem) + sizeof(DbgMemHeader_t) + + nNewSize)) = g_TailSentinel; + LMDValidateBlock(pInternalMem, false); + +#ifdef OSX + return pNewAllocedMem; +#else + return pInternalMem + 1; +#endif + +#else // WIN32 + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#if !defined(_DEBUG) + pInternalMem = (DbgMemHeader_t *)realloc(pInternalMem, + nNewSize + sizeof(DbgMemHeader_t)); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_realloc_dbg( + pInternalMem, nNewSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, + nLine); +#endif + + pInternalMem->nLogicalSize = nNewSize; + return pInternalMem + 1; +#endif // WIN32 +} +#endif + +inline void InternalFree(void *pMem) { + if (!pMem) return; + + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; + +#if defined(POSIX) + // Record it in recent free blocks list + DbgMemHeader_t **pRecentFrees = GetRecentFrees(); + uint32 iNextSlot = ThreadInterlockedIncrement(&g_iNextFreeSlot); + iNextSlot %= g_nRecentFrees; + + if (memcmp(&pInternalMem->sentinal, &g_HeadSentinelAllocated, + sizeof(Sentinal_t)) != 0) { + Assert(!"Double Free or Corrupt Block Header!"); + DebuggerBreak(); + } + LMDValidateBlock(pInternalMem, false); + if (g_break_BytesFree == pInternalMem->nLogicalSize) { + DebuggerBreak(); + } + pInternalMem->sentinal = g_HeadSentinelFree; + memset(pMem, g_FreeFill, pInternalMem->nLogicalSize); + + DbgMemHeader_t *pToFree = pInternalMem; + if (pInternalMem->nLogicalSize < 16 * 1024) { + pToFree = pRecentFrees[iNextSlot]; + pRecentFrees[iNextSlot] = pInternalMem; + + if (pToFree) { + LMDValidateBlock(pToFree, true); + } + } + + // Validate several last frees + for (uint32 k = iNextSlot - 1, iteration = 0; iteration < 10; + ++iteration, --k) { + if (DbgMemHeader_t *pLastFree = pRecentFrees[k % g_nRecentFrees]) { + LMDValidateBlock(pLastFree, true); + } + } + + if (!pToFree) return; + +#ifdef OSX + malloc_zone_free(malloc_default_zone(), pToFree); +#elif defined(_PS3) + (g_pMemOverrideRawCrtFns->pfn_free)(*((void **)pToFree->m_Reserved2)); +#elif LINUX + free(pToFree); +#else + free(pToFree); +#endif +#elif defined(_DEBUG) + _free_dbg(pInternalMem, _NORMAL_BLOCK); +#else + free(pInternalMem); +#endif +} + +inline size_t InternalMSize(void *pMem) { +#if defined(_PS3) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); + return pInternalMem->nLogicalSize; +#elif defined(POSIX) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); + return pInternalMem->nLogicalSize; +#elif !defined(_DEBUG) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); + return _msize(pInternalMem) - sizeof(DbgMemHeader_t); +#else + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; + return _msize_dbg(pInternalMem, _NORMAL_BLOCK) - sizeof(DbgMemHeader_t); +#endif +} + +inline size_t InternalLogicalSize(void *pMem) { +#if defined(POSIX) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(pMem); +#elif !defined(_DEBUG) + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#else + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#endif + return pInternalMem->nLogicalSize; +} + +#ifndef _DEBUG +#define _CrtDbgReport(nRptType, szFile, nLine, szModule, pMsg) 0 +#endif + +//----------------------------------------------------------------------------- + +// Custom allocator protects this module from recursing on operator new +template +class CNoRecurseAllocator { + public: + // type definitions + typedef T value_type; + typedef T *pointer; + typedef const T *const_pointer; + typedef T &reference; + typedef const T &const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + CNoRecurseAllocator() {} + CNoRecurseAllocator(const CNoRecurseAllocator &) {} + template + CNoRecurseAllocator(const CNoRecurseAllocator &) {} + ~CNoRecurseAllocator() {} + + // rebind allocator to type U + template + struct rebind { + typedef CNoRecurseAllocator other; + }; + + // return address of values + pointer address(reference value) const { return &value; } + + const_pointer address(const_reference value) const { return &value; } + size_type max_size() const { return INT_MAX; } + + pointer allocate(size_type num, const void * = 0) { + return (pointer)DebugAlloc(num * sizeof(T)); + } + void deallocate(pointer p, size_type num) { DebugFree(p); } + void construct(pointer p, const T &value) { new ((void *)p) T(value); } + void destroy(pointer p) { p->~T(); } +}; + +template +bool operator==(const CNoRecurseAllocator &, + const CNoRecurseAllocator &) { + return true; +} + +template +bool operator!=(const CNoRecurseAllocator &, + const CNoRecurseAllocator &) { + return false; +} + +class CStringLess { + public: + bool operator()(const char *pszLeft, const char *pszRight) const { + return (V_tier0_stricmp(pszLeft, pszRight) < 0); + } +}; + +//----------------------------------------------------------------------------- + +#pragma warning(disable : 4074) // warning C4074: initializers put in compiler + // reserved initialization area +#pragma init_seg(compiler) + +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +class CDbgMemAlloc : public IMemAlloc { + public: + CDbgMemAlloc(); + virtual ~CDbgMemAlloc(); + + // Release versions + virtual void *Alloc(size_t nSize); + virtual void *Realloc(void *pMem, size_t nSize); + virtual void Free(void *pMem); + virtual void *Expand_NoLongerSupported(void *pMem, size_t nSize); + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + virtual void *AllocAlign(size_t nSize, size_t align); + virtual void *AllocAlign(size_t nSize, size_t align, const char *pFileName, + int nLine); + virtual void *ReallocAlign(void *pMem, size_t nSize, size_t align); + virtual void *ReallocAlign(void *pMem, size_t nSize, size_t align, + const char *pFileName, int nLine); +#endif + + // Debug versions + virtual void *Alloc(size_t nSize, const char *pFileName, int nLine); + virtual void *Realloc(void *pMem, size_t nSize, const char *pFileName, + int nLine); + virtual void Free(void *pMem, const char *pFileName, int nLine); + virtual void *Expand_NoLongerSupported(void *pMem, size_t nSize, + const char *pFileName, int nLine); + + virtual void *RegionAlloc(int region, size_t nSize) { return Alloc(nSize); } + virtual void *RegionAlloc(int region, size_t nSize, const char *pFileName, + int nLine) { + return Alloc(nSize, pFileName, nLine); + } + + // Returns the size of a particular allocation (NOTE: may be larger than the + // size requested!) + virtual size_t GetSize(void *pMem); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo(const char *pFileName, int nLine); + virtual void PopAllocDbgInfo(); + + virtual int32 CrtSetBreakAlloc(int32 lNewBreakAlloc); + virtual int CrtSetReportMode(int nReportType, int nReportMode); + virtual int CrtIsValidHeapPointer(const void *pMem); + virtual int CrtIsValidPointer(const void *pMem, unsigned int size, + int access); + virtual int CrtCheckMemory(void); + virtual int CrtSetDbgFlag(int nNewFlag); + virtual void CrtMemCheckpoint(_CrtMemState *pState); + + // handles storing allocation info for coroutines + virtual uint32 GetDebugInfoSize(); + virtual void SaveDebugInfo(void *pvDebugInfo); + virtual void RestoreDebugInfo(const void *pvDebugInfo); + virtual void InitDebugInfo(void *pvDebugInfo, const char *pchRootFileName, + int nLine); + + // FIXME: Remove when we have our own allocator + virtual void *CrtSetReportFile(int nRptType, void *hFile); + virtual void *CrtSetReportHook(void *pfnNewHook); + virtual int CrtDbgReport(int nRptType, const char *szFile, int nLine, + const char *szModule, const char *szFormat); + + virtual int heapchk(); + + virtual bool IsDebugHeap() { return true; } + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void CompactHeap() { +#if defined(_X360) && defined(_DEBUG) + HeapCompact(GetProcessHeap(), 0); +#endif + } + + virtual void CompactIncremental() {} + virtual void OutOfMemory(size_t nBytesAttempted = 0) {} + + virtual MemAllocFailHandler_t SetAllocFailHandler( + MemAllocFailHandler_t pfnMemAllocFailHandler) { + return NULL; + } // debug heap doesn't attempt retries + + void SetStatsExtraInfo(const char *pMapName, const char *pComment) { +#if defined(_MEMTEST) + strncpy(s_szStatsMapName, pMapName, sizeof(s_szStatsMapName)); + s_szStatsMapName[sizeof(s_szStatsMapName) - 1] = '\0'; + + strncpy(s_szStatsComment, pComment, sizeof(s_szStatsComment)); + s_szStatsComment[sizeof(s_szStatsComment) - 1] = '\0'; +#endif + } + + virtual size_t MemoryAllocFailed(); + void SetCRTAllocFailed(size_t nMemSize); + + enum { + BYTE_COUNT_16 = 0, + BYTE_COUNT_32, + BYTE_COUNT_128, + BYTE_COUNT_2048, + BYTE_COUNT_GREATER, + + NUM_BYTE_COUNT_BUCKETS + }; + + private: + struct MemInfo_t { +#if defined(USE_STACK_TRACES) + DECLARE_CALLSTACKSTATSTRUCT(); + DECLARE_CALLSTACKSTATSTRUCT_FIELDDESCRIPTION(); +#endif + + MemInfo_t() { memset(this, 0, sizeof(*this)); } + + // Size in bytes + size_t m_nCurrentSize; + size_t m_nPeakSize; + size_t m_nTotalSize; + size_t m_nOverheadSize; + size_t m_nPeakOverheadSize; + + // Count in terms of # of allocations + int m_nCurrentCount; + int m_nPeakCount; + int m_nTotalCount; + + int m_nSumTargetRange; + int m_nCurTargetRange; + int m_nMaxTargetRange; + + // Count in terms of # of allocations of a particular size + int m_pCount[NUM_BYTE_COUNT_BUCKETS]; + + // Time spent allocating + deallocating (microseconds) + int64 m_nTime; + }; + + struct MemInfoKey_FileLine_t { + MemInfoKey_FileLine_t(const char *pFileName, int line) + : m_pFileName(pFileName), m_nLine(line) {} + bool operator<(const MemInfoKey_FileLine_t &key) const { + int iret = V_tier0_stricmp(m_pFileName, key.m_pFileName); + if (iret < 0) return true; + + if (iret > 0) return false; + + return m_nLine < key.m_nLine; + } + + const char *m_pFileName; + int m_nLine; + }; + + // NOTE: Deliberately using STL here because the UTL stuff + // is a client of this library; want to avoid circular dependency + + // Maps file name to info + typedef std::map< + MemInfoKey_FileLine_t, MemInfo_t, std::less, + CNoRecurseAllocator>> + StatMap_FileLine_t; + typedef StatMap_FileLine_t::iterator StatMapIter_FileLine_t; + typedef StatMap_FileLine_t::value_type StatMapEntry_FileLine_t; + + typedef std::set> + Filenames_t; + + // Heap reporting method + typedef void (*HeapReportFunc_t)(PRINTF_FORMAT_STRING char const *pFormat, + ...); + + private: + // Returns the actual debug info + virtual void GetActualDbgInfo(const char *&pFileName, int &nLine); + + // Finds the file in our map + MemInfo_t &FindOrCreateEntry(const char *pFileName, int line); + const char *FindOrCreateFilename(const char *pFileName); + +#if defined(USE_STACK_TRACES) + int GetCallStackForIndex(unsigned int index, void **pCallStackOut, + int iMaxEntriesOut); + friend int GetAllocationCallStack(void *mem, void **pCallStackOut, + int iMaxEntriesOut); +#endif + + // Updates stats + virtual void RegisterAllocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime); + virtual void RegisterDeallocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime); +#if defined(USE_STACK_TRACES) + void RegisterAllocation(unsigned int nStatIndex, size_t nLogicalSize, + size_t nActualSize, unsigned nTime); + void RegisterDeallocation(unsigned int nStatIndex, size_t nLogicalSize, + size_t nActualSize, unsigned nTime); +#endif + void RegisterAllocation(MemInfo_t &info, size_t nLogicalSize, + size_t nActualSize, unsigned nTime); + void RegisterDeallocation(MemInfo_t &info, size_t nLogicalSize, + size_t nActualSize, unsigned nTime); + + // Gets the allocation file name + const char *GetAllocatonFileName(void *pMem); + int GetAllocatonLineNumber(void *pMem); + + // FIXME: specify a spew output func for dumping stats + // Stat output + void DumpMemInfo(const char *pAllocationName, int line, + const MemInfo_t &info); + void DumpFileStats(); +#if defined(USE_STACK_TRACES) + void DumpMemInfo(void *const CallStack[STACK_TRACE_LENGTH], + const MemInfo_t &info); + void DumpCallStackFlow(char const *pchFileBase); +#endif + virtual void DumpStats(); + virtual void DumpStatsFileBase(char const *pchFileBase); + virtual void DumpBlockStats(void *p); + virtual void GlobalMemoryStatus(size_t *pUsedMemory, size_t *pFreeMemory); + + virtual size_t ComputeMemoryUsedBy(char const *pchSubStr); + + virtual IVirtualMemorySection *AllocateVirtualMemorySection( + size_t numMaxBytes) { +#if defined(_GAMECONSOLE) + extern IVirtualMemorySection * + VirtualMemoryManager_AllocateVirtualMemorySection(size_t numMaxBytes); + return VirtualMemoryManager_AllocateVirtualMemorySection(numMaxBytes); +#else + return NULL; +#endif + } + + virtual int GetGenericMemoryStats(GenericMemoryStat_t **ppMemoryStats) { + // TODO: reuse code from GlobalMemoryStatus (though this is only really + // useful when using CStdMemAlloc...) + return 0; + } + + private: + StatMap_FileLine_t m_StatMap_FileLine; +#if defined(USE_STACK_TRACES) + typedef CCallStackStatsGatherer< + MemInfo_t, STACK_TRACE_LENGTH, GetCallStack_Fast, + CCallStackStatsGatherer_StatMutexPool<128>, CNoRecurseAllocator> + CallStackStatsType_t; + CallStackStatsType_t m_CallStackStats; +#endif + + MemInfo_t m_GlobalInfo; + CFastTimer m_Timer; + bool m_bInitialized; + Filenames_t m_Filenames; + + HeapReportFunc_t m_OutputFunc; + + static size_t s_pCountSizes[NUM_BYTE_COUNT_BUCKETS]; + static const char *s_pCountHeader[NUM_BYTE_COUNT_BUCKETS]; + + size_t m_sMemoryAllocFailed; +}; + +static char const *g_pszUnknown = "unknown"; + +#if defined(USE_STACK_TRACES) +BEGIN_STATSTRUCTDESCRIPTION(CDbgMemAlloc::MemInfo_t) +WRITE_STATSTRUCT_FIELDDESCRIPTION(); +END_STATSTRUCTDESCRIPTION() + +BEGIN_STATSTRUCTFIELDDESCRIPTION(CDbgMemAlloc::MemInfo_t) +DEFINE_STATSTRUCTFIELD(m_nCurrentSize, BasicStatStructFieldDesc, + (BSSFT_SIZE_T, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nPeakSize, BasicStatStructFieldDesc, + (BSSFT_SIZE_T, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nTotalSize, BasicStatStructFieldDesc, + (BSSFT_SIZE_T, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nOverheadSize, BasicStatStructFieldDesc, + (BSSFT_SIZE_T, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nPeakOverheadSize, BasicStatStructFieldDesc, + (BSSFT_SIZE_T, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nCurrentCount, BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nPeakCount, BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nTotalCount, BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nSumTargetRange, BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nCurTargetRange, BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nMaxTargetRange, BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD_ARRAYENTRY(m_pCount, BYTE_COUNT_16, + BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD_ARRAYENTRY(m_pCount, BYTE_COUNT_32, + BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD_ARRAYENTRY(m_pCount, BYTE_COUNT_128, + BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD_ARRAYENTRY(m_pCount, BYTE_COUNT_2048, + BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD_ARRAYENTRY(m_pCount, BYTE_COUNT_GREATER, + BasicStatStructFieldDesc, + (BSSFT_INT, BSSFCM_ADD)) +DEFINE_STATSTRUCTFIELD(m_nTime, BasicStatStructFieldDesc, + (BSSFT_INT64, BSSFCM_ADD)) +END_STATSTRUCTFIELDDESCRIPTION() +#endif + +//----------------------------------------------------------------------------- + +const int DBG_INFO_STACK_DEPTH = 32; + +struct DbgInfoStack_t { + const char *m_pFileName; + int m_nLine; +}; + +#ifdef _PS3 +#ifndef _CERT +extern TLSGlobals *(*g_pfnElfGetTlsGlobals)(); +#define IfDbgInfoIsReady() \ + if (TLSGlobals *IfDbgInfoIsReady_pTlsGlobals = \ + g_pfnElfGetTlsGlobals ? g_pfnElfGetTlsGlobals() : NULL) +#else +#define IfDbgInfoIsReady() \ + if (TLSGlobals *IfDbgInfoIsReady_pTlsGlobals = GetTLSGlobals()) +#endif +#define g_DbgInfoStack \ + ((DbgInfoStack_t *&)IfDbgInfoIsReady_pTlsGlobals->pMallocDbgInfoStack) +#define g_nDbgInfoStackDepth \ + (IfDbgInfoIsReady_pTlsGlobals->nMallocDbgInfoStackDepth) +#else +CTHREADLOCALPTR(DbgInfoStack_t) g_DbgInfoStack CONSTRUCT_EARLY; +CTHREADLOCALINT g_nDbgInfoStackDepth CONSTRUCT_EARLY; +#define IfDbgInfoIsReady() if (true) +#endif + +#ifdef _PS3 +struct CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early { + CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early() { + malloc_managed_size mms; + mms.current_inuse_size = 0x12345678; + mms.current_system_size = 0x09ABCDEF; + mms.max_system_size = 0; + int iResult = malloc_stats(&mms); + g_pMemOverrideRawCrtFns = + reinterpret_cast(iResult); + } +} g_CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early CONSTRUCT_EARLY; +#endif + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +static CDbgMemAlloc s_DbgMemAlloc CONSTRUCT_EARLY; + +#ifdef _PS3 + +IMemAlloc *g_pMemAllocInternalPS3 = &s_DbgMemAlloc; +PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3_IMPL + +#else // !_PS3 + +#ifndef TIER0_VALIDATE_HEAP +IMemAlloc *g_pMemAlloc CONSTRUCT_EARLY = &s_DbgMemAlloc; +#else +IMemAlloc *g_pActualAlloc = &s_DbgMemAlloc; +#endif + +#endif // _PS3 + +//----------------------------------------------------------------------------- + +CThreadMutex g_DbgMemMutex CONSTRUCT_EARLY; + +#define HEAP_LOCK() AUTO_LOCK(g_DbgMemMutex) + +//----------------------------------------------------------------------------- +// Byte count buckets +//----------------------------------------------------------------------------- +size_t CDbgMemAlloc::s_pCountSizes[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = { + 16, 32, 128, 2048, INT_MAX}; + +const char *CDbgMemAlloc::s_pCountHeader[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = + {"<=16 byte allocations", "17-32 byte allocations", + "33-128 byte allocations", "129-2048 byte allocations", + ">2048 byte allocations"}; + +size_t g_TargetCountRangeMin = 0, g_TargetCountRangeMax = 0; + +//----------------------------------------------------------------------------- +// Standard output +//----------------------------------------------------------------------------- +static FILE *s_DbgFile; + +static void DefaultHeapReportFunc(PRINTF_FORMAT_STRING char const *pFormat, + ...) { + va_list args; + va_start(args, pFormat); + vfprintf(s_DbgFile, pFormat, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDbgMemAlloc::CDbgMemAlloc() : m_sMemoryAllocFailed((size_t)0) { + CClockSpeedInit::Init(); + + m_OutputFunc = DefaultHeapReportFunc; + m_bInitialized = true; + + if (!IsDebug() && !IsX360()) { + Plat_DebugString( + "USE_MEM_DEBUG is enabled in a release build. Don't check this in!\n"); + } + +#ifdef _PS3 + g_pMemAllocInternalPS3 = &s_DbgMemAlloc; + PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3.m_pMemAllocCached = &s_DbgMemAlloc; + malloc_managed_size mms; + mms.current_inuse_size = 0x12345678; + mms.current_system_size = 0x09ABCDEF; + mms.max_system_size = reinterpret_cast(this); + int iResult = malloc_stats(&mms); + g_pMemOverrideRawCrtFns = + reinterpret_cast(iResult); +#endif +} + +CDbgMemAlloc::~CDbgMemAlloc() { + Filenames_t::const_iterator iter = m_Filenames.begin(); + while (iter != m_Filenames.end()) { + char *pFileName = (char *)(*iter); + free(pFileName); + iter++; + } + m_bInitialized = false; +} + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- + +void *CDbgMemAlloc::Alloc(size_t nSize) { + /* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } + */ + char szModule[MAX_PATH]; + if (GetCallerModule(szModule, MAX_PATH)) { + return Alloc(nSize, szModule, 0); + } else { + return Alloc(nSize, g_pszUnknown, 0); + } + // return malloc( nSize ); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CDbgMemAlloc::AllocAlign(size_t nSize, size_t align) { + /* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } + */ + char szModule[MAX_PATH]; + if (GetCallerModule(szModule, MAX_PATH)) { + return AllocAlign(nSize, align, szModule, 0); + } else { + return AllocAlign(nSize, align, g_pszUnknown, 0); + } + // return malloc( nSize ); +} +#endif + +void *CDbgMemAlloc::Realloc(void *pMem, size_t nSize) { + /* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } + */ + // FIXME: Should these gather stats? + char szModule[MAX_PATH]; + if (GetCallerModule(szModule, MAX_PATH)) { + return Realloc(pMem, nSize, szModule, 0); + } else { + return Realloc(pMem, nSize, g_pszUnknown, 0); + } + // return realloc( pMem, nSize ); +} + +void CDbgMemAlloc::Free(void *pMem) { + // FIXME: Should these gather stats? + Free(pMem, g_pszUnknown, 0); + // free( pMem ); +} + +void *CDbgMemAlloc::Expand_NoLongerSupported(void *pMem, size_t nSize) { + return NULL; +} + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CDbgMemAlloc::PushAllocDbgInfo(const char *pFileName, int nLine) { + IfDbgInfoIsReady() { + if (g_DbgInfoStack == NULL) { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc(sizeof(DbgInfoStack_t) * + DBG_INFO_STACK_DEPTH); + g_nDbgInfoStackDepth = -1; + } + + ++g_nDbgInfoStackDepth; + Assert(g_nDbgInfoStackDepth < DBG_INFO_STACK_DEPTH); + g_DbgInfoStack[g_nDbgInfoStackDepth].m_pFileName = + FindOrCreateFilename(pFileName); + g_DbgInfoStack[g_nDbgInfoStackDepth].m_nLine = nLine; + } +} + +void CDbgMemAlloc::PopAllocDbgInfo() { + IfDbgInfoIsReady() { + if (g_DbgInfoStack == NULL) { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc(sizeof(DbgInfoStack_t) * + DBG_INFO_STACK_DEPTH); + g_nDbgInfoStackDepth = -1; + } + + --g_nDbgInfoStackDepth; + Assert(g_nDbgInfoStackDepth >= -1); + } +} + +//----------------------------------------------------------------------------- +// handles storing allocation info for coroutines +//----------------------------------------------------------------------------- +uint32 CDbgMemAlloc::GetDebugInfoSize() { + return sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH + sizeof(int32); +} + +void CDbgMemAlloc::SaveDebugInfo(void *pvDebugInfo) { + IfDbgInfoIsReady() { + if (g_DbgInfoStack == NULL) { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc(sizeof(DbgInfoStack_t) * + DBG_INFO_STACK_DEPTH); + g_nDbgInfoStackDepth = -1; + } + + int32 *pnStackDepth = (int32 *)pvDebugInfo; + *pnStackDepth = g_nDbgInfoStackDepth; + memcpy(pnStackDepth + 1, &g_DbgInfoStack[0], + sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH); + } +} + +void CDbgMemAlloc::RestoreDebugInfo(const void *pvDebugInfo) { + IfDbgInfoIsReady() { + if (g_DbgInfoStack == NULL) { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc(sizeof(DbgInfoStack_t) * + DBG_INFO_STACK_DEPTH); + } + + const int32 *pnStackDepth = (const int32 *)pvDebugInfo; + g_nDbgInfoStackDepth = *pnStackDepth; + memcpy(&g_DbgInfoStack[0], pnStackDepth + 1, + sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH); + } +} + +void CDbgMemAlloc::InitDebugInfo(void *pvDebugInfo, const char *pchRootFileName, + int nLine) { + int32 *pnStackDepth = (int32 *)pvDebugInfo; + + if (pchRootFileName) { + *pnStackDepth = 0; + + DbgInfoStack_t *pStackRoot = (DbgInfoStack_t *)(pnStackDepth + 1); + pStackRoot->m_pFileName = FindOrCreateFilename(pchRootFileName); + pStackRoot->m_nLine = nLine; + } else { + *pnStackDepth = -1; + } +} + +//----------------------------------------------------------------------------- +// Returns the actual debug info +//----------------------------------------------------------------------------- +void CDbgMemAlloc::GetActualDbgInfo(const char *&pFileName, int &nLine) { +#if defined(USE_STACK_TRACES_DETAILED) + return; +#endif + + IfDbgInfoIsReady() { + if (g_DbgInfoStack == NULL) { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc(sizeof(DbgInfoStack_t) * + DBG_INFO_STACK_DEPTH); + g_nDbgInfoStackDepth = -1; + } + + if (g_nDbgInfoStackDepth >= 0 && g_DbgInfoStack[0].m_pFileName) { + pFileName = g_DbgInfoStack[0].m_pFileName; + nLine = g_DbgInfoStack[0].m_nLine; + } + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDbgMemAlloc::FindOrCreateFilename(const char *pFileName) { + // If we created it for the first time, actually *allocate* the filename + // memory + HEAP_LOCK(); + // This is necessary for shutdown conditions: the file name is stored + // in some piece of memory in a DLL; if that DLL becomes unloaded, + // we'll have a pointer to crap memory + + if (!pFileName) { + pFileName = g_pszUnknown; + } + +#if defined(USE_STACK_TRACES_DETAILED) + { + // Walk the stack to determine what's causing the allocation + void *arrStackAddresses[10] = {0}; + int numStackAddrRetrieved = GetCallStack_Fast( + arrStackAddresses, 10, + 2); // Skip this function, and either CDbgMemAlloc::Alloc() or + // CDbgMemAlloc::Realloc() + char *szStack = StackDescribe(arrStackAddresses, numStackAddrRetrieved); + if (szStack && *szStack) { + pFileName = szStack; // Use the stack description for the allocation + } + } +#endif // #if defined( USE_STACK_TRACES_DETAILED ) + + char *pszFilenameCopy; + Filenames_t::const_iterator iter = m_Filenames.find(pFileName); + if (iter == m_Filenames.end()) { + size_t nLen = strlen(pFileName) + 1; + pszFilenameCopy = (char *)DebugAlloc(nLen); + memcpy(pszFilenameCopy, pFileName, nLen); + m_Filenames.insert(pszFilenameCopy); + } else { + pszFilenameCopy = (char *)(*iter); + } + + return pszFilenameCopy; +} + +//----------------------------------------------------------------------------- +// Finds the file in our map +//----------------------------------------------------------------------------- +CDbgMemAlloc::MemInfo_t &CDbgMemAlloc::FindOrCreateEntry(const char *pFileName, + int line) { + // Oh how I love crazy STL. retval.first == the StatMapIter_t in the std::pair + // retval.first->second == the MemInfo_t that's part of the StatMapIter_t + std::pair retval; + retval = m_StatMap_FileLine.insert(StatMapEntry_FileLine_t( + MemInfoKey_FileLine_t(pFileName, line), MemInfo_t())); + return retval.first->second; +} + +#if defined(USE_STACK_TRACES) +int CDbgMemAlloc::GetCallStackForIndex(unsigned int index, void **pCallStackOut, + int iMaxEntriesOut) { + if (iMaxEntriesOut > STACK_TRACE_LENGTH) iMaxEntriesOut = STACK_TRACE_LENGTH; + + CallStackStatsType_t::StackReference stackRef = + m_CallStackStats.GetCallStackForIndex(index); + + memcpy(pCallStackOut, stackRef, iMaxEntriesOut * sizeof(void *)); + for (int i = 0; i != iMaxEntriesOut; ++i) { + if (pCallStackOut[i] == NULL) return i; + } + return iMaxEntriesOut; +} +#endif + +//----------------------------------------------------------------------------- +// Updates stats +//----------------------------------------------------------------------------- +void CDbgMemAlloc::RegisterAllocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) { + HEAP_LOCK(); + RegisterAllocation(m_GlobalInfo, nLogicalSize, nActualSize, nTime); + RegisterAllocation(FindOrCreateEntry(pFileName, nLine), nLogicalSize, + nActualSize, nTime); +} + +void CDbgMemAlloc::RegisterDeallocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) { + HEAP_LOCK(); + RegisterDeallocation(m_GlobalInfo, nLogicalSize, nActualSize, nTime); + RegisterDeallocation(FindOrCreateEntry(pFileName, nLine), nLogicalSize, + nActualSize, nTime); +} + +#if defined(USE_STACK_TRACES) +void CDbgMemAlloc::RegisterAllocation(unsigned int nStatIndex, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) { + HEAP_LOCK(); + RegisterAllocation(m_GlobalInfo, nLogicalSize, nActualSize, nTime); + CCallStackStatsGatherer_StructAccessor_AutoLock entryAccessor = + m_CallStackStats.GetEntry(nStatIndex); + RegisterAllocation(*entryAccessor.GetStruct(), nLogicalSize, nActualSize, + nTime); +} + +void CDbgMemAlloc::RegisterDeallocation(unsigned int nStatIndex, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) { + HEAP_LOCK(); + RegisterDeallocation(m_GlobalInfo, nLogicalSize, nActualSize, nTime); + CCallStackStatsGatherer_StructAccessor_AutoLock entryAccessor = + m_CallStackStats.GetEntry(nStatIndex); + RegisterDeallocation(*entryAccessor.GetStruct(), nLogicalSize, nActualSize, + nTime); +} +#endif + +void CDbgMemAlloc::RegisterAllocation(MemInfo_t &info, size_t nLogicalSize, + size_t nActualSize, unsigned nTime) { + ++info.m_nCurrentCount; + ++info.m_nTotalCount; + if (info.m_nCurrentCount > info.m_nPeakCount) { + info.m_nPeakCount = info.m_nCurrentCount; + } + + info.m_nCurrentSize += nLogicalSize; + info.m_nTotalSize += nLogicalSize; + if (info.m_nCurrentSize > info.m_nPeakSize) { + info.m_nPeakSize = info.m_nCurrentSize; + } + + if (nLogicalSize > g_TargetCountRangeMin && + nLogicalSize <= g_TargetCountRangeMax) { + info.m_nSumTargetRange++; + info.m_nCurTargetRange++; + if (info.m_nCurTargetRange > info.m_nMaxTargetRange) { + info.m_nMaxTargetRange = info.m_nCurTargetRange; + } + } + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { + if (nLogicalSize <= s_pCountSizes[i]) { + ++info.m_pCount[i]; + break; + } + } + + Assert(info.m_nPeakCount >= info.m_nCurrentCount); + Assert(info.m_nPeakSize >= info.m_nCurrentSize); + + info.m_nOverheadSize += (nActualSize - nLogicalSize); + if (info.m_nOverheadSize > info.m_nPeakOverheadSize) { + info.m_nPeakOverheadSize = info.m_nOverheadSize; + } + + info.m_nTime += nTime; +} + +void CDbgMemAlloc::RegisterDeallocation(MemInfo_t &info, size_t nLogicalSize, + size_t nActualSize, unsigned nTime) { + --info.m_nCurrentCount; + info.m_nCurrentSize -= nLogicalSize; + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { + if (nLogicalSize <= s_pCountSizes[i]) { + --info.m_pCount[i]; + break; + } + } + + if (nLogicalSize > g_TargetCountRangeMin && + nLogicalSize <= g_TargetCountRangeMax) { + info.m_nCurTargetRange--; + } + + Assert(info.m_nPeakCount >= info.m_nCurrentCount); + Assert(info.m_nPeakSize >= info.m_nCurrentSize); + Assert(info.m_nCurrentCount >= 0); + + info.m_nOverheadSize -= (nActualSize - nLogicalSize); + + info.m_nTime += nTime; +} + +//----------------------------------------------------------------------------- +// Gets the allocation file name +//----------------------------------------------------------------------------- + +const char *CDbgMemAlloc::GetAllocatonFileName(void *pMem) { + if (!pMem) return ""; + + CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader(pMem); + if (pHeader->m_pFileName) + return pHeader->m_pFileName; + else + return g_pszUnknown; +} + +//----------------------------------------------------------------------------- +// Gets the allocation file name +//----------------------------------------------------------------------------- +int CDbgMemAlloc::GetAllocatonLineNumber(void *pMem) { + if (!pMem) return 0; + + CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader(pMem); + return pHeader->m_nLineNumber; +} + +//----------------------------------------------------------------------------- +// Debug versions of the main allocation methods +//----------------------------------------------------------------------------- +void *CDbgMemAlloc::Alloc(size_t nSize, const char *pFileName, int nLine) { + HEAP_LOCK(); + +#if defined(USE_STACK_TRACES) + unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndex( + CCallStackStorage(m_CallStackStats.StackFunction, 1)); +#endif + + if (!m_bInitialized) { + void *pRetval = InternalMalloc(nSize, pFileName, nLine); + +#if defined(USE_STACK_TRACES) + if (pRetval) { + GetAllocationStatIndex_Internal(pRetval) = iStatEntryIndex; + } +#endif + + return pRetval; + } + + if (pFileName != g_pszUnknown) pFileName = FindOrCreateFilename(pFileName); + + GetActualDbgInfo(pFileName, nLine); + + /* + if ( strcmp( pFileName, "class CUtlVector >" ) == 0) + { + GetActualDbgInfo( pFileName, nLine ); + } + */ + + m_Timer.Start(); + void *pMem = InternalMalloc(nSize, pFileName, nLine); + m_Timer.End(); + +#if defined(USE_STACK_TRACES) + if (pMem) { + GetAllocationStatIndex_Internal(pMem) = iStatEntryIndex; + } +#endif + + ApplyMemoryInitializations(pMem, nSize); + +#if defined(USE_STACK_TRACES) + RegisterAllocation(GetAllocationStatIndex_Internal(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#else + RegisterAllocation(GetAllocatonFileName(pMem), GetAllocatonLineNumber(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#endif + + if (!pMem) { + SetCRTAllocFailed(nSize); + } + return pMem; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CDbgMemAlloc::AllocAlign(size_t nSize, size_t align, + const char *pFileName, int nLine) { + HEAP_LOCK(); + +#if defined(USE_STACK_TRACES) + unsigned int iStatEntryIndex = + m_CallStackStats.GetEntryIndexForCurrentCallStack(1); +#endif + + if (!m_bInitialized) { + void *pRetval = InternalMalloc(nSize, pFileName, nLine); + +#if defined(USE_STACK_TRACES) + if (pRetval) { + GetAllocationStatIndex_Internal(pRetval) = iStatEntryIndex; + } +#endif + + return pRetval; + } + + if (pFileName != g_pszUnknown) pFileName = FindOrCreateFilename(pFileName); + + GetActualDbgInfo(pFileName, nLine); + + /* + if ( strcmp( pFileName, "class CUtlVector >" ) == 0) + { + GetActualDbgInfo( pFileName, nLine ); + } + */ + + m_Timer.Start(); + void *pMem = InternalMallocAligned(nSize, align, pFileName, nLine); + m_Timer.End(); + +#if defined(USE_STACK_TRACES) + if (pMem) { + GetAllocationStatIndex_Internal(pMem) = iStatEntryIndex; + } +#endif + + ApplyMemoryInitializations(pMem, nSize); + +#if defined(USE_STACK_TRACES) + RegisterAllocation(GetAllocationStatIndex_Internal(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#else + RegisterAllocation(GetAllocatonFileName(pMem), GetAllocatonLineNumber(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#endif + + if (!pMem) { + SetCRTAllocFailed(nSize); + } + return pMem; +} +#endif + +void *CDbgMemAlloc::Realloc(void *pMem, size_t nSize, const char *pFileName, + int nLine) { + HEAP_LOCK(); + + pFileName = FindOrCreateFilename(pFileName); + +#if defined(USE_STACK_TRACES) + unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndex( + CCallStackStorage(m_CallStackStats.StackFunction, 1)); +#endif + + if (!m_bInitialized) { + pMem = InternalRealloc(pMem, nSize, pFileName, nLine); + +#if defined(USE_STACK_TRACES) + if (pMem) { + GetAllocationStatIndex_Internal(pMem) = iStatEntryIndex; + } +#endif + return pMem; + } + + if (pMem != 0) { +#if defined(USE_STACK_TRACES) + RegisterDeallocation(GetAllocationStatIndex_Internal(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), 0); +#else + RegisterDeallocation(GetAllocatonFileName(pMem), + GetAllocatonLineNumber(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), 0); +#endif + } + + GetActualDbgInfo(pFileName, nLine); + + m_Timer.Start(); + pMem = InternalRealloc(pMem, nSize, pFileName, nLine); + m_Timer.End(); + +#if defined(USE_STACK_TRACES) + if (pMem) { + GetAllocationStatIndex_Internal(pMem) = iStatEntryIndex; + } +#endif + +#if defined(USE_STACK_TRACES) + RegisterAllocation(GetAllocationStatIndex_Internal(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#else + RegisterAllocation(GetAllocatonFileName(pMem), GetAllocatonLineNumber(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#endif + + if (!pMem) { + SetCRTAllocFailed(nSize); + } + return pMem; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CDbgMemAlloc::ReallocAlign(void *pMem, size_t nSize, size_t align) { + /* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } + */ + char szModule[MAX_PATH]; + if (GetCallerModule(szModule, MAX_PATH)) { + return ReallocAlign(pMem, nSize, align, szModule, 0); + } else { + return ReallocAlign(pMem, nSize, align, g_pszUnknown, 0); + } + // return malloc( nSize ); +} +void *CDbgMemAlloc::ReallocAlign(void *pMem, size_t nSize, size_t align, + const char *pFileName, int nLine) { + HEAP_LOCK(); + + pFileName = FindOrCreateFilename(pFileName); + +#if defined(USE_STACK_TRACES) + unsigned int iStatEntryIndex = + m_CallStackStats.GetEntryIndexForCurrentCallStack(1); +#endif + + if (!m_bInitialized) { + pMem = InternalReallocAligned(pMem, nSize, align, pFileName, nLine); + +#if defined(USE_STACK_TRACES) + if (pMem) { + GetAllocationStatIndex_Internal(pMem) = iStatEntryIndex; + } +#endif + return pMem; + } + + if (pMem != 0) { +#if defined(USE_STACK_TRACES) + RegisterDeallocation(GetAllocationStatIndex_Internal(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), 0); +#else + RegisterDeallocation(GetAllocatonFileName(pMem), + GetAllocatonLineNumber(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), 0); +#endif + } + + GetActualDbgInfo(pFileName, nLine); + + m_Timer.Start(); + pMem = InternalReallocAligned(pMem, nSize, align, pFileName, nLine); + m_Timer.End(); + +#if defined(USE_STACK_TRACES) + if (pMem) { + GetAllocationStatIndex_Internal(pMem) = iStatEntryIndex; + } +#endif + +#if defined(USE_STACK_TRACES) + RegisterAllocation(GetAllocationStatIndex_Internal(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#else + RegisterAllocation(GetAllocatonFileName(pMem), GetAllocatonLineNumber(pMem), + InternalLogicalSize(pMem), InternalMSize(pMem), + m_Timer.GetDuration().GetMicroseconds()); +#endif + + if (!pMem) { + SetCRTAllocFailed(nSize); + } + return pMem; +} +#endif + +void CDbgMemAlloc::Free(void *pMem, const char * /*pFileName*/, int nLine) { + if (!pMem) return; + + HEAP_LOCK(); + + if (!m_bInitialized) { + InternalFree(pMem); + return; + } + + size_t nOldLogicalSize = InternalLogicalSize(pMem); + size_t nOldSize = InternalMSize(pMem); + +#if defined(USE_STACK_TRACES) + unsigned int oldStatIndex = GetAllocationStatIndex_Internal(pMem); +#else + const char *pOldFileName = GetAllocatonFileName(pMem); + int oldLine = GetAllocatonLineNumber(pMem); +#endif + + m_Timer.Start(); + InternalFree(pMem); + m_Timer.End(); + +#if defined(USE_STACK_TRACES) + RegisterDeallocation(oldStatIndex, nOldLogicalSize, nOldSize, + m_Timer.GetDuration().GetMicroseconds()); +#else + RegisterDeallocation(pOldFileName, oldLine, nOldLogicalSize, nOldSize, + m_Timer.GetDuration().GetMicroseconds()); +#endif +} + +void *CDbgMemAlloc::Expand_NoLongerSupported(void *pMem, size_t nSize, + const char *pFileName, int nLine) { + return NULL; +} + +//----------------------------------------------------------------------------- +// Returns the size of a particular allocation (NOTE: may be larger than the +// size requested!) +//----------------------------------------------------------------------------- +size_t CDbgMemAlloc::GetSize(void *pMem) { + HEAP_LOCK(); + + if (!pMem) return m_GlobalInfo.m_nCurrentSize; + + return InternalMSize(pMem); +} + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +int32 CDbgMemAlloc::CrtSetBreakAlloc(int32 lNewBreakAlloc) { +#ifdef POSIX + return 0; +#else + return _CrtSetBreakAlloc(lNewBreakAlloc); +#endif +} + +int CDbgMemAlloc::CrtSetReportMode(int nReportType, int nReportMode) { +#ifdef POSIX + return 0; +#else + return _CrtSetReportMode(nReportType, nReportMode); +#endif +} + +int CDbgMemAlloc::CrtIsValidHeapPointer(const void *pMem) { +#ifdef POSIX + return 0; +#else + return _CrtIsValidHeapPointer(pMem); +#endif +} + +int CDbgMemAlloc::CrtIsValidPointer(const void *pMem, unsigned int size, + int access) { +#ifdef POSIX + return 0; +#else + return _CrtIsValidPointer(pMem, size, access); +#endif +} + +#define DBGMEM_CHECKMEMORY 1 + +int CDbgMemAlloc::CrtCheckMemory(void) { +#if !defined(DBGMEM_CHECKMEMORY) || defined(POSIX) + return 1; +#elif defined(_WIN32) + if (!_CrtCheckMemory()) { + Msg("Memory check failed!\n"); + return 0; + } + return 1; +#else + return 1; +#endif +} + +int CDbgMemAlloc::CrtSetDbgFlag(int nNewFlag) { +#ifdef POSIX + return 0; +#else + return _CrtSetDbgFlag(nNewFlag); +#endif +} + +void CDbgMemAlloc::CrtMemCheckpoint(_CrtMemState *pState) { +#ifndef POSIX + _CrtMemCheckpoint(pState); +#endif +} + +// FIXME: Remove when we have our own allocator +void *CDbgMemAlloc::CrtSetReportFile(int nRptType, void *hFile) { +#ifdef POSIX + return 0; +#else + return (void *)_CrtSetReportFile(nRptType, (_HFILE)hFile); +#endif +} + +void *CDbgMemAlloc::CrtSetReportHook(void *pfnNewHook) { +#ifdef POSIX + return 0; +#else + return (void *)_CrtSetReportHook((_CRT_REPORT_HOOK)pfnNewHook); +#endif +} + +int CDbgMemAlloc::CrtDbgReport(int nRptType, const char *szFile, int nLine, + const char *szModule, const char *pMsg) { +#ifdef POSIX + return 0; +#else + return _CrtDbgReport(nRptType, szFile, nLine, szModule, pMsg); +#endif +} + +int CDbgMemAlloc::heapchk() { +#ifdef POSIX + return 0; +#else + if (CrtCheckMemory()) + return _HEAPOK; + else + return _HEAPBADPTR; +#endif +} + +void CDbgMemAlloc::DumpBlockStats(void *p) { + DbgMemHeader_t *pBlock = (DbgMemHeader_t *)p - 1; + if (!CrtIsValidHeapPointer(pBlock)) { + Msg("0x%x is not valid heap pointer\n", p); + return; + } + + const char *pFileName = GetAllocatonFileName(p); + int line = GetAllocatonLineNumber(p); + + Msg("0x%x allocated by %s line %d, %d bytes\n", p, pFileName, line, + GetSize(p)); +} + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +void CDbgMemAlloc::DumpMemInfo(const char *pAllocationName, int line, + const MemInfo_t &info) { + m_OutputFunc( + "%s, line %i\t%.1f\t%.1f\t%.1f\t%.1f\t%.1f\t%d\t%d\t%d\t%d\t%d\t%d\t%d", + pAllocationName, line, info.m_nCurrentSize / 1024.0f, + info.m_nPeakSize / 1024.0f, info.m_nTotalSize / 1024.0f, + info.m_nOverheadSize / 1024.0f, info.m_nPeakOverheadSize / 1024.0f, + (int)(info.m_nTime / 1000), info.m_nCurrentCount, info.m_nPeakCount, + info.m_nTotalCount, info.m_nSumTargetRange, info.m_nCurTargetRange, + info.m_nMaxTargetRange); + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { + m_OutputFunc("\t%d", info.m_pCount[i]); + } + + m_OutputFunc("\n"); +} + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +size_t CDbgMemAlloc::ComputeMemoryUsedBy(char const *pchSubStr) { + size_t total = 0; + StatMapIter_FileLine_t iter = m_StatMap_FileLine.begin(); + while (iter != m_StatMap_FileLine.end()) { + if (!pchSubStr || strstr(iter->first.m_pFileName, pchSubStr)) { + total += iter->second.m_nCurrentSize; + } + iter++; + } + return total; +} + +void CDbgMemAlloc::DumpFileStats() { + StatMapIter_FileLine_t iter = m_StatMap_FileLine.begin(); + while (iter != m_StatMap_FileLine.end()) { + DumpMemInfo(iter->first.m_pFileName, iter->first.m_nLine, iter->second); + iter++; + } +} + +void CDbgMemAlloc::DumpStatsFileBase(char const *pchFileBase) { + char szFileName[MAX_PATH]; + static int s_FileCount = 0; + if (m_OutputFunc == DefaultHeapReportFunc) { + const char pPath[] = ""; +#ifdef _X360 + const char pPath[] = "D:\\"; +#elif defined(_PS3) + const char pPath[] = "/app_home/"; +#endif + +#if defined(_MEMTEST) && defined(_WIN32) + char szXboxName[32]; + strcpy(szXboxName, "xbox"); + DWORD numChars = sizeof(szXboxName); + DmGetXboxName(szXboxName, &numChars); + char *pXboxName = strstr(szXboxName, "_360"); + if (pXboxName) { + *pXboxName = '\0'; + } + + SYSTEMTIME systemTime; + GetLocalTime(&systemTime); + //_snprintf( szFileName, sizeof( szFileName ), + //"%s%s_%2.2d%2.2d_%2.2d%2.2d%2.2d_%d.txt", pPath, s_szStatsMapName, + // systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, + // systemTime.wSecond, s_FileCount ); + _snprintf(szFileName, sizeof(szFileName), "%s%s_%d.txt", pPath, + s_szStatsMapName, s_FileCount); +#else + _snprintf(szFileName, sizeof(szFileName), "%s%s_%d.txt", pPath, pchFileBase, + s_FileCount); + szFileName[sizeof(szFileName) - 1] = '\0'; +#endif + + ++s_FileCount; + + s_DbgFile = fopen(szFileName, "wt"); + if (!s_DbgFile) return; + } + + { + HEAP_LOCK(); + + m_OutputFunc( + "Allocation type\tCurrent Size(k)\tPeak Size(k)\tTotal " + "Allocations(k)\tOverhead Size(k)\tPeak Overhead " + "Size(k)\tTime(ms)\tCurrent Count\tPeak Count\tTotal " + "Count\tTNum\tTCur\tTMax"); + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { + m_OutputFunc("\t%s", s_pCountHeader[i]); + } + + m_OutputFunc("\n"); + + MemInfo_t totals = m_GlobalInfo; +#ifdef _PS3 + { + // Add a line for system heap stats + static malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)(&mms); + + MemInfo_t info; + info.m_nCurrentSize = mms.current_inuse_size; + info.m_nPeakSize = mms.max_system_size; + info.m_nOverheadSize = mms.current_system_size - mms.current_inuse_size; + DumpMemInfo("||PS3 malloc_stats||", 0, info); + + // Add a line for PRXs + char prxFilename[256]; + sys_prx_id_t prxIDs[256]; + sys_prx_segment_info_t prxSegments[32]; + sys_prx_get_module_list_t prxList = {sizeof(sys_prx_get_module_list_t), + ARRAYSIZE(prxIDs), 0, prxIDs, NULL}; + sys_prx_get_module_list(0, &prxList); + Assert(prxList.count < ARRAYSIZE(prxIDs)); + memset(&info, 0, sizeof(info)); + for (int i = 0; i < prxList.count; i++) { + sys_prx_module_info_t prxInfo; + prxInfo.size = sizeof(sys_prx_module_info_t); + prxInfo.filename = prxFilename; + prxInfo.filename_size = sizeof(prxFilename); + prxInfo.segments = prxSegments; + prxInfo.segments_num = ARRAYSIZE(prxSegments); + sys_prx_get_module_info(prxList.idlist[i], 0, &prxInfo); + Assert(prxInfo.segments_num < ARRAYSIZE(prxSegments)); + for (int j = 0; j < prxInfo.segments_num; j++) { + info.m_nCurrentSize += prxInfo.segments[j].memsz; + } + } + DumpMemInfo("PS3 PRXs", 0, info); + + // Add PRX sizes to our global tracked total: + totals.m_nCurrentSize += info.m_nCurrentSize; + } +#endif // _PS3 + + // The total of all memory usage we know about: + DumpMemInfo("||Totals||", 0, totals); + + if (IsGameConsole()) { + // Add a line showing total system memory usage from the OS (if this is + // more than + // "||Totals||", then there is unknown memory usage that we need to track + // down): + size_t usedMemory, freeMemory; + GlobalMemoryStatus(&usedMemory, &freeMemory); + MemInfo_t info; + info.m_nCurrentSize = usedMemory; + DumpMemInfo("||Used Memory||", 0, info); + } + +#ifdef _MEMTEST + { + // Add lines for GPU allocations + int nGPUMemSize, nGPUMemFree, nTextureSize, nRTSize, nVBSize, nIBSize, + nUnknown; + if (7 == sscanf(s_szStatsComment, "%d %d %d %d %d %d %d", &nGPUMemSize, + &nGPUMemFree, &nTextureSize, &nRTSize, &nVBSize, &nIBSize, + &nUnknown)) { + int nTotalUsed = nTextureSize + nRTSize + nVBSize + nIBSize + nUnknown; + int nOverhead = (nGPUMemSize - nTotalUsed) - nGPUMemFree; + m_OutputFunc("||PS3 RSX: total used||, line 0\t%.1f\n", + nTotalUsed / 1024.0f); + m_OutputFunc("PS3 RSX: textures, line 0\t%.1f\n", + nTextureSize / 1024.0f); + m_OutputFunc("PS3 RSX: render targets, line 0\t%.1f\n", + nRTSize / 1024.0f); + m_OutputFunc("PS3 RSX: vertex buffers, line 0\t%.1f\n", + nVBSize / 1024.0f); + m_OutputFunc("PS3 RSX: index buffers, line 0\t%.1f\n", + nIBSize / 1024.0f); + m_OutputFunc("PS3 RSX: unknown, line 0\t%.1f\n", nUnknown / 1024.0f); + m_OutputFunc("PS3 RSX: overhead, line 0\t%.1f\n", nOverhead / 1024.0f); + } + } +#endif + + // m_OutputFunc("File/Line Based\n"); + DumpFileStats(); + } + + if (m_OutputFunc == DefaultHeapReportFunc) { + fclose(s_DbgFile); + +#if defined(_X360) + XBX_rMemDump(szFileName); +#endif + } +} + +void CDbgMemAlloc::GlobalMemoryStatus(size_t *pUsedMemory, + size_t *pFreeMemory) { + if (!pUsedMemory || !pFreeMemory) return; + +#if defined(_X360) + + // GlobalMemoryStatus tells us how much physical memory is free + MEMORYSTATUS stat; + ::GlobalMemoryStatus(&stat); + *pFreeMemory = stat.dwAvailPhys; + + // Used is total minus free (discount the 32MB system reservation) + *pUsedMemory = (stat.dwTotalPhys - 32 * 1024 * 1024) - *pFreeMemory; + +#elif defined(_PS3) + + // need to factor in how much empty space there is in the heap + // (since it NEVER returns pages back to the OS after hitting a + // high-watermark) + static malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)(&mms); + int heapFree = mms.current_system_size - mms.current_inuse_size; + Assert(heapFree >= 0); + + // sys_memory_get_user_memory_size tells us how much PPU memory is used/free + static sys_memory_info stat; + sys_memory_get_user_memory_size(&stat); + *pFreeMemory = stat.available_user_memory; + *pFreeMemory += heapFree; + *pUsedMemory = stat.total_user_memory - *pFreeMemory; + // 213MB are available in retail mode, so adjust free mem to reflect that even + // if we're in devkit mode + const size_t RETAIL_SIZE = 213 * 1024 * 1024; + if (stat.total_user_memory > RETAIL_SIZE) + *pFreeMemory -= stat.total_user_memory - RETAIL_SIZE; + +#else + + // no data + *pFreeMemory = 0; + *pUsedMemory = 0; + +#endif +} + +#ifdef USE_STACK_TRACES +void CDbgMemAlloc::DumpCallStackFlow(char const *pchFileBase) { + HEAP_LOCK(); + + char szFileName[MAX_PATH]; + static int s_FileCount = 0; + + char *pPath = ""; + if (IsX360()) { + pPath = "D:\\"; + } + +#if defined(_MEMTEST) && defined(_WIN32) + char szXboxName[32]; + strcpy(szXboxName, "xbox"); + DWORD numChars = sizeof(szXboxName); + DmGetXboxName(szXboxName, &numChars); + char *pXboxName = strstr(szXboxName, "_360"); + if (pXboxName) { + *pXboxName = '\0'; + } + + SYSTEMTIME systemTime; + GetLocalTime(&systemTime); + _snprintf(szFileName, sizeof(szFileName), + "%s%s_%2.2d%2.2d_%2.2d%2.2d%2.2d_%d.csf", pPath, s_szStatsMapName, + systemTime.wMonth, systemTime.wDay, systemTime.wHour, + systemTime.wMinute, systemTime.wSecond, s_FileCount); +#else + _snprintf(szFileName, sizeof(szFileName), "%s%s%d.vcsf", pPath, pchFileBase, + s_FileCount); +#endif + + ++s_FileCount; + m_CallStackStats.DumpToFile(szFileName, false); +} +#endif + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +void CDbgMemAlloc::DumpStats() { + DumpStatsFileBase("memstats"); +#ifdef USE_STACK_TRACES + DumpCallStackFlow("memflow"); +#endif +} + +void CDbgMemAlloc::SetCRTAllocFailed(size_t nSize) { + m_sMemoryAllocFailed = nSize; + DebuggerBreakIfDebugging(); + char buffer[256]; + _snprintf(buffer, sizeof(buffer), + "***** OUT OF MEMORY! attempted allocation size: %zu ****\n", + nSize); + buffer[sizeof(buffer) - 1] = '\0'; +#if defined(_PS3) && defined(_DEBUG) + DebuggerBreak(); +#endif // _PS3 + +#ifdef _X360 + XBX_OutputDebugString(buffer); + if (!Plat_IsInDebugSession()) { + XBX_CrashDump(true); +#if defined(_DEMO) + XLaunchNewImage(XLAUNCH_KEYWORD_DEFAULT_APP, 0); +#else + XLaunchNewImage("default.xex", 0); +#endif + } +#elif defined(_WIN32) + OutputDebugString(buffer); + if (!Plat_IsInDebugSession()) { + AssertFatalMsg(false, buffer); + abort(); + } +#else + fprintf(stderr, "%s\n", buffer); + if (!Plat_IsInDebugSession()) { + AssertFatalMsg(false, buffer); + exit(0); + } +#endif +} + +size_t CDbgMemAlloc::MemoryAllocFailed() { return m_sMemoryAllocFailed; } + +#ifdef LINUX +// +// Under linux we can ask GLIBC to override malloc for us +// Base on code from Ryan, +// https://github.com/icculus/mallocmonitor/blob/fc7c207fb18f61977ba4e46a995f1b9f349246b1/monitor_client/malloc_hook_glibc.c +// +// +static void *glibc_malloc_hook = NULL; +static void *glibc_realloc_hook = NULL; +static void *glibc_memalign_hook = NULL; +static void *glibc_free_hook = NULL; + +/* convenience functions for setting the hooks... */ +static inline void save_glibc_hooks(void); +static inline void set_glibc_hooks(void); +static inline void set_override_hooks(void); + +CThreadMutex g_HookMutex; +/* + * Our overriding hooks...they call through to the original C runtime + * implementations and report to the monitoring daemon. + */ + +static void *override_malloc_hook(size_t s, const void *caller) { + void *retval; + AUTO_LOCK(g_HookMutex); + set_glibc_hooks(); /* put glibc back in control. */ + retval = InternalMalloc(s, NULL, 0); + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return (retval); +} /* override_malloc_hook */ + +static void *override_realloc_hook(void *ptr, size_t s, const void *caller) { + void *retval; + AUTO_LOCK(g_HookMutex); + + set_glibc_hooks(); /* put glibc back in control. */ + retval = InternalRealloc(ptr, s, NULL, 0); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return (retval); +} /* override_realloc_hook */ + +static void *override_memalign_hook(size_t a, size_t s, const void *caller) { + void *retval; + AUTO_LOCK(g_HookMutex); + + set_glibc_hooks(); /* put glibc back in control. */ + retval = memalign(a, s); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return (retval); +} /* override_memalign_hook */ + +static void override_free_hook(void *ptr, const void *caller) { + AUTO_LOCK(g_HookMutex); + + set_glibc_hooks(); /* put glibc back in control. */ + InternalFree(ptr); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ +} /* override_free_hook */ + +/* + * Convenience functions for swapping the hooks around... + */ + +/* + * Save a copy of the original allocation hooks, so we can call into them + * from our overriding functions. It's possible that glibc might change + * these hooks under various conditions (so the manual's examples seem + * to suggest), so we update them whenever we finish calling into the + * the originals. + */ +static inline void save_glibc_hooks(void) { + glibc_malloc_hook = (void *)__malloc_hook; + glibc_realloc_hook = (void *)__realloc_hook; + glibc_memalign_hook = (void *)__memalign_hook; + glibc_free_hook = (void *)__free_hook; +} /* save_glibc_hooks */ + +/* + * Restore the hooks to the glibc versions. This is needed since, say, + * their realloc() might call malloc() or free() under the hood, etc, so + * it's safer to let them have complete control over the subsystem, which + * also makes our logging saner, too. + */ +static inline void set_glibc_hooks(void) { + __malloc_hook = (void *(*)(size_t, const void *))glibc_malloc_hook; + __realloc_hook = (void *(*)(void *, size_t, const void *))glibc_realloc_hook; + __memalign_hook = + (void *(*)(size_t, size_t, const void *))glibc_memalign_hook; + __free_hook = (void (*)(void *, const void *))glibc_free_hook; +} /* set_glibc_hooks */ + +/* + * Put our hooks back in place. This should be done after the original + * glibc version has been called and we've finished any logging (which + * may call glibc functions, too). This sets us up for the next calls from + * the application. + */ +static inline void set_override_hooks(void) { + __malloc_hook = override_malloc_hook; + __realloc_hook = override_realloc_hook; + __memalign_hook = override_memalign_hook; + __free_hook = override_free_hook; +} /* set_override_hooks */ + +/* + * The Hook Of All Hooks...how we get in there in the first place. + */ + +/* + * glibc will call this when the malloc subsystem is initializing, giving + * us a chance to install hooks that override the functions. + */ +static void override_init_hook(void) { + AUTO_LOCK(g_HookMutex); + + /* install our hooks. Will connect to daemon on first malloc, etc. */ + save_glibc_hooks(); + set_override_hooks(); +} /* override_init_hook */ + +/* + * __malloc_initialize_hook is apparently a "weak variable", so you can + * define and assign it here even though it's in glibc, too. This lets + * us hook into malloc as soon as the runtime initializes, and before + * main() is called. Basically, this whole trick depends on this. + */ +void (*__malloc_initialize_hook)(void) + __attribute__((visibility("default"))) = override_init_hook; + +#elif defined(OSX) +// +// pointers to the osx versions of these functions +static void *osx_malloc_hook = NULL; +static void *osx_realloc_hook = NULL; +static void *osx_free_hook = NULL; + +// convenience functions for setting the hooks... +static inline void save_osx_hooks(void); +static inline void set_osx_hooks(void); +static inline void set_override_hooks(void); + +CThreadMutex g_HookMutex; +// +// Our overriding hooks...they call through to the original C runtime +// implementations and report to the monitoring daemon. +// + +static void *override_malloc_hook(struct _malloc_zone_t *zone, size_t s) { + void *retval; + set_osx_hooks(); + retval = InternalMalloc(s, NULL, 0); + set_override_hooks(); + + return (retval); +} + +static void *override_realloc_hook(struct _malloc_zone_t *zone, void *ptr, + size_t s) { + void *retval; + + set_osx_hooks(); + retval = InternalRealloc(ptr, s, NULL, 0); + set_override_hooks(); + + return (retval); +} + +static void override_free_hook(struct _malloc_zone_t *zone, void *ptr) { + // sometime they pass in a null pointer from higher level calls, just ignore + // it + if (!ptr) return; + + set_osx_hooks(); + + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader(ptr); + if (*((int *)pInternalMem->m_Reserved) == 0xf00df00d) { + InternalFree(ptr); + } + + set_override_hooks(); +} + +/* + + These are func's we could optionally override right now on OSX but don't need + to + + static size_t override_size_hook(struct _malloc_zone_t *zone, const void *ptr) + { + set_osx_hooks(); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( (void *)ptr ); + set_override_hooks(); + if ( *((int*)pInternalMem->m_Reserved) == 0xf00df00d ) + { + return pInternalMem->nLogicalSize; + } + return 0; + } + + + static void *override_calloc_hook(struct _malloc_zone_t *zone, size_t + num_items, size_t size ) + { + void *ans = override_malloc_hook( zone, num_items*size ); + if ( !ans ) + return 0; + memset( ans, 0x0, num_items*size ); + return ans; + } + + static void *override_valloc_hook(struct _malloc_zone_t *zone, size_t size ) + { + return override_calloc_hook( zone, 1, size ); + } + + static void override_destroy_hook(struct _malloc_zone_t *zone) + { + } + */ + +// +// Save a copy of the original allocation hooks, so we can call into them +// from our overriding functions. It's possible that osx might change +// these hooks under various conditions (so the manual's examples seem +// to suggest), so we update them whenever we finish calling into the +// the originals. +// +static inline void save_osx_hooks(void) { + malloc_zone_t *malloc_zone = malloc_default_zone(); + + osx_malloc_hook = (void *)malloc_zone->malloc; + osx_realloc_hook = (void *)malloc_zone->realloc; + osx_free_hook = (void *)malloc_zone->free; + + // These are func's we could optionally override right now on OSX but don't + // need to osx_size_hook = (void *)malloc_zone->size; osx_calloc_hook = (void + // *)malloc_zone->calloc; osx_valloc_hook = (void *)malloc_zone->valloc; + // osx_destroy_hook = (void *)malloc_zone->destroy; +} + +// +// Restore the hooks to the osx versions. This is needed since, say, +// their realloc() might call malloc() or free() under the hood, etc, so +// it's safer to let them have complete control over the subsystem, which +// also makes our logging saner, too. +// +static inline void set_osx_hooks(void) { + malloc_zone_t *malloc_zone = malloc_default_zone(); + malloc_zone->malloc = (void *(*)(_malloc_zone_t *, size_t))osx_malloc_hook; + malloc_zone->realloc = + (void *(*)(_malloc_zone_t *, void *, size_t))osx_realloc_hook; + malloc_zone->free = (void (*)(_malloc_zone_t *, void *))osx_free_hook; + + // These are func's we could optionally override right now on OSX but don't + // need to + + // malloc_zone->size = (size_t (*)(_malloc_zone_t*, const void + // *))osx_size_hook; + // malloc_zone->calloc = (void* (*)(_malloc_zone_t*, size_t, + // size_t))osx_calloc_hook; malloc_zone->valloc = (void* (*)(_malloc_zone_t*, + // size_t))osx_valloc_hook; malloc_zone->destroy = (void + // (*)(_malloc_zone_t*))osx_destroy_hook; +} + +/* + * Put our hooks back in place. This should be done after the original + * osx version has been called and we've finished any logging (which + * may call osx functions, too). This sets us up for the next calls from + * the application. + */ +static inline void set_override_hooks(void) { + malloc_zone_t *malloc_zone = malloc_default_zone(); + + malloc_zone->malloc = override_malloc_hook; + malloc_zone->realloc = override_realloc_hook; + malloc_zone->free = override_free_hook; + + // These are func's we could optionally override right now on OSX but don't + // need to + // malloc_zone->size = override_size_hook; + // malloc_zone->calloc = override_calloc_hook; + // malloc_zone->valloc = override_valloc_hook; + // malloc_zone->destroy = override_destroy_hook; +} + +// +// The Hook Of All Hooks...how we get in there in the first place. +// +// osx will call this when the malloc subsystem is initializing, giving +// us a chance to install hooks that override the functions. +// + +void __attribute__((constructor)) mem_init(void) { + AUTO_LOCK(g_HookMutex); + save_osx_hooks(); + set_override_hooks(); +} + +void *operator new(size_t nSize, int nBlockUse, const char *pFileName, + int nLine) { + set_osx_hooks(); + void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); + set_override_hooks(); + return pMem; +} + +void *operator new[](size_t nSize, int nBlockUse, const char *pFileName, + int nLine) { + set_osx_hooks(); + void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); + set_override_hooks(); + return pMem; +} + +#endif // OSX + +int GetAllocationCallStack(void *mem, void **pCallStackOut, + int iMaxEntriesOut) { +#if defined(USE_MEM_DEBUG) && (defined(USE_STACK_TRACES)) + return s_DbgMemAlloc.GetCallStackForIndex( + GetAllocationStatIndex_Internal(mem), pCallStackOut, iMaxEntriesOut); +#else + return 0; +#endif +} + +#endif // MEM_IMPL_TYPE_DBG + +#endif // !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) diff --git a/tier0/memstd.cpp b/tier0/memstd.cpp new file mode 100644 index 0000000..17b02d2 --- /dev/null +++ b/tier0/memstd.cpp @@ -0,0 +1,2275 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Memory allocation! + +#include "tier0/platform.h" + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +//#include + +#include + +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "tier0/threadtools.h" +#include "mem_helpers.h" +#include "memstd.h" +#include "tier0/stacktools.h" +#include "tier0/minidump.h" +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif + +#ifdef _PS3 +#include "memoverride_ps3.h" +#endif + +#ifndef _WIN32 +#define IsDebuggerPresent() false +#endif + +#ifdef USE_LIGHT_MEM_DEBUG +#undef USE_MEM_DEBUG +#pragma message("*** USE_LIGHT_MEM_DEBUG is ON ***") +#pragma optimize("", off) +#endif + +#define DEF_REGION 0 + +#if defined(_WIN32) || defined(_PS3) +#define USE_DLMALLOC +#define MEMALLOC_SEGMENT_MIXED +#define MBH_SIZE_MB (45 + MBYTES_STEAM_MBH_USAGE) +//#define MEMALLOC_REGIONS +#endif // _WIN32 || _PS3 + +#ifndef USE_DLMALLOC +#ifdef _PS3 +#define malloc_internal(region, bytes) \ + (g_pMemOverrideRawCrtFns->pfn_malloc)(bytes) +#define malloc_aligned_internal(region, bytes, align) \ + (g_pMemOverrideRawCrtFns->pfn_memalign)(align, bytes) +#define realloc_internal (g_pMemOverrideRawCrtFns->pfn_realloc) +#define realloc_aligned_internal (g_pMemOverrideRawCrtFns->pfn_reallocalign) +#define free_internal (g_pMemOverrideRawCrtFns->pfn_free) +#define msize_internal (g_pMemOverrideRawCrtFns->pfn_malloc_usable_size) +#define compact_internal() (0) +#define heapstats_internal(p) (void)(0) +#else // _PS3 +#define malloc_internal(region, bytes) malloc(bytes) +#define malloc_aligned_internal(region, bytes, align) memalign(align, bytes) +#define realloc_internal realloc +#define realloc_aligned_internal realloc +#define free_internal free +#ifdef POSIX +#define msize_internal malloc_usable_size +#else // POSIX +#define msize_internal _msize +#endif // POSIX +#define compact_internal() (0) +#define heapstats_internal(p) (void)(0) +#endif // _PS3 +#else // USE_DLMALLOC +#define MSPACES 1 +#include "dlmalloc/malloc-2.8.3.h" + +void *g_AllocRegions[] = { +#ifndef MEMALLOC_REGIONS +#ifdef MEMALLOC_SEGMENT_MIXED + create_mspace(0, 1), // unified + create_mspace(MBH_SIZE_MB * 1024 * 1024, 1), +#else + create_mspace(100 * 1024 * 1024, 1), +#endif +#else // MEMALLOC_REGIONS + // @TODO: per DLL regions didn't work out very well. flux of usage left + // too much overhead. need to try lifetime-based management [6/9/2009 + // tom] + create_mspace(82 * 1024 * 1024, 1), // unified +#endif // MEMALLOC_REGIONS +}; + +#ifndef MEMALLOC_REGIONS +#ifndef MEMALLOC_SEGMENT_MIXED +#define SelectRegion(region, bytes) 0 +#else +// NOTE: this split is designed to force the 'large block' heap to ONLY perform +// virtual allocs (see +// DEFAULT_MMAP_THRESHOLD in malloc.cpp), to avoid ANY fragmentation or +// waste in an internal arena +#define REGION_SPLIT (256 * 1024) +#define SelectRegion(region, bytes) g_AllocRegions[(bytes) < REGION_SPLIT] +#endif +#else // MEMALLOC_REGIONS +#define SelectRegion(region, bytes) g_AllocRegions[region] +#endif // MEMALLOC_REGIONS + +#define malloc_internal(region, bytes) \ + mspace_malloc(SelectRegion(region, bytes), bytes) +#define malloc_aligned_internal(region, bytes, align) \ + mspace_memalign(SelectRegion(region, bytes), align, bytes) +FORCEINLINE void *realloc_aligned_internal(void *mem, size_t bytes, + size_t align) { + // TODO: implement realloc_aligned inside dlmalloc (requires splitting + // realloc's existing + // 'grow in-place' code into a new function, then call that w/ + // alloc_align/copy/free on failure) + byte *newMem = (byte *)dlrealloc(mem, bytes); + if (((size_t)newMem & (align - 1)) == 0) return newMem; + // realloc broke alignment... + byte *fallback = (byte *)malloc_aligned_internal(DEF_REGION, bytes, align); + if (!fallback) return NULL; + memcpy(fallback, newMem, bytes); + dlfree(newMem); + return fallback; +} + +inline size_t compact_internal() { + size_t start = 0, end = 0; + + for (auto *reg : g_AllocRegions) { + start += mspace_footprint(reg); + mspace_trim(reg, 0); + end += mspace_footprint(reg); + } + + return (start - end); +} + +inline void heapstats_internal(FILE *pFile) { + // @TODO: improve this presentation, as a table [6/1/2009 tom] + char buf[1024]; + for (size_t i = 0; i < std::size(g_AllocRegions); i++) { + struct mallinfo info = mspace_mallinfo(g_AllocRegions[i]); + size_t footPrint = mspace_footprint(g_AllocRegions[i]); + size_t maxFootPrint = mspace_max_footprint(g_AllocRegions[i]); + _snprintf( + buf, sizeof(buf), + "\ndlmalloc mspace %zu (%s)\n" + " %zu:footprint -%10zu (total space used by the mspace)\n" + " %zu:footprint_max -%10zu (maximum total space used by the " + "mspace)\n" + " %zu:arena -%10zu (non-mmapped space allocated from " + "system)\n" + " %zu:ordblks -%10zu (number of free chunks)\n" + " %zu:hblkhd -%10zu (space in mmapped regions)\n" + " %zu:usmblks -%10zu (maximum total allocated space)\n" + " %zu:uordblks -%10zu (total allocated space)\n" + " %zu:fordblks -%10zu (total free space)\n" + " %zu:keepcost -%10zu (releasable (via malloc_trim) space)\n", + i, i ? "medium-block" : "large-block", i, footPrint, i, maxFootPrint, i, + info.arena, i, info.ordblks, i, info.hblkhd, i, info.usmblks, i, + info.uordblks, i, info.fordblks, i, info.keepcost); + if (pFile) + fprintf(pFile, "%s", buf); + else + Msg("%s", buf); + } +} + +#define realloc_internal dlrealloc +#define free_internal dlfree +#define msize_internal dlmalloc_usable_size +#endif // USE_DLMALLOC + +#ifdef TIME_ALLOC +CAverageCycleCounter g_MallocCounter; +CAverageCycleCounter g_ReallocCounter; +CAverageCycleCounter g_FreeCounter; + +#define PrintOne(name) \ + Msg("%-48s: %6.4f avg (%8.1f total, %7.3f peak, %5d iters)\n", #name, \ + g_##name##Counter.GetAverageMilliseconds(), \ + g_##name##Counter.GetTotalMilliseconds(), \ + g_##name##Counter.GetPeakMilliseconds(), g_##name##Counter.GetIters()); \ + memset(&g_##name##Counter, 0, sizeof(g_##name##Counter)) + +void PrintAllocTimes() { + PrintOne(Malloc); + PrintOne(Realloc); + PrintOne(Free); +} + +#define PROFILE_ALLOC(name) CAverageTimeMarker name##_ATM(&g_##name##Counter) + +#else // TIME_ALLOC +#define PROFILE_ALLOC(name) ((void)0) +#define PrintAllocTimes() ((void)0) +#endif // TIME_ALLOC + +#if _MSC_VER < 1400 && defined(MSVC) && !defined(_STATIC_LINKED) && \ + (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +void *operator new(unsigned int nSize, int nBlockUse, const char *pFileName, + int nLine) { + return ::operator new(nSize); +} + +void *operator new[](unsigned int nSize, int nBlockUse, const char *pFileName, + int nLine) { + return ::operator new[](nSize); +} +#endif + +#include "mem_impl_type.h" +#if MEM_IMPL_TYPE_STD + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +#pragma warning(disable : 4074) // warning C4074: initializers put in compiler + // reserved initialization area +#pragma init_seg(compiler) + +#if MEM_SBH_ENABLED +CSmallBlockPool>:: + SharedData_t CSmallBlockPool< + CStdMemAlloc::CFixedAllocator>::gm_SharedData + CONSTRUCT_EARLY; +#ifdef MEMALLOC_USE_SECONDARY_SBH +CSmallBlockPool>:: + SharedData_t CSmallBlockPool>::gm_SharedData CONSTRUCT_EARLY; +#endif +#ifndef MEMALLOC_NO_FALLBACK +CSmallBlockPool::SharedData_t CSmallBlockPool< + CStdMemAlloc::CVirtualAllocator>::gm_SharedData CONSTRUCT_EARLY; +#endif +#endif // MEM_SBH_ENABLED + +static CStdMemAlloc s_StdMemAlloc CONSTRUCT_EARLY; + +#ifdef _PS3 + +MemOverrideRawCrtFunctions_t *g_pMemOverrideRawCrtFns; +IMemAlloc *g_pMemAllocInternalPS3 = &s_StdMemAlloc; +PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3_IMPL + +#else // !_PS3 + +#ifndef TIER0_VALIDATE_HEAP +IMemAlloc *g_pMemAlloc = &s_StdMemAlloc; +#else +IMemAlloc *g_pActualAlloc = &s_StdMemAlloc; +#endif + +#endif // _PS3 + +CStdMemAlloc::CStdMemAlloc() + : m_pfnFailHandler(DefaultFailHandler), + m_sMemoryAllocFailed((size_t)0), + m_bInCompact(false) { +#ifdef _PS3 + g_pMemAllocInternalPS3 = &s_StdMemAlloc; + PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3.m_pMemAllocCached = &s_StdMemAlloc; + malloc_managed_size mms; + mms.current_inuse_size = 0x12345678; + mms.current_system_size = 0x09ABCDEF; + mms.max_system_size = reinterpret_cast(this); + int iResult = malloc_stats(&mms); + g_pMemOverrideRawCrtFns = + reinterpret_cast(iResult); +#endif +} + +#if MEM_SBH_ENABLED +//----------------------------------------------------------------------------- +// Small block heap (multi-pool) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template +inline T MemAlign(T val, unsigned alignment) { + return (T)(((unsigned)val + alignment - 1) & ~(alignment - 1)); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +template +void CSmallBlockPool::Init(unsigned nBlockSize) { + SharedData_t *pSharedData = GetSharedData(); + if (!pSharedData->m_pBase) { + pSharedData->m_pBase = pSharedData->m_Allocator.AllocatePoolMemory(); + pSharedData->m_pLimit = pSharedData->m_pBase + CAllocator::TOTAL_BYTES; + pSharedData->m_pNextBlock = pSharedData->m_pBase; + } + + if (!(nBlockSize % MIN_SBH_ALIGN == 0 && nBlockSize >= MIN_SBH_BLOCK && + nBlockSize >= sizeof(TSLNodeBase_t))) + DebuggerBreak(); + + m_nBlockSize = nBlockSize; + m_pNextAlloc = NULL; + m_nCommittedPages = 0; +} + +template +size_t CSmallBlockPool::GetBlockSize() { + return m_nBlockSize; +} + +// Define VALIDATE_SBH_FREE_LIST to a given block size to validate that pool's +// freelist (it'll crash on the next alloc/free after the list is corrupted) +// NOTE: this may affect perf more than USE_LIGHT_MEM_DEBUG +//#define VALIDATE_SBH_FREE_LIST 320 +template +void CSmallBlockPool::ValidateFreelist(SharedData_t *pSharedData) { +#ifdef VALIDATE_SBH_FREE_LIST + if (m_nBlockSize != VALIDATE_SBH_FREE_LIST) return; + static int count = 0; + count++; // Track when the corruption occurs, if repeatable + pSharedData->m_Lock.LockForWrite(); +#ifdef USE_NATIVE_SLIST + TSLNodeBase_t *pNode = + (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->Next.Next); +#else + TSLNodeBase_t *pNode = + (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->value.Next); +#endif + while (pNode) pNode = pNode->Next; + pSharedData->m_Lock.UnlockWrite(); +#endif // VALIDATE_SBH_FREE_LIST +} + +template +void *CSmallBlockPool::Alloc() { + SharedData_t *pSharedData = GetSharedData(); + + ValidateFreelist(pSharedData); + + CThreadSpinRWLock &sharedLock = pSharedData->m_Lock; + if (!sharedLock.TryLockForRead()) { + sharedLock.LockForRead(); + } + byte *pResult; + intp iPage = -1; + int iThreadPriority = INT_MAX; + + while (1) { + pResult = m_FreeList.Pop(); + if (!pResult) { + int nBlockSize = m_nBlockSize; + byte *pNextAlloc; + while (1) { + pResult = m_pNextAlloc; + if (pResult) { + pNextAlloc = pResult + nBlockSize; + if ((((uintp)(pNextAlloc)-1) % BYTES_PAGE) + nBlockSize > + BYTES_PAGE) { + // Crossed a page boundary + pNextAlloc = 0; + } + if (m_pNextAlloc.AssignIf(pResult, pNextAlloc)) { + iPage = + (size_t)((byte *)pResult - pSharedData->m_pBase) / BYTES_PAGE; + break; + } + } else if (m_CommitMutex.TryLock()) { + if (!m_pNextAlloc) { + PageStatus_t *pAllocatedPageStatus = + (PageStatus_t *)pSharedData->m_FreePages.Pop(); + if (pAllocatedPageStatus) { + iPage = pAllocatedPageStatus - &pSharedData->m_PageStatus[0]; + } else { + while (1) { + byte *pBlock = pSharedData->m_pNextBlock; + if (pBlock >= pSharedData->m_pLimit) { + break; + } + if (ThreadInterlockedAssignPointerIf( + (void **)&pSharedData->m_pNextBlock, + (void *)(pBlock + BYTES_PAGE), (void *)pBlock)) { + iPage = (size_t)((byte *)pBlock - pSharedData->m_pBase) / + BYTES_PAGE; + pAllocatedPageStatus = &pSharedData->m_PageStatus[iPage]; + break; + } + } + } + + if (pAllocatedPageStatus) { + byte *pBlock = pSharedData->m_pBase + (iPage * BYTES_PAGE); + if (pAllocatedPageStatus->m_nAllocated == NOT_COMMITTED) { + pSharedData->m_Allocator.Commit(pBlock); + } + + pAllocatedPageStatus->m_pPool = this; + pAllocatedPageStatus->m_nAllocated = 0; + pAllocatedPageStatus->m_pNextPageInPool = m_pFirstPage; + m_pFirstPage = pAllocatedPageStatus; +#ifdef TRACK_SBH_COUNTS + m_nFreeBlocks += (BYTES_PAGE / m_nBlockSize); +#endif + m_nCommittedPages++; + m_pNextAlloc = pBlock; + } else { + m_pNextAlloc = NULL; + m_CommitMutex.Unlock(); + sharedLock.UnlockRead(); + return NULL; + } + } + m_CommitMutex.Unlock(); + } else { + if (iThreadPriority == INT_MAX) { + iThreadPriority = ThreadGetPriority(); + } + + if (iThreadPriority > 0) { + ThreadSleep(0); + } + } + } + + if (pResult) { + break; + } + } else { + iPage = (size_t)((byte *)pResult - pSharedData->m_pBase) / BYTES_PAGE; + break; + } + } + +#ifdef TRACK_SBH_COUNTS + --m_nFreeBlocks; +#endif + ++pSharedData->m_PageStatus[iPage].m_nAllocated; + sharedLock.UnlockRead(); + + return pResult; +} + +template +void CSmallBlockPool::Free(void *p) { + SharedData_t *pSharedData = GetSharedData(); + size_t iPage = (size_t)((byte *)p - pSharedData->m_pBase) / BYTES_PAGE; + + CThreadSpinRWLock &sharedLock = pSharedData->m_Lock; + if (!sharedLock.TryLockForRead()) { + sharedLock.LockForRead(); + } + --pSharedData->m_PageStatus[iPage].m_nAllocated; +#ifdef TRACK_SBH_COUNTS + ++m_nFreeBlocks; +#endif + m_FreeList.Push(p); + pSharedData->m_Lock.UnlockRead(); + + ValidateFreelist(pSharedData); +} + +// Count the free blocks. +template +int CSmallBlockPool::CountFreeBlocks() { +#ifdef TRACK_SBH_COUNTS + return m_nFreeBlocks; +#else + return 0; +#endif +} + +// Size of committed memory managed by this heap: +template +int CSmallBlockPool::GetCommittedSize() { + return m_nCommittedPages * BYTES_PAGE; +} + +// Return the total blocks memory is committed for in the heap +template +int CSmallBlockPool::CountCommittedBlocks() { + return m_nCommittedPages * (BYTES_PAGE / m_nBlockSize); +} + +// Count the number of allocated blocks in the heap: +template +int CSmallBlockPool::CountAllocatedBlocks() { +#ifdef TRACK_SBH_COUNTS + return CountCommittedBlocks() - CountFreeBlocks(); +#else + return 0; +#endif +} + +template +int CSmallBlockPool::PageSort(const void *p1, const void *p2) { + SharedData_t *pSharedData = GetSharedData(); + return pSharedData->m_PageStatus[*((int *)p1)].m_SortList.Count() - + pSharedData->m_PageStatus[*((int *)p2)].m_SortList.Count(); +} + +template +bool CSmallBlockPool::RemovePagesFromFreeList(byte **pPages, + int nPages, + bool bSortList) { + // Since we don't use the depth of the tslist, and sequence is only used for + // push, we can remove in-place + int i; + byte **pLimits = (byte **)stackalloc(nPages * sizeof(byte *)); + int nBlocksNotInFreeList = 0; + for (i = 0; i < nPages; i++) { + pLimits[i] = pPages[i] + BYTES_PAGE; + + if (m_pNextAlloc >= pPages[i] && m_pNextAlloc < pLimits[i]) { + nBlocksNotInFreeList = (pLimits[i] - m_pNextAlloc) / m_nBlockSize; + m_pNextAlloc = NULL; + } + } + + int iTarget = ((BYTES_PAGE / m_nBlockSize) * nPages) - nBlocksNotInFreeList; + int iCount = 0; + + TSLHead_t *pRawFreeList = m_FreeList.AccessUnprotected(); + bool bRemove; + if (!bSortList || m_nCommittedPages - nPages == 1) { +#ifdef USE_NATIVE_SLIST + TSLNodeBase_t **ppPrevNext = (TSLNodeBase_t **)&(pRawFreeList->Next); +#else + TSLNodeBase_t **ppPrevNext = (TSLNodeBase_t **)&(pRawFreeList->value.Next); +#endif + TSLNodeBase_t *pNode = *ppPrevNext; + while (pNode && iCount != iTarget) { + bRemove = false; + for (i = 0; i < nPages; i++) { + if ((byte *)pNode >= pPages[i] && (byte *)pNode < pLimits[i]) { + bRemove = true; + break; + } + } + + if (bRemove) { + iCount++; + *ppPrevNext = pNode->Next; + } else { + *ppPrevNext = pNode; + ppPrevNext = &pNode->Next; + } + pNode = pNode->Next; + } + } else { + SharedData_t *pSharedData = GetSharedData(); + byte *pSharedBase = pSharedData->m_pBase; + TSLNodeBase_t *pNode = m_FreeList.Detach(); + TSLNodeBase_t *pNext; + int iSortPage; + + int nSortPages = 0; + int *sortPages = (int *)stackalloc(m_nCommittedPages * sizeof(int)); + while (pNode) { + pNext = pNode->Next; + bRemove = false; + for (i = 0; i < nPages; i++) { + if ((byte *)pNode >= pPages[i] && (byte *)pNode < pLimits[i]) { + iCount++; + bRemove = true; + break; + } + } + + if (!bRemove) { + iSortPage = ((byte *)pNode - pSharedBase) / BYTES_PAGE; + if (!pSharedData->m_PageStatus[iSortPage].m_SortList.Count()) { + sortPages[nSortPages++] = iSortPage; + } + pSharedData->m_PageStatus[iSortPage].m_SortList.Push(pNode); + } + + pNode = pNext; + } + + if (nSortPages > 1) { + qsort(sortPages, nSortPages, sizeof(int), &PageSort); + } + for (i = 0; i < nSortPages; i++) { + while ( + (pNode = pSharedData->m_PageStatus[sortPages[i]].m_SortList.Pop()) != + NULL) { + m_FreeList.Push(pNode); + } + } + } + if (iTarget != iCount) { + DebuggerBreakIfDebugging(); + } + + return (iTarget == iCount); +} + +template +size_t CSmallBlockPool::Compact(bool bIncremental) { + static bool bWarnedCorruption; + bool bIsCorrupt = false; + int i; + size_t nFreed = 0; + SharedData_t *pSharedData = GetSharedData(); + pSharedData->m_Lock.LockForWrite(); + + if (m_pFirstPage) { + PageStatus_t **pReleasedPages = + (PageStatus_t **)stackalloc(m_nCommittedPages * sizeof(PageStatus_t *)); + PageStatus_t **pReleasedPagesPrevs = + (PageStatus_t **)stackalloc(m_nCommittedPages * sizeof(PageStatus_t *)); + byte **pPageBases = (byte **)stackalloc(m_nCommittedPages * sizeof(byte *)); + int nPages = 0; + + // Gather the pages to return to the backing pool + PageStatus_t *pPage = m_pFirstPage; + PageStatus_t *pPagePrev = NULL; + while (pPage) { + if (pPage->m_nAllocated == 0) { + pReleasedPages[nPages] = pPage; + pPageBases[nPages] = + pSharedData->m_pBase + + (pPage - &pSharedData->m_PageStatus[0]) * BYTES_PAGE; + pReleasedPagesPrevs[nPages] = pPagePrev; + nPages++; + + if (bIncremental) { + break; + } + } + pPagePrev = pPage; + pPage = pPage->m_pNextPageInPool; + } + + if (nPages) { + // Remove the pages from the pool's free list + if (!RemovePagesFromFreeList(pPageBases, nPages, !bIncremental) && + !bWarnedCorruption) { + // We don't know which of the pages encountered an incomplete free list + // so we'll just push them all back in and hope for the best. This isn't + // ventilator control software! + bWarnedCorruption = true; + bIsCorrupt = true; + } + + nFreed = nPages * BYTES_PAGE; + m_nCommittedPages -= nPages; + +#ifdef TRACK_SBH_COUNTS + m_nFreeBlocks -= nPages * (BYTES_PAGE / m_nBlockSize); +#endif + + // Unlink the pages + for (i = nPages - 1; i >= 0; --i) { + if (pReleasedPagesPrevs[i]) { + pReleasedPagesPrevs[i]->m_pNextPageInPool = + pReleasedPages[i]->m_pNextPageInPool; + } else { + m_pFirstPage = pReleasedPages[i]->m_pNextPageInPool; + } + pReleasedPages[i]->m_pNextPageInPool = NULL; + pReleasedPages[i]->m_pPool = NULL; + } + + // Push them onto the backing free lists + if (!pSharedData->m_Allocator.IsVirtual()) { + for (i = 0; i < nPages; i++) { + pSharedData->m_FreePages.Push(pReleasedPages[i]); + } + } else { + int nMinReserve = (bIncremental) ? CAllocator::MIN_RESERVE_PAGES * 8 + : CAllocator::MIN_RESERVE_PAGES; + int nReserveNeeded = nMinReserve - pSharedData->m_FreePages.Count(); + if (nReserveNeeded > 0) { + int nToKeepCommitted = MIN(nReserveNeeded, nPages); + while (nToKeepCommitted--) { + nPages--; + pSharedData->m_FreePages.Push(pReleasedPages[nPages]); + } + } + + if (nPages) { + // Detach the list, push the decommitted page on, iterate up to + // previous decommits, but them on, then push the committed pages on + TSLNodeBase_t *pNodes = pSharedData->m_FreePages.Detach(); + for (i = 0; i < nPages; i++) { + pReleasedPages[i]->m_nAllocated = NOT_COMMITTED; + pSharedData->m_Allocator.Decommit(pPageBases[i]); + pSharedData->m_FreePages.Push(pReleasedPages[i]); + } + + TSLNodeBase_t *pCur, *pTemp = NULL; + pCur = pNodes; + while (pCur) { + if (((PageStatus_t *)pCur)->m_nAllocated == NOT_COMMITTED) { + if (pTemp) { + pTemp->Next = NULL; + } else { + pNodes = NULL; // The list only has decommitted pages, don't go + // circular + } + + while (pCur) { + pTemp = pCur->Next; + pSharedData->m_FreePages.Push(pCur); + pCur = pTemp; + } + break; + } + pTemp = pCur; + pCur = pCur->Next; + } + + while (pNodes) { + pTemp = pNodes->Next; + pSharedData->m_FreePages.Push(pNodes); + pNodes = pTemp; + } + } + } + } + } + pSharedData->m_Lock.UnlockWrite(); + if (bIsCorrupt) { + Warning( + "***** HEAP IS CORRUPT (free compromised for block size %d,in %s heap, " + "possible write after free *****)\n", + m_nBlockSize, + (pSharedData->m_Allocator.IsVirtual()) ? "virtual" : "physical"); + } + return nFreed; +} + +template +bool CSmallBlockPool::Validate() { +#ifdef NO_SBH + return true; +#else + int invalid = 0; + + SharedData_t *pSharedData = GetSharedData(); + pSharedData->m_Lock.LockForWrite(); + + byte **pPageBases = (byte **)stackalloc(m_nCommittedPages * sizeof(byte *)); + unsigned *pageCounts = + (unsigned *)stackalloc(m_nCommittedPages * sizeof(unsigned)); + memset(pageCounts, 0, m_nCommittedPages * sizeof(int)); + unsigned nPages = 0; + unsigned sumAllocated = 0; + unsigned freeNotInFreeList = 0; + + // Validate page list is consistent + if (!m_pFirstPage) { + if (m_nCommittedPages != 0) { + invalid = __LINE__; + goto notValid; + } + } else { + PageStatus_t *pPage = m_pFirstPage; + while (pPage) { + pPageBases[nPages] = pSharedData->m_pBase + + (pPage - &pSharedData->m_PageStatus[0]) * BYTES_PAGE; + if (pPage->m_pPool != this) { + invalid = __LINE__; + goto notValid; + } + if (nPages > m_nCommittedPages) { + invalid = __LINE__; + goto notValid; + } + sumAllocated += pPage->m_nAllocated; + if (m_pNextAlloc >= pPageBases[nPages] && + m_pNextAlloc < pPageBases[nPages] + BYTES_PAGE) { + freeNotInFreeList = pageCounts[nPages] = + ((pPageBases[nPages] + BYTES_PAGE) - m_pNextAlloc) / m_nBlockSize; + } + + nPages++; + pPage = pPage->m_pNextPageInPool; + }; + + if (nPages != m_nCommittedPages) { + invalid = __LINE__; + goto notValid; + } + } + + // Validate block counts + { + unsigned blocksPerPage = (BYTES_PAGE / m_nBlockSize); +#ifdef USE_NATIVE_SLIST + TSLNodeBase_t *pNode = + (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->Next.Next); +#else + TSLNodeBase_t *pNode = + (TSLNodeBase_t *)(m_FreeList.AccessUnprotected()->value.Next); +#endif + unsigned i; + while (pNode) { + for (i = 0; i < nPages; i++) { + if ((byte *)pNode >= pPageBases[i] && + (byte *)pNode < pPageBases[i] + BYTES_PAGE) { + pageCounts[i]++; + break; + } + } + + if (i == nPages) { + invalid = __LINE__; + goto notValid; + } + + pNode = pNode->Next; + } + + PageStatus_t *pPage = m_pFirstPage; + i = 0; + while (pPage) { + unsigned nFreeOnPage = blocksPerPage - pPage->m_nAllocated; + if (nFreeOnPage != pageCounts[i++]) { + invalid = __LINE__; + goto notValid; + } + pPage = pPage->m_pNextPageInPool; + } + } + +notValid: + pSharedData->m_Lock.UnlockWrite(); + + if (invalid != 0) { + return false; + } + + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template +CSmallBlockHeap::CSmallBlockHeap() { + m_pSharedData = CPool::GetSharedData(); + + // Build a lookup table used to find the correct pool based on size + const int MAX_TABLE = MAX_SBH_BLOCK >> 2; + int i = 0; + int nBytesElement = 0; + CPool *pCurPool = NULL; + int iCurPool = 0; + + // Blocks sized 0 - 128 are in pools in increments of 8 + for (; i < 32; i++) { + if ((i + 1) % 2 == 1) { + nBytesElement += 8; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init(nBytesElement); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } else { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 129 - 256 are in pools in increments of 16 + for (; i < 64; i++) { + if ((i + 1) % 4 == 1) { + nBytesElement += 16; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init(nBytesElement); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } else { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 257 - 512 are in pools in increments of 32 + for (; i < 128; i++) { + if ((i + 1) % 8 == 1) { + nBytesElement += 32; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init(nBytesElement); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } else { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 513 - 768 are in pools in increments of 64 + for (; i < 192; i++) { + if ((i + 1) % 16 == 1) { + nBytesElement += 64; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init(nBytesElement); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } else { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 769 - 1024 are in pools in increments of 128 + for (; i < 256; i++) { + if ((i + 1) % 32 == 1) { + nBytesElement += 128; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init(nBytesElement); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } else { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 1025 - 2048 are in pools in increments of 256 + for (; i < MAX_TABLE; i++) { + if ((i + 1) % 64 == 1) { + nBytesElement += 256; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init(nBytesElement); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } else { + m_PoolLookup[i] = pCurPool; + } + } + + Assert(iCurPool == NUM_POOLS); +} + +template +bool CSmallBlockHeap::ShouldUse(size_t nBytes) { + return (nBytes <= MAX_SBH_BLOCK); +} + +template +bool CSmallBlockHeap::IsOwner(void *p) { + if (uintp(p) >= uintp(m_pSharedData->m_pBase)) { + intp index = (intp)((byte *)p - m_pSharedData->m_pBase) / BYTES_PAGE; + return (index < ARRAYSIZE(m_pSharedData->m_PageStatus)); + } + return false; +} + +template +void *CSmallBlockHeap::Alloc(size_t nBytes) { + if (nBytes == 0) { + nBytes = 1; + } + Assert(ShouldUse(nBytes)); + CPool *pPool = FindPool(nBytes); + void *p = pPool->Alloc(); + return p; +} + +template +void *CSmallBlockHeap::Realloc(void *p, size_t nBytes) { + if (nBytes == 0) { + nBytes = 1; + } + + CPool *pOldPool = FindPool(p); + CPool *pNewPool = (ShouldUse(nBytes)) ? FindPool(nBytes) : NULL; + + if (pOldPool == pNewPool) { + return p; + } + + void *pNewBlock = NULL; + + if (!pNewBlock) { + pNewBlock = + MemAlloc_Alloc(nBytes); // Call back out so blocks can move from the + // secondary to the primary pools + } + + if (!pNewBlock) { + pNewBlock = malloc_internal(DEF_REGION, nBytes); + } + + if (pNewBlock) { + size_t nBytesCopy = MIN(nBytes, pOldPool->GetBlockSize()); + memcpy(pNewBlock, p, nBytesCopy); + } else if (nBytes < pOldPool->GetBlockSize()) { + return p; + } + + pOldPool->Free(p); + + return pNewBlock; +} + +template +void CSmallBlockHeap::Free(void *p) { + CPool *pPool = FindPool(p); + if (pPool) { + pPool->Free(p); + } else { + // we probably didn't hook some allocation and now we're freeing it or the + // heap has been trashed! + DebuggerBreakIfDebugging(); + } +} + +template +size_t CSmallBlockHeap::GetSize(void *p) { + CPool *pPool = FindPool(p); + return pPool->GetBlockSize(); +} + +template +void CSmallBlockHeap::Usage(size_t &bytesCommitted, + size_t &bytesAllocated) { + bytesCommitted = 0; + bytesAllocated = 0; + for (int i = 0; i < NUM_POOLS; i++) { + bytesCommitted += m_Pools[i].GetCommittedSize(); + bytesAllocated += + (m_Pools[i].CountAllocatedBlocks() * m_Pools[i].GetBlockSize()); + } +} + +template +void CSmallBlockHeap::DumpStats(const char *pszTag, FILE *pFile) { + size_t bytesCommitted, bytesAllocated; + Usage(bytesCommitted, bytesAllocated); + + if (pFile) { + for (int i = 0; i < NUM_POOLS; i++) { + // output for vxconsole parsing + fprintf(pFile, + "Pool %2i: (size: %4u) blocks: allocated:%5i free:%5i " + "committed:%5i (committed size:%4u kb)\n", + i, m_Pools[i].GetBlockSize(), m_Pools[i].CountAllocatedBlocks(), + m_Pools[i].CountFreeBlocks(), m_Pools[i].CountCommittedBlocks(), + m_Pools[i].GetCommittedSize()); + } + fprintf(pFile, "Totals (%s): Committed:%5u kb Allocated:%5u kb\n", pszTag, + bytesCommitted / 1024, bytesAllocated / 1024); + } else { + for (int i = 0; i < NUM_POOLS; i++) { + Msg("Pool %2i: (size: %4u) blocks: allocated:%5i free:%5i committed:%5i " + "(committed size:%4u kb)\n", + i, m_Pools[i].GetBlockSize(), m_Pools[i].CountAllocatedBlocks(), + m_Pools[i].CountFreeBlocks(), m_Pools[i].CountCommittedBlocks(), + m_Pools[i].GetCommittedSize() / 1024); + } + + Msg("Totals (%s): Committed:%5u kb Allocated:%5u kb\n", pszTag, + bytesCommitted / 1024, bytesAllocated / 1024); + } +} + +template +CSmallBlockPool *CSmallBlockHeap::FindPool( + size_t nBytes) { + return m_PoolLookup[(nBytes - 1) >> 2]; +} + +template +CSmallBlockPool *CSmallBlockHeap::FindPool(void *p) { + // NOTE: If p < m_pBase, cast to unsigned size_t will cause it to be large + size_t index = (size_t)((byte *)p - m_pSharedData->m_pBase) / BYTES_PAGE; + if (index < ARRAYSIZE(m_pSharedData->m_PageStatus)) + return m_pSharedData->m_PageStatus[index].m_pPool; + return NULL; +} + +template +size_t CSmallBlockHeap::Compact(bool bIncremental) { + size_t nRecovered = 0; + if (bIncremental) { + static int iLastIncremental; + + iLastIncremental++; + for (int i = 0; i < NUM_POOLS; i++) { + int idx = (i + iLastIncremental) % NUM_POOLS; + nRecovered = m_Pools[idx].Compact(bIncremental); + if (nRecovered) { + iLastIncremental = idx; + break; + } + } + } else { + for (int i = 0; i < NUM_POOLS; i++) { + nRecovered += m_Pools[i].Compact(bIncremental); + } + } + return nRecovered; +} + +template +bool CSmallBlockHeap::Validate() { + bool valid = true; + for (int i = 0; i < NUM_POOLS; i++) { + valid = m_Pools[i].Validate() && valid; + } + return valid; +} + +#endif // MEM_SBH_ENABLED + +//----------------------------------------------------------------------------- +// Lightweight memory tracking +//----------------------------------------------------------------------------- + +#ifdef USE_LIGHT_MEM_DEBUG + +#ifndef LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH +#define UsingLMD() true +#else // LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH +bool g_bUsingLMD = (Plat_GetCommandLineA()) + ? (strstr(Plat_GetCommandLineA(), "-uselmd") != NULL) + : false; +#define UsingLMD() g_bUsingLMD +#if defined(_PS3) +#error "Plat_GetCommandLineA() not implemented on PS3" +#endif +#endif // LIGHT_MEM_DEBUG_REQUIRES_CMD_LINE_SWITCH + +const char *g_pszUnknown = "unknown"; + +struct Sentinal_t { + DWORD value[4]; +}; + +Sentinal_t g_HeadSentinel = { + 0xdeadbeef, + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, +}; + +Sentinal_t g_TailSentinel = { + 0xbaadf00d, + 0xbd122969, + 0xdeadbeef, + 0xbaadf00d, +}; + +const byte g_FreeFill = 0xdd; + +static const uint LWD_FREE = 0; +static const uint LWD_ALLOCATED = 1; + +#define LMD_STATUS_BITS (1) +#define LMD_ALIGN_BITS (32 - LMD_STATUS_BITS) +#define LMD_MAX_ALIGN (1 << (LMD_ALIGN_BITS - 1)) + +struct AllocHeader_t { + const char *pszModule; + int line; + size_t nBytes; + uint status : LMD_STATUS_BITS; + uint align : LMD_ALIGN_BITS; + Sentinal_t sentinal; +}; + +const int g_nRecentFrees = (IsPC()) ? 8192 : 512; +AllocHeader_t **g_pRecentFrees = + (AllocHeader_t **)calloc(g_nRecentFrees, sizeof(AllocHeader_t *)); +int g_iNextFreeSlot; + +#define INTERNAL_INLINE + +#define LMDToHeader(pUserPtr) (((AllocHeader_t *)(pUserPtr)) - 1) +#define LMDFromHeader(pHeader) ((byte *)((pHeader) + 1)) + +CThreadFastMutex g_LMDMutex; + +const char *g_pLMDFileName = NULL; +int g_nLMDLine; +int g_iLMDDepth; + +void LMDPushAllocDbgInfo(const char *pFileName, int nLine) { + if (ThreadInMainThread()) { + if (!g_iLMDDepth) { + g_pLMDFileName = pFileName; + g_nLMDLine = nLine; + } + g_iLMDDepth++; + } +} + +void LMDPopAllocDbgInfo() { + if (ThreadInMainThread() && g_iLMDDepth > 0) { + g_iLMDDepth--; + if (g_iLMDDepth == 0) { + g_pLMDFileName = NULL; + g_nLMDLine = 0; + } + } +} + +void LMDReportInvalidBlock(AllocHeader_t *pHeader, const char *pszMessage) { + char szMsg[256]; + if (pHeader) { + sprintf(szMsg, "HEAP IS CORRUPT: %s (block 0x%x, size %d, alignment %d)\n", + pszMessage, (size_t)LMDFromHeader(pHeader), pHeader->nBytes, + pHeader->align); + } else { + sprintf(szMsg, "HEAP IS CORRUPT: %s\n", pszMessage); + } + if (Plat_IsInDebugSession()) { + DebuggerBreak(); + } else { + WriteMiniDump(); + } +#ifdef IS_WINDOWS_PC + ::MessageBox(NULL, szMsg, "Error", MB_SYSTEMMODAL | MB_OK); +#else + Warning(szMsg); +#endif +} + +void LMDValidateBlock(AllocHeader_t *pHeader, bool bFreeList) { + if (!pHeader) return; + + if (memcmp(&pHeader->sentinal, &g_HeadSentinel, sizeof(Sentinal_t)) != 0) { + LMDReportInvalidBlock(pHeader, "Head sentinel corrupt"); + } + if (memcmp(((Sentinal_t *)(LMDFromHeader(pHeader) + pHeader->nBytes)), + &g_TailSentinel, sizeof(Sentinal_t)) != 0) { + LMDReportInvalidBlock(pHeader, "Tail sentinel corrupt"); + } + if (bFreeList) { + byte *pCur = (byte *)pHeader + sizeof(AllocHeader_t); + byte *pLimit = pCur + pHeader->nBytes; + while (pCur != pLimit) { + if (*pCur++ != g_FreeFill) { + LMDReportInvalidBlock(pHeader, "Write after free"); + } + } + } +} + +size_t LMDComputeHeaderSize(size_t align = 0) { + if (!align) return sizeof(AllocHeader_t); + // For aligned allocs, the header is preceded by padding which maintains + // alignment + if (align > LMD_MAX_ALIGN) + s_StdMemAlloc.SetCRTAllocFailed( + align); // TODO: could convert alignment to exponent to get around + // this, or use a flag for alignments over 1KB or 1MB... + return ((sizeof(AllocHeader_t) + (align - 1)) & ~(align - 1)); +} + +size_t LMDAdjustSize(size_t &nBytes, size_t align = 0) { + if (!UsingLMD()) return nBytes; + // Add data before+after each alloc + return (nBytes + LMDComputeHeaderSize(align) + sizeof(Sentinal_t)); +} + +void *LMDNoteAlloc(void *p, size_t nBytes, size_t align = 0, + const char *pszModule = g_pszUnknown, int line = 0) { + if (!UsingLMD()) { + return p; + } + + if (g_pLMDFileName) { + pszModule = g_pLMDFileName; + line = g_nLMDLine; + } + + if (p) { + byte *pUserPtr = ((byte *)p) + LMDComputeHeaderSize(align); + AllocHeader_t *pHeader = LMDToHeader(pUserPtr); + pHeader->pszModule = pszModule; + pHeader->line = line; + pHeader->status = LWD_ALLOCATED; + pHeader->nBytes = nBytes; + pHeader->align = (uint)align; + pHeader->sentinal = g_HeadSentinel; + *((Sentinal_t *)(pUserPtr + pHeader->nBytes)) = g_TailSentinel; + LMDValidateBlock(pHeader, false); + return pUserPtr; + } + return NULL; + + // Some SBH clients rely on allocations > 16 bytes being 16-byte aligned, so + // we mustn't break that assumption: + MEMSTD_COMPILE_TIME_ASSERT(sizeof(AllocHeader_t) % 16 == 0); +} + +void *LMDNoteFree(void *p) { + if (!UsingLMD()) { + return p; + } + + AUTO_LOCK(g_LMDMutex); + if (!p) { + return NULL; + } + + AllocHeader_t *pHeader = LMDToHeader(p); + if (pHeader->status == LWD_FREE) { + LMDReportInvalidBlock(pHeader, "Double free"); + } + LMDValidateBlock(pHeader, false); + + AllocHeader_t *pToReturn; + if (pHeader->nBytes < 16 * 1024) { + pToReturn = g_pRecentFrees[g_iNextFreeSlot]; + LMDValidateBlock(pToReturn, true); + + g_pRecentFrees[g_iNextFreeSlot] = pHeader; + g_iNextFreeSlot = (g_iNextFreeSlot + 1) % g_nRecentFrees; + } else { + pToReturn = pHeader; + LMDValidateBlock(g_pRecentFrees[rand() % g_nRecentFrees], true); + } + + pHeader->status = LWD_FREE; + memset(pHeader + 1, g_FreeFill, pHeader->nBytes); + + if (pToReturn && (pToReturn->align)) { + // For aligned allocations, the actual system allocation starts *before* the + // LMD header: + size_t headerPadding = + LMDComputeHeaderSize(pToReturn->align) - sizeof(AllocHeader_t); + return (((byte *)pToReturn) - headerPadding); + } + + return pToReturn; +} + +size_t LMDGetSize(void *p) { + if (!UsingLMD()) { + return (size_t)(-1); + } + + AllocHeader_t *pHeader = LMDToHeader(p); + return pHeader->nBytes; +} + +bool LMDValidateHeap() { + if (!UsingLMD()) { + return true; + } + + AUTO_LOCK(g_LMDMutex); + for (int i = 0; i < g_nRecentFrees && g_pRecentFrees[i]; i++) { + LMDValidateBlock(g_pRecentFrees[i], true); + } + return true; +} + +void *LMDRealloc(void *pMem, size_t nSize, size_t align = 0, + const char *pszModule = g_pszUnknown, int line = 0) { + if (nSize == 0) { + s_StdMemAlloc.Free(pMem); + return NULL; + } + void *pNew; +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + if (align) + pNew = s_StdMemAlloc.AllocAlign(nSize, align, pszModule, line); + else +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + pNew = s_StdMemAlloc.Alloc(nSize, pszModule, line); + if (!pMem) { + return pNew; + } + AllocHeader_t *pHeader = LMDToHeader(pMem); + if (align != pHeader->align) { + LMDReportInvalidBlock(pHeader, "Realloc changed alignment!"); + } + size_t nCopySize = MIN(nSize, pHeader->nBytes); + memcpy(pNew, pMem, nCopySize); + s_StdMemAlloc.Free(pMem, pszModule, line); + return pNew; +} + +#else // USE_LIGHT_MEM_DEBUG + +#define INTERNAL_INLINE FORCEINLINE +#define UsingLMD() false +FORCEINLINE size_t LMDAdjustSize(size_t &nBytes, size_t align = 0) { + return nBytes; +} +#define LMDNoteAlloc(pHeader, ...) (pHeader) +#define LMDNoteFree(pHeader, ...) (pHeader) +#define LMDGetSize(pHeader) (size_t)(-1) +#define LMDToHeader(pHeader) (pHeader) +#define LMDFromHeader(pHeader) (pHeader) +#define LMDValidateHeap() (true) +#define LMDPushAllocDbgInfo(pFileName, nLine) ((void)0) +#define LMDPopAllocDbgInfo() ((void)0) +FORCEINLINE void *LMDRealloc(void *pMem, size_t nSize, size_t align = 0, + const char *pszModule = NULL, int line = 0) { + return NULL; +} + +#endif // USE_LIGHT_MEM_DEBUG + +//----------------------------------------------------------------------------- +// Internal versions +//----------------------------------------------------------------------------- + +INTERNAL_INLINE void *CStdMemAlloc::InternalAllocFromPools(size_t nSize) { +#if MEM_SBH_ENABLED + void *pMem; + + pMem = m_PrimarySBH.Alloc(nSize); + if (pMem) { + return pMem; + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + pMem = m_SecondarySBH.Alloc(nSize); + if (pMem) { + return pMem; + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + pMem = m_FallbackSBH.Alloc(nSize); + if (pMem) { + return pMem; + } +#endif // MEMALLOC_NO_FALLBACK + + CallAllocFailHandler(nSize); +#endif // MEM_SBH_ENABLED + return NULL; +} + +INTERNAL_INLINE void *CStdMemAlloc::InternalAlloc(int region, size_t nSize) { + PROFILE_ALLOC(Malloc); + + void *pMem; + +#if MEM_SBH_ENABLED + if (m_PrimarySBH.ShouldUse(nSize)) // test valid for either pool + { + pMem = InternalAllocFromPools(nSize); + if (!pMem) { + CompactOnFail(); + pMem = InternalAllocFromPools(nSize); + } + if (pMem) { + ApplyMemoryInitializations(pMem, nSize); + return pMem; + } + + ExecuteOnce(DevWarning( + "\n\nDRASTIC MEMORY OVERFLOW: Fell out of small block heap!\n\n\n")); + } +#endif // MEM_SBH_ENABLED + + pMem = malloc_internal(region, nSize); + if (!pMem) { + CompactOnFail(); + pMem = malloc_internal(region, nSize); + if (!pMem) { + SetCRTAllocFailed(nSize); + return NULL; + } + } + + ApplyMemoryInitializations(pMem, nSize); + return pMem; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +INTERNAL_INLINE void *CStdMemAlloc::InternalAllocAligned(int region, + size_t nSize, + size_t align) { + PROFILE_ALLOC(MallocAligned); + + void *pMem; + +#if MEM_SBH_ENABLED + size_t nSizeAligned = (nSize + align - 1) & ~(align - 1); + if (m_PrimarySBH.ShouldUse(nSizeAligned)) // test valid for either pool + { + pMem = InternalAllocFromPools(nSizeAligned); + if (!pMem) { + CompactOnFail(); + pMem = InternalAllocFromPools(nSizeAligned); + } + if (pMem) { + ApplyMemoryInitializations(pMem, nSizeAligned); + return pMem; + } + + ExecuteOnce(DevWarning("Warning: Fell out of small block heap!\n")); + } +#endif // MEM_SBH_ENABLED + + pMem = malloc_aligned_internal(region, nSize, align); + if (!pMem) { + CompactOnFail(); + pMem = malloc_aligned_internal(region, nSize, align); + if (!pMem) { + SetCRTAllocFailed(nSize); + return NULL; + } + } + + ApplyMemoryInitializations(pMem, nSize); + return pMem; +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +INTERNAL_INLINE void *CStdMemAlloc::InternalRealloc(void *pMem, size_t nSize) { + if (!pMem) { + return RegionAlloc(DEF_REGION, nSize); + } + + PROFILE_ALLOC(Realloc); + +#if MEM_SBH_ENABLED + if (m_PrimarySBH.IsOwner(pMem)) { + return m_PrimarySBH.Realloc(pMem, nSize); + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if (m_SecondarySBH.IsOwner(pMem)) { + return m_SecondarySBH.Realloc(pMem, nSize); + } + +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if (m_FallbackSBH.IsOwner(pMem)) { + return m_FallbackSBH.Realloc(pMem, nSize); + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + void *pRet = realloc_internal(pMem, nSize); + if (!pRet) { + CompactOnFail(); + pRet = realloc_internal(pMem, nSize); + if (!pRet) { + SetCRTAllocFailed(nSize); + } + } + + return pRet; +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +INTERNAL_INLINE void *CStdMemAlloc::InternalReallocAligned(void *pMem, + size_t nSize, + size_t align) { + if (!pMem) { + return InternalAllocAligned(DEF_REGION, nSize, align); + } + + PROFILE_ALLOC(ReallocAligned); + +#if MEM_SBH_ENABLED + if (m_PrimarySBH.IsOwner(pMem)) { + return m_PrimarySBH.Realloc(pMem, nSize); + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if (m_SecondarySBH.IsOwner(pMem)) { + return m_SecondarySBH.Realloc(pMem, nSize); + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if (m_FallbackSBH.IsOwner(pMem)) { + return m_FallbackSBH.Realloc(pMem, nSize); + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + void *pRet = realloc_aligned_internal(pMem, nSize, align); + if (!pRet) { + CompactOnFail(); + pRet = realloc_aligned_internal(pMem, nSize, align); + if (!pRet) { + SetCRTAllocFailed(nSize); + } + } + + return pRet; +} +#endif + +INTERNAL_INLINE void CStdMemAlloc::InternalFree(void *pMem) { + if (!pMem) { + return; + } + + PROFILE_ALLOC(Free); + +#if MEM_SBH_ENABLED + if (m_PrimarySBH.IsOwner(pMem)) { + m_PrimarySBH.Free(pMem); + return; + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if (m_SecondarySBH.IsOwner(pMem)) { + return m_SecondarySBH.Free(pMem); + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if (m_FallbackSBH.IsOwner(pMem)) { + m_FallbackSBH.Free(pMem); + return; + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + free_internal(pMem); +} + +void CStdMemAlloc::CompactOnFail() { CompactHeap(); } + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- + +void *CStdMemAlloc::Alloc(size_t nSize) { + size_t nAdjustedSize = LMDAdjustSize(nSize); + return LMDNoteAlloc(CStdMemAlloc::InternalAlloc(DEF_REGION, nAdjustedSize), + nSize); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CStdMemAlloc::AllocAlign(size_t nSize, size_t align) { + size_t nAdjustedSize = LMDAdjustSize(nSize, align); + return LMDNoteAlloc( + CStdMemAlloc::InternalAllocAligned(DEF_REGION, nAdjustedSize, align), + nSize, align); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void *CStdMemAlloc::Realloc(void *pMem, size_t nSize) { + if (UsingLMD()) return LMDRealloc(pMem, nSize); + return CStdMemAlloc::InternalRealloc(pMem, nSize); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CStdMemAlloc::ReallocAlign(void *pMem, size_t nSize, size_t align) { + if (UsingLMD()) return LMDRealloc(pMem, nSize, align); + return CStdMemAlloc::InternalReallocAligned(pMem, nSize, align); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void CStdMemAlloc::Free(void *pMem) { + pMem = LMDNoteFree(pMem); + CStdMemAlloc::InternalFree(pMem); +} + +void *CStdMemAlloc::Expand_NoLongerSupported(void *pMem, size_t nSize) { + return NULL; +} + +//----------------------------------------------------------------------------- +// Debug versions +//----------------------------------------------------------------------------- +void *CStdMemAlloc::Alloc(size_t nSize, const char *pFileName, int nLine) { + size_t nAdjustedSize = LMDAdjustSize(nSize); + return LMDNoteAlloc(CStdMemAlloc::InternalAlloc(DEF_REGION, nAdjustedSize), + nSize, 0, pFileName, nLine); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CStdMemAlloc::AllocAlign(size_t nSize, size_t align, + const char *pFileName, int nLine) { + size_t nAdjustedSize = LMDAdjustSize(nSize, align); + return LMDNoteAlloc( + CStdMemAlloc::InternalAllocAligned(DEF_REGION, nAdjustedSize, align), + nSize, align, pFileName, nLine); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void *CStdMemAlloc::Realloc(void *pMem, size_t nSize, const char *pFileName, + int nLine) { + if (UsingLMD()) return LMDRealloc(pMem, nSize, 0, pFileName, nLine); + return CStdMemAlloc::InternalRealloc(pMem, nSize); +} + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS +void *CStdMemAlloc::ReallocAlign(void *pMem, size_t nSize, size_t align, + const char *pFileName, int nLine) { + if (UsingLMD()) return LMDRealloc(pMem, nSize, align, pFileName, nLine); + return CStdMemAlloc::InternalReallocAligned(pMem, nSize, align); +} +#endif // MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + +void CStdMemAlloc::Free(void *pMem, const char *pFileName, int nLine) { + pMem = LMDNoteFree(pMem); + CStdMemAlloc::InternalFree(pMem); +} + +void *CStdMemAlloc::Expand_NoLongerSupported(void *pMem, size_t nSize, + const char *pFileName, int nLine) { + return NULL; +} + +//----------------------------------------------------------------------------- +// Region support +//----------------------------------------------------------------------------- +void *CStdMemAlloc::RegionAlloc(int region, size_t nSize) { + size_t nAdjustedSize = LMDAdjustSize(nSize); + return LMDNoteAlloc(CStdMemAlloc::InternalAlloc(region, nAdjustedSize), + nSize); +} + +void *CStdMemAlloc::RegionAlloc(int region, size_t nSize, const char *pFileName, + int nLine) { + size_t nAdjustedSize = LMDAdjustSize(nSize); + return LMDNoteAlloc(CStdMemAlloc::InternalAlloc(region, nAdjustedSize), nSize, + 0, pFileName, nLine); +} + +#if defined(LINUX) +#include +#elif defined(OSX) +#define malloc_usable_size(ptr) malloc_size(ptr) +extern "C" { +extern size_t malloc_size(const void *ptr); +} +#endif // LINUX/OSX + +//----------------------------------------------------------------------------- +// Returns the size of a particular allocation (NOTE: may be larger than the +// size requested!) +//----------------------------------------------------------------------------- +size_t CStdMemAlloc::GetSize(void *pMem) { + if (!pMem) return CalcHeapUsed(); + + if (UsingLMD()) { + return LMDGetSize(pMem); + } + +#if MEM_SBH_ENABLED + if (m_PrimarySBH.IsOwner(pMem)) { + return m_PrimarySBH.GetSize(pMem); + } + +#ifdef MEMALLOC_USE_SECONDARY_SBH + if (m_SecondarySBH.IsOwner(pMem)) { + return m_SecondarySBH.GetSize(pMem); + } +#endif // MEMALLOC_USE_SECONDARY_SBH + +#ifndef MEMALLOC_NO_FALLBACK + if (m_FallbackSBH.IsOwner(pMem)) { + return m_FallbackSBH.GetSize(pMem); + } +#endif // MEMALLOC_NO_FALLBACK + +#endif // MEM_SBH_ENABLED + + return msize_internal(pMem); +} + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CStdMemAlloc::PushAllocDbgInfo(const char *pFileName, int nLine) { + LMDPushAllocDbgInfo(pFileName, nLine); +} + +void CStdMemAlloc::PopAllocDbgInfo() { LMDPopAllocDbgInfo(); } + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +int32 CStdMemAlloc::CrtSetBreakAlloc(int32 lNewBreakAlloc) { return 0; } + +int CStdMemAlloc::CrtSetReportMode(int nReportType, int nReportMode) { + return 0; +} + +int CStdMemAlloc::CrtIsValidHeapPointer(const void *pMem) { return 1; } + +int CStdMemAlloc::CrtIsValidPointer(const void *pMem, unsigned int size, + int access) { + return 1; +} + +int CStdMemAlloc::CrtCheckMemory(void) { +#ifndef _CERT + LMDValidateHeap(); +#if MEM_SBH_ENABLED + if (!m_PrimarySBH.Validate()) { + ExecuteOnce(Msg("Small block heap is corrupt (primary)\n ")); + } +#ifdef MEMALLOC_USE_SECONDARY_SBH + if (!m_SecondarySBH.Validate()) { + ExecuteOnce(Msg("Small block heap is corrupt (secondary)\n ")); + } +#endif // MEMALLOC_USE_SECONDARY_SBH +#ifndef MEMALLOC_NO_FALLBACK + if (!m_FallbackSBH.Validate()) { + ExecuteOnce(Msg("Small block heap is corrupt (fallback)\n ")); + } +#endif // MEMALLOC_NO_FALLBACK +#endif // MEM_SBH_ENABLED +#endif // _CERT + return 1; +} + +int CStdMemAlloc::CrtSetDbgFlag(int nNewFlag) { return 0; } + +void CStdMemAlloc::CrtMemCheckpoint(_CrtMemState *pState) {} + +// FIXME: Remove when we have our own allocator +void *CStdMemAlloc::CrtSetReportFile(int nRptType, void *hFile) { return 0; } + +void *CStdMemAlloc::CrtSetReportHook(void *pfnNewHook) { return 0; } + +int CStdMemAlloc::CrtDbgReport(int nRptType, const char *szFile, int nLine, + const char *szModule, const char *pMsg) { + return 0; +} + +int CStdMemAlloc::heapchk() { +#ifdef _WIN32 + CrtCheckMemory(); + return _HEAPOK; +#else + return 1; +#endif +} + +void CStdMemAlloc::DumpStats() { DumpStatsFileBase("memstats"); } + +void CStdMemAlloc::DumpStatsFileBase(char const *pchFileBase) { +#if defined(_WIN32) || defined(_GAMECONSOLE) + char filename[512]; + _snprintf(filename, sizeof(filename) - 1, +#ifdef _X360 + "D:\\%s.txt", +#elif defined(_PS3) + "/app_home/%s.txt", +#else + "%s.txt", +#endif + pchFileBase); + filename[sizeof(filename) - 1] = 0; + FILE *pFile = (IsGameConsole()) ? NULL : fopen(filename, "wt"); + +#if MEM_SBH_ENABLED + if (pFile) + fprintf(pFile, "Fixed Page SBH:\n"); + else + Msg("Fixed Page SBH:\n"); + m_PrimarySBH.DumpStats("Fixed Page SBH", pFile); +#ifdef MEMALLOC_USE_SECONDARY_SBH + if (pFile) + fprintf(pFile, "Secondary Fixed Page SBH:\n"); + else + Msg("Secondary Page SBH:\n"); + m_SecondarySBH.DumpStats("Secondary Page SBH", pFile); +#endif // MEMALLOC_USE_SECONDARY_SBH +#ifndef MEMALLOC_NO_FALLBACK + if (pFile) + fprintf(pFile, "\nFallback SBH:\n"); + else + Msg("\nFallback SBH:\n"); + m_FallbackSBH.DumpStats("Fallback SBH", + pFile); // Dump statistics to small block heap +#endif // MEMALLOC_NO_FALLBACK +#endif // MEM_SBH_ENABLED + +#ifdef _PS3 + malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)(&mms); + Msg("PS3 malloc_stats: %u / %u / %u \n", mms.current_inuse_size, + mms.current_system_size, mms.max_system_size); +#endif // _PS3 + + heapstats_internal(pFile); +#if defined(_X360) + XBX_rMemDump(filename); +#endif + + if (pFile) fclose(pFile); +#endif // _WIN32 || _GAMECONSOLE +} + +IVirtualMemorySection *CStdMemAlloc::AllocateVirtualMemorySection( + size_t numMaxBytes) { +#if defined(_GAMECONSOLE) + extern IVirtualMemorySection * + VirtualMemoryManager_AllocateVirtualMemorySection(size_t numMaxBytes); + return VirtualMemoryManager_AllocateVirtualMemorySection(numMaxBytes); +#else + return NULL; +#endif +} + +size_t CStdMemAlloc::ComputeMemoryUsedBy(char const *pchSubStr) { + return 0; // dbg heap only. +} + +static inline size_t ExtraDevkitMemory(void) { +#if defined(_PS3) + // 213MB are available in retail mode, so adjust free mem to reflect that even + // if we're in devkit mode + const size_t RETAIL_SIZE = 213 * 1024 * 1024; + static sys_memory_info stat; + sys_memory_get_user_memory_size(&stat); + if (stat.total_user_memory > RETAIL_SIZE) + return (stat.total_user_memory - RETAIL_SIZE); +#elif defined(_X360) + // TODO: detect the new 1GB devkit... +#endif // _PS3/_X360 + return 0; +} + +void CStdMemAlloc::GlobalMemoryStatus(size_t *pUsedMemory, + size_t *pFreeMemory) { + if (!pUsedMemory || !pFreeMemory) return; + + size_t dlMallocFree = 0; +#if defined(USE_DLMALLOC) + // Account for free memory contained within DLMalloc's FIRST region. The + // rationale is as follows: + // - the first region is supposed to service large allocations via virtual + // allocation, and to grow as + // needed (until all physical pages are used), so true 'out of memory' + // failures should occur there. + // - other regions (the 2-256kb 'medium block heap', or per-DLL heaps, and + // the Small Block Heap) + // are sized to a pre-determined high watermark, and not intended to grow. + // Free memory within those regions is not available for large allocations, + // so adding that to the 'free memory' yields confusing data which does not + // correspond well with out-of-memory failures. + mallinfo info = mspace_mallinfo(g_AllocRegions[0]); + dlMallocFree += info.fordblks; +#endif // USE_DLMALLOC + +#if defined(_X360) + + // GlobalMemoryStatus tells us how much physical memory is free + MEMORYSTATUS stat; + ::GlobalMemoryStatus(&stat); + *pFreeMemory = stat.dwAvailPhys; + *pFreeMemory += dlMallocFree; + // Adjust free mem to reflect a retail box, even if we're using a devkit with + // extra memory + *pFreeMemory -= ExtraDevkitMemory(); + + // Used is total minus free (discount the 32MB system reservation) + *pUsedMemory = (stat.dwTotalPhys - 32 * 1024 * 1024) - *pFreeMemory; + +#elif defined(_PS3) + + // NOTE: we use dlmalloc instead of the system heap, so we do NOT count the + // system heap's free space! + // static malloc_managed_size mms; + //(g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); + // int heapFree = mms.current_system_size - mms.current_inuse_size; + + // sys_memory_get_user_memory_size tells us how much PPU memory is used/free + static sys_memory_info stat; + sys_memory_get_user_memory_size(&stat); + *pFreeMemory = stat.available_user_memory; + *pFreeMemory += dlMallocFree; + *pUsedMemory = stat.total_user_memory - *pFreeMemory; + // Adjust free mem to reflect a retail box, even if we're using a devkit with + // extra memory + *pFreeMemory -= ExtraDevkitMemory(); + +#else // _X360/_PS3/other + + // no data + *pFreeMemory = 0; + *pUsedMemory = 0; + +#endif // _X360/_PS3//other +} + +#define MAX_GENERIC_MEMORY_STATS 64 +GenericMemoryStat_t g_MemStats[MAX_GENERIC_MEMORY_STATS]; +int g_nMemStats = 0; +static inline int AddGenericMemoryStat(const char *name, int value) { + Assert(g_nMemStats < MAX_GENERIC_MEMORY_STATS); + if (g_nMemStats < MAX_GENERIC_MEMORY_STATS) { + g_MemStats[g_nMemStats].name = name; + g_MemStats[g_nMemStats].value = value; + g_nMemStats++; + } + return g_nMemStats; +} + +int CStdMemAlloc::GetGenericMemoryStats(GenericMemoryStat_t **ppMemoryStats) { + if (!ppMemoryStats) return 0; + g_nMemStats = 0; + +#if MEM_SBH_ENABLED + { + // Small block heap + size_t SBHCommitted = 0, SBHAllocated = 0; + size_t commitTmp, allocTmp; +#if MEM_SBH_ENABLED + m_PrimarySBH.Usage(commitTmp, allocTmp); + SBHCommitted += commitTmp; + SBHAllocated += allocTmp; +#ifdef MEMALLOC_USE_SECONDARY_SBH + m_SecondarySBH.Usage(commitTmp, allocTmp); + SBHCommitted += commitTmp; + SBHAllocated += allocTmp; +#endif // MEMALLOC_USE_SECONDARY_SBH +#ifndef MEMALLOC_NO_FALLBACK + m_FallbackSBH.Usage(commitTmp, allocTmp); + SBHCommitted += commitTmp; + SBHAllocated += allocTmp; +#endif // MEMALLOC_NO_FALLBACK +#endif // MEM_SBH_ENABLED + + static size_t SBHMaxCommitted = 0; + SBHMaxCommitted = MAX(SBHMaxCommitted, SBHCommitted); + AddGenericMemoryStat("SBH_cur", (int)SBHCommitted); + AddGenericMemoryStat("SBH_max", (int)SBHMaxCommitted); + } +#endif // MEM_SBH_ENABLED + +#if defined(USE_DLMALLOC) +#if !defined(MEMALLOC_REGIONS) && defined(MEMALLOC_SEGMENT_MIXED) + { + // Medium block heap + mallinfo infoMBH = mspace_mallinfo(g_AllocRegions[1]); + size_t nMBHCurUsed = + infoMBH.uordblks; // nMBH_WRONG_MaxUsed = infoMBH.usmblks; // TODO: + // figure out why dlmalloc mis-reports MBH max usage + // (it just returns the footprint) + static size_t nMBHMaxUsed = 0; + nMBHMaxUsed = MAX(nMBHMaxUsed, nMBHCurUsed); + AddGenericMemoryStat("MBH_cur", (int)nMBHCurUsed); + AddGenericMemoryStat("MBH_max", (int)nMBHMaxUsed); + + // Large block heap + mallinfo infoLBH = mspace_mallinfo(g_AllocRegions[0]); + size_t nLBHCurUsed = mspace_footprint(g_AllocRegions[0]), + nLBHMaxUsed = mspace_max_footprint(g_AllocRegions[0]), + nLBHArenaSize = infoLBH.arena, nLBHFree = infoLBH.fordblks; + AddGenericMemoryStat("LBH_cur", (int)nLBHCurUsed); + AddGenericMemoryStat("LBH_max", (int)nLBHMaxUsed); + // LBH arena used+free (these are non-virtual allocations - there should be + // none, since we only allocate 256KB+ items in the LBH) + // TODO: I currently see the arena grow to 320KB due to a larger allocation + // being realloced down... if this gets worse, add an 'ALWAYS use VMM' flag + // to the mspace. + AddGenericMemoryStat("LBH_arena", (int)nLBHArenaSize); + AddGenericMemoryStat("LBH_free", (int)nLBHFree); + } +#else // (!MEMALLOC_REGIONS && MEMALLOC_SEGMENT_MIXED) + { + // Single dlmalloc heap (TODO: per-DLL heap stats, if we resurrect that) + mallinfo info = mspace_mallinfo(g_AllocRegions[0]); + AddGenericMemoryStat("mspace_cur", (int)info.uordblks); + AddGenericMemoryStat("mspace_max", (int)info.usmblks); + AddGenericMemoryStat("mspace_size", + (int)mspace_footprint(g_AllocRegions[0])); + } +#endif // (!MEMALLOC_REGIONS && MEMALLOC_SEGMENT_MIXED) +#endif // USE_DLMALLOC + + size_t nMaxPhysMemUsed_Delta; + nMaxPhysMemUsed_Delta = 0; +#ifdef _PS3 + { + // System heap (should not exist!) + static malloc_managed_size mms; + (g_pMemOverrideRawCrtFns->pfn_malloc_stats)(&mms); + if (mms.current_system_size) + AddGenericMemoryStat("sys_heap", (int)mms.current_system_size); + + // Virtual Memory Manager + size_t nReserved = 0, nReservedMax = 0, nCommitted = 0, nCommittedMax = 0; + extern void VirtualMemoryManager_GetStats( + size_t & nReserved, size_t & nReservedMax, size_t & nCommitted, + size_t & nCommittedMax); + VirtualMemoryManager_GetStats(nReserved, nReservedMax, nCommitted, + nCommittedMax); + AddGenericMemoryStat("VMM_reserved", (int)nReserved); + AddGenericMemoryStat("VMM_reserved_max", (int)nReservedMax); + AddGenericMemoryStat("VMM_committed", (int)nCommitted); + AddGenericMemoryStat("VMM_committed_max", (int)nCommittedMax); + + // Estimate memory committed by memory stacks (these account for all VMM + // allocations other than the SBH/MBH/LBH) + size_t nHeapTotal = 1024 * 1024 * MBYTES_PRIMARY_SBH; +#if defined(USE_DLMALLOC) + for (auto *reg : g_AllocRegions) { + nHeapTotal += mspace_footprint(reg); + } +#endif // USE_DLMALLOC + size_t nMemStackTotal = nCommitted - nHeapTotal; + AddGenericMemoryStat("MemStacks", (int)nMemStackTotal); + + // On PS3, we can more accurately determine 'phys_free_min', since we know + // nCommittedMax (otherwise nPhysFreeMin is only updated intermittently; + // when this function is called): + nMaxPhysMemUsed_Delta = nCommittedMax - nCommitted; + } +#endif // _PS3 + +#if defined(_GAMECONSOLE) + // Total/free/min-free physical pages + { +#if defined(_X360) + MEMORYSTATUS stat; + ::GlobalMemoryStatus(&stat); + size_t nPhysTotal = stat.dwTotalPhys, + nPhysFree = stat.dwAvailPhys - ExtraDevkitMemory(); +#elif defined(_PS3) + static sys_memory_info stat; + sys_memory_get_user_memory_size(&stat); + size_t nPhysTotal = stat.total_user_memory, + nPhysFree = stat.available_user_memory - ExtraDevkitMemory(); +#endif // _X360/_PS3 + static size_t nPhysFreeMin = nPhysTotal; + nPhysFreeMin = MIN(nPhysFreeMin, (nPhysFree - nMaxPhysMemUsed_Delta)); + AddGenericMemoryStat("phys_total", (int)nPhysTotal); + AddGenericMemoryStat("phys_free", (int)nPhysFree); + AddGenericMemoryStat("phys_free_min", (int)nPhysFreeMin); + } +#endif // _GAMECONSOLE + + *ppMemoryStats = &g_MemStats[0]; + return g_nMemStats; +} + +void CStdMemAlloc::CompactHeap() { +#if MEM_SBH_ENABLED + if (!m_CompactMutex.TryLock()) { + return; + } + if (m_bInCompact) { + m_CompactMutex.Unlock(); + return; + } + + m_bInCompact = true; + size_t nBytesRecovered; +#ifndef MEMALLOC_NO_FALLBACK + nBytesRecovered = m_FallbackSBH.Compact(false); + if (nBytesRecovered && IsGameConsole()) { + Msg("Compact freed %d bytes from virtual heap (up to 256k still " + "committed)\n", + nBytesRecovered); + } +#endif // MEMALLOC_NO_FALLBACK + nBytesRecovered = m_PrimarySBH.Compact(false); +#ifdef MEMALLOC_USE_SECONDARY_SBH + nBytesRecovered += m_SecondarySBH.Compact(false); +#endif + if (nBytesRecovered && IsGameConsole()) { + Msg("Compact released %d bytes from the SBH\n", nBytesRecovered); + } + + nBytesRecovered = compact_internal(); + if (nBytesRecovered && IsGameConsole()) { + Msg("Compact released %d bytes from the mixed block heap\n", + nBytesRecovered); + } + + m_bInCompact = false; + m_CompactMutex.Unlock(); +#endif // MEM_SBH_ENABLED +} + +void CStdMemAlloc::CompactIncremental() { +#if MEM_SBH_ENABLED + if (!m_CompactMutex.TryLock()) { + return; + } + if (m_bInCompact) { + m_CompactMutex.Unlock(); + return; + } + + m_bInCompact = true; +#ifndef MEMALLOC_NO_FALLBACK + m_FallbackSBH.Compact(true); +#endif + m_PrimarySBH.Compact(true); +#ifdef MEMALLOC_USE_SECONDARY_SBH + m_SecondarySBH.Compact(true); +#endif + m_bInCompact = false; + m_CompactMutex.Unlock(); +#endif // MEM_SBH_ENABLED +} + +MemAllocFailHandler_t CStdMemAlloc::SetAllocFailHandler( + MemAllocFailHandler_t pfnMemAllocFailHandler) { + MemAllocFailHandler_t pfnPrevious = m_pfnFailHandler; + m_pfnFailHandler = pfnMemAllocFailHandler; + return pfnPrevious; +} + +size_t CStdMemAlloc::DefaultFailHandler(size_t nBytes) { + if (IsX360()) { +#ifdef _X360 + ExecuteOnce({ + char buffer[256]; + _snprintf(buffer, sizeof(buffer), + "***** Memory pool overflow, attempted allocation size: %u " + "(not a critical error)\n", + nBytes); + XBX_OutputDebugString(buffer); + }); +#endif // _X360 + } + return 0; +} + +void CStdMemAlloc::SetStatsExtraInfo(const char *pMapName, + const char *pComment) {} + +void CStdMemAlloc::SetCRTAllocFailed(size_t nSize) { + m_sMemoryAllocFailed = nSize; + + DebuggerBreakIfDebugging(); +#if defined(_PS3) && defined(_DEBUG) + DebuggerBreak(); +#endif // _PS3 + + char buffer[256]; +#ifdef COMPILER_GCC + _snprintf(buffer, sizeof(buffer), + "***** OUT OF MEMORY! attempted allocation size: %zu ****\n", nSize); +#else + _snprintf(buffer, sizeof(buffer), + "***** OUT OF MEMORY! attempted allocation size: %zu ****\n", nSize); +#endif // COMPILER_GCC + +#ifdef _X360 + XBX_OutputDebugString(buffer); + if (!Plat_IsInDebugSession()) { + XBX_CrashDump(true); +#if defined(_DEMO) + XLaunchNewImage(XLAUNCH_KEYWORD_DEFAULT_APP, 0); +#else + XLaunchNewImage("default.xex", 0); +#endif // _DEMO + } +#elif defined(_WIN32) + OutputDebugString(buffer); + if (!Plat_IsInDebugSession()) { + WriteMiniDump(); + abort(); + } +#else // _X360/_WIN32/other + fprintf(stderr, "%s\n", buffer); + if (!Plat_IsInDebugSession()) { + WriteMiniDump(); +#if defined(_PS3) + DumpStats(); +#endif + Plat_ExitProcess(0); + } +#endif // _X360/_WIN32/other +} + +size_t CStdMemAlloc::MemoryAllocFailed() { return m_sMemoryAllocFailed; } + +#endif // MEM_IMPL_TYPE_STD + +#endif // STEAM diff --git a/tier0/memstd.h b/tier0/memstd.h new file mode 100644 index 0000000..6e5afba --- /dev/null +++ b/tier0/memstd.h @@ -0,0 +1,448 @@ +// Copyright Valve Corporation, All rights reserved. +// +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually + +#include "pch_tier0.h" + +#if IS_WINDOWS_PC +#include "winlite.h" +#define VA_COMMIT_FLAGS MEM_COMMIT +#define VA_RESERVE_FLAGS MEM_RESERVE +#elif defined(_X360) +#undef Verify +#define _XBOX +#include +#undef _XBOX +#include "xbox/xbox_win32stubs.h" +#define VA_COMMIT_FLAGS (MEM_COMMIT | MEM_NOZERO | MEM_LARGE_PAGES) +#define VA_RESERVE_FLAGS (MEM_RESERVE | MEM_LARGE_PAGES) +#elif defined(_PS3) +#include "sys/memory.h" +#include "sys/mempool.h" +#include "sys/process.h" +#include +#endif + +#include + +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "tier0/threadtools.h" +#include "tier0/tslist.h" +#include "mem_helpers.h" + +#ifndef _PS3 +#pragma pack(4) +#endif + +#define MIN_SBH_BLOCK 8 +#define MIN_SBH_ALIGN 8 +#define MAX_SBH_BLOCK 2048 +#define MAX_POOL_REGION (4 * 1024 * 1024) + +#define NUM_POOLS 42 + +#if defined(_WIN32) || defined(_PS3) +// FIXME: Disable small block heap on win64 for now; it's busted because +// it's expecting SLIST_HEADER to look different than it does on win64 +#if !defined(PLATFORM_WINDOWS_PC64) +#define MEM_SBH_ENABLED 1 +#endif +#endif + +#if !defined(_CERT) && (defined(_X360) || defined(_PS3)) +#define TRACK_SBH_COUNTS +#endif + +#if defined(_X360) + +// 360 uses a 48MB primary (physical) SBH and 10MB secondary (virtual) SBH, with +// no fallback +#define MBYTES_PRIMARY_SBH 48 +#define MEMALLOC_USE_SECONDARY_SBH +#define MBYTES_SECONDARY_SBH 10 +#define MEMALLOC_NO_FALLBACK + +#elif defined(_PS3) + +// PS3 uses just a 32MB SBH - this was enough to avoid overflow when Portal 2 +// shipped. NOTE: when Steam uses the game's tier0 allocator (see memalloc.h), +// we increase the size +// of the SBH and MBH (see memstd.cpp) to accommodate those extra +// allocations. +#define MBYTES_PRIMARY_SBH (32 + MBYTES_STEAM_SBH_USAGE) +#define MEMALLOC_NO_FALLBACK + +#else // _X360 | _PS3 + +// Other platforms use a 48MB primary SBH and a (32MB) fallback SBH +#define MBYTES_PRIMARY_SBH 48 + +#endif // _X360 | _PS3 + +#define MEMSTD_COMPILE_TIME_ASSERT(pred) \ + switch (0) { \ + case 0: \ + case pred:; \ + } + +//----------------------------------------------------------------------------- +// Small block pool +//----------------------------------------------------------------------------- + +class CFreeList : public CTSListBase { + public: + void Push(void *p) { CTSListBase::Push((TSLNodeBase_t *)p); } + byte *Pop() { return (byte *)CTSListBase::Pop(); } +}; + +template +class CSmallBlockHeap; + +template +class CSmallBlockPool { + public: + CSmallBlockPool() { + m_nBlockSize = 0; + m_nCommittedPages = 0; + m_pFirstPage = NULL; + } + + void Init(unsigned nBlockSize); + size_t GetBlockSize(); + void *Alloc(); + void Free(void *p); + int CountFreeBlocks(); + int GetCommittedSize(); + int CountCommittedBlocks(); + int CountAllocatedBlocks(); + size_t Compact(bool bIncremental); + bool Validate(); + + enum { BYTES_PAGE = CAllocator::BYTES_PAGE, NOT_COMMITTED = -1 }; + + private: + typedef CSmallBlockHeap CHeap; + friend class CSmallBlockHeap; + + struct PageStatus_t : public TSLNodeBase_t { + PageStatus_t() { + m_pPool = NULL; + m_nAllocated = NOT_COMMITTED; + m_pNextPageInPool = NULL; + } + + CSmallBlockPool *m_pPool; + PageStatus_t *m_pNextPageInPool; + CInterlockedInt m_nAllocated; + CTSListBase m_SortList; + }; + + struct SharedData_t { + CAllocator m_Allocator; + CTSListBase m_FreePages; + CThreadSpinRWLock m_Lock; + PageStatus_t m_PageStatus[CAllocator::TOTAL_BYTES / CAllocator::BYTES_PAGE]; + byte *m_pNextBlock; + byte *m_pBase; + byte *m_pLimit; + }; + + static int PageSort(const void *p1, const void *p2); + bool RemovePagesFromFreeList(byte **pPages, int nPages, bool bSortList); + + void ValidateFreelist(SharedData_t *pSharedData); + + CFreeList m_FreeList; + + CInterlockedPtr m_pNextAlloc; + + PageStatus_t *m_pFirstPage; + unsigned m_nBlockSize; + unsigned m_nCommittedPages; + + CThreadFastMutex m_CommitMutex; + +#ifdef TRACK_SBH_COUNTS + CInterlockedInt m_nFreeBlocks; +#endif + + static SharedData_t *GetSharedData() { return &gm_SharedData; } + + static SharedData_t gm_SharedData; +}; + +//----------------------------------------------------------------------------- +// Small block heap (multi-pool) +//----------------------------------------------------------------------------- + +template +class CSmallBlockHeap { + public: + CSmallBlockHeap(); + bool ShouldUse(size_t nBytes); + bool IsOwner(void *p); + void *Alloc(size_t nBytes); + void *Realloc(void *p, size_t nBytes); + void Free(void *p); + size_t GetSize(void *p); + void DumpStats(const char *pszTag, FILE *pFile = NULL); + void Usage(size_t &bytesCommitted, size_t &bytesAllocated); + size_t Compact(bool bIncremental); + bool Validate(); + + enum { BYTES_PAGE = CAllocator::BYTES_PAGE }; + + private: + typedef CSmallBlockPool CPool; + typedef struct CSmallBlockPool::SharedData_t SharedData_t; + + CPool *FindPool(size_t nBytes); + CPool *FindPool(void *p); + + // Map size to a pool address to a pool + CPool *m_PoolLookup[MAX_SBH_BLOCK >> 2]; + CPool m_Pools[NUM_POOLS]; + + SharedData_t *m_pSharedData; +}; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +class CStdMemAlloc : public IMemAlloc { + public: + CStdMemAlloc(); + + // Internal versions + void *InternalAlloc(int region, size_t nSize); +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + void *InternalAllocAligned(int region, size_t nSize, size_t align); +#endif + void *InternalAllocFromPools(size_t nSize); + void *InternalRealloc(void *pMem, size_t nSize); +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + void *InternalReallocAligned(void *pMem, size_t nSize, size_t align); +#endif + void InternalFree(void *pMem); + + void CompactOnFail(); + + // Release versions + virtual void *Alloc(size_t nSize); + virtual void *Realloc(void *pMem, size_t nSize); + virtual void Free(void *pMem); + virtual void *Expand_NoLongerSupported(void *pMem, size_t nSize); + + // Debug versions + virtual void *Alloc(size_t nSize, const char *pFileName, int nLine); + virtual void *Realloc(void *pMem, size_t nSize, const char *pFileName, + int nLine); + virtual void Free(void *pMem, const char *pFileName, int nLine); + virtual void *Expand_NoLongerSupported(void *pMem, size_t nSize, + const char *pFileName, int nLine); + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + virtual void *AllocAlign(size_t nSize, size_t align); + virtual void *AllocAlign(size_t nSize, size_t align, const char *pFileName, + int nLine); + virtual void *ReallocAlign(void *pMem, size_t nSize, size_t align); + virtual void *ReallocAlign(void *pMem, size_t nSize, size_t align, + const char *pFileName, int nLine); +#endif + + virtual void *RegionAlloc(int region, size_t nSize); + virtual void *RegionAlloc(int region, size_t nSize, const char *pFileName, + int nLine); + + // Returns size of a particular allocation + virtual size_t GetSize(void *pMem); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo(const char *pFileName, int nLine); + virtual void PopAllocDbgInfo(); + + virtual int32 CrtSetBreakAlloc(int32 lNewBreakAlloc); + virtual int CrtSetReportMode(int nReportType, int nReportMode); + virtual int CrtIsValidHeapPointer(const void *pMem); + virtual int CrtIsValidPointer(const void *pMem, unsigned int size, + int access); + virtual int CrtCheckMemory(void); + virtual int CrtSetDbgFlag(int nNewFlag); + virtual void CrtMemCheckpoint(_CrtMemState *pState); + void *CrtSetReportFile(int nRptType, void *hFile); + void *CrtSetReportHook(void *pfnNewHook); + int CrtDbgReport(int nRptType, const char *szFile, int nLine, + const char *szModule, const char *pMsg); + virtual int heapchk(); + + virtual void DumpStats(); + virtual void DumpStatsFileBase(char const *pchFileBase); + virtual size_t ComputeMemoryUsedBy(char const *pchSubStr); + virtual void GlobalMemoryStatus(size_t *pUsedMemory, size_t *pFreeMemory); + + virtual bool IsDebugHeap() { return false; } + + virtual void GetActualDbgInfo(const char *&pFileName, int &nLine) {} + virtual void RegisterAllocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) {} + virtual void RegisterDeallocation(const char *pFileName, int nLine, + size_t nLogicalSize, size_t nActualSize, + unsigned nTime) {} + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void OutOfMemory(size_t nBytesAttempted = 0) { + SetCRTAllocFailed(nBytesAttempted); + } + + virtual IVirtualMemorySection *AllocateVirtualMemorySection( + size_t numMaxBytes); + + virtual int GetGenericMemoryStats(GenericMemoryStat_t **ppMemoryStats); + + virtual void CompactHeap(); + virtual void CompactIncremental(); + + virtual MemAllocFailHandler_t SetAllocFailHandler( + MemAllocFailHandler_t pfnMemAllocFailHandler); + size_t CallAllocFailHandler(size_t nBytes) { + return (*m_pfnFailHandler)(nBytes); + } + + virtual uint32 GetDebugInfoSize() { return 0; } + virtual void SaveDebugInfo(void *pvDebugInfo) {} + virtual void RestoreDebugInfo(const void *pvDebugInfo) {} + virtual void InitDebugInfo(void *pvDebugInfo, const char *pchRootFileName, + int nLine) {} + + static size_t DefaultFailHandler(size_t); + void DumpBlockStats(void *p) {} + +#if MEM_SBH_ENABLED + class CVirtualAllocator { + public: + enum { + BYTES_PAGE = (64 * 1024), + TOTAL_BYTES = (32 * 1024 * 1024), + MIN_RESERVE_PAGES = 4, + }; + + byte *AllocatePoolMemory() { +#ifdef _WIN32 + return (byte *)VirtualAlloc(NULL, TOTAL_BYTES, VA_RESERVE_FLAGS, + PAGE_NOACCESS); +#elif defined(_PS3) + Error(""); + return NULL; +#else +#error +#endif + } + + bool IsVirtual() { return true; } + + bool Decommit(void *pPage) { +#ifdef _WIN32 + return (VirtualFree(pPage, BYTES_PAGE, MEM_DECOMMIT) != 0); +#elif defined(_PS3) + return false; +#else +#error +#endif + } + + bool Commit(void *pPage) { +#ifdef _WIN32 + return (VirtualAlloc(pPage, BYTES_PAGE, VA_COMMIT_FLAGS, + PAGE_READWRITE) != NULL); +#elif defined(_PS3) + return false; +#else +#error +#endif + } + }; + + typedef CSmallBlockHeap CVirtualSmallBlockHeap; + + template + class CFixedAllocator { + public: + enum { + BYTES_PAGE = (16 * 1024), + TOTAL_BYTES = (SIZE_MB * 1024 * 1024), + MIN_RESERVE_PAGES = TOTAL_BYTES / BYTES_PAGE, + }; + + byte *AllocatePoolMemory() { +#ifdef _WIN32 +#ifdef _X360 + if (bPhysical) + return (byte *)XPhysicalAlloc(TOTAL_BYTES, MAXULONG_PTR, 4096, + PAGE_READWRITE | MEM_16MB_PAGES); +#endif + return (byte *)VirtualAlloc(NULL, TOTAL_BYTES, VA_COMMIT_FLAGS, + PAGE_READWRITE); +#elif defined(_PS3) + // TODO: release this section on shutdown (use GetMemorySectionForAddress) + extern IVirtualMemorySection * + VirtualMemoryManager_AllocateVirtualMemorySection(size_t numMaxBytes); + IVirtualMemorySection *pSection = + VirtualMemoryManager_AllocateVirtualMemorySection(TOTAL_BYTES); + if (!pSection) + Error( + "CFixedAllocator::AllocatePoolMemory() failed in " + "VirtualMemoryManager_AllocateVirtualMemorySection\n"); + if (!pSection->CommitPages(pSection->GetBaseAddress(), TOTAL_BYTES)) + Error( + "CFixedAllocator::AllocatePoolMemory() failed in " + "IVirtualMemorySection::CommitPages\n"); + return reinterpret_cast(pSection->GetBaseAddress()); +#else +#error +#endif + } + + bool IsVirtual() { return false; } + + bool Decommit(void *pPage) { return false; } + + bool Commit(void *pPage) { return false; } + }; + + typedef CSmallBlockHeap> + CFixedSmallBlockHeap; +#ifdef MEMALLOC_USE_SECONDARY_SBH + typedef CSmallBlockHeap> + CFixedVirtualSmallBlockHeap; // @TODO: move back into above heap if + // number stays at 16 [7/15/2009 tom] +#endif + + CFixedSmallBlockHeap m_PrimarySBH; +#ifdef MEMALLOC_USE_SECONDARY_SBH + CFixedVirtualSmallBlockHeap m_SecondarySBH; +#endif +#ifndef MEMALLOC_NO_FALLBACK + CVirtualSmallBlockHeap m_FallbackSBH; +#endif + +#endif // MEM_SBH_ENABLED + + virtual void SetStatsExtraInfo(const char *pMapName, const char *pComment); + + virtual size_t MemoryAllocFailed(); + + void SetCRTAllocFailed(size_t nMemSize); + + MemAllocFailHandler_t m_pfnFailHandler; + size_t m_sMemoryAllocFailed; + CThreadFastMutex m_CompactMutex; + bool m_bInCompact; +}; + +#ifndef _PS3 +#pragma pack() +#endif diff --git a/tier0/memvalidate.cpp b/tier0/memvalidate.cpp new file mode 100644 index 0000000..17c9514 --- /dev/null +++ b/tier0/memvalidate.cpp @@ -0,0 +1,498 @@ +//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" + +#ifndef STEAM + +#ifdef TIER0_VALIDATE_HEAP + +#include +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "mem_helpers.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +extern IMemAlloc *g_pActualAlloc; + +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +class CValidateAlloc : public IMemAlloc +{ +public: + enum + { + HEAP_PREFIX_BUFFER_SIZE = 12, + HEAP_SUFFIX_BUFFER_SIZE = 8, + }; + + CValidateAlloc(); + + // Release versions + virtual void *Alloc( size_t nSize ); + virtual void *Realloc( void *pMem, size_t nSize ); + virtual void Free( void *pMem ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); + virtual void Free( void *pMem, const char *pFileName, int nLine ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); + + // Returns size of a particular allocation + virtual size_t GetSize( void *pMem ); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); + virtual void PopAllocDbgInfo(); + + virtual long CrtSetBreakAlloc( long lNewBreakAlloc ); + virtual int CrtSetReportMode( int nReportType, int nReportMode ); + virtual int CrtIsValidHeapPointer( const void *pMem ); + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); + virtual int CrtCheckMemory( void ); + virtual int CrtSetDbgFlag( int nNewFlag ); + virtual void CrtMemCheckpoint( _CrtMemState *pState ); + void* CrtSetReportFile( int nRptType, void* hFile ); + void* CrtSetReportHook( void* pfnNewHook ); + int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ); + virtual int heapchk(); + + virtual void DumpStats() {} + virtual void DumpStatsFileBase( char const *pchFileBase ) {} + + virtual bool IsDebugHeap() + { + return true; + } + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void CompactHeap(); + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ); + + virtual uint32 GetDebugInfoSize() { return 0; } + virtual void SaveDebugInfo( void *pvDebugInfo ) { } + virtual void RestoreDebugInfo( const void *pvDebugInfo ) {} + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {} + +private: + struct HeapPrefix_t + { + HeapPrefix_t *m_pPrev; + HeapPrefix_t *m_pNext; + int m_nSize; + unsigned char m_Prefix[HEAP_PREFIX_BUFFER_SIZE]; + }; + + struct HeapSuffix_t + { + unsigned char m_Suffix[HEAP_SUFFIX_BUFFER_SIZE]; + }; + +private: + // Returns the actual debug info + void GetActualDbgInfo( const char *&pFileName, int &nLine ); + + // Updates stats + void RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + void RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + + HeapSuffix_t *Suffix( HeapPrefix_t *pPrefix ); + void *AllocationStart( HeapPrefix_t *pBase ); + HeapPrefix_t *PrefixFromAllocation( void *pAlloc ); + const HeapPrefix_t *PrefixFromAllocation( const void *pAlloc ); + + // Add to the list! + void AddToList( HeapPrefix_t *pHeap, int nSize ); + + // Remove from the list! + void RemoveFromList( HeapPrefix_t *pHeap ); + + // Validate the allocation + bool ValidateAllocation( HeapPrefix_t *pHeap ); + +private: + HeapPrefix_t *m_pFirstAllocation; + char m_pPrefixImage[HEAP_PREFIX_BUFFER_SIZE]; + char m_pSuffixImage[HEAP_SUFFIX_BUFFER_SIZE]; +}; + + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +static CValidateAlloc s_ValidateAlloc; + +#ifdef _PS3 + +IMemAlloc *g_pMemAllocInternalPS3 = &s_ValidateAlloc; + +#else // !_PS3 + +IMemAlloc *g_pMemAlloc = &s_ValidateAlloc; + +#endif // _PS3 + + +//----------------------------------------------------------------------------- +// Constructor. +//----------------------------------------------------------------------------- +CValidateAlloc::CValidateAlloc() +{ + m_pFirstAllocation = 0; + memset( m_pPrefixImage, 0xBE, HEAP_PREFIX_BUFFER_SIZE ); + memset( m_pSuffixImage, 0xAF, HEAP_SUFFIX_BUFFER_SIZE ); +} + + +//----------------------------------------------------------------------------- +// Accessors... +//----------------------------------------------------------------------------- +inline CValidateAlloc::HeapSuffix_t *CValidateAlloc::Suffix( HeapPrefix_t *pPrefix ) +{ + return reinterpret_cast( (unsigned char*)( pPrefix + 1 ) + pPrefix->m_nSize ); +} + +inline void *CValidateAlloc::AllocationStart( HeapPrefix_t *pBase ) +{ + return static_cast( pBase + 1 ); +} + +inline CValidateAlloc::HeapPrefix_t *CValidateAlloc::PrefixFromAllocation( void *pAlloc ) +{ + if ( !pAlloc ) + return NULL; + + return ((HeapPrefix_t*)pAlloc) - 1; +} + +inline const CValidateAlloc::HeapPrefix_t *CValidateAlloc::PrefixFromAllocation( const void *pAlloc ) +{ + return ((const HeapPrefix_t*)pAlloc) - 1; +} + + +//----------------------------------------------------------------------------- +// Add to the list! +//----------------------------------------------------------------------------- +void CValidateAlloc::AddToList( HeapPrefix_t *pHeap, int nSize ) +{ + pHeap->m_pPrev = NULL; + pHeap->m_pNext = m_pFirstAllocation; + if ( m_pFirstAllocation ) + { + m_pFirstAllocation->m_pPrev = pHeap; + } + pHeap->m_nSize = nSize; + + m_pFirstAllocation = pHeap; + + HeapSuffix_t *pSuffix = Suffix( pHeap ); + memcpy( pHeap->m_Prefix, m_pPrefixImage, HEAP_PREFIX_BUFFER_SIZE ); + memcpy( pSuffix->m_Suffix, m_pSuffixImage, HEAP_SUFFIX_BUFFER_SIZE ); +} + + +//----------------------------------------------------------------------------- +// Remove from the list! +//----------------------------------------------------------------------------- +void CValidateAlloc::RemoveFromList( HeapPrefix_t *pHeap ) +{ + if ( !pHeap ) + return; + + ValidateAllocation( pHeap ); + if ( pHeap->m_pPrev ) + { + pHeap->m_pPrev->m_pNext = pHeap->m_pNext; + } + else + { + m_pFirstAllocation = pHeap->m_pNext; + } + + if ( pHeap->m_pNext ) + { + pHeap->m_pNext->m_pPrev = pHeap->m_pPrev; + } +} + + +//----------------------------------------------------------------------------- +// Validate the allocation +//----------------------------------------------------------------------------- +bool CValidateAlloc::ValidateAllocation( HeapPrefix_t *pHeap ) +{ + HeapSuffix_t *pSuffix = Suffix( pHeap ); + + bool bOk = true; + if ( memcmp( pHeap->m_Prefix, m_pPrefixImage, HEAP_PREFIX_BUFFER_SIZE ) ) + { + bOk = false; + } + + if ( memcmp( pSuffix->m_Suffix, m_pSuffixImage, HEAP_SUFFIX_BUFFER_SIZE ) ) + { + bOk = false; + } + + if ( !bOk ) + { + Warning("Memory trash detected in allocation %X!\n", (void*)(pHeap+1) ); + Assert( 0 ); + } + + return bOk; +} + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- +void *CValidateAlloc::Alloc( size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + HeapPrefix_t *pHeap = (HeapPrefix_t*)g_pActualAlloc->Alloc( nActualSize ); + AddToList( pHeap, nSize ); + return AllocationStart( pHeap ); +} + +void *CValidateAlloc::Realloc( void *pMem, size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Realloc( pHeap, nActualSize ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + +void CValidateAlloc::Free( void *pMem ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + g_pActualAlloc->Free( pHeap ); +} + +void *CValidateAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Expand_NoLongerSupported( pHeap, nActualSize ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + + +//----------------------------------------------------------------------------- +// Debug versions +//----------------------------------------------------------------------------- +void *CValidateAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + HeapPrefix_t *pHeap = (HeapPrefix_t*)g_pActualAlloc->Alloc( nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + return AllocationStart( pHeap ); +} + +void *CValidateAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Realloc( pHeap, nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + +void CValidateAlloc::Free( void *pMem, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + g_pActualAlloc->Free( pHeap, pFileName, nLine ); +} + +void *CValidateAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Expand_NoLongerSupported( pHeap, nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + + +//----------------------------------------------------------------------------- +// Returns size of a particular allocation +//----------------------------------------------------------------------------- +size_t CValidateAlloc::GetSize( void *pMem ) +{ + if ( !pMem ) + return CalcHeapUsed(); + + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return pHeap->m_nSize; +} + + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CValidateAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) +{ + g_pActualAlloc->PushAllocDbgInfo( pFileName, nLine ); +} + +void CValidateAlloc::PopAllocDbgInfo() +{ + g_pActualAlloc->PopAllocDbgInfo( ); +} + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +long CValidateAlloc::CrtSetBreakAlloc( long lNewBreakAlloc ) +{ + return g_pActualAlloc->CrtSetBreakAlloc( lNewBreakAlloc ); +} + +int CValidateAlloc::CrtSetReportMode( int nReportType, int nReportMode ) +{ + return g_pActualAlloc->CrtSetReportMode( nReportType, nReportMode ); +} + +int CValidateAlloc::CrtIsValidHeapPointer( const void *pMem ) +{ + const HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return g_pActualAlloc->CrtIsValidHeapPointer( pHeap ); +} + +int CValidateAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ + const HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return g_pActualAlloc->CrtIsValidPointer( pHeap, size, access ); +} + +int CValidateAlloc::CrtCheckMemory( void ) +{ + return g_pActualAlloc->CrtCheckMemory( ); +} + +int CValidateAlloc::CrtSetDbgFlag( int nNewFlag ) +{ + return g_pActualAlloc->CrtSetDbgFlag( nNewFlag ); +} + +void CValidateAlloc::CrtMemCheckpoint( _CrtMemState *pState ) +{ + g_pActualAlloc->CrtMemCheckpoint( pState ); +} + +void* CValidateAlloc::CrtSetReportFile( int nRptType, void* hFile ) +{ + return g_pActualAlloc->CrtSetReportFile( nRptType, hFile ); +} + +void* CValidateAlloc::CrtSetReportHook( void* pfnNewHook ) +{ + return g_pActualAlloc->CrtSetReportHook( pfnNewHook ); +} + +int CValidateAlloc::CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) +{ + return g_pActualAlloc->CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ); +} + +int CValidateAlloc::heapchk() +{ + bool bOk = true; + + // Validate the heap + HeapPrefix_t *pHeap = m_pFirstAllocation; + for( pHeap = m_pFirstAllocation; pHeap; pHeap = pHeap->m_pNext ) + { + if ( !ValidateAllocation( pHeap ) ) + { + bOk = false; + } + } + +#ifdef _WIN32 + return bOk ? _HEAPOK : 0; +#elif POSIX + return bOk; +#else +#error +#endif +} + +// Returns the actual debug info +void CValidateAlloc::GetActualDbgInfo( const char *&pFileName, int &nLine ) +{ + g_pActualAlloc->GetActualDbgInfo( pFileName, nLine ); +} + +// Updates stats +void CValidateAlloc::RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + g_pActualAlloc->RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ); +} + +void CValidateAlloc::RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + g_pActualAlloc->RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ); +} + +void CValidateAlloc::CompactHeap() +{ + g_pActualAlloc->CompactHeap(); +} + +MemAllocFailHandler_t CValidateAlloc::SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) +{ + return g_pActualAlloc->SetAllocFailHandler( pfnMemAllocFailHandler ); +} + +#endif // TIER0_VALIDATE_HEAP + +#endif // STEAM diff --git a/tier0/minidump.cpp b/tier0/minidump.cpp new file mode 100644 index 0000000..04caade --- /dev/null +++ b/tier0/minidump.cpp @@ -0,0 +1,291 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" +#include "tier0/minidump.h" +#include "tier0/platform.h" + +#if defined(_WIN32) && !defined(_X360) && (_MSC_VER >= 1300) + +#include "tier0/valve_off.h" + +#include "winlite.h" + +#include +#include + +#endif + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +#if defined(_WIN32) && !defined(_X360) + +#if _MSC_VER >= 1300 + +// MiniDumpWriteDump() function declaration (so we can just get the function +// directly from windows) +typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)( + HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); + +// true if we're currently writing a minidump caused by an assert +static bool g_bWritingNonfatalMinidump = false; +// counter used to make sure minidump names are unique +static int g_nMinidumpsWritten = 0; + +//----------------------------------------------------------------------------- +// Purpose: Creates a new file and dumps the exception info into it +// Input : uStructuredExceptionCode - windows exception code, unused. +// pExceptionInfo - call stack. +// minidumpType - type of +//minidump to +// write. ptchMinidumpFileNameBuffer - if not-NULL points +// to a writable tchar +// buffer of length at +// least _MAX_PATH to contain the name of the written minidump file on return. +//----------------------------------------------------------------------------- +bool WriteMiniDumpUsingExceptionInfo( + [[maybe_unused]] unsigned int uStructuredExceptionCode, + ExceptionInfo_t *pExceptionInfo, uint32 minidumpType, + tchar *ptchMinidumpFileNameBuffer /* = NULL */ +) { + if (ptchMinidumpFileNameBuffer) { + *ptchMinidumpFileNameBuffer = tchar(0); + } + + // get the function pointer directly so that we don't have to include the + // .lib, and that we can easily change it to using our own dll when this code + // is used on win98/ME/2K machines + HMODULE hDbgHelpDll = ::LoadLibrary("DbgHelp.dll"); + if (!hDbgHelpDll) return false; + + bool bReturnValue = false; + MINIDUMPWRITEDUMP pfnMiniDumpWrite = + (MINIDUMPWRITEDUMP)::GetProcAddress(hDbgHelpDll, "MiniDumpWriteDump"); + + if (pfnMiniDumpWrite) { + // create a unique filename for the minidump based on the current time and + // module name + struct tm curtime; + Plat_GetLocalTime(&curtime); + ++g_nMinidumpsWritten; + + // strip off the rest of the path from the .exe name + tchar rgchModuleName[MAX_PATH]; +#ifdef TCHAR_IS_WCHAR + ::GetModuleFileNameW(NULL, rgchModuleName, + sizeof(rgchModuleName) / sizeof(tchar)); +#else + ::GetModuleFileName(NULL, rgchModuleName, + sizeof(rgchModuleName) / sizeof(tchar)); +#endif + tchar *pch = _tcsrchr(rgchModuleName, '.'); + if (pch) { + *pch = 0; + } + pch = _tcsrchr(rgchModuleName, '\\'); + if (pch) { + // move past the last slash + pch++; + } + + // can't use the normal string functions since we're in tier0 + tchar rgchFileName[MAX_PATH]; + _sntprintf(rgchFileName, sizeof(rgchFileName) / sizeof(tchar), + _T("%s_%s_%d%.2d%2d%.2d%.2d%.2d_%d.mdmp"), + pch ? pch : _T("unknown"), + g_bWritingNonfatalMinidump ? "assert" : "crash", + curtime.tm_year + 1900, /* Year less 2000 */ + curtime.tm_mon + 1, /* month (0 - 11 : 0 = January) */ + curtime.tm_mday, /* day of month (1 - 31) */ + curtime.tm_hour, /* hour (0 - 23) */ + curtime.tm_min, /* minutes (0 - 59) */ + curtime.tm_sec, /* seconds (0 - 59) */ + g_nMinidumpsWritten // ensures the filename is unique + ); + rgchFileName[ARRAYSIZE(rgchFileName) - 1] = '\0'; + + BOOL bMinidumpResult = FALSE; +#ifdef TCHAR_IS_WCHAR + HANDLE hFile = + ::CreateFileW(rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); +#else + HANDLE hFile = + ::CreateFile(rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + + if (hFile) { + // dump the exception information into the file + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = (PEXCEPTION_POINTERS)pExceptionInfo; + ExInfo.ClientPointers = FALSE; + + bMinidumpResult = (*pfnMiniDumpWrite)( + ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, + (MINIDUMP_TYPE)minidumpType, &ExInfo, NULL, NULL); + ::CloseHandle(hFile); + + if (bMinidumpResult) { + bReturnValue = true; + + if (ptchMinidumpFileNameBuffer) { + // Copy the file name from "pSrc = rgchFileName" into "pTgt = + // ptchMinidumpFileNameBuffer" + tchar *pTgt = ptchMinidumpFileNameBuffer; + tchar const *pSrc = rgchFileName; + while ((*(pTgt++) = *(pSrc++)) != tchar(0)) continue; + } + } + + // fall through to trying again + } + + // mark any failed minidump writes by renaming them + if (!bMinidumpResult) { + tchar rgchFailedFileName[MAX_PATH]; + _sntprintf(rgchFailedFileName, ARRAYSIZE(rgchFailedFileName), + "(failed)%s", rgchFileName); + rgchFailedFileName[ARRAYSIZE(rgchFailedFileName) - 1] = '\0'; + rename(rgchFileName, rgchFailedFileName); + } + } + + ::FreeLibrary(hDbgHelpDll); + + // call the log flush function if one is registered to try to flush any logs + // CallFlushLogFunc(); + + return bReturnValue; +} + +void InternalWriteMiniDumpUsingExceptionInfo( + unsigned int uStructuredExceptionCode, ExceptionInfo_t *pExceptionInfo) { + // First try to write it with all the indirectly referenced memory (ie: a + // large file). If that doesn't work, then write a smaller one. + uint32 iType = + MINIDUMP_WithDataSegs | MINIDUMP_WithIndirectlyReferencedMemory; + if (!WriteMiniDumpUsingExceptionInfo(uStructuredExceptionCode, pExceptionInfo, + iType)) { + iType = MINIDUMP_WithDataSegs; + WriteMiniDumpUsingExceptionInfo(uStructuredExceptionCode, pExceptionInfo, + iType); + } +} + +// minidump function to use +static FnMiniDump g_pfnWriteMiniDump = InternalWriteMiniDumpUsingExceptionInfo; + +//----------------------------------------------------------------------------- +// Purpose: Set a function to call which will write our minidump, overriding +// the default function +// Input : pfn - Pointer to minidump function to set +// Output : Previously set function +//----------------------------------------------------------------------------- +FnMiniDump SetMiniDumpFunction(FnMiniDump pfn) { + FnMiniDump pfnTemp = g_pfnWriteMiniDump; + g_pfnWriteMiniDump = pfn; + return pfnTemp; +} + +//----------------------------------------------------------------------------- +// Unhandled exceptions +//----------------------------------------------------------------------------- +static FnMiniDump g_UnhandledExceptionFunction; +static LONG STDCALL +ValveUnhandledExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo) { + uint uStructuredExceptionCode = + pExceptionInfo->ExceptionRecord->ExceptionCode; + g_UnhandledExceptionFunction(uStructuredExceptionCode, + (ExceptionInfo_t *)pExceptionInfo); + return EXCEPTION_CONTINUE_SEARCH; +} + +void MinidumpSetUnhandledExceptionFunction(FnMiniDump pfn) { + g_UnhandledExceptionFunction = pfn; + SetUnhandledExceptionFilter(ValveUnhandledExceptionFilter); +} + +//----------------------------------------------------------------------------- +// Purpose: writes out a minidump from the current process +//----------------------------------------------------------------------------- +typedef void (*FnMiniDumpInternal_t)(unsigned int uStructuredExceptionCode, + _EXCEPTION_POINTERS *pExceptionInfo); + +void WriteMiniDump() { + // throw an exception so we can catch it and get the stack info + g_bWritingNonfatalMinidump = true; + __try { + ::RaiseException(0, // dwExceptionCode + EXCEPTION_NONCONTINUABLE, // dwExceptionFlags + 0, // nNumberOfArguments, + NULL // const ULONG_PTR* lpArguments + ); + + // Never get here (non-continuable exception) + } + // Write the minidump from inside the filter (GetExceptionInformation() is + // only valid in the filter) + __except (g_pfnWriteMiniDump(0, (ExceptionInfo_t *)GetExceptionInformation()), + EXCEPTION_EXECUTE_HANDLER) { + } + g_bWritingNonfatalMinidump = false; +} + +PLATFORM_OVERLOAD bool g_bInException = false; +#include + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function +//----------------------------------------------------------------------------- +void CatchAndWriteMiniDump(FnWMain pfn, int argc, tchar *argv[]) { + if (Plat_IsInDebugSession()) { + // don't mask exceptions when running in the debugger + pfn(argc, argv); + return; + } + + const _se_translator_function oldSetFunction{ + _set_se_translator((FnMiniDumpInternal_t)g_pfnWriteMiniDump)}; + try { + pfn(argc, argv); + } catch (...) { + g_bInException = true; + Log_Msg(LOG_CONSOLE, _T("Fatal exception caught, minidump written\n")); + // handle everything and just quit, we've already written out our minidump + } + // Restore old se translator on return. + _set_se_translator(oldSetFunction); +} + +#else + +PLATFORM_INTERFACE void WriteMiniDump() {} + +PLATFORM_INTERFACE void CatchAndWriteMiniDump(FnWMain pfn, int argc, + tchar *argv[]) { + pfn(argc, argv); +} + +#endif +#elif defined(_X360) +PLATFORM_INTERFACE void WriteMiniDump() { +#if !defined(_CERT) + DmCrashDump(false); +#endif +} + +#else // !_WIN32 + +PLATFORM_INTERFACE void WriteMiniDump() {} + +PLATFORM_INTERFACE void CatchAndWriteMiniDump(FnWMain pfn, int argc, + tchar *argv[]) { + pfn(argc, argv); +} + +#endif diff --git a/tier0/pch_tier0.cpp b/tier0/pch_tier0.cpp new file mode 100644 index 0000000..929d34b --- /dev/null +++ b/tier0/pch_tier0.cpp @@ -0,0 +1,3 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" diff --git a/tier0/pch_tier0.h b/tier0/pch_tier0.h new file mode 100644 index 0000000..6d7806e --- /dev/null +++ b/tier0/pch_tier0.h @@ -0,0 +1,38 @@ +// Copyright Valve Corporation, All rights reserved. + +#if defined(PLATFORM_WINDOWS_PC) +#include "winlite.h" +#elif defined(_PS3) +#include +#include +#endif + +#include "tier0/platform.h" + +// First include standard libraries +#include "tier0/valve_off.h" + +#include +#include +#include +#include +#include +#include + +#ifdef PLATFORM_POSIX +#include +#define _MAX_PATH PATH_MAX +#endif + +#include "tier0/valve_on.h" + +#include "tier0/basetypes.h" +#include "tier0/dbgflag.h" +#include "tier0/dbg.h" + +#ifdef STEAM +#include "tier0/memhook.h" +#endif + +#include "tier0/validator.h" +#include "tier0/fasttimer.h" diff --git a/tier0/platform.cpp b/tier0/platform.cpp new file mode 100644 index 0000000..d1bcda3 --- /dev/null +++ b/tier0/platform.cpp @@ -0,0 +1,486 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" + +#include "tier0/platform.h" + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#endif + +#include +#include +#include + +#if defined(_X360) +#include "xbox/xbox_console.h" +#endif + +#include "tier0/threadtools.h" + +#include "tier0/memalloc.h" + +#if defined(_PS3) +#include +#include +#include +#include + +#if !defined(_CERT) +#include "sn/LibSN.h" +#endif + +/* +#include +#include +#include + +#include + +#include +*/ +#include +#endif // _PS3 + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef _WIN32 +static LARGE_INTEGER g_PerformanceFrequency; +static LARGE_INTEGER g_MSPerformanceFrequency; +static LARGE_INTEGER g_ClockStart; +static bool s_bTimeInitted; +#endif + +// Benchmark mode uses this heavy-handed method +static bool g_bBenchmarkMode = false; +#ifdef _WIN32 +static double g_FakeBenchmarkTime = 0; +static double g_FakeBenchmarkTimeInc = 1.0 / 66.0; +#endif + +static CThreadFastMutex g_LocalTimeMutex; + +#ifdef _WIN32 +static void InitTime() { + if (!s_bTimeInitted) { + s_bTimeInitted = true; + QueryPerformanceFrequency(&g_PerformanceFrequency); + g_MSPerformanceFrequency.QuadPart = g_PerformanceFrequency.QuadPart / 1000; + QueryPerformanceCounter(&g_ClockStart); + } +} +#endif + +bool Plat_IsInBenchmarkMode() { return g_bBenchmarkMode; } + +void Plat_SetBenchmarkMode(bool bBenchmark) { g_bBenchmarkMode = bBenchmark; } + +#ifdef _PS3 +cell::fios::abstime_t g_fiosLaunchTime = 0; +#endif + +double Plat_FloatTime() { +#ifdef _WIN32 + if (!s_bTimeInitted) InitTime(); + if (g_bBenchmarkMode) { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + + LARGE_INTEGER CurrentTime; + + QueryPerformanceCounter(&CurrentTime); + + double fRawSeconds = (double)(CurrentTime.QuadPart - g_ClockStart.QuadPart) / + (double)(g_PerformanceFrequency.QuadPart); + + return fRawSeconds; +#else + return cell::fios::FIOSAbstimeToMicroseconds( + cell::fios::FIOSGetCurrentTime() - g_fiosLaunchTime) * + 1e-6; +#endif +} + +uint32 Plat_MSTime() { +#ifdef _WIN32 + if (!s_bTimeInitted) InitTime(); + if (g_bBenchmarkMode) { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (uint32)(g_FakeBenchmarkTime * 1000.0); + } + + LARGE_INTEGER CurrentTime; + + QueryPerformanceCounter(&CurrentTime); + + return (uint32)((CurrentTime.QuadPart - g_ClockStart.QuadPart) / + g_MSPerformanceFrequency.QuadPart); +#elif defined(_PS3) + return (uint32)cell::fios::FIOSAbstimeToMilliseconds( + cell::fios::FIOSGetCurrentTime() - g_fiosLaunchTime); +#else +#error +#endif +} + +uint64 Timer_GetTimeUS() { +#ifdef _PS3 + return cell::fios::FIOSAbstimeToMicroseconds( + cell::fios::FIOSGetCurrentTime() - g_fiosLaunchTime); +#else + return uint64(Plat_FloatTime() * 1000000); +#endif +} + +uint64 Plat_GetClockStart() { +#if defined(_WIN32) + if (!s_bTimeInitted) InitTime(); + + return g_ClockStart.QuadPart; +#elif defined(_PS3) + return g_fiosLaunchTime; +#else + return 0; +#endif +} + +void Plat_GetLocalTime(struct tm *pNow) { + // We just provide a wrapper on this function so we can protect access to + // time() everywhere. + time_t ltime; + time(<ime); + + Plat_ConvertToLocalTime(ltime, pNow); +} + +void Plat_ConvertToLocalTime(uint64 nTime, struct tm *pNow) { + // Since localtime() returns a global, we need to protect against multiple + // threads stomping it. + g_LocalTimeMutex.Lock(); + + time_t ltime = (time_t)nTime; + tm *pTime = localtime(<ime); + if (pTime) + *pNow = *pTime; + else + memset(pNow, 0, sizeof(*pNow)); + + g_LocalTimeMutex.Unlock(); +} + +void Plat_GetTimeString(struct tm *pTime, char *pOut, int nMaxBytes) { + g_LocalTimeMutex.Lock(); + + char *pStr = asctime(pTime); + if (pStr) { + strncpy(pOut, pStr, nMaxBytes); + pOut[nMaxBytes - 1] = '\0'; + } else { + // asctime failed. + pOut[0] = '\0'; + } + + g_LocalTimeMutex.Unlock(); +} + +void Plat_gmtime(uint64 nTime, struct tm *pTime) { + time_t tmtTime = nTime; +#ifdef _PS3 + struct tm *tmp = gmtime(&tmtTime); + *pTime = *tmp; +#else + gmtime_s(pTime, &tmtTime); +#endif +} + +time_t Plat_timegm(struct tm *timeptr) { +#ifndef _GAMECONSOLE + return _mkgmtime(timeptr); +#else + int *pnCrashHereBecauseConsolesDontSupportMkGmTime = 0; + *pnCrashHereBecauseConsolesDontSupportMkGmTime = 0; + return 0; +#endif +} + +void Plat_GetModuleFilename(char *pOut, int nMaxBytes) { +#ifdef PLATFORM_WINDOWS_PC + GetModuleFileName(NULL, pOut, nMaxBytes); + if (GetLastError() != ERROR_SUCCESS) + Error("Plat_GetModuleFilename: The buffer given is too small (%d bytes).", + nMaxBytes); +#elif PLATFORM_X360 + pOut[0] = 0x00; // return null string on Xbox 360 +#else + // We shouldn't need this on POSIX. + Assert(false); + pOut[0] = 0x00; // Null the returned string in release builds +#endif +} + +void Plat_ExitProcess(int nCode) { +#if defined(_WIN32) && !defined(_X360) + // We don't want global destructors in our process OR in any DLL to get + // executed. _exit() avoids calling global destructors in our module, but not + // in other DLLs. + TerminateProcess(GetCurrentProcess(), nCode); +#elif defined(_PS3) + // We do not use this path to exit on PS3 (naturally), rather we want a clear + // crash: + int *x = NULL; + *x = 1; +#else + _exit(nCode); +#endif +} + +void GetCurrentDate(int *pDay, int *pMonth, int *pYear) { + struct tm long_time; + Plat_GetLocalTime(&long_time); + + *pDay = long_time.tm_mday; + *pMonth = long_time.tm_mon + 1; + *pYear = long_time.tm_year + 1900; +} + +// Wraps the thread-safe versions of asctime. buf must be at least 26 bytes +char *Plat_asctime(const struct tm *tm, char *buf, size_t bufsize) { +#ifdef _PS3 + snprintf(buf, bufsize, "%s", asctime(tm)); + return buf; +#else + if (EINVAL == asctime_s(buf, bufsize, tm)) + return NULL; + else + return buf; +#endif +} + +// Wraps the thread-safe versions of ctime. buf must be at least 26 bytes +char *Plat_ctime(const time_t *timep, char *buf, size_t bufsize) { +#ifdef _PS3 + snprintf(buf, bufsize, "%s", ctime(timep)); + return buf; +#else + if (EINVAL == ctime_s(buf, bufsize, timep)) + return NULL; + else + return buf; +#endif +} + +// Wraps the thread-safe versions of gmtime +struct tm *Plat_gmtime(const time_t *timep, struct tm *result) { +#ifdef _PS3 + *result = *gmtime(timep); + return result; +#else + if (EINVAL == gmtime_s(result, timep)) + return NULL; + else + return result; +#endif +} + +// Wraps the thread-safe versions of localtime +struct tm *Plat_localtime(const time_t *timep, struct tm *result) { +#ifdef _PS3 + *result = *localtime(timep); + return result; +#else + if (EINVAL == localtime_s(result, timep)) + return NULL; + else + return result; +#endif +} + +bool vtune(bool resume) { +#if IS_WINDOWS_PC + static bool bInitialized = false; + static void(__cdecl * VTResume)(void) = NULL; + static void(__cdecl * VTPause)(void) = NULL; + + // Grab the Pause and Resume function pointers from the VTune DLL the first + // time through: + if (!bInitialized) { + bInitialized = true; + + HINSTANCE pVTuneDLL = LoadLibrary("vtuneapi.dll"); + + if (pVTuneDLL) { + VTResume = (void(__cdecl *)())GetProcAddress(pVTuneDLL, "VTResume"); + VTPause = (void(__cdecl *)())GetProcAddress(pVTuneDLL, "VTPause"); + } + } + + // Call the appropriate function, as indicated by the argument: + if (resume && VTResume) { + VTResume(); + return true; + + } else if (!resume && VTPause) { + VTPause(); + return true; + } +#endif + return false; +} + +bool Plat_IsInDebugSession() { +#if defined(_X360) + return (XBX_IsDebuggerPresent() != 0); +#elif defined(_WIN32) + return (IsDebuggerPresent() != 0); +#elif defined(_PS3) && !defined(_CERT) + return snIsDebuggerPresent(); +#else + return false; +#endif +} + +void Plat_DebugString(const char *psz) { +#ifdef _CERT + return; // do nothing! +#endif + +#if defined(_X360) + XBX_OutputDebugString(psz); +#elif defined(_WIN32) + ::OutputDebugStringA(psz); +#elif defined(_PS3) + printf("%s", psz); +#else + // do nothing? +#endif +} + +#if defined(PLATFORM_WINDOWS_PC) +void Plat_MessageBox(const char *pTitle, const char *pMessage) { + MessageBox(NULL, pMessage, pTitle, MB_OK); +} +#endif + +#if defined(PLATFORM_PS3) +// copied from platform_posix.cpp +static char g_CmdLine[2048] = ""; +PLATFORM_INTERFACE void Plat_SetCommandLine(const char *cmdLine) { + strncpy(g_CmdLine, cmdLine, sizeof(g_CmdLine)); + g_CmdLine[sizeof(g_CmdLine) - 1] = 0; +} +#endif + +PLATFORM_INTERFACE const tchar *Plat_GetCommandLine() { +#if defined(_PS3) +#pragma message("Plat_GetCommandLine() not implemented on PS3") // **** + return g_CmdLine; +#elif defined(TCHAR_IS_WCHAR) + return GetCommandLineW(); +#else + return GetCommandLine(); +#endif +} + +PLATFORM_INTERFACE const char *Plat_GetCommandLineA() { +#if defined(_PS3) +#pragma message("Plat_GetCommandLineA() not implemented on PS3") // **** + return g_CmdLine; +#else + return GetCommandLineA(); +#endif +} + +//----------------------------------------------------------------------------- +// Dynamically load a function +//----------------------------------------------------------------------------- +#ifdef PLATFORM_WINDOWS + +void *Plat_GetProcAddress(const char *pszModule, const char *pszName) { + HMODULE hModule = ::LoadLibrary(pszModule); + return (hModule) ? ::GetProcAddress(hModule, pszName) : NULL; +} + +#endif + +// -------------------------------------------------------------------------------------------------- +// // Memory stuff. +// +// DEPRECATED. Still here to support binary back compatability of tier0.dll +// +// -------------------------------------------------------------------------------------------------- +// // +#ifndef _X360 +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +typedef void (*Plat_AllocErrorFn)(unsigned long size); + +void Plat_DefaultAllocErrorFn(unsigned long) {} + +Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn; +#endif + +#if !defined(_X360) && !defined(_PS3) + +CRITICAL_SECTION g_AllocCS; +class CAllocCSInit { + public: + CAllocCSInit() { InitializeCriticalSection(&g_AllocCS); } +} g_AllocCSInit; + +PLATFORM_INTERFACE void *Plat_Alloc(unsigned long size) { + EnterCriticalSection(&g_AllocCS); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + void *pRet = MemAlloc_Alloc(size); +#else + void *pRet = malloc(size); +#endif + LeaveCriticalSection(&g_AllocCS); + if (pRet) { + return pRet; + } else { +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_AllocError(size); +#endif + return 0; + } +} + +PLATFORM_INTERFACE void *Plat_Realloc(void *ptr, unsigned long size) { + EnterCriticalSection(&g_AllocCS); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + void *pRet = g_pMemAlloc->Realloc(ptr, size); +#else + void *pRet = realloc(ptr, size); +#endif + LeaveCriticalSection(&g_AllocCS); + if (pRet) { + return pRet; + } else { +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_AllocError(size); +#endif + return 0; + } +} + +PLATFORM_INTERFACE void Plat_Free(void *ptr) { + EnterCriticalSection(&g_AllocCS); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->Free(ptr); +#else + free(ptr); +#endif + LeaveCriticalSection(&g_AllocCS); +} + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) +PLATFORM_INTERFACE void Plat_SetAllocErrorFn(Plat_AllocErrorFn fn) { + g_AllocError = fn; +} +#endif + +#endif + +#endif \ No newline at end of file diff --git a/tier0/platform_posix.cpp b/tier0/platform_posix.cpp new file mode 100644 index 0000000..e66c668 --- /dev/null +++ b/tier0/platform_posix.cpp @@ -0,0 +1,421 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" +#include "tier0/platform.h" +#include "tier0/memalloc.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" + +#include +#include + +#ifdef OSX +#include +#include +#include +#endif + + +static bool g_bBenchmarkMode = false; +static double g_FakeBenchmarkTime = 0; +static double g_FakeBenchmarkTimeInc = 1.0 / 66.0; + + +bool Plat_IsInBenchmarkMode() +{ + return g_bBenchmarkMode; +} + +void Plat_SetBenchmarkMode( bool bBenchmark ) +{ + g_bBenchmarkMode = bBenchmark; +} + + + +#ifdef OSX + +static uint64 start_time = 0; +static mach_timebase_info_data_t sTimebaseInfo; +static double conversion = 0.0; + +void InitTime() +{ + start_time = mach_absolute_time(); + mach_timebase_info(&sTimebaseInfo); + conversion = 1e-9 * (double) sTimebaseInfo.numer / (double) sTimebaseInfo.denom; +} + +uint64 Plat_GetClockStart() +{ + if ( !start_time ) + { + InitTime(); + } + + return start_time * conversion; +} + +double Plat_FloatTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + + if ( !start_time ) + { + InitTime(); + } + + uint64 now = mach_absolute_time(); + + return ( now - start_time ) * conversion; +} +#else + +static int secbase = 0; + +void InitTime( struct timeval &tp ) +{ + secbase = tp.tv_sec; +} + +uint64 Plat_GetClockStart() +{ + if ( !secbase ) + { + struct timeval tp; + gettimeofday( &tp, NULL ); + InitTime( tp ); + } + + return secbase; +} + + +double Plat_FloatTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + + struct timeval tp; + + gettimeofday( &tp, NULL ); + + if ( !secbase ) + { + InitTime( tp ); + return ( tp.tv_usec / 1000000.0 ); + } + + return (( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0 ); +} +#endif + + +uint32 Plat_MSTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (unsigned long)(g_FakeBenchmarkTime * 1000.0); + } + + struct timeval tp; + static int secbase = 0; + + gettimeofday( &tp, NULL ); + + if ( !secbase ) + { + secbase = tp.tv_sec; + return ( tp.tv_usec / 1000.0 ); + } + + return (unsigned long)(( tp.tv_sec - secbase )*1000.0 + tp.tv_usec / 1000.0 ); + +} + +// Wraps the thread-safe versions of asctime. buf must be at least 26 bytes +char *Plat_asctime( const struct tm *tm, char *buf, size_t bufsize ) +{ + return asctime_r( tm, buf ); +} + +// Wraps the thread-safe versions of ctime. buf must be at least 26 bytes +char *Plat_ctime( const time_t *timep, char *buf, size_t bufsize ) +{ + return ctime_r( timep, buf ); +} + +// Wraps the thread-safe versions of gmtime +struct tm *Plat_gmtime( const time_t *timep, struct tm *result ) +{ + return gmtime_r( timep, result ); +} + +time_t Plat_timegm( struct tm *timeptr ) +{ + return timegm( timeptr ); +} + +// Wraps the thread-safe versions of localtime +struct tm *Plat_localtime( const time_t *timep, struct tm *result ) +{ + return localtime_r( timep, result ); +} + +bool vtune( bool resume ) +{ +} + + +// -------------------------------------------------------------------------------------------------- // +// Memory stuff. +// -------------------------------------------------------------------------------------------------- // + +PLATFORM_INTERFACE void Plat_DefaultAllocErrorFn( unsigned long size ) +{ +} + +typedef void (*Plat_AllocErrorFn)( unsigned long size ); +Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn; + +PLATFORM_INTERFACE void* Plat_Alloc( unsigned long size ) +{ + void *pRet = g_pMemAlloc->Alloc( size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void* Plat_Realloc( void *ptr, unsigned long size ) +{ + void *pRet = g_pMemAlloc->Realloc( ptr, size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void Plat_Free( void *ptr ) +{ +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->Free( ptr ); +#else + free( ptr ); +#endif +} + + +PLATFORM_INTERFACE void Plat_SetAllocErrorFn( Plat_AllocErrorFn fn ) +{ + g_AllocError = fn; +} + +static char g_CmdLine[ 2048 ]; +PLATFORM_INTERFACE void Plat_SetCommandLine( const char *cmdLine ) +{ + strncpy( g_CmdLine, cmdLine, sizeof(g_CmdLine) ); + g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0; +} + +PLATFORM_INTERFACE void Plat_SetCommandLineArgs( char **argv, int argc ) +{ + g_CmdLine[0] = 0; + for ( int i = 0; i < argc; i++ ) + { + strncat( g_CmdLine, argv[i], sizeof(g_CmdLine) - strlen(g_CmdLine) ); + } + + g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0; +} + + +PLATFORM_INTERFACE const tchar *Plat_GetCommandLine() +{ + return g_CmdLine; +} + +PLATFORM_INTERFACE bool Is64BitOS() +{ +#if defined OSX + return true; +#elif defined LINUX + FILE *pp = popen( "uname -m", "r" ); + if ( pp != NULL ) + { + char rgchArchString[256]; + fgets( rgchArchString, sizeof( rgchArchString ), pp ); + pclose( pp ); + if ( !strncasecmp( rgchArchString, "x86_64", strlen( "x86_64" ) ) ) + return true; + } +#else + Assert( !"implement Is64BitOS" ); +#endif + return false; +} + + +bool Plat_IsInDebugSession() +{ +#if defined(OSX) + int mib[4]; + struct kinfo_proc info; + size_t size; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + size = sizeof(info); + info.kp_proc.p_flag = 0; + sysctl(mib,4,&info,&size,NULL,0); + bool result = ((info.kp_proc.p_flag & P_TRACED) == P_TRACED); + return result; +#elif defined(LINUX) + char s[256]; + snprintf(s, 256, "/proc/%d/cmdline", getppid()); + FILE * fp = fopen(s, "r"); + if (fp != NULL) + { + fread(s, 256, 1, fp); + fclose(fp); + return (0 == strncmp(s, "gdb", 3)); + } + return false; +#endif +} + + + +void Plat_ExitProcess( int nCode ) +{ + _exit( nCode ); +} + +static int s_nWatchDogTimerTimeScale = 0; +static bool s_bInittedWD = false; + + +static void InitWatchDogTimer( void ) +{ + if( !strstr( g_CmdLine, "-nowatchdog" ) ) + { +#ifdef _DEBUG + s_nWatchDogTimerTimeScale = 10; // debug is slow +#else + s_nWatchDogTimerTimeScale = 1; +#endif + + } + +} + +// watchdog timer support +void BeginWatchdogTimer( int nSecs ) +{ + if (! s_bInittedWD ) + { + s_bInittedWD = true; + InitWatchDogTimer(); + } + nSecs *= s_nWatchDogTimerTimeScale; + nSecs = MIN( nSecs, 5 * 60 ); // no more than 5 minutes no matter what + if ( nSecs ) + alarm( nSecs ); +} + +void EndWatchdogTimer( void ) +{ + alarm( 0 ); +} + +static CThreadMutex g_LocalTimeMutex; + + +void Plat_GetLocalTime( struct tm *pNow ) +{ + // We just provide a wrapper on this function so we can protect access to time() everywhere. + time_t ltime; + time( <ime ); + + Plat_ConvertToLocalTime( ltime, pNow ); +} + +void Plat_ConvertToLocalTime( uint64 nTime, struct tm *pNow ) +{ + // Since localtime() returns a global, we need to protect against multiple threads stomping it. + g_LocalTimeMutex.Lock(); + + time_t ltime = (time_t)nTime; + tm *pTime = localtime( <ime ); + if ( pTime ) + *pNow = *pTime; + else + memset( pNow, 0, sizeof( *pNow ) ); + + g_LocalTimeMutex.Unlock(); +} + +void Plat_GetTimeString( struct tm *pTime, char *pOut, int nMaxBytes ) +{ + g_LocalTimeMutex.Lock(); + + char *pStr = asctime( pTime ); + strncpy( pOut, pStr, nMaxBytes ); + pOut[nMaxBytes-1] = 0; + + g_LocalTimeMutex.Unlock(); +} + + +void Plat_gmtime( uint64 nTime, struct tm *pTime ) +{ + time_t tmtTime = nTime; + struct tm * tmp = gmtime( &tmtTime ); + * pTime = * tmp; +} + +#ifdef LINUX +size_t ApproximateProcessMemoryUsage( void ) +{ + int nRet = 0; + FILE *pFile = fopen( "/proc/self/statm", "r" ); + if ( pFile ) + { + int nSize, nTotalProgramSize, nResident, nResidentSetSize, nShare, nSharedPagesTotal, nDummy0; + if ( fscanf( pFile, "%d %d %d %d %d %d %d", &nSize, &nTotalProgramSize, &nResident, &nResidentSetSize, &nShare, &nSharedPagesTotal, &nDummy0 ) ) + { + nRet = 4096 * nSize; + } + fclose( pFile ); + } + return nRet; +} +#else + +size_t ApproximateProcessMemoryUsage( void ) +{ + return 0; +} + +#endif diff --git a/tier0/pme.cpp b/tier0/pme.cpp new file mode 100644 index 0000000..8f717e1 --- /dev/null +++ b/tier0/pme.cpp @@ -0,0 +1,136 @@ +// Copyright (c) 1996-2005, Valve Corporation, All rights reserved. + +#include "tier0/platform.h" + +#include "pch_tier0.h" +#include "winlite.h" + +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include "tier0/pmelib.h" +#include "tier0/l2cache.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Initialization +//----------------------------------------------------------------------------- +void InitPME(void) { + bool bInit = false; + + PME *pPME = PME::Instance(); + if (pPME) { + if (pPME->GetVendor() != INTEL) return; + + if (pPME->GetProcessorFamily() != PENTIUM4_FAMILY) return; + + pPME->SetProcessPriority(ProcessPriorityHigh); + + bInit = true; + + DevMsg(1, _T("PME Initialized.\n")); + } else { + DevMsg(1, _T("PME Uninitialized.\n")); + } + +#ifdef VPROF_ENABLED + g_VProfCurrentProfile.PMEInitialized(bInit); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown +//----------------------------------------------------------------------------- +void ShutdownPME(void) { + PME *pPME = PME::Instance(); + if (pPME) { + pPME->SetProcessPriority(ProcessPriorityNormal); + } + +#ifdef VPROF_ENABLED + g_VProfCurrentProfile.PMEInitialized(false); +#endif +} + +//============================================================================= +// +// CL2Cache Code. +// + +static int s_nCreateCount = 0; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::CL2Cache() { + m_nID = s_nCreateCount++; + m_pL2CacheEvent = new P4Event_BSQ_cache_reference; + m_iL2CacheMissCount = 0; + m_i64Start = 0; + m_i64End = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::~CL2Cache() { + if (m_pL2CacheEvent) { + delete m_pL2CacheEvent; + m_pL2CacheEvent = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::Start(void) { + if (m_pL2CacheEvent) { + // Set this up to check for L2 cache misses. + m_pL2CacheEvent->eventMask->RD_2ndL_MISS = 1; + + // Set the event mask and set the capture mode. + // m_pL2CacheEvent->SetCaptureMode( USR_Only ); + m_pL2CacheEvent->SetCaptureMode(OS_and_USR); + + // That's it, now sw capture events + m_pL2CacheEvent->StopCounter(); + m_pL2CacheEvent->ClearCounter(); + + m_pL2CacheEvent->StartCounter(); + m_i64Start = m_pL2CacheEvent->ReadCounter(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::End(void) { + if (m_pL2CacheEvent) { + // Stop the counter and find the delta. + m_i64End = m_pL2CacheEvent->ReadCounter(); + int64 i64Delta = m_i64End - m_i64Start; + m_pL2CacheEvent->StopCounter(); + + // Save the delta for later query. + m_iL2CacheMissCount = (int)i64Delta; + } +} + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our +//container) +//----------------------------------------------------------------------------- +void CL2Cache::Validate(CValidator &validator, tchar *pchName) { + validator.Push(_T("CL2Cache"), this, pchName); + + validator.ClaimMemory(m_pL2CacheEvent); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE diff --git a/tier0/pme_posix.cpp b/tier0/pme_posix.cpp new file mode 100644 index 0000000..1e66ee4 --- /dev/null +++ b/tier0/pme_posix.cpp @@ -0,0 +1,35 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include "tier0/dbg.h" + +//----------------------------------------------------------------------------- +// Purpose: Initialization +//----------------------------------------------------------------------------- +void InitPME(void) {} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown +//----------------------------------------------------------------------------- +void ShutdownPME(void) {} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::CL2Cache() {} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::~CL2Cache() {} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::Start(void) {} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::End(void) {} diff --git a/tier0/pmelib.cpp b/tier0/pmelib.cpp new file mode 100644 index 0000000..8620ded --- /dev/null +++ b/tier0/pmelib.cpp @@ -0,0 +1,574 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifdef _WIN32 +#include "winlite.h" + +#include "tier0/valve_off.h" +#include "tier0/pmelib.h" + +#if _MSC_VER >= 1300 +#else +#include "winioctl.h" +#endif + +#include "tier0/valve_on.h" + +#include "tier0/ioctlcodes.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +PME* PME::_singleton = 0; + +// Single interface. +PME* PME::Instance() { + if (_singleton == 0) { + _singleton = new PME; + } + return _singleton; +} + +//--------------------------------------------------------------------------- +// Open the device driver and detect the processor +//--------------------------------------------------------------------------- +HRESULT PME::Init(void) { + if (bDriverOpen) return E_DRIVER_ALREADY_OPEN; + + switch (vendor) { + case INTEL: + case AMD: + break; + default: + bDriverOpen = FALSE; // not an Intel or Athlon processor so return false + return E_UNKNOWN_CPU_VENDOR; + } + + //----------------------------------------------------------------------- + // Get the operating system version + //----------------------------------------------------------------------- + + if (IsWindowsXPOrGreater()) { + hFile = CreateFile( // WINDOWS NT+ + "\\\\.\\GDPERF", GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + } else { + hFile = CreateFile( // WINDOWS NT- + "\\\\.\\GDPERF.VXD", GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + } + + if (hFile == INVALID_HANDLE_VALUE) return E_CANT_OPEN_DRIVER; + + bDriverOpen = TRUE; + + //------------------------------------------------------------------- + // We have successfully opened the device driver, get the family + // of the processor. + //------------------------------------------------------------------- + + //------------------------------------------------------------------- + // We need to write to counter 0 on the pro family to enable both + // of the performance counters. We write to both so they start in a + // known state. For the pentium this is not necessary. + //------------------------------------------------------------------- + if (vendor == INTEL && version.Family == PENTIUMPRO_FAMILY) { + SelectP5P6PerformanceEvent(P6_CLOCK, 0, TRUE, TRUE); + SelectP5P6PerformanceEvent(P6_CLOCK, 1, TRUE, TRUE); + } + + return S_OK; +} + +//--------------------------------------------------------------------------- +// Close the device driver +//--------------------------------------------------------------------------- +HRESULT PME::Close(void) { + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + bDriverOpen = false; + + if (hFile) // if we have no driver handle, return FALSE + { + HRESULT hr = CloseHandle(hFile) ? S_OK : E_FAIL; + + hFile = NULL; + return hr; + } else + return E_DRIVER_NOT_OPEN; +} + +//--------------------------------------------------------------------------- +// Select the event to monitor with counter 0 +// +HRESULT PME::SelectP5P6PerformanceEvent(uint32 dw_event, uint32 dw_counter, + bool b_user, bool b_kernel) { + HRESULT hr = S_OK; + + if (dw_counter > 1) // is the counter valid + return E_BAD_COUNTER; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + if (((dw_event >> 28) & 0xF) != (uint32)version.Family) { + return E_ILLEGAL_OPERATION; // this operation is not for this processor + } + + if ((((dw_event & 0x300) >> 8) & (dw_counter + 1)) == 0) { + return E_ILLEGAL_OPERATION; // this operation is not for this counter + } + + switch (version.Family) { + case PENTIUM_FAMILY: { + uint64 i64_cesr; + int i_kernel_bit, i_user_bit; + BYTE u1_event = (BYTE)((dw_event & (0x3F0000)) >> 16); + + if (dw_counter == 0) // the kernel and user mode bits depend on + { // counter being used. + i_kernel_bit = 6; + i_user_bit = 7; + } else { + i_kernel_bit = 22; + i_user_bit = 23; + } + + ReadMSR(0x11, &i64_cesr); // get current P5 event select (cesr) + + // top 32bits of cesr are not valid so ignore them + i64_cesr &= ((dw_counter == 0) ? 0xffff0000 : 0x0000ffff); + WriteMSR(0x11, i64_cesr); // stop the counter + WriteMSR((dw_counter == 0) ? 0x12 : 0x13, 0ui64); // clear the p.counter + + // set the user and kernel mode bits + i64_cesr |= (b_user ? (1 << 7) : 0) | (b_kernel ? (1 << 6) : 0); + + // is this the special P5 value that signals count clocks?? + if (u1_event == 0x3f) { + WriteMSR(0x11, i64_cesr | 0x100); // Count clocks + } else { + WriteMSR(0x11, i64_cesr | u1_event); // Count events + } + + } break; + + case PENTIUMPRO_FAMILY: + + { + BYTE u1_event = (BYTE)((dw_event & (0xFF0000)) >> 16); + BYTE u1_mask = (BYTE)((dw_event & 0xFF)); + + // Event select 0 and 1 are identical. + hr = WriteMSR((dw_counter == 0) ? 0x186 : 0x187, + + uint64((u1_event | (b_user ? (1 << 16) : 0) | + (b_kernel ? (1 << 17) : 0) | (1 << 22) | (1 << 18) | + (u1_mask << 8)))); + } break; + + case PENTIUM4_FAMILY: + // use the p4 path + break; + + default: + return E_UNKNOWN_CPU; + } + + return hr; +} + +//--------------------------------------------------------------------------- +// Read model specific register +//--------------------------------------------------------------------------- +HRESULT PME::ReadMSR(uint32 dw_reg, int64* pi64_value) { + HRESULT hr; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + hr = DeviceIoControl(hFile, // Handle to device + (DWORD)IOCTL_READ_MSR, // IO Control code for Read + &dw_reg, // Input Buffer to driver. + sizeof(uint32), // Length of input buffer. + pi64_value, // Output Buffer from driver. + sizeof(int64), // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in output buffer. + NULL // NULL means wait till op. completes + ) + ? S_OK + : E_FAIL; + + if (hr == S_OK && dw_ret_len != sizeof(int64)) hr = E_BAD_DATA; + + return hr; +} + +HRESULT PME::ReadMSR(uint32 dw_reg, uint64* pi64_value) { + HRESULT hr; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + hr = DeviceIoControl(hFile, // Handle to device + (DWORD)IOCTL_READ_MSR, // IO Control code for Read + &dw_reg, // Input Buffer to driver. + sizeof(uint32), // Length of input buffer. + pi64_value, // Output Buffer from driver. + sizeof(uint64), // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in output buffer. + NULL // NULL means wait till op. completes + ) + ? S_OK + : E_FAIL; + + if (hr == S_OK && dw_ret_len != sizeof(uint64)) hr = E_BAD_DATA; + + return hr; +} + +//--------------------------------------------------------------------------- +// Write model specific register +//--------------------------------------------------------------------------- +HRESULT PME::WriteMSR(uint32 dw_reg, const int64& i64_value) { + HRESULT hr; + alignas(int64) DWORD dw_buffer[3]; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + dw_buffer[0] = dw_reg; // setup the 12 byte input + *((int64*)(&dw_buffer[1])) = i64_value; + + hr = DeviceIoControl(hFile, // Handle to device + (DWORD)IOCTL_WRITE_MSR, // IO Control code for Read + dw_buffer, // Input Buffer to driver. + 12, // Length of Input buffer + NULL, // Buffer from driver, None for WRMSR + 0, // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in DataBuffer. + NULL // NULL means wait till op. completes. + ) + ? S_OK + : E_FAIL; + + if (hr == S_OK && dw_ret_len != 0) hr = E_BAD_DATA; + + return hr; +} + +HRESULT PME::WriteMSR(uint32 dw_reg, const uint64& i64_value) { + HRESULT hr; + alignas(uint64) DWORD dw_buffer[3]; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + dw_buffer[0] = dw_reg; // setup the 12 byte input + *((uint64*)(&dw_buffer[1])) = i64_value; + + hr = DeviceIoControl(hFile, // Handle to device + (DWORD)IOCTL_WRITE_MSR, // IO Control code for Read + dw_buffer, // Input Buffer to driver. + 12, // Length of Input buffer + NULL, // Buffer from driver, None for WRMSR + 0, // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in DataBuffer. + NULL // NULL means wait till op. completes. + ) + ? S_OK + : E_FAIL; + + // E_POINTER + if (hr == S_OK && dw_ret_len != 0) hr = E_BAD_DATA; + + return hr; +} + +#pragma hdrstop + +//--------------------------------------------------------------------------- +// Return the frequency of the processor in Hz. +// + +double PME::GetCPUClockSpeedFast(void) { + int64 i64_perf_start, i64_perf_freq, i64_perf_end; + int64 i64_clock_start, i64_clock_end; + double d_loop_period, d_clock_freq; + + //----------------------------------------------------------------------- + // Query the performance of the Windows high resolution timer. + //----------------------------------------------------------------------- + QueryPerformanceFrequency((LARGE_INTEGER*)&i64_perf_freq); + + //----------------------------------------------------------------------- + // Query the current value of the Windows high resolution timer. + //----------------------------------------------------------------------- + QueryPerformanceCounter((LARGE_INTEGER*)&i64_perf_start); + i64_perf_end = 0; + + //----------------------------------------------------------------------- + // Time of loop of 250000 windows cycles with RDTSC + //----------------------------------------------------------------------- + RDTSC(i64_clock_start); + while (i64_perf_end < i64_perf_start + 250000) { + QueryPerformanceCounter((LARGE_INTEGER*)&i64_perf_end); + } + RDTSC(i64_clock_end); + + //----------------------------------------------------------------------- + // Caclulate the frequency of the RDTSC timer and therefore calculate + // the frequency of the processor. + //----------------------------------------------------------------------- + i64_clock_end -= i64_clock_start; + + d_loop_period = ((double)(i64_perf_freq)) / 250000.0; + d_clock_freq = ((double)(i64_clock_end & 0xffffffff)) * d_loop_period; + + return (float)d_clock_freq; +} + +// takes 1 second +double PME::GetCPUClockSpeedSlow(void) { + if (m_CPUClockSpeed != 0) return m_CPUClockSpeed; + + unsigned long long start_ms, stop_ms; + unsigned long long start_tsc, stop_tsc; + + // boosting priority helps with noise. its optional and i dont think + // it helps all that much + + PME* pme = PME::Instance(); + + pme->SetProcessPriority(ProcessPriorityHigh); + + // wait for millisecond boundary + start_ms = GetTickCount64() + 5; + while (start_ms <= GetTickCount64()) + ; + + // read timestamp (you could use QueryPerformanceCounter in hires mode if + // you want) +#ifdef COMPILER_MSVC64 + start_tsc = __rdtsc(); +#else + __asm + { + rdtsc + mov dword ptr [start_tsc+0],eax + mov dword ptr [start_tsc+4],edx + } +#endif + + // wait for end + stop_ms = start_ms + 1000; // longer wait gives better resolution + while (stop_ms > GetTickCount64()) + ; + + // read timestamp (you could use QueryPerformanceCounter in hires mode if + // you want) +#ifdef COMPILER_MSVC64 + stop_tsc = __rdtsc(); +#else + __asm + { + rdtsc + mov dword ptr [stop_tsc+0],eax + mov dword ptr [stop_tsc+4],edx + } +#endif + + // normalize priority + pme->SetProcessPriority(ProcessPriorityNormal); + + // return clock speed + // optionally here you could round to known clocks, like speeds that are + // multimples of 100, 133, 166, etc. + m_CPUClockSpeed = + ((stop_tsc - start_tsc) * 1000.0) / (double)(stop_ms - start_ms); + return m_CPUClockSpeed; +} + +const unsigned short cccr_escr_map[NCOUNTERS][8] = { + { + 0x3B2, + 0x3B4, + 0x3AA, + 0x3B6, + 0x3AC, + 0x3C8, + 0x3A2, + 0x3A0, + }, + { + 0x3B2, + 0x3B4, + 0x3AA, + 0x3B6, + 0x3AC, + 0x3C8, + 0x3A2, + 0x3A0, + }, + { + 0x3B3, + 0x3B5, + 0x3AB, + 0x3B7, + 0x3AD, + 0x3C9, + 0x3A3, + 0x3A1, + }, + { + 0x3B3, + 0x3B5, + 0x3AB, + 0x3B7, + 0x3AD, + 0x3C9, + 0x3A3, + 0x3A1, + }, + { + + 0x3C0, + 0x3C4, + 0x3C2, + }, + { + 0x3C0, + 0x3C4, + 0x3C2, + }, + { + 0x3C1, + 0x3C5, + 0x3C3, + }, + { + 0x3C1, + 0x3C5, + 0x3C3, + }, + { + 0x3A6, + 0x3A4, + 0x3AE, + 0x3B0, + 0, + 0x3A8, + }, + { + 0x3A6, + 0x3A4, + 0x3AE, + 0x3B0, + 0, + 0x3A8, + }, + { + + 0x3A7, + 0x3A5, + 0x3AF, + 0x3B1, + 0, + 0x3A9, + }, + { + + 0x3A7, + 0x3A5, + 0x3AF, + 0x3B1, + 0, + 0x3A9, + }, + { + + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, + { + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, + { + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, +}; + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void PME::Validate(CValidator& validator, tchar* pchName) { + validator.Push(_T("PME"), this, pchName); + + validator.ClaimMemory(this); + + validator.ClaimMemory(cache); + + validator.ClaimMemory((void*)vendor_name.c_str()); + validator.ClaimMemory((void*)brand.c_str()); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + +#endif diff --git a/tier0/resource.h b/tier0/resource.h new file mode 100644 index 0000000..b9ea211 --- /dev/null +++ b/tier0/resource.h @@ -0,0 +1,30 @@ +// Copyright Valve Corporation, All rights reserved. +// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by assert_dialog.rc +// +#define IDD_ASSERT_DIALOG 101 +#define IDC_FILENAME_CONTROL 1000 +#define IDC_LINE_CONTROL 1001 +#define IDC_IGNORE_FILE 1002 +#define IDC_IGNORE_NEARBY 1003 +#define IDC_IGNORE_NUMLINES 1004 +#define IDC_IGNORE_THIS 1005 +#define IDC_BREAK 1006 +#define IDC_IGNORE_ALL 1008 +#define IDC_IGNORE_ALWAYS 1009 +#define IDC_IGNORE_NUMTIMES 1010 +#define IDC_ASSERT_MSG_CTRL 1011 +#define IDC_NOID -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/tier0/stacktools.cpp b/tier0/stacktools.cpp new file mode 100644 index 0000000..c747a1e --- /dev/null +++ b/tier0/stacktools.cpp @@ -0,0 +1,1699 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" +#include "tier0/stacktools.h" +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" + +#include "tier0/valve_off.h" + +#if defined(PLATFORM_WINDOWS_PC) +#include "winlite.h" +#include +#endif + +#if defined(PLATFORM_X360) +#include +#include "xbox/xbox_console.h" +#include "xbox/xbox_vxconsole.h" +#include +#include +#endif + +#include "tier0/valve_on.h" + +#include "tier0/memdbgon.h" + +#if !defined(ENABLE_RUNTIME_STACK_TRANSLATION) // disable the whole toolset + +int GetCallStack(void **pReturnAddressesOut, int iArrayCount, int iSkipCount) { + return 0; +} + +int GetCallStack_Fast(void **pReturnAddressesOut, int iArrayCount, + int iSkipCount) { + return 0; +} + +// where we'll find our PDB's for win32. Translation will not work until this +// has been called once (even if with NULL) +void SetStackTranslationSymbolSearchPath(const char *szSemicolonSeparatedList) { +} + +void StackToolsNotify_LoadedLibrary(const char *szLibName) {} + +int TranslateStackInfo(const void *const *pCallStack, int iCallStackCount, + tchar *szOutput, int iOutBufferSize, + const tchar *szEntrySeparator, + TranslateStackInfo_StyleFlags_t style) { + if (iOutBufferSize > 0) *szOutput = '\0'; + + return 0; +} + +void PreloadStackInformation(const void **pAddresses, int iAddressCount) {} + +bool GetFileAndLineFromAddress(const void *pAddress, tchar *pFileNameOut, + int iMaxFileNameLength, uint32 &iLineNumberOut, + uint32 *pDisplacementOut) { + if (iMaxFileNameLength > 0) *pFileNameOut = '\0'; + + return false; +} + +bool GetSymbolNameFromAddress(const void *pAddress, tchar *pSymbolNameOut, + int iMaxSymbolNameLength, + uint64 *pDisplacementOut) { + if (iMaxSymbolNameLength > 0) *pSymbolNameOut = '\0'; + + return false; +} + +bool GetModuleNameFromAddress(const void *pAddress, tchar *pModuleNameOut, + int iMaxModuleNameLength) { + if (iMaxModuleNameLength > 0) *pModuleNameOut = '\0'; + + return false; +} + +#else //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +//=============================================================================================================== +// Shared Windows/X360 code +//=============================================================================================================== + +CTHREADLOCALPTR(CStackTop_Base) g_StackTop; + +class CStackTop_FriendFuncs : public CStackTop_Base { + public: + friend int AppendParentStackTrace(void **pReturnAddressesOut, int iArrayCount, + int iAlreadyFilled); + friend int GetCallStack_Fast(void **pReturnAddressesOut, int iArrayCount, + int iSkipCount); +}; + +inline int AppendParentStackTrace(void **pReturnAddressesOut, int iArrayCount, + int iAlreadyFilled) { + CStackTop_FriendFuncs *pTop = + (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; + if (pTop != NULL) { + if (pTop->m_pReplaceAddress != NULL) { + for (int i = iAlreadyFilled; --i >= 0;) { + if (pReturnAddressesOut[i] == pTop->m_pReplaceAddress) { + iAlreadyFilled = i; + break; + } + } + } + + if (pTop->m_iParentStackTraceLength != 0) { + int iCopy = + MIN(iArrayCount - iAlreadyFilled, pTop->m_iParentStackTraceLength); + memcpy(pReturnAddressesOut + iAlreadyFilled, pTop->m_pParentStackTrace, + iCopy * sizeof(void *)); + iAlreadyFilled += iCopy; + } + } + + return iAlreadyFilled; +} + +inline bool ValidStackAddress(void *pAddress, const void *pNoLessThan, + const void *pNoGreaterThan) { + if ((uint64)pAddress & 3) return false; + if (pAddress < pNoLessThan) // frame pointer traversal should always increase + // the pointer + return false; + if (pAddress > pNoGreaterThan) // never traverse outside the stack (Oh + // 0xCCCCCCCC, how I hate you) + return false; + +#if defined(WIN32) && !defined(_X360) && 1 + if (IsBadReadPtr(pAddress, (sizeof(void *) * + 2))) // safety net, but also throws an exception + // (handled internally) to stop bad access + return false; +#endif + + return true; +} + +#pragma auto_inline(off) +int GetCallStack_Fast(void **pReturnAddressesOut, int iArrayCount, + int iSkipCount) { + // Only tested in windows. This function won't work with frame pointer + // omission enabled. "vpc /nofpo" all projects +#if (defined(TIER0_FPO_DISABLED) || defined(_DEBUG)) && \ + (defined(WIN32) && !defined(_X360) && !defined(_WIN64)) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + + /* + With frame pointer omission disabled, this should be the pattern all the way + up the stack [ebp+00] Old ebp value [ebp+04] Return address + */ + + void *pNoLessThan = pStackCrawlEBP; // impossible for a valid stack to + // traverse before this address + int i; + + auto *pTop = (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; + // we can do fewer error checks if we have a valid reference point for the top + // of the stack + if (pTop != NULL) { + void *pNoGreaterThan = pTop->m_pStackBase; + + // skips + for (i = 0; i != iSkipCount; ++i) { + if ((pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan)) + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, 0); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = + *(void **)pStackCrawlEBP; // should be pointing at old ebp value + } + + // store + for (i = 0; i != iArrayCount; ++i) { + if ((pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan)) + break; + + pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = + *(void **)pStackCrawlEBP; // should be pointing at old ebp value + } + + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, i); + } + + // standard stack is 1MB. TODO: Get actual stack end address if available + // since this check isn't foolproof + void *pNoGreaterThan = ((unsigned char *)pNoLessThan) + (1024 * 1024); + + // skips + for (i = 0; i != iSkipCount; ++i) { + if (!ValidStackAddress(pStackCrawlEBP, pNoLessThan, pNoGreaterThan)) + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, 0); + + pNoLessThan = pStackCrawlEBP; + // should be pointing at old ebp value + pStackCrawlEBP = *(void **)pStackCrawlEBP; + } + + // store + for (i = 0; i != iArrayCount; ++i) { + if (!ValidStackAddress(pStackCrawlEBP, pNoLessThan, pNoGreaterThan)) break; + + pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); + + pNoLessThan = pStackCrawlEBP; + // should be pointing at old ebp value + pStackCrawlEBP = *(void **)pStackCrawlEBP; + } + + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, i); +#else + return 0; +#endif +} +#pragma auto_inline(on) + +#if defined(WIN32) && !defined(_X360) +//=============================================================================================================== +// Windows version of the toolset +//=============================================================================================================== + +#if defined(TIER0_FPO_DISABLED) +//# define USE_CAPTURESTACKBACKTRACE //faster than StackWalk64, but only +// works on XP or newer and only with Frame Pointer Omission optimization +// disabled(/Oy-) for every function it traces through +#endif + +#if defined(_M_IX86) || defined(_M_X64) +#define USE_STACKWALK64 +#if defined(_M_IX86) +#define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_I386 +#else +#define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_AMD64 +#endif +#endif + +typedef DWORD(WINAPI *PFN_SymGetOptions)(VOID); +typedef DWORD(WINAPI *PFN_SymSetOptions)(IN DWORD SymOptions); +typedef BOOL(WINAPI *PFN_SymSetSearchPath)(IN HANDLE hProcess, + IN PSTR SearchPath); +typedef BOOL(WINAPI *PFN_SymInitialize)(IN HANDLE hProcess, + IN PSTR UserSearchPath, + IN BOOL fInvadeProcess); +typedef BOOL(WINAPI *PFN_SymCleanup)(IN HANDLE hProcess); +typedef BOOL(WINAPI *PFN_SymEnumerateModules64)( + IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, + IN PVOID UserContext); +typedef BOOL(WINAPI *PFN_EnumerateLoadedModules64)( + IN HANDLE hProcess, + IN PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, + IN PVOID UserContext); +typedef DWORD64(WINAPI *PFN_SymLoadModule64)(IN HANDLE hProcess, + IN HANDLE hFile, IN PSTR ImageName, + IN PSTR ModuleName, + IN DWORD64 BaseOfDll, + IN DWORD SizeOfDll); +typedef BOOL(WINAPI *PFN_SymUnloadModule64)(IN HANDLE hProcess, + IN DWORD64 BaseOfDll); +typedef BOOL(WINAPI *PFN_SymFromAddr)(IN HANDLE hProcess, IN DWORD64 Address, + OUT PDWORD64 Displacement, + IN OUT PSYMBOL_INFO Symbol); +typedef BOOL(WINAPI *PFN_SymGetLineFromAddr64)(IN HANDLE hProcess, + IN DWORD64 qwAddr, + OUT PDWORD pdwDisplacement, + OUT PIMAGEHLP_LINE64 Line64); +typedef BOOL(WINAPI *PFN_SymGetModuleInfo64)(IN HANDLE hProcess, + IN DWORD64 dwAddr, + OUT PIMAGEHLP_MODULE64 ModuleInfo); +typedef BOOL(WINAPI *PFN_StackWalk64)( + DWORD MachineType, HANDLE hProcess, HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); +typedef USHORT(WINAPI *PFN_CaptureStackBackTrace)( + IN ULONG FramesToSkip, IN ULONG FramesToCapture, OUT PVOID *BackTrace, + OUT OPTIONAL PULONG BackTraceHash); + +DWORD WINAPI SymGetOptions_DummyFn(VOID) { return 0; } + +DWORD WINAPI SymSetOptions_DummyFn(IN DWORD SymOptions) { return 0; } + +BOOL WINAPI SymSetSearchPath_DummyFn(IN HANDLE hProcess, IN PSTR SearchPath) { + return FALSE; +} + +BOOL WINAPI SymInitialize_DummyFn(IN HANDLE hProcess, IN PSTR UserSearchPath, + IN BOOL fInvadeProcess) { + return FALSE; +} + +BOOL WINAPI SymCleanup_DummyFn(IN HANDLE hProcess) { return TRUE; } + +BOOL WINAPI SymEnumerateModules64_DummyFn( + IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, + IN PVOID UserContext) { + return FALSE; +} + +BOOL WINAPI EnumerateLoadedModules64_DummyFn(IN HANDLE hProcess, + IN PENUMLOADED_MODULES_CALLBACK64 + EnumLoadedModulesCallback, + IN PVOID UserContext) { + return FALSE; +} + +DWORD64 WINAPI SymLoadModule64_DummyFn(IN HANDLE hProcess, IN HANDLE hFile, + IN PSTR ImageName, IN PSTR ModuleName, + IN DWORD64 BaseOfDll, + IN DWORD SizeOfDll) { + return 0; +} + +BOOL WINAPI SymUnloadModule64_DummyFn(IN HANDLE hProcess, + IN DWORD64 BaseOfDll) { + return FALSE; +} + +BOOL WINAPI SymFromAddr_DummyFn(IN HANDLE hProcess, IN DWORD64 Address, + OUT PDWORD64 Displacement, + IN OUT PSYMBOL_INFO Symbol) { + return FALSE; +} + +BOOL WINAPI SymGetLineFromAddr64_DummyFn(IN HANDLE hProcess, IN DWORD64 qwAddr, + OUT PDWORD pdwDisplacement, + OUT PIMAGEHLP_LINE64 Line64) { + return FALSE; +} + +BOOL WINAPI SymGetModuleInfo64_DummyFn(IN HANDLE hProcess, IN DWORD64 dwAddr, + OUT PIMAGEHLP_MODULE64 ModuleInfo) { + return FALSE; +} + +BOOL WINAPI +StackWalk64_DummyFn(DWORD MachineType, HANDLE hProcess, HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress) { + return FALSE; +} + +USHORT WINAPI CaptureStackBackTrace_DummyFn(IN ULONG FramesToSkip, + IN ULONG FramesToCapture, + OUT PVOID *BackTrace, + OUT OPTIONAL PULONG BackTraceHash) { + return 0; +} + +class CHelperFunctionsLoader { + public: + CHelperFunctionsLoader(void) { + m_bIsInitialized = false; + m_bShouldReloadSymbols = false; + m_hProcess = NULL; + m_hDbgHelpDll = NULL; + m_szPDBSearchPath = NULL; + m_pSymInitialize = SymInitialize_DummyFn; + m_pSymCleanup = SymCleanup_DummyFn; + m_pSymSetOptions = SymSetOptions_DummyFn; + m_pSymGetOptions = SymGetOptions_DummyFn; + m_pSymSetSearchPath = SymSetSearchPath_DummyFn; + m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; + m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; + m_pSymLoadModule64 = SymLoadModule64_DummyFn; + m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; + m_pSymFromAddr = SymFromAddr_DummyFn; + m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; + m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; + +#if defined(USE_STACKWALK64) + m_pStackWalk64 = StackWalk64_DummyFn; +#endif + +#if defined(USE_CAPTURESTACKBACKTRACE) + m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; + m_hNTDllDll = NULL; +#endif + } + + ~CHelperFunctionsLoader(void) { + m_pSymCleanup(m_hProcess); + + if (m_hDbgHelpDll != NULL) ::FreeLibrary(m_hDbgHelpDll); + +#if defined(USE_CAPTURESTACKBACKTRACE) + if (m_hNTDllDll != NULL) ::FreeLibrary(m_hNTDllDll); +#endif + + if (m_szPDBSearchPath != NULL) delete[] m_szPDBSearchPath; + } + + static BOOL CALLBACK UnloadSymbolsCallback(PSTR ModuleName, DWORD64 BaseOfDll, + PVOID UserContext) { + const CHelperFunctionsLoader *pThis = + ((CHelperFunctionsLoader *)UserContext); + pThis->m_pSymUnloadModule64(pThis->m_hProcess, BaseOfDll); + return TRUE; + } + +#if _MSC_VER >= 1600 + static BOOL CALLBACK LoadSymbolsCallback(PCSTR ModuleName, DWORD64 ModuleBase, + ULONG ModuleSize, PVOID UserContext) +#else + static BOOL CALLBACK LoadSymbolsCallback(PSTR ModuleName, DWORD64 ModuleBase, + ULONG ModuleSize, PVOID UserContext) +#endif + { + const CHelperFunctionsLoader *pThis = + ((CHelperFunctionsLoader *)UserContext); + // SymLoadModule64( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, + // IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); + pThis->m_pSymLoadModule64(pThis->m_hProcess, NULL, (PSTR)ModuleName, + (PSTR)ModuleName, ModuleBase, ModuleSize); + return TRUE; + } + + void TryLoadingNewSymbols(void) { + AUTO_LOCK_FM(m_Mutex); + + if (m_bIsInitialized) { + // m_pSymEnumerateModules64( m_hProcess, UnloadSymbolsCallback, this ); + // //unloaded modules we've already loaded + m_pEnumerateLoadedModules64(m_hProcess, LoadSymbolsCallback, + this); // load everything + m_bShouldReloadSymbols = false; + } + } + + void SetStackTranslationSymbolSearchPath( + const char *szSemicolonSeparatedList) { + AUTO_LOCK_FM(m_Mutex); + + if (m_szPDBSearchPath != NULL) delete[] m_szPDBSearchPath; + + if (szSemicolonSeparatedList == NULL) { + m_szPDBSearchPath = NULL; + return; + } + + int iLength = (int)strlen(szSemicolonSeparatedList) + 1; + char *pNewPath = new char[iLength]; + memcpy(pNewPath, szSemicolonSeparatedList, iLength); + m_szPDBSearchPath = pNewPath; + + // re-init search paths. Or if we haven't yet loaded dbghelp.dll, this will + // go to the dummy function and do nothing + m_pSymSetSearchPath(m_hProcess, m_szPDBSearchPath); + // TryLoadingNewSymbols(); + m_bShouldReloadSymbols = true; + } + + bool GetSymbolNameFromAddress(const void *pAddress, tchar *pSymbolNameOut, + int iMaxSymbolNameLength, + uint64 *pDisplacementOut) { + if (pAddress == NULL) return false; + + AUTO_LOCK_FM(m_Mutex); + + alignas(SYMBOL_INFO) unsigned char + genericbuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + + ((PSYMBOL_INFO)genericbuffer)->SizeOfStruct = sizeof(SYMBOL_INFO); + ((PSYMBOL_INFO)genericbuffer)->MaxNameLen = MAX_SYM_NAME; + + DWORD64 dwDisplacement; + if (m_pSymFromAddr(m_hProcess, (DWORD64)pAddress, &dwDisplacement, + (PSYMBOL_INFO)genericbuffer)) { + strncpy(pSymbolNameOut, ((PSYMBOL_INFO)genericbuffer)->Name, + iMaxSymbolNameLength); + if (pDisplacementOut != NULL) *pDisplacementOut = dwDisplacement; + return true; + } + + return false; + } + + bool GetFileAndLineFromAddress(const void *pAddress, tchar *pFileNameOut, + int iMaxFileNameLength, uint32 &iLineNumberOut, + uint32 *pDisplacementOut) { + if (pAddress == NULL) return false; + + AUTO_LOCK_FM(m_Mutex); + + tchar szBuffer[1024]; + szBuffer[0] = _T('\0'); + + IMAGEHLP_LINE64 imageHelpLine64; + imageHelpLine64.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + imageHelpLine64.FileName = szBuffer; + + DWORD dwDisplacement; + if (m_pSymGetLineFromAddr64(m_hProcess, (DWORD64)pAddress, &dwDisplacement, + &imageHelpLine64)) { + strncpy(pFileNameOut, imageHelpLine64.FileName, iMaxFileNameLength); + iLineNumberOut = imageHelpLine64.LineNumber; + + if (pDisplacementOut != NULL) *pDisplacementOut = dwDisplacement; + + return true; + } + + return false; + } + + bool GetModuleNameFromAddress(const void *pAddress, tchar *pModuleNameOut, + int iMaxModuleNameLength) { + AUTO_LOCK_FM(m_Mutex); + IMAGEHLP_MODULE64 moduleInfo; + + moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); + + if (m_pSymGetModuleInfo64(m_hProcess, (DWORD64)pAddress, &moduleInfo)) { + strncpy(pModuleNameOut, moduleInfo.ModuleName, iMaxModuleNameLength); + return true; + } + + return false; + } + + // only returns false if we ran out of buffer space. + bool TranslatePointer(const void *const pAddress, tchar *pTranslationOut, + int iTranslationBufferLength, + TranslateStackInfo_StyleFlags_t style) { + // AUTO_LOCK( m_Mutex ); + + if (pTranslationOut == NULL) return false; + + if (iTranslationBufferLength <= 0) return false; + + // sample desired output + // valid translation - + // "tier0.dll!CHelperFunctionsLoader::TranslatePointer + // - u:\Dev\L4D\src\tier0\stacktools.cpp(162) + 4 bytes" fallback + // translation - "tier0.dll!0x01234567" + + tchar *pWrite = pTranslationOut; + *pWrite = '\0'; + int iLength; + + if (style & TSISTYLEFLAG_MODULENAME) { + if (!this->GetModuleNameFromAddress(pAddress, pWrite, + iTranslationBufferLength)) + strncpy(pWrite, "unknown_module", iTranslationBufferLength); + + iLength = (int)strlen(pWrite); + pWrite += iLength; + iTranslationBufferLength -= iLength; + + if (iTranslationBufferLength < 2) return false; // need more buffer + + if (style & TSISTYLEFLAG_SYMBOLNAME) { + *pWrite = '!'; + ++pWrite; + --iTranslationBufferLength; + *pWrite = '\0'; + } + } + + // use symbol name to test if the rest is going to work. So grab it whether + // they want it or not + if (!this->GetSymbolNameFromAddress(pAddress, pWrite, + iTranslationBufferLength, NULL)) { + int nBytesWritten = + _snprintf(pWrite, iTranslationBufferLength, "0x%p", pAddress); + if (nBytesWritten < 0) { + *pWrite = '\0'; // if we can't write all of the line/lineandoffset, + // don't write any at all + return false; + } + return true; + } else if (style & TSISTYLEFLAG_SYMBOLNAME) { + iLength = (int)strlen(pWrite); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } else { + *pWrite = '\0'; // symbol name lookup worked, but unwanted, discard + } + + if (style & (TSISTYLEFLAG_FULLPATH | TSISTYLEFLAG_SHORTPATH | + TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET)) { + if (pWrite != + pTranslationOut) // if we've written anything yet, separate the + // printed data from the file name and line + { + if (iTranslationBufferLength < 6) return false; // need more buffer + + pWrite[0] = ' '; // append " - " + pWrite[1] = '-'; + pWrite[2] = ' '; + pWrite[3] = '\0'; + pWrite += 3; + iTranslationBufferLength -= 3; + } + + uint32 iLine; + uint32 iDisplacement; + char szFileName[MAX_PATH]; + if (this->GetFileAndLineFromAddress(pAddress, szFileName, MAX_PATH, iLine, + &iDisplacement)) { + if (style & TSISTYLEFLAG_FULLPATH) { + iLength = (int)strlen(szFileName); + if (iTranslationBufferLength < iLength + 1) return false; + + memcpy(pWrite, szFileName, iLength + 1); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } else if (style & TSISTYLEFLAG_SHORTPATH) { + // shorten the path and copy + iLength = (int)strlen(szFileName); + char *pShortened = szFileName + iLength; + int iSlashesAllowed = 3; + while (pShortened > szFileName) { + if ((*pShortened == '\\') || (*pShortened == '/')) { + --iSlashesAllowed; + if (iSlashesAllowed == 0) break; + } + + --pShortened; + } + + iLength = (int)strlen(pShortened); + if (iTranslationBufferLength < iLength + 1) { + // Remove the " - " that we can't append to + pWrite -= 3; + iTranslationBufferLength += 3; + *pWrite = '\0'; + return false; + } + + memcpy(pWrite, szFileName, iLength + 1); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } + + if (style & (TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET)) { + int nBytesWritten = _snprintf( + pWrite, iTranslationBufferLength, + ((style & TSISTYLEFLAG_LINEANDOFFSET) && (iDisplacement != 0)) + ? "(%d) + %d bytes" + : "(%d)", + iLine, iDisplacement); + if (nBytesWritten < 0) { + *pWrite = '\0'; // if we can't write all of the line/lineandoffset, + // don't write any at all + return false; + } + + pWrite += nBytesWritten; + iTranslationBufferLength -= nBytesWritten; + } + } else { + // Remove the " - " that we didn't append to + pWrite -= 3; + iTranslationBufferLength += 3; + *pWrite = '\0'; + } + } + + return true; + } + + // about to actually use the functions, load if necessary + void EnsureReady(void) { + if (m_bIsInitialized) { + if (m_bShouldReloadSymbols) TryLoadingNewSymbols(); + + return; + } + + AUTO_LOCK_FM(m_Mutex); + + // Only enabled for P4 and Steam Beta builds + if ((CommandLine()->FindParm("-steam") != 0) && // is steam + (CommandLine()->FindParm("-internalbuild") == 0)) // is not steam beta + { + // disable the toolset by falsifying initialized state + m_bIsInitialized = true; + return; + } + + m_hProcess = GetCurrentProcess(); + if (m_hProcess == NULL) return; + + m_bIsInitialized = true; + + // get the function pointer directly so that we don't have to include the + // .lib, and that we can easily change it to using our own dll when this + // code is used on win98/ME/2K machines + m_hDbgHelpDll = ::LoadLibrary("DbgHelp.dll"); + if (!m_hDbgHelpDll) { + // it's possible it's just way too early to initialize (as shown with + // attempts at using these tools in the memory allocator) + if (m_szPDBSearchPath == + NULL) // not a rock solid check, but pretty good compromise between + // endless failing initialization and general failure due to + // trying too early + m_bIsInitialized = false; + + return; + } + + m_pSymInitialize = + (PFN_SymInitialize)::GetProcAddress(m_hDbgHelpDll, "SymInitialize"); + if (m_pSymInitialize == NULL) { + // very bad + ::FreeLibrary(m_hDbgHelpDll); + m_hDbgHelpDll = NULL; + m_pSymInitialize = SymInitialize_DummyFn; + return; + } + + m_pSymCleanup = + (PFN_SymCleanup)::GetProcAddress(m_hDbgHelpDll, "SymCleanup"); + if (m_pSymCleanup == NULL) m_pSymCleanup = SymCleanup_DummyFn; + + m_pSymGetOptions = + (PFN_SymGetOptions)::GetProcAddress(m_hDbgHelpDll, "SymGetOptions"); + if (m_pSymGetOptions == NULL) m_pSymGetOptions = SymGetOptions_DummyFn; + + m_pSymSetOptions = + (PFN_SymSetOptions)::GetProcAddress(m_hDbgHelpDll, "SymSetOptions"); + if (m_pSymSetOptions == NULL) m_pSymSetOptions = SymSetOptions_DummyFn; + + m_pSymSetSearchPath = (PFN_SymSetSearchPath)::GetProcAddress( + m_hDbgHelpDll, "SymSetSearchPath"); + if (m_pSymSetSearchPath == NULL) + m_pSymSetSearchPath = SymSetSearchPath_DummyFn; + + m_pSymEnumerateModules64 = (PFN_SymEnumerateModules64)::GetProcAddress( + m_hDbgHelpDll, "SymEnumerateModules64"); + if (m_pSymEnumerateModules64 == NULL) + m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; + + m_pEnumerateLoadedModules64 = + (PFN_EnumerateLoadedModules64)::GetProcAddress( + m_hDbgHelpDll, "EnumerateLoadedModules64"); + if (m_pEnumerateLoadedModules64 == NULL) + m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; + + m_pSymLoadModule64 = + (PFN_SymLoadModule64)::GetProcAddress(m_hDbgHelpDll, "SymLoadModule64"); + if (m_pSymLoadModule64 == NULL) + m_pSymLoadModule64 = SymLoadModule64_DummyFn; + + m_pSymUnloadModule64 = (PFN_SymUnloadModule64)::GetProcAddress( + m_hDbgHelpDll, "SymUnloadModule64"); + if (m_pSymUnloadModule64 == NULL) + m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; + + m_pSymFromAddr = + (PFN_SymFromAddr)::GetProcAddress(m_hDbgHelpDll, "SymFromAddr"); + if (m_pSymFromAddr == NULL) m_pSymFromAddr = SymFromAddr_DummyFn; + + m_pSymGetLineFromAddr64 = (PFN_SymGetLineFromAddr64)::GetProcAddress( + m_hDbgHelpDll, "SymGetLineFromAddr64"); + if (m_pSymGetLineFromAddr64 == NULL) + m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; + + m_pSymGetModuleInfo64 = (PFN_SymGetModuleInfo64)::GetProcAddress( + m_hDbgHelpDll, "SymGetModuleInfo64"); + if (m_pSymGetModuleInfo64 == NULL) + m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; + +#if defined(USE_STACKWALK64) + m_pStackWalk64 = + (PFN_StackWalk64)::GetProcAddress(m_hDbgHelpDll, "StackWalk64"); + if (m_pStackWalk64 == NULL) m_pStackWalk64 = StackWalk64_DummyFn; +#endif + +#if defined(USE_CAPTURESTACKBACKTRACE) + m_hNTDllDll = ::LoadLibrary("ntdll.dll"); + + m_pCaptureStackBackTrace = (PFN_CaptureStackBackTrace)::GetProcAddress( + m_hNTDllDll, "RtlCaptureStackBackTrace"); + if (m_pCaptureStackBackTrace == NULL) + m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; +#endif + + m_pSymSetOptions( + m_pSymGetOptions() | SYMOPT_DEFERRED_LOADS | // load on demand + SYMOPT_EXACT_SYMBOLS | // don't load the wrong file + SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_NO_PROMPTS | // don't prompt ever + SYMOPT_LOAD_LINES); // load line info + + m_pSymInitialize(m_hProcess, m_szPDBSearchPath, FALSE); + TryLoadingNewSymbols(); + } + + bool m_bIsInitialized; + bool m_bShouldReloadSymbols; + HANDLE m_hProcess; + HMODULE m_hDbgHelpDll; + char *m_szPDBSearchPath; + CThreadFastMutex m_Mutex; // DbgHelp functions are all single threaded. + + PFN_SymInitialize m_pSymInitialize; + PFN_SymCleanup m_pSymCleanup; + PFN_SymGetOptions m_pSymGetOptions; + PFN_SymSetOptions m_pSymSetOptions; + PFN_SymSetSearchPath m_pSymSetSearchPath; + PFN_SymEnumerateModules64 m_pSymEnumerateModules64; + PFN_EnumerateLoadedModules64 m_pEnumerateLoadedModules64; + PFN_SymLoadModule64 m_pSymLoadModule64; + PFN_SymUnloadModule64 m_pSymUnloadModule64; + + PFN_SymFromAddr m_pSymFromAddr; + PFN_SymGetLineFromAddr64 m_pSymGetLineFromAddr64; + PFN_SymGetModuleInfo64 m_pSymGetModuleInfo64; + +#if defined(USE_STACKWALK64) + PFN_StackWalk64 m_pStackWalk64; +#endif + +#if defined(USE_CAPTURESTACKBACKTRACE) + HMODULE m_hNTDllDll; + PFN_CaptureStackBackTrace m_pCaptureStackBackTrace; +#endif +}; +static CHelperFunctionsLoader s_HelperFunctions; + +#if defined(USE_STACKWALK64) // most reliable method thanks to boatloads of + // windows helper functions. Also the slowest. +int CrawlStack_StackWalk64(CONTEXT *pExceptionContext, + void **pReturnAddressesOut, int iArrayCount, + int iSkipCount) { + s_HelperFunctions.EnsureReady(); + + AUTO_LOCK_FM(s_HelperFunctions.m_Mutex); + + CONTEXT currentContext; + memcpy(¤tContext, pExceptionContext, sizeof(CONTEXT)); + + STACKFRAME64 sfFrame = {0}; // memset(&sfFrame, 0x0, sizeof(sfFrame)); + sfFrame.AddrPC.Mode = sfFrame.AddrFrame.Mode = AddrModeFlat; +#ifdef _WIN64 + sfFrame.AddrPC.Offset = currentContext.Rip; + sfFrame.AddrFrame.Offset = currentContext.Rbp; +#elif defined(_WIN32) + sfFrame.AddrPC.Offset = currentContext.Eip; + sfFrame.AddrFrame.Offset = currentContext.Ebp; +#else +#error Unknown CPU arch. +#endif + + HANDLE hThread = GetCurrentThread(); + + int i; + for (i = 0; i != iSkipCount; ++i) // skip entries that the requesting + // function thinks are uninformative + { + if (!s_HelperFunctions.m_pStackWalk64( + STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, + &sfFrame, ¤tContext, NULL, NULL, NULL, NULL) || + (sfFrame.AddrFrame.Offset == 0)) { + return 0; + } + } + + for (i = 0; i != iArrayCount; ++i) { + if (!s_HelperFunctions.m_pStackWalk64( + STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, + &sfFrame, ¤tContext, NULL, NULL, NULL, NULL) || + (sfFrame.AddrFrame.Offset == 0)) { + break; + } + pReturnAddressesOut[i] = (void *)sfFrame.AddrPC.Offset; + } + + return i; +} + +void GetCallStackReturnAddresses_Exception( + void **CallStackReturnAddresses, int *pRetCount, int iSkipCount, + _EXCEPTION_POINTERS *pExceptionInfo) { + int iCount = CrawlStack_StackWalk64( + pExceptionInfo->ContextRecord, CallStackReturnAddresses, *pRetCount, + iSkipCount + 1); // skipping RaiseException() + *pRetCount = iCount; +} +#endif //#if defined( USE_STACKWALK64 ) + +int GetCallStack(void **pReturnAddressesOut, int iArrayCount, int iSkipCount) { + s_HelperFunctions.EnsureReady(); + + ++iSkipCount; // skip this function + +#if defined(USE_CAPTURESTACKBACKTRACE) + if (s_HelperFunctions.m_pCaptureStackBackTrace != + CaptureStackBackTrace_DummyFn) { + // docs state a total limit of 63 back traces between skipped and stored + int iRetVal = s_HelperFunctions.m_pCaptureStackBackTrace( + iSkipCount, MIN(iArrayCount, 63 - iSkipCount), pReturnAddressesOut, + NULL); + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, iRetVal); + } +#endif +#if defined(USE_STACKWALK64) + if (s_HelperFunctions.m_pStackWalk64 != StackWalk64_DummyFn) { + // array count becomes both input and output with exception handler version + int iInOutArrayCount = iArrayCount; + __try { + ::RaiseException(0, EXCEPTION_NONCONTINUABLE, 0, NULL); + } __except (GetCallStackReturnAddresses_Exception( + pReturnAddressesOut, &iInOutArrayCount, iSkipCount, + GetExceptionInformation()), + EXCEPTION_EXECUTE_HANDLER) { + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, + iInOutArrayCount); + } + } +#endif + + return GetCallStack_Fast(pReturnAddressesOut, iArrayCount, iSkipCount); +} + +void SetStackTranslationSymbolSearchPath(const char *szSemicolonSeparatedList) { + s_HelperFunctions.SetStackTranslationSymbolSearchPath( + szSemicolonSeparatedList); +} + +void StackToolsNotify_LoadedLibrary(const char *szLibName) { + s_HelperFunctions.m_bShouldReloadSymbols = true; +} + +int TranslateStackInfo(const void *const *pCallStack, int iCallStackCount, + tchar *szOutput, int iOutBufferSize, + const tchar *szEntrySeparator, + TranslateStackInfo_StyleFlags_t style) { + s_HelperFunctions.EnsureReady(); + tchar *szStartOutput = szOutput; + + if (szEntrySeparator == NULL) szEntrySeparator = _T(""); + + int iSeparatorSize = (int)strlen(szEntrySeparator); + + for (int i = 0; i < iCallStackCount; ++i) { + if (!s_HelperFunctions.TranslatePointer(pCallStack[i], szOutput, + iOutBufferSize, style)) { + return i; + } + + int iLength = (int)strlen(szOutput); + szOutput += iLength; + iOutBufferSize -= iLength; + + if (iOutBufferSize > iSeparatorSize) { + memcpy(szOutput, szEntrySeparator, iSeparatorSize * sizeof(tchar)); + szOutput += iSeparatorSize; + iOutBufferSize -= iSeparatorSize; + } + *szOutput = '\0'; + } + + szOutput -= iSeparatorSize; + if (szOutput >= szStartOutput) *szOutput = '\0'; + + return iCallStackCount; +} + +void PreloadStackInformation(void *const *pAddresses, int iAddressCount) { + // nop on anything but 360 +} + +bool GetFileAndLineFromAddress(const void *pAddress, tchar *pFileNameOut, + int iMaxFileNameLength, uint32 &iLineNumberOut, + uint32 *pDisplacementOut) { + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetFileAndLineFromAddress( + pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, + pDisplacementOut); +} + +bool GetSymbolNameFromAddress(const void *pAddress, tchar *pSymbolNameOut, + int iMaxSymbolNameLength, + uint64 *pDisplacementOut) { + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetSymbolNameFromAddress( + pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut); +} + +bool GetModuleNameFromAddress(const void *pAddress, tchar *pModuleNameOut, + int iMaxModuleNameLength) { + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetModuleNameFromAddress(pAddress, pModuleNameOut, + iMaxModuleNameLength); +} + +#else //#if defined( WIN32 ) && !defined( _X360 ) + +//=============================================================================================================== +// X360 version of the toolset +//=============================================================================================================== + +class C360StackTranslationHelper { + public: + C360StackTranslationHelper(void) { m_bInitialized = true; } + + ~C360StackTranslationHelper(void) { + StringSet_t::const_iterator iter; + + // module names + { + iter = m_ModuleNameSet.begin(); + while (iter != m_ModuleNameSet.end()) { + char *pModuleName = (char *)(*iter); + delete[] pModuleName; + iter++; + } + m_ModuleNameSet.clear(); + } + + // file names + { + iter = m_FileNameSet.begin(); + while (iter != m_FileNameSet.end()) { + char *pFileName = (char *)(*iter); + delete[] pFileName; + iter++; + } + m_FileNameSet.clear(); + } + + // symbol names + { + iter = m_SymbolNameSet.begin(); + while (iter != m_SymbolNameSet.end()) { + char *pSymbolName = (char *)(*iter); + delete[] pSymbolName; + iter++; + } + m_SymbolNameSet.clear(); + } + + m_bInitialized = false; + } + + private: + struct StackAddressInfo_t; + + public: + inline StackAddressInfo_t *CreateEntry(const FullStackInfo_t &info) { + std::pair retval = m_AddressInfoMap.insert( + AddressInfoMapEntry_t(info.pAddress, StackAddressInfo_t())); + if (retval.first->second.szModule != NULL) + return &retval.first->second; // already initialized + + retval.first->second.iLine = info.iLine; + + // share strings + + // module + { + const char *pModuleName; + StringSet_t::const_iterator iter = + m_ModuleNameSet.find(info.szModuleName); + if (iter == m_ModuleNameSet.end()) { + int nLen = strlen(info.szModuleName) + 1; + pModuleName = new char[nLen]; + memcpy((char *)pModuleName, info.szModuleName, nLen); + m_ModuleNameSet.insert(pModuleName); + } else { + pModuleName = (char *)(*iter); + } + + retval.first->second.szModule = pModuleName; + } + + // file + { + const char *pFileName; + StringSet_t::const_iterator iter = m_FileNameSet.find(info.szFileName); + if (iter == m_FileNameSet.end()) { + int nLen = strlen(info.szFileName) + 1; + pFileName = new char[nLen]; + memcpy((char *)pFileName, info.szFileName, nLen); + m_FileNameSet.insert(pFileName); + } else { + pFileName = (char *)(*iter); + } + + retval.first->second.szFileName = pFileName; + } + + // symbol + { + const char *pSymbolName; + StringSet_t::const_iterator iter = m_SymbolNameSet.find(info.szSymbol); + if (iter == m_SymbolNameSet.end()) { + int nLen = strlen(info.szSymbol) + 1; + pSymbolName = new char[nLen]; + memcpy((char *)pSymbolName, info.szSymbol, nLen); + m_SymbolNameSet.insert(pSymbolName); + } else { + pSymbolName = (char *)(*iter); + } + + retval.first->second.szSymbol = pSymbolName; + } + + return &retval.first->second; + } + + inline StackAddressInfo_t *FindInfoEntry(const void *pAddress) { + AddressInfoMapIter_t Iter = m_AddressInfoMap.find(pAddress); + if (Iter != m_AddressInfoMap.end()) return &Iter->second; + + return NULL; + } + + inline int RetrieveStackInfo(const void *const *pAddresses, + FullStackInfo_t *pReturnedStructs, + int iAddressCount) { + int ReturnedTranslatedCount = -1; + // construct the message + // Header Finished Count(out) Input + // Count Input Array Returned data write address + int iMessageSize = 2 + sizeof(int *) + sizeof(uint32) + + (sizeof(void *) * iAddressCount) + + sizeof(FullStackInfo_t *); + uint8 *pMessage = (uint8 *)stackalloc(iMessageSize); + uint8 *pMessageWrite = pMessage; + pMessageWrite[0] = + XBX_DBG_BNH_STACKTRANSLATOR; // have this message handled by stack + // translator handler + pMessageWrite[1] = ST_BHC_GETTRANSLATIONINFO; + pMessageWrite += 2; + *(int **)pMessageWrite = (int *)BigDWord((DWORD)&ReturnedTranslatedCount); + pMessageWrite += sizeof(int *); + *(uint32 *)pMessageWrite = (uint32)BigDWord((DWORD)iAddressCount); + pMessageWrite += sizeof(uint32); + memcpy(pMessageWrite, pAddresses, iAddressCount * sizeof(void *)); + pMessageWrite += iAddressCount * sizeof(void *); + *(FullStackInfo_t **)pMessageWrite = + (FullStackInfo_t *)BigDWord((DWORD)pReturnedStructs); + bool bSuccess = XBX_SendBinaryData(pMessage, iMessageSize, false, 30000); + ReturnedTranslatedCount = BigDWord(ReturnedTranslatedCount); + + if (bSuccess && (ReturnedTranslatedCount > 0)) { + return ReturnedTranslatedCount; + } + return 0; + } + + inline StackAddressInfo_t *CreateEntry(const void *pAddress) { + // ask VXConsole for information about the addresses we're clueless about + + FullStackInfo_t ReturnedData; + ReturnedData.pAddress = pAddress; + ReturnedData.szFileName[0] = + '\0'; // strncpy( ReturnedData.szFileName, "FileUninitialized", sizeof( + // ReturnedData.szFileName ) ); + ReturnedData.szModuleName[0] = + '\0'; // strncpy( ReturnedData.szModuleName, "ModuleUninitialized", + // sizeof( ReturnedData.szModuleName ) ); + ReturnedData.szSymbol[0] = + '\0'; // strncpy( ReturnedData.szSymbol, "SymbolUninitialized", sizeof( + // ReturnedData.szSymbol ) ); + ReturnedData.iLine = 0; + ReturnedData.iSymbolOffset = 0; + + int iTranslated = RetrieveStackInfo(&pAddress, &ReturnedData, 1); + + if (iTranslated == 1) { + // store + return CreateEntry(ReturnedData); + } + + return FindInfoEntry(pAddress); // probably won't work, but last ditch. + } + + inline StackAddressInfo_t *FindOrCreateEntry(const void *pAddress) { + StackAddressInfo_t *pReturn = FindInfoEntry(pAddress); + if (pReturn == NULL) { + pReturn = CreateEntry(pAddress); + } + + return pReturn; + } + + inline void LoadStackInformation(void *const *pAddresses, int iAddressCount) { + Assert((iAddressCount > 0) && (pAddresses != NULL)); + + int iNeedLoading = 0; + void **pNeedLoading = (void **)stackalloc( + sizeof(const void *) * + iAddressCount); // addresses we need to ask VXConsole about + + for (int i = 0; i != iAddressCount; ++i) { + if (FindInfoEntry(pAddresses[i]) == NULL) { + // need to load this address + pNeedLoading[iNeedLoading] = pAddresses[i]; + ++iNeedLoading; + } + } + + if (iNeedLoading != 0) { + // ask VXConsole for information about the addresses we're clueless about + FullStackInfo_t *pReturnedStructs = + (FullStackInfo_t *)stackalloc(sizeof(FullStackInfo_t) * iNeedLoading); + + for (int i = 0; i < iNeedLoading; ++i) { + pReturnedStructs[i].pAddress = 0; + pReturnedStructs[i].szFileName[0] = + '\0'; // strncpy( pReturnedStructs[i].szFileName, + // "FileUninitialized", sizeof( + // pReturnedStructs[i].szFileName ) ); + pReturnedStructs[i].szModuleName[0] = + '\0'; // strncpy( pReturnedStructs[i].szModuleName, + // "ModuleUninitialized", sizeof( + // pReturnedStructs[i].szModuleName ) ); + pReturnedStructs[i].szSymbol[0] = + '\0'; // strncpy( pReturnedStructs[i].szSymbol, + // "SymbolUninitialized", sizeof( + // pReturnedStructs[i].szSymbol ) ); + pReturnedStructs[i].iLine = 0; + pReturnedStructs[i].iSymbolOffset = 0; + } + + int iTranslated = + RetrieveStackInfo(pNeedLoading, pReturnedStructs, iNeedLoading); + + if (iTranslated == iNeedLoading) { + // store + for (int i = 0; i < iTranslated; ++i) { + CreateEntry(pReturnedStructs[i]); + } + } + } + } + + inline bool GetFileAndLineFromAddress(const void *pAddress, + tchar *pFileNameOut, + int iMaxFileNameLength, + uint32 &iLineNumberOut, + uint32 *pDisplacementOut) { + StackAddressInfo_t *pInfo = FindOrCreateEntry(pAddress); + if (pInfo && (pInfo->szFileName[0] != '\0')) { + strncpy(pFileNameOut, pInfo->szFileName, iMaxFileNameLength); + iLineNumberOut = pInfo->iLine; + + if (pDisplacementOut) + *pDisplacementOut = 0; // can't get line displacement on 360 + + return true; + } + + return false; + } + + inline bool GetSymbolNameFromAddress(const void *pAddress, + tchar *pSymbolNameOut, + int iMaxSymbolNameLength, + uint64 *pDisplacementOut) { + StackAddressInfo_t *pInfo = FindOrCreateEntry(pAddress); + if (pInfo && (pInfo->szSymbol[0] != '\0')) { + strncpy(pSymbolNameOut, pInfo->szSymbol, iMaxSymbolNameLength); + + if (pDisplacementOut) *pDisplacementOut = pInfo->iSymbolOffset; + + return true; + } + + return false; + } + + inline bool GetModuleNameFromAddress(const void *pAddress, + tchar *pModuleNameOut, + int iMaxModuleNameLength) { + StackAddressInfo_t *pInfo = FindOrCreateEntry(pAddress); + if (pInfo && (pInfo->szModule[0] != '\0')) { + strncpy(pModuleNameOut, pInfo->szModule, iMaxModuleNameLength); + return true; + } + + return false; + } + + CThreadFastMutex m_hMutex; + + private: + +#pragma pack(push) +#pragma pack(1) + struct StackAddressInfo_t { + StackAddressInfo_t(void) + : szModule(NULL), + szFileName(NULL), + szSymbol(NULL), + iLine(0), + iSymbolOffset(0) {} + const char *szModule; + const char *szFileName; + const char *szSymbol; + uint32 iLine; + uint32 iSymbolOffset; + }; +#pragma pack(pop) + + typedef std::map> + AddressInfoMap_t; + typedef AddressInfoMap_t::iterator AddressInfoMapIter_t; + typedef AddressInfoMap_t::value_type AddressInfoMapEntry_t; + + class CStringLess { + public: + bool operator()(const char *pszLeft, const char *pszRight) const { + return (V_tier0_stricmp(pszLeft, pszRight) < 0); + } + }; + + typedef std::set StringSet_t; + + AddressInfoMap_t m_AddressInfoMap; // TODO: retire old entries? + StringSet_t m_ModuleNameSet; + StringSet_t m_FileNameSet; + StringSet_t m_SymbolNameSet; + bool m_bInitialized; +}; +static C360StackTranslationHelper s_360StackTranslator; + +int GetCallStack(void **pReturnAddressesOut, int iArrayCount, int iSkipCount) { + ++iSkipCount; // skip this function + + // DmCaptureStackBackTrace() has no skip functionality, so we need to grab + // everything and skip within that list + void **pAllResults = + (void **)stackalloc(sizeof(void *) * (iArrayCount + iSkipCount)); + if (DmCaptureStackBackTrace(iArrayCount + iSkipCount, pAllResults) == + XBDM_NOERR) { + for (int i = 0; i != iSkipCount; ++i) { + if (*pAllResults == + NULL) // DmCaptureStackBackTrace() NULL terminates the list instead + // of telling us how many were returned + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, 0); + + ++pAllResults; // move the pointer forward so the second loop indices + // match up + } + + for (int i = 0; i != iArrayCount; ++i) { + if (pAllResults[i] == + NULL) // DmCaptureStackBackTrace() NULL terminates the list instead + // of telling us how many were returned + return AppendParentStackTrace(pReturnAddressesOut, iArrayCount, i); + + pReturnAddressesOut[i] = pAllResults[i]; + } + + return iArrayCount; // no room to append parent + } + + return GetCallStack_Fast(pReturnAddressesOut, iArrayCount, iSkipCount); +} + +void SetStackTranslationSymbolSearchPath(const char *szSemicolonSeparatedList) { + // nop on 360 +} + +void StackToolsNotify_LoadedLibrary(const char *szLibName) { + // send off the notice to VXConsole + uint8 message[2]; + message[0] = XBX_DBG_BNH_STACKTRANSLATOR; // have this message handled by + // stack translator handler + message[1] = ST_BHC_LOADEDLIBARY; // loaded a library notification + XBX_SendBinaryData(message, 2); +} + +int TranslateStackInfo(const void *const *pCallStack, int iCallStackCount, + tchar *szOutput, int iOutBufferSize, + const tchar *szEntrySeparator, + TranslateStackInfo_StyleFlags_t style) { + if (iCallStackCount == 0) { + if (iOutBufferSize > 1) *szOutput = '\0'; + + return 0; + } + + if (szEntrySeparator == NULL) szEntrySeparator = ""; + + int iSeparatorLength = strlen(szEntrySeparator) + 1; + int iDataSize = (sizeof(void *) * iCallStackCount) + + (iSeparatorLength * sizeof(tchar)) + 1; // 1 for style flags + + // 360 is incapable of translation on it's own. Encode the stack for + // translation in VXConsole Encoded section is as such ":CSDECODE[encoded + // binary]" + int iEncodedSize = -EncodeBinaryToString(NULL, iDataSize, NULL, + 0); // get needed buffer size + const tchar cControlPrefix[] = XBX_CALLSTACKDECODEPREFIX; + const size_t cControlLength = (sizeof(cControlPrefix) / sizeof(tchar)) - + 1; //-1 to remove null terminator + + if (iOutBufferSize > + (iEncodedSize + (int)cControlLength + 2)) //+2 for ']' and null term + { + static_assert(TSISTYLEFLAG_LAST < + (1 << 8)); // need to update the encoder/decoder to use + // more than a byte for style flags + + uint8 *pData = (uint8 *)stackalloc(iDataSize); + pData[0] = (uint8)style; + memcpy(pData + 1, szEntrySeparator, iSeparatorLength * sizeof(tchar)); + memcpy(pData + 1 + (iSeparatorLength * sizeof(tchar)), pCallStack, + iCallStackCount * sizeof(void *)); + + memcpy(szOutput, XBX_CALLSTACKDECODEPREFIX, cControlLength * sizeof(tchar)); + int iLength = + cControlLength + + EncodeBinaryToString(pData, iDataSize, &szOutput[cControlLength], + (iOutBufferSize - cControlLength)); + + szOutput[iLength] = ']'; + szOutput[iLength + 1] = '\0'; + } + + return iCallStackCount; +} + +void PreloadStackInformation(void *const *pAddresses, int iAddressCount) { + AUTO_LOCK_FM(s_360StackTranslator.m_hMutex); + s_360StackTranslator.LoadStackInformation(pAddresses, iAddressCount); +} + +bool GetFileAndLineFromAddress(const void *pAddress, tchar *pFileNameOut, + int iMaxFileNameLength, uint32 &iLineNumberOut, + uint32 *pDisplacementOut) { + AUTO_LOCK_FM(s_360StackTranslator.m_hMutex); + return s_360StackTranslator.GetFileAndLineFromAddress( + pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, + pDisplacementOut); +} + +bool GetSymbolNameFromAddress(const void *pAddress, tchar *pSymbolNameOut, + int iMaxSymbolNameLength, + uint64 *pDisplacementOut) { + AUTO_LOCK_FM(s_360StackTranslator.m_hMutex); + return s_360StackTranslator.GetSymbolNameFromAddress( + pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut); +} + +bool GetModuleNameFromAddress(const void *pAddress, tchar *pModuleNameOut, + int iMaxModuleNameLength) { + AUTO_LOCK_FM(s_360StackTranslator.m_hMutex); + return s_360StackTranslator.GetModuleNameFromAddress(pAddress, pModuleNameOut, + iMaxModuleNameLength); +} + +#endif //#else //#if defined( WIN32 ) && !defined( _X360 ) + +#endif //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +CCallStackStorage::CCallStackStorage(FN_GetCallStack GetStackFunction, + uint32 iSkipCalls) { + iValidEntries = GetStackFunction(pStack, ARRAYSIZE(pStack), iSkipCalls + 1); +} + +CStackTop_CopyParentStack::CStackTop_CopyParentStack( + void *const *pParentStackTrace, int iParentStackTraceLength) { +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) + // miniature version of GetCallStack_Fast() +#if (defined(TIER0_FPO_DISABLED) || defined(_DEBUG)) && \ + (defined(WIN32) && !defined(_X360) && !defined(_WIN64)) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + pStackCrawlEBP = *(void **)pStackCrawlEBP; + m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); + m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); +#else + m_pReplaceAddress = NULL; + m_pStackBase = this; +#endif + + m_pParentStackTrace = NULL; + + if ((pParentStackTrace != NULL) && (iParentStackTraceLength > 0)) { + while ((iParentStackTraceLength > 0) && + (pParentStackTrace[iParentStackTraceLength - 1] == NULL)) { + --iParentStackTraceLength; + } + + if (iParentStackTraceLength > 0) { + m_pParentStackTrace = new void *[iParentStackTraceLength]; + memcpy((void **)m_pParentStackTrace, pParentStackTrace, + sizeof(void *) * iParentStackTraceLength); + } + } + + m_iParentStackTraceLength = iParentStackTraceLength; + + m_pPrevTop = g_StackTop; + g_StackTop = this; + Assert((CStackTop_Base *)g_StackTop == this); +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +} + +CStackTop_CopyParentStack::~CStackTop_CopyParentStack(void) { +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) + Assert((CStackTop_Base *)g_StackTop == this); + g_StackTop = m_pPrevTop; + + if (m_pParentStackTrace != NULL) { + delete[] m_pParentStackTrace; + } +#endif +} + +CStackTop_ReferenceParentStack::CStackTop_ReferenceParentStack( + void *const *pParentStackTrace, int iParentStackTraceLength) { +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) + // miniature version of GetCallStack_Fast() +#if (defined(TIER0_FPO_DISABLED) || defined(_DEBUG)) && \ + (defined(WIN32) && !defined(_X360) && !defined(_WIN64)) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + pStackCrawlEBP = *(void **)pStackCrawlEBP; + m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); + m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); +#else + m_pReplaceAddress = NULL; + m_pStackBase = this; +#endif + + m_pParentStackTrace = pParentStackTrace; + + if ((pParentStackTrace != NULL) && (iParentStackTraceLength > 0)) { + while ((iParentStackTraceLength > 0) && + (pParentStackTrace[iParentStackTraceLength - 1] == NULL)) { + --iParentStackTraceLength; + } + } + + m_iParentStackTraceLength = iParentStackTraceLength; + + m_pPrevTop = g_StackTop; + g_StackTop = this; + Assert((CStackTop_Base *)g_StackTop == this); +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +} + +CStackTop_ReferenceParentStack::~CStackTop_ReferenceParentStack(void) { +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) + Assert((CStackTop_Base *)g_StackTop == this); + g_StackTop = m_pPrevTop; + + ReleaseParentStackReferences(); +#endif +} + +void CStackTop_ReferenceParentStack::ReleaseParentStackReferences(void) { +#if defined(ENABLE_RUNTIME_STACK_TRANSLATION) + m_pParentStackTrace = NULL; + m_iParentStackTraceLength = 0; +#endif +} + +// Encodes data so that every byte's most significant bit is a 1. Ensuring no +// null terminators. This puts the encoded data in the 128-255 value range. +// Leaving all standard ascii characters for control. Returns string length (not +// including the written null terminator as is standard). Or if the buffer is +// too small. Returns negative of necessary buffer size (including room needed +// for null terminator) +int EncodeBinaryToString(const void *pToEncode, int iDataLength, + char *pEncodeOut, int iEncodeBufferSize) { + int iEncodedSize = iDataLength; + iEncodedSize += + (iEncodedSize + 6) / 7; // Have 1 control byte for every 7 actual bytes + iEncodedSize += sizeof(uint32) + 1; // data size at the beginning of the blob + // and null terminator at the end + + if ((iEncodedSize > iEncodeBufferSize) || (pEncodeOut == NULL) || + (pToEncode == NULL)) + return -iEncodedSize; // not enough room + + uint8 *pEncodeWrite = (uint8 *)pEncodeOut; + + // first encode the data size. Encodes lowest 28 bits and discards the high 4 + pEncodeWrite[0] = ((iDataLength >> 21) & 0xFF) | 0x80; + pEncodeWrite[1] = ((iDataLength >> 14) & 0xFF) | 0x80; + pEncodeWrite[2] = ((iDataLength >> 7) & 0xFF) | 0x80; + pEncodeWrite[3] = ((iDataLength >> 0) & 0xFF) | 0x80; + pEncodeWrite += 4; + + const uint8 *pEncodeRead = (const uint8 *)pToEncode; + const uint8 *pEncodeStop = pEncodeRead + iDataLength; + uint8 *pEncodeWriteLastControlByte = pEncodeWrite; + int iControl = 0; + + // Encode the data + while (pEncodeRead < pEncodeStop) { + if (iControl == 0) { + pEncodeWriteLastControlByte = pEncodeWrite; + *pEncodeWriteLastControlByte = 0x80; + } else { + *pEncodeWrite = + *pEncodeRead | 0x80; // encoded data always has the MSB bit set + // (cheap avoidance of null terminators) + *pEncodeWriteLastControlByte |= + (((*pEncodeRead) & 0x80) ^ 0x80) >> + iControl; // We use the control byte to XOR the MSB back to original + // values on decode + ++pEncodeRead; + } + + ++pEncodeWrite; + ++iControl; + iControl &= 7; // 8->0 + } + *pEncodeWrite = '\0'; + + return iEncodedSize - 1; +} + +// Decodes a string produced by EncodeBinaryToString(). Safe to decode in place +// if you don't mind trashing your string, binary byte count always less than +// string byte count. Returns: +// >= 0 is the decoded data size +// INT_MIN (most negative value possible) indicates an improperly formatted +// string (not our data) all other negative values are the negative of +// how much dest buffer size is necessary. +int DecodeBinaryFromString(const char *pString, void *pDestBuffer, + int iDestBufferSize, char **ppParseFinishOut) { + const uint8 *pDecodeRead = (const uint8 *)pString; + + if ((pDecodeRead[0] < 0x80) || (pDecodeRead[1] < 0x80) || + (pDecodeRead[2] < 0x80) || (pDecodeRead[3] < 0x80)) { + if (ppParseFinishOut != NULL) *ppParseFinishOut = (char *)pString; + + return INT_MIN; // Don't know what the string is, but it's not our format + } + + int iDecodedSize = 0; + iDecodedSize |= (pDecodeRead[0] & 0x7F) << 21; + iDecodedSize |= (pDecodeRead[1] & 0x7F) << 14; + iDecodedSize |= (pDecodeRead[2] & 0x7F) << 7; + iDecodedSize |= (pDecodeRead[3] & 0x7F) << 0; + pDecodeRead += 4; + + int iTextLength = iDecodedSize; + iTextLength += + (iTextLength + 6) / 7; // Have 1 control byte for every 7 actual bytes + + // make sure it's formatted properly + for (int i = 0; i != iTextLength; ++i) { + if (pDecodeRead[i] < 0x80) // encoded data always has MSB set + { + if (ppParseFinishOut != NULL) *ppParseFinishOut = (char *)pString; + + return INT_MIN; // either not our data, or part of the string is missing + } + } + + if (iDestBufferSize < iDecodedSize) { + if (ppParseFinishOut != NULL) *ppParseFinishOut = (char *)pDecodeRead; + + return -iDecodedSize; // dest buffer not big enough to hold the data + } + + const uint8 *pStopDecoding = pDecodeRead + iTextLength; + uint8 *pDecodeWrite = (uint8 *)pDestBuffer; + int iControl = 0; + int iLSBXOR = 0; + + while (pDecodeRead < pStopDecoding) { + if (iControl == 0) { + iLSBXOR = *pDecodeRead; + } else { + *pDecodeWrite = *pDecodeRead ^ ((iLSBXOR << iControl) & 0x80); + ++pDecodeWrite; + } + + ++pDecodeRead; + ++iControl; + iControl &= 7; // 8->0 + } + + if (ppParseFinishOut != NULL) *ppParseFinishOut = (char *)pDecodeRead; + + return iDecodedSize; +} diff --git a/tier0/threadtools.cpp b/tier0/threadtools.cpp new file mode 100644 index 0000000..cb52bd3 --- /dev/null +++ b/tier0/threadtools.cpp @@ -0,0 +1,3075 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier0/platform.h" + +#if defined( PLATFORM_WINDOWS_PC ) +#include "winlite.h" +#endif + +#ifdef PLATFORM_WINDOWS + #include + #ifdef PLATFORM_WINDOWS_PC + #include + #pragma comment(lib, "winmm.lib") + #endif +#elif PLATFORM_PS3 + #include + #include + #include + #include + #include + #include + #include + #define GetLastError() errno + typedef void *LPVOID; +#elif PLATFORM_POSIX + #include + #include + #include + #include + #include + #include + #define GetLastError() errno + typedef void *LPVOID; +#if !defined(OSX) + #include + #include + #define sem_unlink( arg ) +#else + #define pthread_yield pthread_yield_np + #include +#endif // !OSX + +#endif + +#ifndef _PS3 +#include +#endif +#include "tier0/threadtools.h" + +#ifdef _X360 +#include "xbox/xbox_win32stubs.h" +#endif + +#include + +// Must be last header... +#include "tier0/memdbgon.h" + +#ifdef _PS3 +#include "ps3/ps3_win32stubs.h" +#define NEW_WAIT_FOR_MULTIPLE_OBJECTS +bool gbCheckNotMultithreaded = true; +#endif + +#define THREADS_DEBUG 1 + +#define DEBUG_ERROR(XX) Assert(0) + +// Need to ensure initialized before other clients call in for main thread ID +#ifdef _WIN32 +#pragma warning(disable:4073) +#pragma init_seg(lib) +#endif + +#ifdef _WIN32 +static_assert(TT_SIZEOF_CRITICALSECTION == sizeof(CRITICAL_SECTION)); +static_assert(TT_INFINITE == INFINITE); +#endif + +// thread creation counter. +// this is used to provide a unique threadid for each running thread in g_nThreadID ( a thread local variable ). + +const int MAX_THREAD_IDS = 128; + +static volatile bool s_bThreadIDAllocated[MAX_THREAD_IDS]; + +#ifdef LINUX + +DLL_CLASS_EXPORT __thread int g_nThreadID; + +#elif defined(_PS3) + #include "tls_ps3.h" +#else + CTHREADLOCALINT g_nThreadID; +#endif + + +static CThreadFastMutex s_ThreadIDMutex; + +PLATFORM_INTERFACE void AllocateThreadID( void ) +{ + AUTO_LOCK( s_ThreadIDMutex ); + for( int i = 1; i < MAX_THREAD_IDS; i++ ) + { + if ( ! s_bThreadIDAllocated[i] ) + { + g_nThreadID = i; + s_bThreadIDAllocated[i] = true; + return; + } + } + Error( "Out of thread ids. Decrease the number of threads or increase MAX_THREAD_IDS\n" ); +} + +PLATFORM_INTERFACE void FreeThreadID( void ) +{ + AUTO_LOCK( s_ThreadIDMutex ); + int nThread = g_nThreadID; + if ( nThread ) + s_bThreadIDAllocated[nThread] = false; +} + + +//----------------------------------------------------------------------------- +// Simple thread functions. +// Because _beginthreadex uses stdcall, we need to convert to cdecl +//----------------------------------------------------------------------------- +struct ThreadProcInfo_t +{ + ThreadProcInfo_t( ThreadFunc_t pfnThread, void *pParam ) + : pfnThread( pfnThread), + pParam( pParam ) + { + } + + ThreadFunc_t pfnThread; + void * pParam; +}; + +//--------------------------------------------------------- + +#ifdef PLATFORM_WINDOWS +static DWORD WINAPI ThreadProcConvert( void *pParam ) +{ + ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); + AllocateThreadID(); + delete ((ThreadProcInfo_t *)pParam); + unsigned nRet = (*info.pfnThread)(info.pParam); + FreeThreadID(); + return nRet; +} +#elif defined( PLATFORM_PS3 ) +union ThreadProcInfoUnion_t +{ + struct Val_t + { + ThreadFunc_t pfnThread; + void * pParam; + } + val; + uint64_t val64; +}; +static void ThreadProcConvertUnion( uint64_t param ) +{ + COMPILE_TIME_ASSERT( sizeof( ThreadProcInfoUnion_t ) == 8 ); + ThreadProcInfoUnion_t info; + info.val64 = param; + AllocateThreadID(); + unsigned nRet = (*info.val.pfnThread)(info.val.pParam); + FreeThreadID(); + sys_ppu_thread_exit( nRet ); +} +static void* ThreadProcConvert( void *pParam ) +{ + ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); + AllocateThreadID(); + delete ((ThreadProcInfo_t *)pParam); + unsigned nRet = (*info.pfnThread)(info.pParam); + FreeThreadID(); + return ( void * ) nRet; +} + +#else +static void* ThreadProcConvert( void *pParam ) +{ + ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); + AllocateThreadID(); + delete ((ThreadProcInfo_t *)pParam); + unsigned nRet = (*info.pfnThread)(info.pParam); + FreeThreadID(); + return ( void * ) nRet; +} +#endif + + + +#if defined( _PS3 ) + +/******************************************************************************* +* Thread Local Storage globals and functions +*******************************************************************************/ +#ifndef _PS3 +__thread void *gTLSValues[ MAX_TLS_VALUES ] = { NULL }; +__thread bool gTLSFlags[ MAX_TLS_VALUES ] = { false }; +__thread bool gbWaitObjectsCreated = false; +__thread sys_semaphore_t gWaitObjectsSemaphore; +#endif // !_PS3 + +static char gThreadName[28] = ""; + +// Simple TLS allocator. Linearly searches for a free slot. +uint32 TlsAlloc() +{ + for ( int i = 0; i < MAX_TLS_VALUES; ++i ) + { + if ( !gTLSFlags[i] ) + { + gTLSFlags[i] = true; + return i; + } + } + +#ifdef _PS3 + DEBUG_ERROR("TlsAlloc(): Out of TLS\n"); +#endif + + return 0xFFFFFFFF; +} + +void TlsFree( uint32 index ) +{ + gTLSValues[ index ] = NULL; + gTLSFlags[ index ] = false; +} + +void *TlsGetValue( uint32 index ) +{ + return gTLSValues[ index ]; +} + +void TlsSetValue( uint32 index, void *pValue ) +{ + gTLSValues[ index ] = pValue; +} +#endif //_PS3 + + + +#ifdef PLATFORM_WINDOWS +class CThreadHandleToIDMap +{ +public: + HANDLE m_hThread; + uint m_ThreadID; + CThreadHandleToIDMap *m_pNext; +}; +static CThreadHandleToIDMap *g_pThreadHandleToIDMaps = NULL; +static CThreadMutex g_ThreadHandleToIDMapMutex; +static volatile int g_nThreadHandleToIDMaps = 0; + +static void AddThreadHandleToIDMap( HANDLE hThread, uint threadID ) +{ + if ( !hThread ) + return; + + // Remember this handle/id combo. + CThreadHandleToIDMap *pMap = new CThreadHandleToIDMap; + pMap->m_hThread = hThread; + pMap->m_ThreadID = threadID; + + // Add it to the global list. + g_ThreadHandleToIDMapMutex.Lock(); + pMap->m_pNext = g_pThreadHandleToIDMaps; + g_pThreadHandleToIDMaps = pMap; + ++g_nThreadHandleToIDMaps; + + g_ThreadHandleToIDMapMutex.Unlock(); + + if ( g_nThreadHandleToIDMaps > 500 ) + Error( "ThreadHandleToIDMap overflow." ); +} + +// This assumes you've got g_ThreadHandleToIDMapMutex locked!! +static bool InternalLookupHandleToThreadIDMap( HANDLE hThread, CThreadHandleToIDMap* &pMap, CThreadHandleToIDMap** &ppPrev ) +{ + ppPrev = &g_pThreadHandleToIDMaps; + for ( pMap=g_pThreadHandleToIDMaps; pMap; pMap=pMap->m_pNext ) + { + if ( pMap->m_hThread == hThread ) + return true; + + ppPrev = &pMap->m_pNext; + } + + return false; +} + +static void RemoveThreadHandleToIDMap( HANDLE hThread ) +{ + if ( !hThread ) + return; + + CThreadHandleToIDMap *pMap, **ppPrev; + + g_ThreadHandleToIDMapMutex.Lock(); + + if ( g_nThreadHandleToIDMaps <= 0 ) + Error( "ThreadHandleToIDMap underflow." ); + + if ( InternalLookupHandleToThreadIDMap( hThread, pMap, ppPrev ) ) + { + *ppPrev = pMap->m_pNext; + delete pMap; + --g_nThreadHandleToIDMaps; + } + + g_ThreadHandleToIDMapMutex.Unlock(); +} + +static uint LookupThreadIDFromHandle( HANDLE hThread ) +{ + if ( hThread == NULL || hThread == GetCurrentThread() ) + return GetCurrentThreadId(); + + double flStartTime = Plat_FloatTime(); + while ( Plat_FloatTime() - flStartTime < 2 ) + { + CThreadHandleToIDMap *pMap, **ppPrev; + + g_ThreadHandleToIDMapMutex.Lock(); + bool bRet = InternalLookupHandleToThreadIDMap( hThread, pMap, ppPrev ); + g_ThreadHandleToIDMapMutex.Unlock(); + + if ( bRet ) + return pMap->m_ThreadID; + + // We should only get here if a thread that is just starting up is currently in AddThreadHandleToIDMap. + // Give up the timeslice and try again. + ThreadSleep( 1 ); + } + + Assert( !"LookupThreadIDFromHandle failed!" ); + Warning( "LookupThreadIDFromHandle couldn't find thread ID for handle." ); + return 0; +} +#endif + + +//--------------------------------------------------------- + +ThreadHandle_t * CreateTestThreads( ThreadFunc_t fnThread, int numThreads, int nProcessorsToDistribute ) +{ + auto *handles = new ThreadHandle_t[numThreads + 1]; + ThreadHandle_t *pHandles = handles + 1; + handles[0] = (ThreadHandle_t)INT_TO_POINTER(numThreads); + for( int i = 0; i < numThreads; ++i ) + { + ThreadHandle_t hThread; + const unsigned int nDefaultStackSize = 64 * 1024; // this stack size is used in case stackSize == 0 + hThread = CreateSimpleThread( fnThread, INT_TO_POINTER( i ), nDefaultStackSize ); + + if ( nProcessorsToDistribute ) + { + int32 mask = 1 << (i % nProcessorsToDistribute); + ThreadSetAffinity( hThread, mask ); + } + + pHandles[i] = hThread; + } + return pHandles; +} + +void JoinTestThreads( ThreadHandle_t *pHandles ) +{ + int nCount = POINTER_TO_INT( pHandles[-1] ); + for( int i = 0; i < nCount; ++i ) + { + ThreadJoin( pHandles[i] ); + ReleaseThreadHandle( pHandles[i] ); + } + delete[]( pHandles - 1 ); +} + + + +ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, unsigned stackSize ) +{ +#ifdef PLATFORM_WINDOWS + DWORD threadID; + HANDLE hThread = (HANDLE)CreateThread( NULL, stackSize, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ), 0, &threadID ); + AddThreadHandleToIDMap( hThread, threadID ); + return (ThreadHandle_t)hThread; +#elif PLATFORM_PS3 + //TestThreads(); + ThreadHandle_t th; + ThreadProcInfoUnion_t info; + info.val.pfnThread = pfnThread; + info.val.pParam = pParam; + const unsigned int nDefaultStackSize = 64 * 1024; // this stack size is used in case stackSize == 0 + if ( sys_ppu_thread_create( &th, ThreadProcConvertUnion, info.val64, 1001, stackSize ? stackSize : nDefaultStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, "SimpleThread" ) != CELL_OK ) + { + AssertMsg1( 0, "Failed to create thread (error 0x%x)", errno ); + return 0; + } + return th; +#elif PLATFORM_POSIX + pthread_t tid; + pthread_create( &tid, NULL, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ) ); + return ( ThreadHandle_t ) tid; +#else + Assert( 0 ); + DebuggerBreak(); + return 0; +#endif +} + + +bool ReleaseThreadHandle( ThreadHandle_t hThread ) +{ +#ifdef _WIN32 + RemoveThreadHandleToIDMap( (HANDLE)hThread ); + // Close handle only after use, not before. + bool bRetVal = ( CloseHandle( hThread ) != 0 ); + return bRetVal; +#else + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +// Wrappers for other simple threading operations +// +//----------------------------------------------------------------------------- + +void ThreadSleep(unsigned nMilliseconds) +{ +#ifdef _WIN32 + +#ifdef PLATFORM_WINDOWS_PC + static bool bInitialized = false; + if ( !bInitialized ) + { + bInitialized = true; + // Set the timer resolution to 1 ms (default is 10.0, 15.6, 2.5, 1.0 or + // some other value depending on hardware and software) so that we can + // use Sleep( 1 ) to avoid wasting CPU time without missing our frame + // rate. + timeBeginPeriod( 1 ); + } +#endif + + Sleep( nMilliseconds ); +#elif PLATFORM_PS3 + if( nMilliseconds == 0 ) + { + // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. + sys_timer_usleep( 60 ); + } + else + { + sys_timer_usleep( nMilliseconds * 1000 ); + } +#elif defined(POSIX) + usleep( nMilliseconds * 1000 ); +#endif +} + +//----------------------------------------------------------------------------- + +#ifndef ThreadGetCurrentId +ThreadId_t ThreadGetCurrentId() +{ +#ifdef _WIN32 + return GetCurrentThreadId(); +#elif defined( _PS3 ) + sys_ppu_thread_t th = 0; + sys_ppu_thread_get_id( &th ); + return th; +#elif defined(POSIX) + return (ThreadId_t)pthread_self(); +#else + Assert(0); + DebuggerBreak(); + return 0; +#endif +} +#endif + +//----------------------------------------------------------------------------- +ThreadHandle_t ThreadGetCurrentHandle() +{ +#ifdef _WIN32 + return (ThreadHandle_t)GetCurrentThread(); +#elif defined( _PS3 ) + sys_ppu_thread_t th = 0; + sys_ppu_thread_get_id( &th ); + return th; +#elif defined(POSIX) + return (ThreadHandle_t)pthread_self(); +#else + Assert(0); + DebuggerBreak(); + return 0; +#endif +} + +//----------------------------------------------------------------------------- + +int ThreadGetPriority( ThreadHandle_t hThread ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + return ::GetThreadPriority( (HANDLE)hThread ); +#elif defined( _PS3 ) + int iPri = 0; + sys_ppu_thread_get_priority( hThread, &iPri ); + return iPri; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- + +bool ThreadSetPriority( ThreadHandle_t hThread, int priority ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + return ( SetThreadPriority(hThread, priority) != 0 ); +#elif defined( _PS3 ) + int retval = sys_ppu_thread_set_priority( hThread, priority ); + return retval >= CELL_OK; +#elif defined(POSIX) + struct sched_param thread_param; + thread_param.sched_priority = priority; + //pthread_setschedparam( (pthread_t ) hThread, SCHED_RR, &thread_param ); + return true; +#endif +} + +//----------------------------------------------------------------------------- + +void ThreadSetAffinity( ThreadHandle_t hThread, int nAffinityMask ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + SetThreadAffinityMask( hThread, nAffinityMask ); +#elif defined(POSIX) +// cpu_set_t cpuSet; +// CPU_ZERO( cpuSet ); +// for( int i = 0 ; i < 32; i++ ) +// if ( nAffinityMask & ( 1 << i ) ) +// CPU_SET( cpuSet, i ); +// sched_setaffinity( hThread, sizeof( cpuSet ), &cpuSet ); +#endif + +} + +//----------------------------------------------------------------------------- + +#ifndef _X360 +uint InitMainThread() +{ + ThreadSetDebugName( "MainThrd" ); + + return ThreadGetCurrentId(); +} + +unsigned long g_ThreadMainThreadID = InitMainThread(); + +bool ThreadInMainThread() +{ + return ( ThreadGetCurrentId() == g_ThreadMainThreadID ); +} + +void DeclareCurrentThreadIsMainThread() +{ + g_ThreadMainThreadID = ThreadGetCurrentId(); +} + +#else +byte *InitMainThread() +{ + byte b; + + return AlignValue( &b, 64*1024 ); +} +#define STACK_SIZE_360 327680 +byte *g_pBaseMainStack = InitMainThread(); +byte *g_pLimitMainStack = InitMainThread() - STACK_SIZE_360; +#endif + +//----------------------------------------------------------------------------- +bool ThreadJoin( ThreadHandle_t hThread, unsigned timeout ) +{ + if ( !hThread ) + { + return false; + } + +#ifdef _WIN32 + DWORD dwWait = WaitForSingleObject( (HANDLE)hThread, timeout ); + if ( dwWait == WAIT_TIMEOUT) + return false; + if ( dwWait != WAIT_OBJECT_0 && ( dwWait != WAIT_FAILED && GetLastError() != 0 ) ) + { + Assert( 0 ); + return false; + } +#elif defined( _PS3 ) + uint64 uiExitCode = 0; + int retval = sys_ppu_thread_join( hThread, &uiExitCode ); + return ( retval >= CELL_OK ); +#elif defined(POSIX) + if ( pthread_join( (pthread_t)hThread, NULL ) != 0 ) + return false; +#else + Assert(0); + DebuggerBreak(); +#endif + return true; +} + +//----------------------------------------------------------------------------- +void ThreadSetDebugName( ThreadHandle_t hThread, const char *pszName ) +{ +#ifdef WIN32 + if ( Plat_IsInDebugSession() ) + { +#define MS_VC_EXCEPTION 0x406d1388 + + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in same addr space) + DWORD dwThreadID; // thread ID (-1 caller thread) + DWORD dwFlags; // reserved for future use, most be zero + } THREADNAME_INFO; + + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = pszName; + info.dwThreadID = LookupThreadIDFromHandle( hThread ); + + if ( info.dwThreadID != 0 ) + { + info.dwFlags = 0; + + __try + { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + // Should stop here, no need to reexecute. + } + } + } +#endif +} + + + +//----------------------------------------------------------------------------- +// Used to thread LoadLibrary on the 360 +//----------------------------------------------------------------------------- +static ThreadedLoadLibraryFunc_t s_ThreadedLoadLibraryFunc = 0; +PLATFORM_INTERFACE void SetThreadedLoadLibraryFunc( ThreadedLoadLibraryFunc_t func ) +{ + s_ThreadedLoadLibraryFunc = func; +} + +PLATFORM_INTERFACE ThreadedLoadLibraryFunc_t GetThreadedLoadLibraryFunc() +{ + return s_ThreadedLoadLibraryFunc; +} + + +//----------------------------------------------------------------------------- +// +// CThreadSyncObject (note nothing uses this directly (I think) ) +// +//----------------------------------------------------------------------------- + + +#ifdef _PS3 +uint32_t CThreadSyncObject::m_bstaticMutexInitialized = false; +uint32_t CThreadSyncObject::m_bstaticMutexInitializing = false; +sys_lwmutex_t CThreadSyncObject::m_staticMutex; +#endif + + +CThreadSyncObject::CThreadSyncObject() +#ifdef _WIN32 + : m_hSyncObject( NULL ), m_bCreatedHandle(false) +#elif defined(POSIX) && !defined(PLATFORM_PS3) + : m_bInitalized( false ) +#endif +{ +#ifdef _PS3 + //Do we nee to initialise the staticMutex? + if (m_bstaticMutexInitialized) return; + + //If we are the first thread then create the mutex + if ( cellAtomicCompareAndSwap32(&m_bstaticMutexInitializing, false, true) == false ) + { + sys_lwmutex_attribute_t mutexAttr; + sys_lwmutex_attribute_initialize( mutexAttr ); + mutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; + int err = sys_lwmutex_create( &m_staticMutex, &mutexAttr ); + Assert(err == CELL_OK); + m_bstaticMutexInitialized = true; + } + else + { + //Another thread is already in the process of initialising the mutex, wait for it + while ( !m_bstaticMutexInitialized ) + { + // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. + sys_timer_usleep( 60 ); + } + } +#endif +} + +//--------------------------------------------------------- + +CThreadSyncObject::~CThreadSyncObject() +{ +#ifdef _WIN32 + if ( m_hSyncObject && m_bCreatedHandle ) + { + if ( !CloseHandle(m_hSyncObject) ) + { + Assert( 0 ); + } + } +#elif defined(POSIX) && !defined( PLATFORM_PS3 ) + if ( m_bInitalized ) + { + pthread_cond_destroy( &m_Condition ); + pthread_mutex_destroy( &m_Mutex ); + m_bInitalized = false; + } +#endif +} + +//--------------------------------------------------------- + +bool CThreadSyncObject::operator!() const +{ +#if PLATFORM_PS3 + return m_bstaticMutexInitialized; +#elif defined( _WIN32 ) + return !m_hSyncObject; +#elif defined(POSIX) + return !m_bInitalized; +#endif +} + +//--------------------------------------------------------- + +void CThreadSyncObject::AssertUseable() +{ +#ifdef THREADS_DEBUG +#if PLATFORM_PS3 + AssertMsg( m_bstaticMutexInitialized, "Thread synchronization object is unusable" ); +#elif defined( _WIN32 ) + AssertMsg( m_hSyncObject, "Thread synchronization object is unusable" ); +#elif defined(POSIX) + AssertMsg( m_bInitalized, "Thread synchronization object is unusable" ); +#endif +#endif +} + +//--------------------------------------------------------- + +#if defined(_WIN32) || ( defined(POSIX) && !defined( _PS3 ) ) +bool CThreadSyncObject::Wait( uint32 dwTimeout ) +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( WaitForSingleObject( m_hSyncObject, dwTimeout ) == WAIT_OBJECT_0 ); +#elif defined( POSIX ) && !defined( PLATFORM_PS3 ) + pthread_mutex_lock( &m_Mutex ); + bool bRet = false; + if ( m_cSet > 0 ) + { + bRet = true; + m_bWakeForEvent = false; + } + else + { + volatile int ret = 0; + + while ( !m_bWakeForEvent && ret != ETIMEDOUT ) + { + struct timeval tv; + gettimeofday( &tv, NULL ); + volatile struct timespec tm; + + uint64 actualTimeout = dwTimeout; + + if ( dwTimeout == TT_INFINITE && m_bManualReset ) + actualTimeout = 10; // just wait 10 msec at most for manual reset events and loop instead + + volatile uint64 nNanoSec = (uint64)tv.tv_usec*1000 + (uint64)actualTimeout*1000000; + tm.tv_sec = tv.tv_sec + nNanoSec /1000000000; + tm.tv_nsec = nNanoSec % 1000000000; + + do + { + ret = pthread_cond_timedwait( &m_Condition, &m_Mutex, (const timespec *)&tm ); + } + while( ret == EINTR ); + + bRet = ( ret == 0 ); + + if ( m_bManualReset ) + { + if ( m_cSet ) + break; + if ( dwTimeout == TT_INFINITE && ret == ETIMEDOUT ) + ret = 0; // force the loop to spin back around + } + } + + if ( bRet ) + m_bWakeForEvent = false; + } + if ( !m_bManualReset && bRet ) + m_cSet = 0; + pthread_mutex_unlock( &m_Mutex ); + return bRet; +#endif +} +#endif + +#ifdef _WIN32 +#pragma optimize( "", off ) // The compiler optimizer screws up the function below. +#endif + +uint32 CThreadSyncObject::WaitForMultiple( int nObjects, CThreadSyncObject **ppObjects, bool bWaitAll, uint32 dwTimeout ) +{ +#if defined( _WIN32 ) + + CThreadSyncObject *pHandles = (CThreadSyncObject*)stackalloc( sizeof(CThreadSyncObject) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + pHandles[i].m_hSyncObject = ppObjects[i]->m_hSyncObject; + } + + return WaitForMultiple( nObjects, pHandles, bWaitAll, dwTimeout ); + +#else + + // TODO: Need a more efficient implementation of this. + uint32 dwStartTime = 0; + + if ( dwTimeout != TT_INFINITE ) + dwStartTime = Plat_MSTime(); + + // If bWaitAll = true, then we need to track which ones were triggered. + char *pWasTriggered = NULL; + int nTriggered = 0; + if ( bWaitAll ) + { + pWasTriggered = (char*)stackalloc( nObjects ); + memset( pWasTriggered, 0, nObjects ); + } + + while ( 1 ) + { + for ( int i=0; i < nObjects; i++ ) + { + if ( bWaitAll && pWasTriggered[i] ) + continue; + +#ifdef _PS3 + Assert( !"Not implemented!" ); + if ( false ) +#else + if ( ppObjects[i]->Wait( 0 ) ) +#endif + { + ++nTriggered; + if ( bWaitAll ) + { + if ( nTriggered == nObjects ) + return 0; + else + pWasTriggered[i] = 1; + } + else + { + return i; + } + } + } + + // Timeout? + if ( dwTimeout != TT_INFINITE ) + { + if ( Plat_MSTime() - dwStartTime >= dwTimeout ) + return TW_TIMEOUT; + } + + ThreadSleep( 0 ); + } + +#endif +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + +uint32 CThreadSyncObject::WaitForMultiple( int nObjects, CThreadSyncObject *pObjects, bool bWaitAll, uint32 dwTimeout ) +{ +#if defined(_WIN32 ) + + HANDLE *pHandles = (HANDLE*)stackalloc( sizeof(HANDLE) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + pHandles[i] = pObjects[i].m_hSyncObject; + } + + DWORD ret = WaitForMultipleObjects( nObjects, pHandles, bWaitAll, dwTimeout ); + if ( ret == WAIT_TIMEOUT ) + return TW_TIMEOUT; + + if ( ret >= WAIT_OBJECT_0 && (ret-WAIT_OBJECT_0) < (uint32)nObjects ) + return (int)(ret - WAIT_OBJECT_0); + + if ( ret >= WAIT_ABANDONED_0 && (ret - WAIT_ABANDONED_0) < (uint32)nObjects ) + Error( "Unhandled WAIT_ABANDONED in WaitForMultipleObjects" ); + else if ( ret == WAIT_FAILED ) + return TW_FAILED; + else + Error( "Unknown return value (%lu) from WaitForMultipleObjects", ret ); + + // We'll never get here.. + return 0; + +#else + + CThreadSyncObject **ppObjects = (CThreadSyncObject**)stackalloc( sizeof( CThreadSyncObject* ) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + ppObjects[i] = &pObjects[i]; + } + + return WaitForMultiple( nObjects, ppObjects, bWaitAll, dwTimeout ); + +#endif +} + +// To implement these, I need to check that casts are safe +uint32 CThreadEvent::WaitForMultiple( int nObjects, CThreadEvent *pObjects, bool bWaitAll, uint32 dwTimeout ) +{ + // If data ever gets added to CThreadEvent, then we need a different implementation. +#ifdef _PS3 + CThreadEvent **ppObjects = (CThreadEvent**)stackalloc( sizeof( CThreadEvent* ) * nObjects ); + for ( int i=0; i < nObjects; i++ ) + { + ppObjects[i] = &pObjects[i]; + } + return WaitForMultipleObjects( nObjects, ppObjects, bWaitAll, dwTimeout ); +#else + COMPILE_TIME_ASSERT( sizeof( CThreadSyncObject ) == 0 || sizeof( CThreadEvent ) == sizeof( CThreadSyncObject ) ); + return CThreadSyncObject::WaitForMultiple( nObjects, (CThreadSyncObject*)pObjects, bWaitAll, dwTimeout ); +#endif +} + + +uint32 CThreadEvent::WaitForMultiple( int nObjects, CThreadEvent **ppObjects, bool bWaitAll, uint32 dwTimeout ) +{ +#ifdef _PS3 + return WaitForMultipleObjects( nObjects, ppObjects, bWaitAll, dwTimeout ); +#else + // If data ever gets added to CThreadEvent, then we need a different implementation. + COMPILE_TIME_ASSERT( sizeof( CThreadSyncObject )== 0 || sizeof( CThreadEvent ) == sizeof( CThreadSyncObject ) ); + return CThreadSyncObject::WaitForMultiple( nObjects, (CThreadSyncObject**)ppObjects, bWaitAll, dwTimeout ); +#endif +} + + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadEvent::CThreadEvent( bool bManualReset ) +{ +#ifdef _WIN32 + m_hSyncObject = CreateEvent( NULL, bManualReset, FALSE, NULL ); + m_bCreatedHandle = true; + AssertMsg1(m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); +#elif defined( _PS3 ) + + m_bManualReset = bManualReset; + m_bSet = 0; + m_bInitalized = false; + m_numWaitingThread = 0; + + // set up linked list of wait objects + + memset(&m_waitObjects[0], 0, sizeof(m_waitObjects)); + m_pWaitObjectsList = &m_waitObjects[0]; + m_pWaitObjectsPool = &m_waitObjects[1]; + + for (int i = 2; i < CTHREADEVENT_MAX_WAITING_THREADS + 2; i++) + { + LLLinkNode(m_pWaitObjectsPool, &m_waitObjects[i]); + } +#elif defined( POSIX ) + pthread_mutexattr_t Attr; + pthread_mutexattr_init( &Attr ); + pthread_mutex_init( &m_Mutex, &Attr ); + pthread_mutexattr_destroy( &Attr ); + pthread_cond_init( &m_Condition, NULL ); + m_bInitalized = true; + m_cSet = 0; + m_bWakeForEvent = false; + m_bManualReset = bManualReset; +#else +#error "Implement me" +#endif +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +#ifdef _PS3 + +// +// linked list functionality +// + +//----------------------------------------------------------------------------- +// Purpose: Linked list implementation +//----------------------------------------------------------------------------- + +CThreadEventWaitObject* CThreadEvent::LLUnlinkNode(CThreadEventWaitObject *node) +{ + // Note: if you have a null-access crash here, it may mean that CTHREADEVENT_MAX_WAITING_THREADS is not high enough + // and the linked list pool is simply exhausted + node->m_pPrev->m_pNext = node->m_pNext; + if (node->m_pNext) node->m_pNext->m_pPrev = node->m_pPrev; + node->m_pNext = node->m_pPrev = NULL; + + return node; +} + +CThreadEventWaitObject* CThreadEvent::LLLinkNode(CThreadEventWaitObject* list, CThreadEventWaitObject *node) +{ + node->m_pNext = list->m_pNext; + if (node->m_pNext) + { + node->m_pNext->m_pPrev = node; + } + + list->m_pNext = node; + node->m_pPrev = list; + + return node; +} + +//----------------------------------------------------------------------------- +// Helper function to atomically write index into destination and set semaphore +// This is used by WaitForMultipleObjects(WAIT_ANY) because once the semaphore +// is set, the waiting thread also needs to know which event triggered it +// We do NOT need this to be atomic because if a number of events fire it doesn't +// matter which one of these we pick +//----------------------------------------------------------------------------- +void CThreadEventWaitObject::Set() +{ + *m_pFlag = m_index; + sys_semaphore_post(*m_pSemaphore, 1); +} + +// +// CThreadEvent::RegisterWaitingThread +// +void CThreadEvent::RegisterWaitingThread(sys_semaphore_t *pSemaphore, int index, int *flag) +{ + sys_lwmutex_lock(&m_staticMutex, 0); + + // if we are already set, then signal this semaphore + if (m_bSet) + { + CThreadEventWaitObject waitObject; + waitObject.Init(pSemaphore, index, flag); + waitObject.Set(); + + if (!m_bManualReset) + { + m_bSet = false; + } + } + else + { + if (!m_pWaitObjectsPool->m_pNext) + { + DEBUG_ERROR("CThreadEvent: Ran out of events; cannot register waiting thread\n"); + } + + // add this semaphore to linked list - can be added more than once it doesn't matter + + CThreadEventWaitObject *pWaitObject = LLUnlinkNode(m_pWaitObjectsPool->m_pNext); + + pWaitObject->Init(pSemaphore, index, flag); + + LLLinkNode(m_pWaitObjectsList, pWaitObject); + } + + sys_lwmutex_unlock(&m_staticMutex); +} + +// +// CThreadEvent::UnregisterWaitingThread +// +void CThreadEvent::UnregisterWaitingThread(sys_semaphore_t *pSemaphore) +{ + // remove all instances of this semaphore from linked list + + sys_lwmutex_lock(&m_staticMutex, 0); + + CThreadEventWaitObject *pWaitObject = m_pWaitObjectsList->m_pNext; + + while (pWaitObject) + { + CThreadEventWaitObject *pNext = pWaitObject->m_pNext; + + if (pWaitObject->m_pSemaphore == pSemaphore) + { + LLUnlinkNode(pWaitObject); + LLLinkNode(m_pWaitObjectsPool, pWaitObject); + } + + pWaitObject = pNext; + } + + sys_lwmutex_unlock(&m_staticMutex); +} + +#endif // _PS3 + + +#ifdef PLATFORM_WINDOWS + CThreadEvent::CThreadEvent( const char *name, bool initialState, bool bManualReset ) + { + m_hSyncObject = CreateEvent( NULL, bManualReset, (BOOL) initialState, name ); + AssertMsg1( m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); + } + + + NamedEventResult_t CThreadEvent::CheckNamedEvent( const char *name, uint32 dwTimeout ) + { + HANDLE eHandle = OpenEvent( SYNCHRONIZE, FALSE, name ); + + if ( eHandle == NULL ) return TT_EventDoesntExist; + + DWORD result = WaitForSingleObject( eHandle, dwTimeout ); + + return ( result == WAIT_OBJECT_0 ) ? TT_EventSignaled : TT_EventNotSignaled; + } +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + + +//--------------------------------------------------------- + +bool CThreadEvent::Set() +{ +////////////////////////////////////////////////////////////// +#ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + AssertUseable(); +#ifdef _WIN32 + return ( SetEvent( m_hSyncObject ) != 0 ); +#elif defined( _PS3 ) + + sys_lwmutex_lock(&m_staticMutex, 0); + + if (m_bManualReset) + { + //Mark event as set + m_bSet = true; + + //If any threads are already waiting then signal them to run + if (m_bInitalized) + { + int err = sys_semaphore_post( m_Semaphore, m_numWaitingThread); + Assert(err == CELL_OK); + } + } + else + { + //If any threads are already waiting then signal ONE to run, else signal next to run + + if (m_numWaitingThread>0) + { + int err = sys_semaphore_post( m_Semaphore, 1); + Assert(err == CELL_OK); + } + else + { + m_bSet=true; + } + } + + sys_lwmutex_unlock(&m_staticMutex); + + return true; + + +#elif defined(POSIX) + pthread_mutex_lock( &m_Mutex ); + m_cSet = 1; + m_bWakeForEvent = true; + int ret = pthread_cond_signal( &m_Condition ); + pthread_mutex_unlock( &m_Mutex ); + return ret == 0; +#endif + + +////////////////////////////////////////////////////////////// +#else // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + + sys_lwmutex_lock(&m_staticMutex, 0); + + //Mark event as set + m_bSet = true; + + // signal registered semaphores + while (m_pWaitObjectsList->m_pNext) + { + CThreadEventWaitObject *pWaitObject = LLUnlinkNode(m_pWaitObjectsList->m_pNext); + + pWaitObject->Set(); + + LLLinkNode(m_pWaitObjectsPool, pWaitObject); + + if (!m_bManualReset) + { + m_bSet = false; + break; + } + } + + sys_lwmutex_unlock(&m_staticMutex); + + return true; + +////////////////////////////////////////////////////////////// +#endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// +} + +//--------------------------------------------------------- + +bool CThreadEvent::Reset() +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( ResetEvent( m_hSyncObject ) != 0 ); +#elif defined( _PS3 ) + + //Just mark us as no longer signaled + m_bSet = 0; + + return true; +#elif defined(POSIX) + pthread_mutex_lock( &m_Mutex ); + m_cSet = 0; + m_bWakeForEvent = false; + pthread_mutex_unlock( &m_Mutex ); + return true; +#endif +} + +//--------------------------------------------------------- + +bool CThreadEvent::Check() +{ + #ifdef _PS3 + return m_bSet; // Please, use for debugging only! + #endif +#ifdef THREADS_DEBUG + AssertUseable(); +#endif + return Wait( 0 ); +} + + + +bool CThreadEvent::Wait( uint32 dwTimeout ) +{ +////////////////////////////////////////////////////////////// +#ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + + +#if defined( _WIN32 ) || ( defined( POSIX ) && !defined( _PS3 ) ) + return CThreadSyncObject::Wait( dwTimeout ); +#elif defined( _PS3 ) + + { + + if (dwTimeout == 0) + { + //If timeout is 0 then just test it now (and reset it if manual ) + if (m_bSet) + { + if ( !m_bManualReset ) m_bSet=false; + return true; + } + return false; + } + + if (!AddWaitingThread()) + { + //Waiting thread NOT added because m_bSet was already set + if ( !m_bManualReset ) m_bSet=false; + return true; + } + + uint32 timeout; + int countTimeout = 0; + int ret = ETIMEDOUT; + while ( timeout=MIN(1, dwTimeout) ) + { + // on the PS3, "infinite timeout" is specified by zero, not + // 0xFFFFFFFF, so we need to perform that ternary here. +#error Untested code: + ret = sys_semaphore_wait( m_Semaphore, timeout == TT_INFINITE ? 0 : timeout * 1000 ); + Assert( (ret == CELL_OK) || (ret == ETIMEDOUT) ); + + if ( ret == CELL_OK ) + break; + + dwTimeout -= timeout; + countTimeout++; + if (countTimeout > 30) + { + fprintf(stderr, "WARNING: possible deadlock in CThreadEvent::Wait() !!!\n"); + } + } + + RemoveWaitingThread(); + + if ( !m_bManualReset ) m_bSet=false; + + return ret == CELL_OK; + } + +#endif + +////////////////////////////////////////////////////////////// +#else // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// + + + CThreadEvent *pThis = this; + DWORD res = WaitForMultipleObjects(1, &pThis, true, dwTimeout); + return res == WAIT_OBJECT_0; + + +////////////////////////////////////////////////////////////// +#endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS +////////////////////////////////////////////////////////////// +} + +#ifdef _WIN32 +//----------------------------------------------------------------------------- +// +// CThreadSemaphore +// +// To get Posix implementation, try https://www.scribd.com/document/341915761/Semaphores-Tutorial-OReilly-Linuxdevcenter +// +//----------------------------------------------------------------------------- + +CThreadSemaphore::CThreadSemaphore( int32 initialValue, int32 maxValue ) +{ +#ifdef _WIN32 + if ( maxValue ) + { + AssertMsg( maxValue > 0, "Invalid max value for semaphore" ); + AssertMsg( initialValue >= 0 && initialValue <= maxValue, "Invalid initial value for semaphore" ); + + m_hSyncObject = CreateSemaphore( NULL, initialValue, maxValue, NULL ); + + AssertMsg1(m_hSyncObject, "Failed to create semaphore (error 0x%x)", GetLastError()); + } + else + { + m_hSyncObject = NULL; + } +#elif defined( _PS3 ) + if ( maxValue ) + { + m_sema_max_val = maxValue; + m_semaCount = initialValue; + } +#endif +} + + +#ifdef _PS3 +//--------------------------------------------------------- + +bool CThreadSemaphore::AddWaitingThread() +{ + bool result; + + sys_lwmutex_lock(&m_staticMutex, 0); + + if (cellAtomicTestAndDecr32(&m_semaCount) > 0) + { + result=false; + } + else + { + result=true; + m_numWaitingThread++; + + if ( m_numWaitingThread == 1 ) + { + sys_semaphore_attribute_t semAttr; + sys_semaphore_attribute_initialize( semAttr ); + Assert(m_semaCount == 0); + int err = sys_semaphore_create( &m_Semaphore, &semAttr, 0, m_sema_max_val ); + Assert( err == CELL_OK ); + m_bInitalized = true; + } + } + + sys_lwmutex_unlock(&m_staticMutex); + return result; +} + +void CThreadSemaphore::RemoveWaitingThread() +{ + sys_lwmutex_lock(&m_staticMutex, 0); + + m_numWaitingThread--; + + if ( m_numWaitingThread == 0) + { + int err = sys_semaphore_destroy( m_Semaphore ); + Assert( err == CELL_OK ); + m_bInitalized = false; + } + + sys_lwmutex_unlock(&m_staticMutex); +} + +#endif + +#ifdef _PS3 + +bool CThreadSemaphore::Wait( uint32 dwTimeout ) +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif + + +#ifndef NO_THREAD_SYNC + if (!AddWaitingThread()) + { + //Waiting thread NOT added because semaphore was already in a signaled state + return true; + } + + int ret = sys_semaphore_wait( m_Semaphore, dwTimeout == TT_INFINITE ? 0 : dwTimeout * 1000 ); + Assert( (ret == CELL_OK) || (ret == ETIMEDOUT) ); + + RemoveWaitingThread(); + + int old = cellAtomicDecr32(&m_semaCount); + Assert(old>0); +#else + int ret = CELL_OK; +#endif + + // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. + sys_timer_usleep( 60 ); + + + + return ret == CELL_OK; +} + +#endif + +//--------------------------------------------------------- + +bool CThreadSemaphore::Release( int32 releaseCount, int32 *pPreviousCount ) +{ +#ifdef THRDTOOL_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( ReleaseSemaphore( m_hSyncObject, releaseCount, (LPLONG)pPreviousCount ) != 0 ); +#elif defined( _PS3 ) + +#ifndef NO_THREAD_SYNC + + if (m_bInitalized) + { + sys_semaphore_value_t previousVal; + sys_semaphore_get_value( m_Semaphore, &previousVal ); + + cellAtomicAdd32(&m_semaCount, releaseCount); + + *pPreviousCount = previousVal; + + int err = sys_semaphore_post( m_Semaphore, releaseCount ); + Assert(err == CELL_OK); + } + +#endif + + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadFullMutex::CThreadFullMutex( bool bEstablishInitialOwnership, const char *pszName ) +{ + m_hSyncObject = CreateMutex( NULL, bEstablishInitialOwnership, pszName ); + + AssertMsg1( m_hSyncObject, "Failed to create mutex (error 0x%x)", GetLastError() ); +} + +//--------------------------------------------------------- + +bool CThreadFullMutex::Release() +{ +#ifdef THRDTOOL_DEBUG + AssertUseable(); +#endif + return ( ReleaseMutex( m_hSyncObject ) != 0 ); +} + +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +#if ( defined(WIN32) || defined(OSX) ) && !defined(PLATFORM_PS3) +namespace GenericThreadLocals +{ +CThreadLocalBase::CThreadLocalBase() +{ +#ifdef _WIN32 + m_index = TlsAlloc(); + AssertMsg( m_index != 0xFFFFFFFF, "Bad thread local" ); + if ( m_index == 0xFFFFFFFF ) + Error( "Out of thread local storage!\n" ); +#elif defined(POSIX) + if ( pthread_key_create( (pthread_key_t *)&m_index, NULL ) != 0 ) + Error( "Out of thread local storage!\n" ); +#endif +} + +//--------------------------------------------------------- + +CThreadLocalBase::~CThreadLocalBase() +{ +#ifdef _WIN32 + if ( m_index != 0xFFFFFFFF ) + TlsFree( m_index ); + m_index = 0xFFFFFFFF; +#elif defined(POSIX) + pthread_key_delete( m_index ); +#endif +} + +//--------------------------------------------------------- + +void * CThreadLocalBase::Get() const +{ +#ifdef _WIN32 + if ( m_index != 0xFFFFFFFF ) + return TlsGetValue( m_index ); + AssertMsg( 0, "Bad thread local" ); + return NULL; +#elif defined(POSIX) + void *value = pthread_getspecific( m_index ); + return value; +#endif +} + +//--------------------------------------------------------- + +void CThreadLocalBase::Set( void *value ) +{ +#ifdef _WIN32 + if (m_index != 0xFFFFFFFF) + TlsSetValue(m_index, value); + else + AssertMsg( 0, "Bad thread local" ); +#elif defined(POSIX) + if ( pthread_setspecific( m_index, value ) != 0 ) + AssertMsg( 0, "Bad thread local" ); +#endif +} +} // namespace GenericThreadLocals +#endif // ( defined(WIN32) || defined(POSIX) ) && !defined(PLATFORM_PS3) +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- + +#ifdef MSVC +//#ifdef _X360 +#define TO_INTERLOCK_PARAM(p) ((volatile long *)p) +#define TO_INTERLOCK_PTR_PARAM(p) ((void **)p) +//#else +//#define TO_INTERLOCK_PARAM(p) (p) +//#define TO_INTERLOCK_PTR_PARAM(p) (p) +//#endif + +#if !defined(USE_INTRINSIC_INTERLOCKED) && !defined(_X360) +int32 ThreadInterlockedIncrement( int32 volatile *pDest ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedIncrement( TO_INTERLOCK_PARAM(pDest) ); +} + +int32 ThreadInterlockedDecrement( int32 volatile *pDest ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedDecrement( TO_INTERLOCK_PARAM(pDest) ); +} + +int32 ThreadInterlockedExchange( int32 volatile *pDest, int32 value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchange( TO_INTERLOCK_PARAM(pDest), value ); +} + +int32 ThreadInterlockedExchangeAdd( int32 volatile *pDest, int32 value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchangeAdd( TO_INTERLOCK_PARAM(pDest), value ); +} + +int32 ThreadInterlockedCompareExchange( int32 volatile *pDest, int32 value, int32 comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ); +} + +bool ThreadInterlockedAssignIf( int32 volatile *pDest, int32 value, int32 comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + +#if !(defined(_WIN64) || defined (_X360)) + __asm + { + mov eax,comperand + mov ecx,pDest + mov edx,value + lock cmpxchg [ecx],edx + mov eax,0 + setz al + } +#else + return ( InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ) == comperand ); +#endif +} + +#endif + +#if !defined( USE_INTRINSIC_INTERLOCKED ) || defined( _WIN64 ) +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value ); +} + +void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ); +} + +bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); +#if !(defined(_WIN64) || defined (_X360)) + __asm + { + mov eax,comperand + mov ecx,pDest + mov edx,value + lock cmpxchg [ecx],edx + mov eax,0 + setz al + } +#else + return ( InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ) == comperand ); +#endif +} +#endif + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + +#if defined(_WIN64) || defined (_X360) + return InterlockedCompareExchange64( pDest, value, comperand ); +#else + __asm + { + lea esi,comperand; + lea edi,value; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,pDest; + lock CMPXCHG8B [esi]; + } +#endif +} + +bool ThreadInterlockedAssignIf64(volatile int64 *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + +#if defined(_X360) || defined(_WIN64) + return ( ThreadInterlockedCompareExchange64( pDest, value, comperand ) == comperand ); +#else + __asm + { + lea esi,comperand; + lea edi,value; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,pDest; + lock CMPXCHG8B [esi]; + mov eax,0; + setz al; + } +#endif +} + +#elif defined(GNUC) + +#ifdef OSX +#include +#endif + + +long ThreadInterlockedIncrement( long volatile *pDest ) +{ + return __sync_fetch_and_add( pDest, 1 ) + 1; +} + +long ThreadInterlockedDecrement( long volatile *pDest ) +{ + return __sync_fetch_and_sub( pDest, 1 ) - 1; +} + +long ThreadInterlockedExchange( long volatile *pDest, long value ) +{ + return __sync_lock_test_and_set( pDest, value ); +} + +long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) +{ + return __sync_fetch_and_add( pDest, value ); +} + +long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) +{ + return __sync_val_compare_and_swap( pDest, comperand, value ); +} + +bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} + +#if !defined( USE_INTRINSIC_INTERLOCKED ) +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + return __sync_lock_test_and_set( pDest, value ); +} + +void *ThreadInterlockedCompareExchangePointer( void *volatile *pDest, void *value, void *comperand ) +{ + return __sync_val_compare_and_swap( pDest, comperand, value ); +} + +bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} +#endif + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ +#if defined(OSX) + int64 retVal = *pDest; + if ( OSAtomicCompareAndSwap64( comperand, value, pDest ) ) + retVal = *pDest; + + return retVal; +#else + return __sync_val_compare_and_swap( pDest, comperand, value ); +#endif +} + +bool ThreadInterlockedAssignIf64( int64 volatile * pDest, int64 value, int64 comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} + + +#elif defined( _PS3 ) + +// This is defined in the header! + +#else +// This will perform horribly, +#error "Falling back to mutexed interlocked operations, you really don't have intrinsics you can use?"ß +CThreadMutex g_InterlockedMutex; + +long ThreadInterlockedIncrement( long volatile *pDest ) +{ + AUTO_LOCK( g_InterlockedMutex ); + return ++(*pDest); +} + +long ThreadInterlockedDecrement( long volatile *pDest ) +{ + AUTO_LOCK( g_InterlockedMutex ); + return --(*pDest); +} + +long ThreadInterlockedExchange( long volatile *pDest, long value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + *pDest = value; + return retVal; +} + +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + void *retVal = *pDest; + *pDest = value; + return retVal; +} + +long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + *pDest += value; + return retVal; +} + +long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + +void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) +{ + AUTO_LOCK( g_InterlockedMutex ); + void *retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + AUTO_LOCK( g_InterlockedMutex ); + int64 retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + +#endif + +int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) +{ + Assert( (size_t)pDest % 8 == 0 ); + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old); + + return Old; +} + + +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && defined(THREAD_PROFILER) +void ThreadNotifySyncNoop(void *p) {} + +#define MAP_THREAD_PROFILER_CALL( from, to ) \ + void from(void *p) \ + { \ + static CDynamicFunction dynFunc( "libittnotify.dll", #to, ThreadNotifySyncNoop ); \ + (*dynFunc)(p); \ + } + +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncPrepare, __itt_notify_sync_prepare ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncCancel, __itt_notify_sync_cancel ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncAcquired, __itt_notify_sync_acquired ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncReleasing, __itt_notify_sync_releasing ); + +#endif + +//----------------------------------------------------------------------------- +// +// CThreadMutex +// +//----------------------------------------------------------------------------- + +#ifdef _PS3 +CThreadMutex::CThreadMutex() +{ + // sys_mutex with recursion enabled is like a win32 critical section + sys_mutex_attribute_t mutexAttr; + sys_mutex_attribute_initialize( mutexAttr ); + mutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; + sys_mutex_create( &m_Mutex, &mutexAttr ); +} +CThreadMutex::~CThreadMutex() +{ + sys_mutex_destroy( m_Mutex ); +} +#elif !defined( POSIX ) +CThreadMutex::CThreadMutex() +{ +#ifdef THREAD_MUTEX_TRACING_ENABLED + memset( &m_CriticalSection, 0, sizeof(m_CriticalSection) ); +#endif + InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION *)&m_CriticalSection, 4000); +#ifdef THREAD_MUTEX_TRACING_SUPPORTED + // These need to be initialized unconditionally in case mixing release & debug object modules + // Lock and unlock may be emitted as COMDATs, in which case may get spurious output + m_currentOwnerID = m_lockCount = 0; + m_bTrace = false; +#endif +} + +CThreadMutex::~CThreadMutex() +{ + DeleteCriticalSection((CRITICAL_SECTION *)&m_CriticalSection); +} +#endif // !POSIX + +#ifdef IS_WINDOWS_PC +typedef BOOL (WINAPI*TryEnterCriticalSectionFunc_t)(LPCRITICAL_SECTION); +static CDynamicFunction DynTryEnterCriticalSection( "Kernel32.dll", "TryEnterCriticalSection" ); +#elif defined( _X360 ) +#define DynTryEnterCriticalSection TryEnterCriticalSection +#endif + +bool CThreadMutex::TryLock() +{ +#if defined( MSVC ) +#ifdef THREAD_MUTEX_TRACING_ENABLED + uint thisThreadID = ThreadGetCurrentId(); + if ( m_bTrace && m_currentOwnerID && ( m_currentOwnerID != thisThreadID ) ) + Msg( "Thread %u about to try-wait for lock %x owned by %u\n", ThreadGetCurrentId(), (CRITICAL_SECTION *)&m_CriticalSection, m_currentOwnerID ); +#endif + if ( DynTryEnterCriticalSection != NULL ) + { + if ( (*DynTryEnterCriticalSection )( (CRITICAL_SECTION *)&m_CriticalSection ) != FALSE ) + { +#ifdef THREAD_MUTEX_TRACING_ENABLED + if (m_lockCount == 0) + { + // we now own it for the first time. Set owner information + m_currentOwnerID = thisThreadID; + if ( m_bTrace ) + Msg( "Thread %u now owns lock 0x%x\n", m_currentOwnerID, (CRITICAL_SECTION *)&m_CriticalSection ); + } + m_lockCount++; +#endif + return true; + } + return false; + } + Lock(); + return true; +#elif defined( _PS3 ) + +#ifndef NO_THREAD_SYNC + if ( sys_mutex_trylock( m_Mutex ) == CELL_OK ) +#endif + + return true; + + return false; // ?? moved from EA code + +#elif defined( POSIX ) + return pthread_mutex_trylock( &m_Mutex ) == 0; +#else +#error "Implement me!" + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +// CThreadFastMutex +// +//----------------------------------------------------------------------------- + +#ifdef THREAD_FAST_MUTEX_TIMINGS +// This is meant to be used in combination with breakpoints and in-debugee, so we turn the optimizer off +#pragma optimize( "", off ) +CThreadFastMutex *g_pIgnoredMutexes[256]; // Ignore noisy non-problem mutex. Probably could be an array. Right now needed only for sound thread +float g_MutexTimingTolerance = 5; +bool g_bMutexTimingOutput; + +void TrapMutexTimings( uint32 probableBlocker, uint32 thisThread, volatile CThreadFastMutex *pMutex, CFastTimer &spikeTimer, CAverageCycleCounter &sleepTimer ) +{ + spikeTimer.End(); + if ( spikeTimer.GetDuration().GetMillisecondsF() > g_MutexTimingTolerance ) + { + bool bIgnore = false; + for ( int j = 0; j < ARRAYSIZE( g_pIgnoredMutexes ) && g_pIgnoredMutexes[j]; j++ ) + { + if ( g_pIgnoredMutexes[j] == pMutex ) + { + bIgnore = true; + break; + } + } + + if ( !bIgnore && spikeTimer.GetDuration().GetMillisecondsF() < 100 ) + { + volatile float FastMutexDuration = spikeTimer.GetDuration().GetMillisecondsF(); + volatile float average = sleepTimer.GetAverageMilliseconds(); + volatile float peak = sleepTimer.GetPeakMilliseconds(); volatile int xx = 6; + if ( g_bMutexTimingOutput ) + { + char szBuf[256]; + Msg( "M (%.8x): [%.8x <-- %.8x] (%f,%f,%f)\n", pMutex, probableBlocker, thisThread, FastMutexDuration, average, peak ); + } + } + } +} + +#else +#define TrapMutexTimings( a, b, c, d, e ) ((void)0) +#endif + +//------------------------------------- + +#define THREAD_SPIN (8*1024) + +void CThreadFastMutex::Lock( const uint32 threadId, unsigned nSpinSleepTime ) volatile +{ +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageCycleCounter sleepTimer; + CFastTimer spikeTimer; + uint32 currentOwner = m_ownerID; + spikeTimer.Start(); + sleepTimer.Init(); +#endif + int i; + if ( nSpinSleepTime != TT_INFINITE ) + { + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageTimeMarker marker( &sleepTimer ); +#endif + ThreadSleep( 0 ); + } + } + +#ifdef _WIN32 + if ( !nSpinSleepTime && GetThreadPriority( GetCurrentThread() ) > THREAD_PRIORITY_NORMAL ) + { + nSpinSleepTime = 1; + } +#endif + + if ( nSpinSleepTime ) + { + for ( i = THREAD_SPIN; i != 0; --i ) + { +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageTimeMarker marker( &sleepTimer ); +#endif + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + } + + for ( ;; ) + { +#ifdef THREAD_FAST_MUTEX_TIMINGS + CAverageTimeMarker marker( &sleepTimer ); +#endif + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + + ThreadPause(); + ThreadSleep( nSpinSleepTime ); + } + } + else + { + for ( ;; ) + { + if ( TryLock( threadId ) ) + { + TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); + return; + } + + ThreadPause(); + } + } +} + +#ifdef THREAD_FAST_MUTEX_TIMINGS +#pragma optimize( "", on ) +#endif + +//----------------------------------------------------------------------------- +// +// CThreadRWLock +// +//----------------------------------------------------------------------------- + +void CThreadRWLock::WaitForRead() +{ + m_nPendingReaders++; + + do + { + m_mutex.Unlock(); + m_CanRead.Wait(); + m_mutex.Lock(); + } + while (m_nWriters); + + m_nPendingReaders--; +} + + +void CThreadRWLock::LockForWrite() +{ + m_mutex.Lock(); + bool bWait = ( m_nWriters != 0 || m_nActiveReaders != 0 ); + m_nWriters++; + m_CanRead.Reset(); + m_mutex.Unlock(); + + if ( bWait ) + { + m_CanWrite.Wait(); + } +} + +void CThreadRWLock::UnlockWrite() +{ + m_mutex.Lock(); + m_nWriters--; + if ( m_nWriters == 0) + { + if ( m_nPendingReaders ) + { + m_CanRead.Set(); + } + } + else + { + m_CanWrite.Set(); + } + m_mutex.Unlock(); +} + +//----------------------------------------------------------------------------- +// +// CThreadSpinRWLock +// +//----------------------------------------------------------------------------- +#ifndef OLD_SPINRWLOCK + +void CThreadSpinRWLock::SpinLockForWrite() +{ + int i; + + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForWrite_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +void CThreadSpinRWLock::SpinLockForRead() +{ + int i; + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForRead_UnforcedInline() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +#else +/* (commented out to reduce distraction in colorized editor, remove entirely when new implementation settles) +void CThreadSpinRWLock::SpinLockForWrite( const uint32 threadId ) +{ + int i; + + if ( TryLockForWrite( threadId ) ) + { + return; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +void CThreadSpinRWLock::LockForRead() +{ + int i; + if ( TryLockForRead() ) + { + return; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead() ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLockForRead() ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if ( TryLockForRead() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForRead() ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +void CThreadSpinRWLock::UnlockRead() +{ + int i; + + Assert( m_lockInfo.m_nReaders > 0 && m_lockInfo.m_writerId == 0 ); + + //uint32 nLockInfoReaders = m_lockInfo.m_nReaders; + LockInfo_t oldValue; + LockInfo_t newValue; + + if( IsX360() ) + { + // this is the code equivalent to original code (see below) that doesn't cause LHS on Xbox360 + // WARNING: This code assumes BIG Endian CPU + oldValue.m_i64 = uint32( m_lockInfo.m_nReaders ); + newValue.m_i64 = oldValue.m_i64 - 1; // NOTE: when we have -1 (or 0xFFFFFFFF) readers, this will result in non-equivalent code + } + else + { + // this is the original code that worked here for a while + oldValue.m_nReaders = m_lockInfo.m_nReaders; + oldValue.m_writerId = 0; + newValue.m_nReaders = oldValue.m_nReaders - 1; + newValue.m_writerId = 0; + } + ThreadMemoryBarrier(); + if( AssignIf( newValue, oldValue ) ) + return; + + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + if ( i % 512 == 0 ) + { + ThreadSleep( 0 ); + } + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( i = THREAD_SPIN * 4; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 0 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 1 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } +} + +void CThreadSpinRWLock::UnlockWrite() +{ + Assert( m_lockInfo.m_writerId == ThreadGetCurrentId() && m_lockInfo.m_nReaders == 0 ); + static const LockInfo_t newValue = { { 0, 0 } }; + ThreadMemoryBarrier(); + ThreadInterlockedExchange64( (int64 *)&m_lockInfo, *((int64 *)&newValue) ); + m_nWriters--; +} +*/ +#endif + +#if defined( _PS3 ) +// All CThread code is inline in the header for PS3 + +// This function is implemented here rather than the header because g_pCurThread resolves to GetCurThread() on PS3 +// and we don't want to create a dependency on the ELF stub for everyone who includes the header. +PLATFORM_INTERFACE CThread *GetCurThreadPS3() +{ + return (CThread*)g_pCurThread; +} + +PLATFORM_INTERFACE void SetCurThreadPS3( CThread *pThread ) +{ + g_pCurThread = pThread; +} +#else +#include +// The CThread implementation needs to be inlined for performance on the PS3 - It makes a difference of more than 1ms/frame +// for other platforms, we include the .inl in the .cpp file where it existed before +#include "../public/tier0/threadtools.inl" +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CWorkerThread::CWorkerThread() +: m_EventSend(true), // must be manual-reset for PeekCall() + m_EventComplete(true), // must be manual-reset to handle multiple wait with thread properly + m_Param(0), + m_ReturnVal(0) +{ +} + +//--------------------------------------------------------- + +int CWorkerThread::CallWorker(unsigned dw, unsigned timeout, bool fBoostWorkerPriorityToMaster) +{ + return Call(dw, timeout, fBoostWorkerPriorityToMaster); +} + +//--------------------------------------------------------- + +int CWorkerThread::CallMaster(unsigned dw, unsigned timeout) +{ + return Call(dw, timeout, false); +} + +//--------------------------------------------------------- + +CThreadEvent &CWorkerThread::GetCallHandle() +{ + return m_EventSend; +} + +//--------------------------------------------------------- + +unsigned CWorkerThread::GetCallParam() const +{ + return m_Param; +} + +//--------------------------------------------------------- + +int CWorkerThread::BoostPriority() +{ + int iInitialPriority = GetPriority(); + +#ifdef WIN32 + const int iNewPriority = ThreadGetPriority( GetThreadHandle() ); + if (iNewPriority > iInitialPriority) + ThreadSetPriority( GetThreadHandle(), iNewPriority); +#elif !defined( _PS3 ) + const int iNewPriority = ThreadGetPriority( (ThreadHandle_t)GetThreadID() ); + if (iNewPriority > iInitialPriority) + ThreadSetPriority( (ThreadHandle_t)GetThreadID(), iNewPriority); +#endif + + return iInitialPriority; +} + +//--------------------------------------------------------- +static uint32 DefaultWaitFunc( uint32 nHandles, CThreadEvent** ppHandles, int bWaitAll, uint32 timeout ) +{ + return CThreadEvent::WaitForMultiple( nHandles, ppHandles, bWaitAll!=0, timeout ) ; +} + + +int CWorkerThread::Call(unsigned dwParam, unsigned timeout, bool fBoostPriority, WaitFunc_t waitFunc) +{ + AssertMsg(!m_EventSend.Check(), "Cannot perform call if there's an existing call pending" ); + + AUTO_LOCK( m_Lock ); + + if (!IsAlive()) + return WTCR_FAIL; + + int iInitialPriority = 0; + if (fBoostPriority) + { + iInitialPriority = BoostPriority(); + } + + // set the parameter, signal the worker thread, wait for the completion to be signaled + m_Param = dwParam; + + m_EventComplete.Reset(); + m_EventSend.Set(); + + WaitForReply( timeout, waitFunc ); + + if (fBoostPriority) + SetPriority(iInitialPriority); + + return m_ReturnVal; +} + +//--------------------------------------------------------- +// +// Wait for a request from the client +// +//--------------------------------------------------------- +int CWorkerThread::WaitForReply( unsigned timeout ) +{ + return WaitForReply( timeout, NULL ); +} + +int CWorkerThread::WaitForReply( unsigned timeout, WaitFunc_t pfnWait ) +{ + if (!pfnWait) + { + pfnWait = &DefaultWaitFunc; + } + + CThreadEvent *waits[] = + { + &m_EventComplete, + &m_ExitEvent + }; + + unsigned result; + bool bInDebugger = Plat_IsInDebugSession(); + + uint32 dwActualTimeout = ( (timeout==TT_INFINITE) ? 30000 : timeout ); + + do + { +#ifdef WIN32 + // Make sure the thread handle hasn't been closed + if ( !GetThreadHandle() ) + { + result = 1; + break; + } +#endif + + result = (*pfnWait)( ARRAYSIZE( waits ), waits, false, dwActualTimeout ); + + AssertMsg(timeout != TT_INFINITE || result != TW_TIMEOUT, "Possible hung thread, call to thread timed out"); + + } while ( bInDebugger && ( timeout == TT_INFINITE && result == TW_TIMEOUT ) ); + + if ( result != 0 ) + { + if (result == TW_TIMEOUT) + { + m_ReturnVal = WTCR_TIMEOUT; + } + else if (result == 1) + { + DevMsg( 2, "Thread failed to respond, probably exited\n"); + m_EventSend.Reset(); + m_ReturnVal = WTCR_TIMEOUT; + } + else + { + m_EventSend.Reset(); + m_ReturnVal = WTCR_THREAD_GONE; + } + } + + return m_ReturnVal; +} + + +//--------------------------------------------------------- +// +// Wait for a request from the client +// +//--------------------------------------------------------- + +bool CWorkerThread::WaitForCall(unsigned * pResult) +{ + return WaitForCall(TT_INFINITE, pResult); +} + +//--------------------------------------------------------- + +bool CWorkerThread::WaitForCall(unsigned dwTimeout, unsigned * pResult) +{ + bool returnVal = m_EventSend.Wait(dwTimeout); + if (pResult) + *pResult = m_Param; + return returnVal; +} + +//--------------------------------------------------------- +// +// is there a request? +// + +bool CWorkerThread::PeekCall(unsigned * pParam) +{ + if (!m_EventSend.Check()) + { + return false; + } + else + { + if (pParam) + { + *pParam = m_Param; + } + return true; + } +} + +//--------------------------------------------------------- +// +// Reply to the request +// + +void CWorkerThread::Reply(unsigned dw) +{ + m_Param = 0; + m_ReturnVal = dw; + + // The request is now complete so PeekCall() should fail from + // now on + // + // This event should be reset BEFORE we signal the client + m_EventSend.Reset(); + + // Tell the client we're finished + m_EventComplete.Set(); +} + + +//----------------------------------------------------------------------------- + + +#if defined( _PS3 ) + +/******************************************************************************* +* PS3 equivalent to Win32 function for setting events +*******************************************************************************/ +BOOL SetEvent( CThreadEvent *pEvent ) +{ + bool bRetVal = pEvent->Set(); + if ( !bRetVal ) + Assert(0); + + return bRetVal; +} + +/******************************************************************************* +* PS3 equivalent to Win32 function for resetting events +*******************************************************************************/ +BOOL ResetEvent( CThreadEvent *pEvent ) +{ + return pEvent->Reset(); +} + +#define MAXIMUM_WAIT_OBJECTS 64 + +/******************************************************************************* +* Wait for a selection of events to terminate +*******************************************************************************/ +DWORD WaitForMultipleObjects( DWORD nCount, CThreadEvent **lppHandles, BOOL bWaitAll, DWORD dwMilliseconds ) +{ + ////////////////////////////////////////////////////////////// +#ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS + ////////////////////////////////////////////////////////////// + + + // Support for a limited amount of events + if ( nCount >= MAXIMUM_WAIT_OBJECTS ) + { + Assert(0); + return false; + } + + bool bRunning = true; + unsigned int result = TW_FAILED; + + // For bWaitAll + int numEvent = 0; + int eventComplete[ MAXIMUM_WAIT_OBJECTS ] = {0}; + + uint64_t timeDiffMS = 0; + uint64_t startTimeMS = Plat_MSTime(); + uint64_t endTimeMS = 0; + + while ( bRunning ) + { + // Check for a timeout + if ( bRunning && ( dwMilliseconds != INFINITE ) && ( timeDiffMS > dwMilliseconds ) ) + { + result = TW_TIMEOUT; + bRunning = false; + } + + // Wait for all the events to be set + if ( bWaitAll ) + { + for ( int event = 0; event < nCount; ++event ) + { + if ( lppHandles[event]->Wait(1) ) + { + // If an event is complete, mark it as complete in our list + if ( eventComplete[ event ] == 0 ) + { + numEvent++; + eventComplete[ event ] = 1; + } + } + } + + // If all the events have been set, terminate the function + if ( numEvent >= nCount ) + { + result = WAIT_OBJECT_0; + bRunning = false; + } + } + + // Wait for one event to be set + else + { + for ( int event = 0; event < nCount; ++event ) + { + if ( lppHandles[event]->Wait(1) ) + { + result = WAIT_OBJECT_0 + event; + bRunning = false; + break; + } + } + } + + endTimeMS = Plat_MSTime(); + timeDiffMS = endTimeMS - startTimeMS; + } + + return result; + + + + ////////////////////////////////////////////////////////////// +#else // NEW_WAIT_FOR_MULTIPLE_OBJECTS // (expected PS3 only) + ////////////////////////////////////////////////////////////// +#ifndef _PS3 +#error This code was written expecting to be run on PS3. +#endif + + // check if we have a wait objects semaphore + if (!gbWaitObjectsCreated) + { + sys_semaphore_attribute_t semAttr; + sys_semaphore_attribute_initialize(semAttr); + sys_semaphore_create(&gWaitObjectsSemaphore, &semAttr, 0, 0xFFFF); + + gbWaitObjectsCreated = true; + } + + // Support for a limited amount of events + if ( nCount >= MAXIMUM_WAIT_OBJECTS ) + { + Assert(0); + return false; + } + + unsigned int result = WAIT_FAILED; + int res = CELL_OK; + int event = -1; + int numEvent = 0; + + // run through events registering this thread with each one + for (int i = 0; i < nCount; i++) + { + lppHandles[i]->RegisterWaitingThread(&gWaitObjectsSemaphore, i, &event); + } + + + // in the Source API, a timeOut of 0 means very short timeOut, not (as in the PS3 spec) an infinite timeout. + // TT_INFINITE is #defined to 2^31-1, which means "infinite timeout" on PC and "72 minutes, 35 seconds" on PS3. + // conversely, the code below (around deltaTime) expects to be able to compare against the timeout + // value given here, so we cannot just replace 0 with 1 and TT_INFINITE with 0. + // So, we replace 0 with 1, meaning "a very short time", and test for the special value TT_INFINITE + // at the moment of calling sys_semaphore_wait, where we replace it with the real "infinite timeout" + // value. It isn't safe to simply increase the declaration size of TT_INFINITE, because as you can + // see it is often assigned to uint32s. + // Also, Source timeouts are specified in milliseconds, and PS3 timeouts are in microseconds, + // so we need to multiply by one thousand. + uint32 timeOut = dwMilliseconds; + if ( timeOut == 0 ) + { + timeOut = 1; + } + else if ( timeOut != TT_INFINITE ) + { + timeOut *= 1000; + // note that it's impossible for dwMilliseconds * 1000 + // to coincidentally equal TT_INFINITE since TT_INFINITE + // is not divisible by 1000. + COMPILE_TIME_ASSERT( TT_INFINITE % 1000 != 0 ); + } + + COMPILE_TIME_ASSERT( TT_INFINITE != 0 ); // The code here was written expecting (working around) that TT_INFINITE is + // MAXINT, so if you changed this number, please read the comment above and + // carefully examine the code here to make sure that timeouts still work + // correctly on PS3. Be aware that in many places in Source, a timeout of + // 0 has some special meaning other than "infinite timeout", so track those + // down too. + + + // Wait for all the events to be set + if ( bWaitAll ) + { + while (numEvent < nCount) + { + uint64_t deltaTime = Timer_GetTimeUS(); + + res = sys_semaphore_wait(gWaitObjectsSemaphore, timeOut == TT_INFINITE ? 0 : timeOut ); + + deltaTime = Timer_GetTimeUS() - deltaTime; + + if (res == ETIMEDOUT) + { + result = TW_TIMEOUT; + break; + } + else if (res == CELL_OK) + { + numEvent++; + + if (deltaTime >= timeOut) + { + // note - if this is not truly a time out + // then it will be set to WAIT_OBJECT_0 + // after this loop + result = TW_TIMEOUT; + break; + } + else + { + timeOut -= deltaTime; + } + } + else + { + result = TW_FAILED; + break; + } + } + + if (numEvent >= nCount) + { + result = WAIT_OBJECT_0; + } + } + else // Wait for one event to be set + { + // no event fired yet, wait on semaphore + res = sys_semaphore_wait( gWaitObjectsSemaphore, timeOut == TT_INFINITE ? 0 : timeOut ); + + if (res == ETIMEDOUT) + { + result = TW_TIMEOUT; + } + else if (res == CELL_OK) + { + if ((event < 0) || (event >= nCount)) + { + DEBUG_ERROR("Bad event\n"); + } + + result = WAIT_OBJECT_0 + event; + } + } + + // run through events unregistering this thread, for benefit + // of those events that did not fire, or fired before semaphore + // was registered + for (int i = 0; i < nCount; i++) + { + lppHandles[i]->UnregisterWaitingThread(&gWaitObjectsSemaphore); + } + + // reset semaphore + while (sys_semaphore_trywait(gWaitObjectsSemaphore) != EBUSY); + + return result; + + + ////////////////////////////////////////////////////////////// +#endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS + ////////////////////////////////////////////////////////////// +} + +#endif diff --git a/tier0/tier0_strtools.cpp b/tier0/tier0_strtools.cpp new file mode 100644 index 0000000..8a52921 --- /dev/null +++ b/tier0/tier0_strtools.cpp @@ -0,0 +1,41 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" +#include "tier0_strtools.h" + +#define TOLOWERC(x) ((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 32) : (x)) + +extern "C" int V_tier0_stricmp(const char *s1, const char *s2) { + uint8 const *pS1 = (uint8 const *)s1; + uint8 const *pS2 = (uint8 const *)s2; + for (;;) { + int c1 = *(pS1++); + int c2 = *(pS2++); + if (c1 == c2) { + if (!c1) return 0; + } else { + if (!c2) { + return c1 - c2; + } + c1 = TOLOWERC(c1); + c2 = TOLOWERC(c2); + if (c1 != c2) { + return c1 - c2; + } + } + c1 = *(pS1++); + c2 = *(pS2++); + if (c1 == c2) { + if (!c1) return 0; + } else { + if (!c2) { + return c1 - c2; + } + c1 = TOLOWERC(c1); + c2 = TOLOWERC(c2); + if (c1 != c2) { + return c1 - c2; + } + } + } +} diff --git a/tier0/tier0_strtools.h b/tier0/tier0_strtools.h new file mode 100644 index 0000000..dc3faf7 --- /dev/null +++ b/tier0/tier0_strtools.h @@ -0,0 +1,8 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_TIER0_TIER0_STRTOOLS_H_ +#define VPC_TIER0_TIER0_STRTOOLS_H_ + +extern "C" int V_tier0_stricmp(const char *s1, const char *s2); + +#endif // !VPC_TIER0_TIER0_STRTOOLS_H_ \ No newline at end of file diff --git a/tier0/valobject.cpp b/tier0/valobject.cpp new file mode 100644 index 0000000..230d8a0 --- /dev/null +++ b/tier0/valobject.cpp @@ -0,0 +1,106 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "pch_tier0.h" +#include "vstdlib/pch_vstdlib.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +#ifdef DBGFLAG_VALIDATE + +//----------------------------------------------------------------------------- +// Purpose: Initializer +// Input: pchType - Type of the object we represent. +// WARNING: pchType must be a +//static (since we keep a copy of it around for a while) pvObj - +//Pointer to the object we represent +// pchName - Name of the individual object we +//represent WARNING: pchName must be a static (since we keep a copy of it around +//for a while) +// pValObjectparent- Our parent object (ie, the object +//that our object is a member of) pValObjectPrev - Object that precedes us +//in the linked list (we're always added to the end) +//----------------------------------------------------------------------------- +void CValObject::Init(tchar *pchType, void *pvObj, tchar *pchName, + CValObject *pValObjectParent, + CValObject *pValObjectPrev) { + m_nUser = 0; + + // Initialize pchType: + if (NULL != pchType) { + V_strncpy(m_rgchType, pchType, + (int)(sizeof(m_rgchType) / sizeof(*m_rgchType))); + } else { + m_rgchType[0] = '\0'; + } + + m_pvObj = pvObj; + + // Initialize pchName: + if (NULL != pchName) { + V_strncpy(m_rgchName, pchName, sizeof(m_rgchName) / sizeof(*m_rgchName)); + } else { + m_rgchName[0] = NULL; + } + + m_pValObjectParent = pValObjectParent; + + if (NULL == pValObjectParent) + m_nLevel = 0; + else + m_nLevel = pValObjectParent->NLevel() + 1; + + m_cpubMemSelf = 0; + m_cubMemSelf = 0; + m_cpubMemTree = 0; + m_cubMemTree = 0; + + // Insert us at the back of the linked list + if (NULL != pValObjectPrev) { + Assert(NULL == pValObjectPrev->m_pValObjectNext); + pValObjectPrev->m_pValObjectNext = this; + } + m_pValObjectNext = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CValObject::~CValObject() {} + +//----------------------------------------------------------------------------- +// Purpose: The object we represent has claimed direct ownership of a block of +// memory. Record that we own it. +// Input: pvMem - Address of the memory block +//----------------------------------------------------------------------------- +void CValObject::ClaimMemoryBlock(void *pvMem) { + // Get the memory block header + CMemBlockHdr *pMemBlockHdr = CMemBlockHdr::PMemBlockHdrFromPvUser(pvMem); + pMemBlockHdr->CheckValid(); + + // Update our counters + m_cpubMemSelf++; + m_cubMemSelf += pMemBlockHdr->CubUser(); + m_cpubMemTree++; + m_cubMemTree += pMemBlockHdr->CubUser(); + + // If we have a parent object, let it know about the memory (it'll recursively + // call up the tree) + if (NULL != m_pValObjectParent) + m_pValObjectParent->ClaimChildMemoryBlock(pMemBlockHdr->CubUser()); +} + +//----------------------------------------------------------------------------- +// Purpose: A child of ours has claimed ownership of a memory block. Make +// a note of it, and pass the message back up the tree. +// Input: cubUser - Size of the memory block +//----------------------------------------------------------------------------- +void CValObject::ClaimChildMemoryBlock(int cubUser) { + m_cpubMemTree++; + m_cubMemTree += cubUser; + + if (NULL != m_pValObjectParent) + m_pValObjectParent->ClaimChildMemoryBlock(cubUser); +} + +#endif // DBGFLAG_VALIDATE \ No newline at end of file diff --git a/tier0/vprof.cpp b/tier0/vprof.cpp new file mode 100644 index 0000000..f0384d0 --- /dev/null +++ b/tier0/vprof.cpp @@ -0,0 +1,1593 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Real-Time Hierarchical Profiling + +#include "pch_tier0.h" + +#include "tier0/memalloc.h" +#include "tier0/valve_off.h" + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#endif + +#include + +#ifdef _WIN32 +#pragma warning(disable : 4073) +#pragma init_seg(lib) +#endif + +#include +#include +#include + +#include "tier0/valve_on.h" +#include "tier0/vprof.h" +#include "tier0/l2cache.h" +#include "strtools.h" + +#ifdef _X360 + +#include "xbox/xbox_console.h" + +#elif defined(_PS3) +#include "ps3/ps3_console.h" + +#else // NOT _X360: + +#include "tier0/memdbgon.h" + +#endif + +// NOTE: Explicitly and intentionally using STL in here to not generate any +// cyclical dependencies between the low-level debug library and the higher +// level data structures (toml 01-27-03) +using namespace std; + +#ifdef VPROF_ENABLED + +#if defined(_X360) && !defined(_CERT) // enable PIX CPU trace: +#include "tracerecording.h" +#pragma comment(lib, "tracerecording.lib") +#pragma comment(lib, "xbdm.lib") +#endif + +//----------------------------------------------------------------------------- +bool g_VProfSignalSpike; + +//----------------------------------------------------------------------------- + +CVProfile g_VProfCurrentProfile; + +int CVProfNode::s_iCurrentUniqueNodeID = 0; + +CVProfNode::~CVProfNode() { +#if !defined(_WIN32) && !defined(POSIX) + delete m_pChild; + delete m_pSibling; +#endif +} + +CVProfNode *CVProfNode::GetSubNode(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName, + int budgetFlags) { + // Try to find this sub node + CVProfNode *child = m_pChild; + while (child) { + if (child->m_pszName == pszName) { + return child; + } + child = child->m_pSibling; + } + + // We didn't find it, so add it + CVProfNode *node = + new CVProfNode(pszName, detailLevel, this, pBudgetGroupName, budgetFlags); + node->m_pSibling = m_pChild; + m_pChild = node; + return node; +} + +CVProfNode *CVProfNode::GetSubNode(const tchar *pszName, int detailLevel, + const tchar *pBudgetGroupName) { + return GetSubNode(pszName, detailLevel, pBudgetGroupName, BUDGETFLAG_OTHER); +} + +//------------------------------------- + +void CVProfNode::EnterScope() { + m_nCurFrameCalls++; + if (m_nRecursions++ == 0) { + m_Timer.Start(); +#ifndef _X360 + if (g_VProfCurrentProfile.UsePME()) { + m_L2Cache.Start(); + } +#else // 360 code: + if (g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0)) { + m_PMCData.Start(); + } + + if ((m_iBitFlags & kCPUTrace) != 0) { + // this node is to be recorded. Which recording mode are we in? + switch (g_VProfCurrentProfile.GetCPUTraceMode()) { + case CVProfile::kFirstHitNode: + case CVProfile::kAllNodesInFrame_Recording: + case CVProfile::kAllNodesInFrame_RecordingMultiFrame: + // we are presently recording. + if (!XTraceStartRecording( + g_VProfCurrentProfile.GetCPUTraceFilename())) { + Msg("XTraceStartRecording failed, error code %d\n", GetLastError()); + } + + default: + // no default. + break; + } + } + +#endif + +#ifdef VPROF_VTUNE_GROUP + g_VProfCurrentProfile.PushGroup(m_BudgetGroupID); +#endif + } +} + +//------------------------------------- + +bool CVProfNode::ExitScope() { + if (--m_nRecursions == 0 && m_nCurFrameCalls != 0) { + m_Timer.End(); + m_CurFrameTime += m_Timer.GetDuration(); +#ifndef _X360 + if (g_VProfCurrentProfile.UsePME()) { + m_L2Cache.End(); + m_iCurL2CacheMiss += m_L2Cache.GetL2CacheMisses(); + } +#else // 360 code: + if (g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0)) { + m_PMCData.End(); + m_iCurL2CacheMiss += m_PMCData.GetL2CacheMisses(); + m_iCurLoadHitStores += m_PMCData.GetLHS(); + } + + if ((m_iBitFlags & kCPUTrace) != 0) { + // this node is enabled to be recorded. What mode are we in? + switch (g_VProfCurrentProfile.GetCPUTraceMode()) { + case CVProfile::kFirstHitNode: { + // one-off recording. stop now. + if (XTraceStopRecording()) { + Msg("CPU trace finished.\n"); + if (g_VProfCurrentProfile.TraceCompleteEvent()) { + // signal VXConsole that trace is completed + XBX_rTraceComplete(); + } + } + // don't trace again next frame, overwriting the file. + g_VProfCurrentProfile.SetCPUTraceEnabled(CVProfile::kDisabled); + break; + } + + case CVProfile::kAllNodesInFrame_Recording: + case CVProfile::kAllNodesInFrame_RecordingMultiFrame: { + // one-off recording. stop now. + if (XTraceStopRecording()) { + if (g_VProfCurrentProfile.GetCPUTraceMode() == + CVProfile::kAllNodesInFrame_RecordingMultiFrame) { + Msg("%.3f msec in %s\n", m_CurFrameTime.GetMillisecondsF(), + g_VProfCurrentProfile.GetCPUTraceFilename()); + } else { + Msg("CPU trace finished.\n"); + } + } + + // Spew time info for file to allow figuring it out later + g_VProfCurrentProfile.LatchMultiFrame(m_CurFrameTime.GetLongCycles()); + +#if 0 // This doesn't want to work on the xbox360-- MoveFile not available or + // file still being put down to disk? + char suffix[ 32 ]; + _snprintf( suffix, sizeof( suffix ), "_%.3f_msecs", flMsecs ); + + char fn[ 512 ]; + + strncpy( fn, g_VProfCurrentProfile.GetCPUTraceFilename(), sizeof( fn ) ); + + char *p = strrchr( fn, '.' ); + if ( *p ) + { + *p = 0; + } + strncat( fn, suffix, sizeof( fn ) ); + strncat( fn, ".pix2", sizeof( fn ) ); + + BOOL bSuccess = MoveFile( g_VProfCurrentProfile.GetCPUTraceFilename(), fn ); + if ( !bSuccess ) + { + DWORD eCode = GetLastError(); + Msg( "Error %d\n", eCode ); + } +#endif + + // we're still recording until the frame is done. + // but, increment the index. + g_VProfCurrentProfile.IncrementMultiTraceIndex(); + break; + } + } + + // g_VProfCurrentProfile.IsCPUTraceEnabled() && + } + +#endif + +#ifdef VPROF_VTUNE_GROUP + g_VProfCurrentProfile.PopGroup(); +#endif + } + return (m_nRecursions == 0); +} + +//------------------------------------- + +void CVProfNode::Pause() { + if (m_nRecursions > 0) { + m_Timer.End(); + m_CurFrameTime += m_Timer.GetDuration(); + +#ifndef _X360 + if (g_VProfCurrentProfile.UsePME()) { + m_L2Cache.End(); + m_iCurL2CacheMiss += m_L2Cache.GetL2CacheMisses(); + } +#else // 360 code: + if (g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0)) { + m_PMCData.End(); + m_iCurL2CacheMiss += m_PMCData.GetL2CacheMisses(); + m_iCurLoadHitStores += m_PMCData.GetLHS(); + } +#endif + } + if (m_pChild) { + m_pChild->Pause(); + } + if (m_pSibling) { + m_pSibling->Pause(); + } +} + +//------------------------------------- + +void CVProfNode::Resume() { + if (m_nRecursions > 0) { + m_Timer.Start(); + +#ifndef _X360 + if (g_VProfCurrentProfile.UsePME()) { + m_L2Cache.Start(); + } +#else + if (g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0)) { + m_PMCData.Start(); + } +#endif + } + if (m_pChild) { + m_pChild->Resume(); + } + if (m_pSibling) { + m_pSibling->Resume(); + } +} + +//------------------------------------- + +void CVProfNode::Reset() { + m_nPrevFrameCalls = 0; + m_PrevFrameTime.Init(); + + m_nCurFrameCalls = 0; + m_CurFrameTime.Init(); + + m_nTotalCalls = 0; + m_TotalTime.Init(); + + m_PeakTime.Init(); + + m_iPrevL2CacheMiss = 0; + m_iCurL2CacheMiss = 0; + m_iTotalL2CacheMiss = 0; + +#ifdef _X360 + m_iPrevLoadHitStores = 0; + m_iCurLoadHitStores = 0; + m_iTotalLoadHitStores = 0; +#endif + + if (m_pChild) { + m_pChild->Reset(); + } + if (m_pSibling) { + m_pSibling->Reset(); + } +} + +//------------------------------------- + +void CVProfNode::MarkFrame() { + m_nPrevFrameCalls = m_nCurFrameCalls; + m_PrevFrameTime = m_CurFrameTime; + m_iPrevL2CacheMiss = m_iCurL2CacheMiss; +#ifdef _X360 + m_iPrevLoadHitStores = m_iCurLoadHitStores; +#endif + m_nTotalCalls += m_nCurFrameCalls; + m_TotalTime += m_CurFrameTime; + + if (m_PeakTime.IsLessThan(m_CurFrameTime)) { + m_PeakTime = m_CurFrameTime; + } + + m_CurFrameTime.Init(); + m_nCurFrameCalls = 0; + m_iTotalL2CacheMiss += m_iCurL2CacheMiss; + m_iCurL2CacheMiss = 0; +#ifdef _X360 + m_iTotalLoadHitStores += m_iCurLoadHitStores; + m_iCurLoadHitStores = 0; +#endif + if (m_pChild) { + m_pChild->MarkFrame(); + } + if (m_pSibling) { + m_pSibling->MarkFrame(); + } +} + +//------------------------------------- + +void CVProfNode::ResetPeak() { + m_PeakTime.Init(); + + if (m_pChild) { + m_pChild->ResetPeak(); + } + if (m_pSibling) { + m_pSibling->ResetPeak(); + } +} + +void CVProfNode::SetCurFrameTime(unsigned long milliseconds) { + m_CurFrameTime.Init((float)milliseconds); +} +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our +//container) +//----------------------------------------------------------------------------- +void CVProfNode::Validate(CValidator &validator, tchar *pchName) { + validator.Push(_T("CVProfNode"), this, pchName); + + m_L2Cache.Validate(validator, _T("m_L2Cache")); + + if (m_pSibling) m_pSibling->Validate(validator, _T("m_pSibling")); + if (m_pChild) m_pChild->Validate(validator, _T("m_pChild")); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + +//----------------------------------------------------------------------------- + +struct TimeSums_t { + const tchar *pszProfileScope; + unsigned calls; + double time; + double timeLessChildren; + double peak; +}; + +static bool TimeCompare(const TimeSums_t &lhs, const TimeSums_t &rhs) { + return (lhs.time > rhs.time); +} + +static bool TimeLessChildrenCompare(const TimeSums_t &lhs, + const TimeSums_t &rhs) { + return (lhs.timeLessChildren > rhs.timeLessChildren); +} + +static bool PeakCompare(const TimeSums_t &lhs, const TimeSums_t &rhs) { + return (lhs.peak > rhs.peak); +} + +static bool AverageTimeCompare(const TimeSums_t &lhs, const TimeSums_t &rhs) { + double avgLhs = (lhs.calls) ? lhs.time / (double)lhs.calls : 0.0; + double avgRhs = (rhs.calls) ? rhs.time / (double)rhs.calls : 0.0; + return (avgLhs > avgRhs); +} + +static bool AverageTimeLessChildrenCompare(const TimeSums_t &lhs, + const TimeSums_t &rhs) { + double avgLhs = (lhs.calls) ? lhs.timeLessChildren / (double)lhs.calls : 0.0; + double avgRhs = (rhs.calls) ? rhs.timeLessChildren / (double)rhs.calls : 0.0; + return (avgLhs > avgRhs); +} +static bool PeakOverAverageCompare(const TimeSums_t &lhs, + const TimeSums_t &rhs) { + double avgLhs = (lhs.calls) ? lhs.time / (double)lhs.calls : 0.0; + double avgRhs = (rhs.calls) ? rhs.time / (double)rhs.calls : 0.0; + + double lhsPoA = (avgLhs != 0) ? lhs.peak / avgLhs : 0.0; + double rhsPoA = (avgRhs != 0) ? rhs.peak / avgRhs : 0.0; + + return (lhsPoA > rhsPoA); +} + +map g_TimesLessChildren; +int g_TotalFrames; +map g_TimeSumsMap; +vector g_TimeSums; +CVProfNode *g_pStartNode; +const tchar *g_pszSumNode; + +//------------------------------------- + +void CVProfile::SumTimes(CVProfNode *pNode, int budgetGroupID) { + if (!pNode) return; // this generally only happens on a failed FindNode() + + bool bSetStartNode; + if (!g_pStartNode && _tcscmp(pNode->GetName(), g_pszSumNode) == 0) { + g_pStartNode = pNode; + bSetStartNode = true; + } else + bSetStartNode = false; + + if (GetRoot() != pNode) { + if (g_pStartNode && pNode->GetTotalCalls() > 0 && + (budgetGroupID == -1 || pNode->GetBudgetGroupID() == budgetGroupID)) { + double timeLessChildren = pNode->GetTotalTimeLessChildren(); + + g_TimesLessChildren.emplace(pNode, timeLessChildren); + + map::iterator iter; + iter = g_TimeSumsMap.find( + pNode->GetName()); // intenionally using address of string rather + // than string compare (toml 01-27-03) + if (iter == g_TimeSumsMap.end()) { + TimeSums_t timeSums = {pNode->GetName(), pNode->GetTotalCalls(), + pNode->GetTotalTime(), timeLessChildren, + pNode->GetPeakTime()}; + g_TimeSumsMap.emplace(pNode->GetName(), g_TimeSums.size()); + g_TimeSums.push_back(timeSums); + } else { + TimeSums_t &timeSums = g_TimeSums[iter->second]; + timeSums.calls += pNode->GetTotalCalls(); + timeSums.time += pNode->GetTotalTime(); + timeSums.timeLessChildren += timeLessChildren; + if (pNode->GetPeakTime() > timeSums.peak) + timeSums.peak = pNode->GetPeakTime(); + } + } + + if ((!g_pStartNode || pNode != g_pStartNode) && pNode->GetSibling()) { + SumTimes(pNode->GetSibling(), budgetGroupID); + } + } + + if (pNode->GetChild()) { + SumTimes(pNode->GetChild(), budgetGroupID); + } + + if (bSetStartNode) g_pStartNode = NULL; +} + +//------------------------------------- + +CVProfNode *CVProfile::FindNode(CVProfNode *pStartNode, const tchar *pszNode) { + if (_tcscmp(pStartNode->GetName(), pszNode) != 0) { + CVProfNode *pFoundNode = NULL; + if (pStartNode->GetSibling()) { + pFoundNode = FindNode(pStartNode->GetSibling(), pszNode); + } + + if (!pFoundNode && pStartNode->GetChild()) { + pFoundNode = FindNode(pStartNode->GetChild(), pszNode); + } + + return pFoundNode; + } + return pStartNode; +} + +//------------------------------------- +#ifdef _X360 + +void CVProfile::PMCDisableAllNodes(CVProfNode *pStartNode) { + if (pStartNode == NULL) { + pStartNode = GetRoot(); + } + + pStartNode->EnableL2andLHS(false); + + if (pStartNode->GetSibling()) { + PMCDisableAllNodes(pStartNode->GetSibling()); + } + + if (pStartNode->GetChild()) { + PMCDisableAllNodes(pStartNode->GetChild()); + } +} + +// recursively set l2/lhs recording state for a node and all children AND +// SIBLINGS +static void PMCRecursiveL2Set(CVProfNode *pNode, bool enableState) { + if (pNode) { + pNode->EnableL2andLHS(enableState); + if (pNode->GetSibling()) { + PMCRecursiveL2Set(pNode->GetSibling(), enableState); + } + if (pNode->GetChild()) { + PMCRecursiveL2Set(pNode->GetChild(), enableState); + } + } +} + +bool CVProfile::PMCEnableL2Upon(const tchar *pszNodeName, bool bRecursive) { + // PMCDisableAllNodes(); + CVProfNode *pNode = FindNode(GetRoot(), pszNodeName); + if (pNode) { + pNode->EnableL2andLHS(true); + if (bRecursive) { + PMCRecursiveL2Set(pNode->GetChild(), true); + } + return true; + } else { + return false; + } +} + +bool CVProfile::PMCDisableL2Upon(const tchar *pszNodeName, bool bRecursive) { + // PMCDisableAllNodes(); + CVProfNode *pNode = FindNode(GetRoot(), pszNodeName); + if (pNode) { + pNode->EnableL2andLHS(false); + if (bRecursive) { + PMCRecursiveL2Set(pNode->GetChild(), false); + } + return true; + } else { + return false; + } +} + +static void DumpEnabledPMCNodesInner(CVProfNode *pNode) { + if (!pNode) return; + + if (pNode->IsL2andLHSEnabled()) { + Msg(_T("\t%s\n"), pNode->GetName()); + } + + // depth first printing clearer + if (pNode->GetChild()) { + DumpEnabledPMCNodesInner(pNode->GetChild()); + } + + if (pNode->GetSibling()) { + DumpEnabledPMCNodesInner(pNode->GetChild()); + } +} + +void CVProfile::DumpEnabledPMCNodes(void) { + Msg(_T("Nodes enabled for PMC counters:\n")); + CVProfNode *pNode = GetRoot(); + DumpEnabledPMCNodesInner(pNode); + + Msg(_T("(end)\n")); +} + +CVProfNode *CVProfile::CPUTraceGetEnabledNode(CVProfNode *pStartNode) { + if (!pStartNode) { + pStartNode = GetRoot(); + } + + if ((pStartNode->m_iBitFlags & CVProfNode::kCPUTrace) != 0) { + return pStartNode; + } + + if (pStartNode->GetSibling()) { + CVProfNode *retval = CPUTraceGetEnabledNode(pStartNode->GetSibling()); + if (retval) return retval; + } + + if (pStartNode->GetChild()) { + CVProfNode *retval = CPUTraceGetEnabledNode(pStartNode->GetChild()); + if (retval) return retval; + } + + return NULL; +} + +const char *CVProfile::SetCPUTraceFilename(const char *filename) { + strncpy(m_CPUTraceFilename, filename, sizeof(m_CPUTraceFilename)); + return GetCPUTraceFilename(); +} + +/// Returns a pointer to an internal static, so you don't need to +/// make temporary char buffers for this to write into. What of it? +/// You're not hanging on to that pointer. That would be foolish. +const char *CVProfile::GetCPUTraceFilename() { + static char retBuf[256]; + + switch (m_iCPUTraceEnabled) { + case kAllNodesInFrame_WaitingForMark: + case kAllNodesInFrame_Recording: + _snprintf(retBuf, sizeof(retBuf), "e:\\%.128s%.4d.pix2", + m_CPUTraceFilename, m_iSuccessiveTraceIndex); + break; + + case kAllNodesInFrame_WaitingForMarkMultiFrame: + case kAllNodesInFrame_RecordingMultiFrame: + _snprintf(retBuf, sizeof(retBuf), "e:\\%.128s_%.4d_%.4d.pix2", + m_CPUTraceFilename, m_nFrameCount, m_iSuccessiveTraceIndex); + break; + + default: + _snprintf(retBuf, sizeof(retBuf), "e:\\%.128s.pix2", m_CPUTraceFilename); + } + + return retBuf; +} + +bool CVProfile::TraceCompleteEvent(void) { return m_bTraceCompleteEvent; } + +CVProfNode *CVProfile::CPUTraceEnableForNode(const tchar *pszNodeName) { + // disable whatever may be enabled already (we can only trace one node at a + // time) + CPUTraceDisableAllNodes(); + + CVProfNode *which = FindNode(GetRoot(), pszNodeName); + if (which) { + which->m_iBitFlags |= CVProfNode::kCPUTrace; + return which; + } else + return NULL; +} + +void CVProfile::CPUTraceDisableAllNodes(CVProfNode *pStartNode) { + if (!pStartNode) { + pStartNode = GetRoot(); + } + + pStartNode->m_iBitFlags &= ~CVProfNode::kCPUTrace; + + if (pStartNode->GetSibling()) { + CPUTraceDisableAllNodes(pStartNode->GetSibling()); + } + + if (pStartNode->GetChild()) { + CPUTraceDisableAllNodes(pStartNode->GetChild()); + } +} + +#endif + +//------------------------------------- + +void CVProfile::SumTimes(const tchar *pszStartNode, int budgetGroupID) { + if (GetRoot()->GetChild()) { + if (pszStartNode == NULL) + g_pStartNode = GetRoot(); + else + g_pStartNode = NULL; + + g_pszSumNode = pszStartNode; + SumTimes(GetRoot(), budgetGroupID); + g_pStartNode = NULL; + } +} + +//------------------------------------- + +void CVProfile::DumpNodes(CVProfNode *pNode, int indent, + bool bAverageAndCountOnly) { + if (!pNode) return; // this generally only happens on a failed FindNode() + + bool fIsRoot = (pNode == GetRoot()); + + if (fIsRoot || pNode == g_pStartNode) { + if (bAverageAndCountOnly) { + Msg(_T(" Avg Time/Frame (ms)\n")); + Msg(_T("[ func+child func ] Count\n")); + Msg(_T(" ---------- --------- --------\n")); + } else { + Msg(_T(" Sum (ms) Avg Time/Frame (ms) Avg ") + _T("Time/Call (ms)\n")); + Msg(_T("[ func+child func ] [ func+child func ] [ func+child ") + _T("func ] Count Peak\n")); + Msg(_T(" ---------- --------- ---------- ------ ---------- ") + _T("------ -------- ------\n")); + } + } + + if (!fIsRoot) { + map::iterator iterTimeLessChildren = + g_TimesLessChildren.find(pNode); + + double dNodeTime = 0; + if (iterTimeLessChildren != g_TimesLessChildren.end()) + dNodeTime = iterTimeLessChildren->second; + + if (bAverageAndCountOnly) { + Msg(_T(" %10.3f %9.2f %8u"), + (pNode->GetTotalCalls() > 0) + ? pNode->GetTotalTime() / (double)NumFramesSampled() + : 0, + (pNode->GetTotalCalls() > 0) ? dNodeTime / (double)NumFramesSampled() + : 0, + pNode->GetTotalCalls()); + } else { + Msg(_T(" %10.3f %9.2f %10.3f %6.2f %10.3f %6.2f %8u %6.2f"), + pNode->GetTotalTime(), dNodeTime, + (pNode->GetTotalCalls() > 0) + ? pNode->GetTotalTime() / (double)NumFramesSampled() + : 0, + (pNode->GetTotalCalls() > 0) ? dNodeTime / (double)NumFramesSampled() + : 0, + (pNode->GetTotalCalls() > 0) + ? pNode->GetTotalTime() / (double)pNode->GetTotalCalls() + : 0, + (pNode->GetTotalCalls() > 0) + ? dNodeTime / (double)pNode->GetTotalCalls() + : 0, + pNode->GetTotalCalls(), pNode->GetPeakTime()); + } + + Msg(_T(" ")); + for (int i = 1; i < indent; i++) { + Msg(_T("| ")); + } + + Msg(_T("%s\n"), pNode->GetName()); + } + + if (pNode->GetChild()) { + DumpNodes(pNode->GetChild(), indent + 1, bAverageAndCountOnly); + } + + if (!(fIsRoot || pNode == g_pStartNode) && pNode->GetSibling()) { + DumpNodes(pNode->GetSibling(), indent, bAverageAndCountOnly); + } +} + +//------------------------------------- + +#if defined(VPROF_VXCONSOLE_EXISTS) +static void CalcBudgetGroupTimes_Recursive(CVProfNode *pNode, + unsigned int *groupTimes, + int numGroups, float flScale) { + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if (groupID >= numGroups) { + return; + } + + groupTimes[groupID] += flScale * pNode->GetPrevTimeLessChildren(); + + nodePtr = pNode->GetSibling(); + if (nodePtr) { + CalcBudgetGroupTimes_Recursive(nodePtr, groupTimes, numGroups, flScale); + } + + nodePtr = pNode->GetChild(); + if (nodePtr) { + CalcBudgetGroupTimes_Recursive(nodePtr, groupTimes, numGroups, flScale); + } +} + +static void CalcBudgetGroupL2CacheMisses_Recursive(CVProfNode *pNode, + unsigned int *groupTimes, + int numGroups, + float flScale) { + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if (groupID >= numGroups) { + return; + } + + groupTimes[groupID] += flScale * pNode->GetPrevL2CacheMissLessChildren(); + + nodePtr = pNode->GetSibling(); + if (nodePtr) { + CalcBudgetGroupL2CacheMisses_Recursive(nodePtr, groupTimes, numGroups, + flScale); + } + + nodePtr = pNode->GetChild(); + if (nodePtr) { + CalcBudgetGroupL2CacheMisses_Recursive(nodePtr, groupTimes, numGroups, + flScale); + } +} + +static void CalcBudgetGroupLHS_Recursive(CVProfNode *pNode, + unsigned int *groupTimes, + int numGroups, float flScale) { + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if (groupID >= numGroups) { + return; + } + + groupTimes[groupID] += flScale * pNode->GetPrevLoadHitStoreLessChildren(); + + nodePtr = pNode->GetSibling(); + if (nodePtr) { + CalcBudgetGroupLHS_Recursive(nodePtr, groupTimes, numGroups, flScale); + } + + nodePtr = pNode->GetChild(); + if (nodePtr) { + CalcBudgetGroupLHS_Recursive(nodePtr, groupTimes, numGroups, flScale); + } +} + +void CVProfile::VXConsoleReportMode(VXConsoleReportMode_t mode) { + m_ReportMode = mode; +} + +void CVProfile::VXConsoleReportScale(VXConsoleReportMode_t mode, + float flScale) { + m_pReportScale[mode] = flScale; +} + +//----------------------------------------------------------------------------- +// Send the all the counter attributes once to VXConsole at profiling start +//----------------------------------------------------------------------------- +void CVProfile::VXProfileStart() { + const char *names[XBX_MAX_PROFILE_COUNTERS]; + COLORREF colors[XBX_MAX_PROFILE_COUNTERS]; + int numGroups; + int counterGroup; + const char *pGroupName; + int i; + int r, g, b, a; + + // vprof system must be running + if (m_enabled <= 0 || !m_UpdateMode) { + return; + } + + if (m_UpdateMode & VPROF_UPDATE_BUDGET) { + // update budget profiling + numGroups = g_VProfCurrentProfile.GetNumBudgetGroups(); + if (numGroups > XBX_MAX_PROFILE_COUNTERS) { + numGroups = XBX_MAX_PROFILE_COUNTERS; + } + for (i = 0; i < numGroups; i++) { + names[i] = g_VProfCurrentProfile.GetBudgetGroupName(i); + g_VProfCurrentProfile.GetBudgetGroupColor(i, r, g, b, a); + colors[i] = XMAKECOLOR(r, g, b); + } + + // send all the profile attributes + XBX_rSetProfileAttributes("cpu", numGroups, names, colors); + } + + if (m_UpdateMode & + (VPROF_UPDATE_TEXTURE_GLOBAL | VPROF_UPDATE_TEXTURE_PERFRAME)) { + // update texture profiling + numGroups = 0; + counterGroup = (m_UpdateMode & VPROF_UPDATE_TEXTURE_GLOBAL) + ? COUNTER_GROUP_TEXTURE_GLOBAL + : COUNTER_GROUP_TEXTURE_PER_FRAME; + for (i = 0; i < g_VProfCurrentProfile.GetNumCounters(); i++) { + if (g_VProfCurrentProfile.GetCounterGroup(i) == counterGroup) { + // strip undesired prefix + pGroupName = g_VProfCurrentProfile.GetCounterName(i); + if (!stricmp(pGroupName, "texgroup_frame_")) { + pGroupName += 15; + } else if (!stricmp(pGroupName, "texgroup_global_")) { + pGroupName += 16; + } + names[numGroups] = pGroupName; + + g_VProfCurrentProfile.GetBudgetGroupColor(numGroups, r, g, b, a); + colors[numGroups] = XMAKECOLOR(r, g, b); + + numGroups++; + if (numGroups == XBX_MAX_PROFILE_COUNTERS) { + break; + } + } + } + + // send all the profile attributes + XBX_rSetProfileAttributes("texture", numGroups, names, colors); + } +} + +//----------------------------------------------------------------------------- +// Send the counters to VXConsole +//----------------------------------------------------------------------------- +void CVProfile::VXProfileUpdate() { + int i; + int counterGroup; + int numGroups; + unsigned int groupData[XBX_MAX_PROFILE_COUNTERS]; + + // vprof system must be running + if (m_enabled <= 0 || !m_UpdateMode) { + return; + } + + if (m_UpdateMode & VPROF_UPDATE_BUDGET) { + // send the cpu counters + numGroups = g_VProfCurrentProfile.GetNumBudgetGroups(); + if (numGroups > XBX_MAX_PROFILE_COUNTERS) { + numGroups = XBX_MAX_PROFILE_COUNTERS; + } + memset(groupData, 0, numGroups * sizeof(unsigned int)); + + CVProfNode *pNode = g_VProfCurrentProfile.GetRoot(); + if (pNode && pNode->GetChild()) { + switch (m_ReportMode) { + default: + case VXCONSOLE_REPORT_TIME: + CalcBudgetGroupTimes_Recursive(pNode->GetChild(), groupData, + numGroups, + m_pReportScale[VXCONSOLE_REPORT_TIME]); + break; + + case VXCONSOLE_REPORT_L2CACHE_MISSES: + CalcBudgetGroupL2CacheMisses_Recursive( + pNode->GetChild(), groupData, numGroups, + m_pReportScale[VXCONSOLE_REPORT_L2CACHE_MISSES]); + break; + + case VXCONSOLE_REPORT_LOAD_HIT_STORE: + CalcBudgetGroupLHS_Recursive( + pNode->GetChild(), groupData, numGroups, + m_pReportScale[VXCONSOLE_REPORT_LOAD_HIT_STORE]); + break; + } + } + + XBX_rSetProfileData("cpu", numGroups, groupData); + } + + if (m_UpdateMode & + (VPROF_UPDATE_TEXTURE_GLOBAL | VPROF_UPDATE_TEXTURE_PERFRAME)) { + // send the texture counters + numGroups = 0; + counterGroup = (m_UpdateMode & VPROF_UPDATE_TEXTURE_GLOBAL) + ? COUNTER_GROUP_TEXTURE_GLOBAL + : COUNTER_GROUP_TEXTURE_PER_FRAME; + for (i = 0; i < g_VProfCurrentProfile.GetNumCounters(); i++) { + if (g_VProfCurrentProfile.GetCounterGroup(i) == counterGroup) { + // get the size in bytes + groupData[numGroups++] = g_VProfCurrentProfile.GetCounterValue(i); + if (numGroups == XBX_MAX_PROFILE_COUNTERS) { + break; + } + } + } + + XBX_rSetProfileData("texture", numGroups, groupData); + } +} + +void CVProfile::VXEnableUpdateMode(int event, bool bEnable) { + // enable or disable the updating of specified events + if (bEnable) { + m_UpdateMode |= event; + } else { + m_UpdateMode &= ~event; + } + + // force a resend of possibly affected attributes + VXProfileStart(); +} + +#define MAX_VPROF_NODES_IN_LIST 4096 +static void VXBuildNodeList_r(CVProfNode *pNode, xVProfNodeItem_t *pNodeList, + int *pNumNodes) { + if (!pNode) { + return; + } + if (*pNumNodes >= MAX_VPROF_NODES_IN_LIST) { + // list full + return; + } + + // add to list + pNodeList[*pNumNodes].pName = (const char *)pNode->GetName(); + + pNodeList[*pNumNodes].pBudgetGroupName = + g_VProfCurrentProfile.GetBudgetGroupName(pNode->GetBudgetGroupID()); + int r, g, b, a; + g_VProfCurrentProfile.GetBudgetGroupColor(pNode->GetBudgetGroupID(), r, g, b, + a); + pNodeList[*pNumNodes].budgetGroupColor = XMAKECOLOR(r, g, b); + + pNodeList[*pNumNodes].totalCalls = pNode->GetTotalCalls(); + pNodeList[*pNumNodes].inclusiveTime = pNode->GetTotalTime(); + pNodeList[*pNumNodes].exclusiveTime = pNode->GetTotalTimeLessChildren(); + (*pNumNodes)++; + + CVProfNode *nodePtr = pNode->GetSibling(); + if (nodePtr) { + VXBuildNodeList_r(nodePtr, pNodeList, pNumNodes); + } + + nodePtr = pNode->GetChild(); + if (nodePtr) { + VXBuildNodeList_r(nodePtr, pNodeList, pNumNodes); + } +} +void CVProfile::VXSendNodes(void) { + Pause(); + + xVProfNodeItem_t *pNodeList = (xVProfNodeItem_t *)stackalloc( + MAX_VPROF_NODES_IN_LIST * sizeof(xVProfNodeItem_t)); + int numNodes = 0; + VXBuildNodeList_r(GetRoot(), pNodeList, &numNodes); + + // send to vxconsole + XBX_rVProfNodeList(numNodes, pNodeList); + + Resume(); +} +#endif + +//------------------------------------- +static void DumpSorted(const tchar *pszHeading, double totalTime, + bool (*pfnSort)(const TimeSums_t &, const TimeSums_t &), + int maxLen = 999999) { + unsigned i; + vector sortedSums; + sortedSums = g_TimeSums; + sort(sortedSums.begin(), sortedSums.end(), pfnSort); + + Msg(_T("%s\n"), pszHeading); + Msg(_T(" Scope Calls ") + _T("Calls/Frame Time+Child Pct Time Pct Avg/Frame ") + _T("Avg/Call Avg-NoChild Peak\n")); + Msg(_T(" ---------------------------------------------------- ----------- ") + _T("----------- ----------- ------ ----------- ------ ----------- ") + _T("----------- ----------- -----------\n")); + for (i = 0; i < sortedSums.size() && i < (unsigned)maxLen; i++) { + double avg = (sortedSums[i].calls) + ? sortedSums[i].time / (double)sortedSums[i].calls + : 0.0; + double avgLessChildren = + (sortedSums[i].calls) + ? sortedSums[i].timeLessChildren / (double)sortedSums[i].calls + : 0.0; + + Msg(_T(" ") + _T("%52.52s%12d%12.3f%12.3f%7.2f%12.3f%7.2f%12.3f%12.3f%12.3f%12.3f\n"), + sortedSums[i].pszProfileScope, sortedSums[i].calls, + (float)sortedSums[i].calls / (float)g_TotalFrames, sortedSums[i].time, + min((sortedSums[i].time / totalTime) * 100.0, 100.0), + sortedSums[i].timeLessChildren, + min((sortedSums[i].timeLessChildren / totalTime) * 100.0, 100.0), + sortedSums[i].time / (float)g_TotalFrames, avg, avgLessChildren, + sortedSums[i].peak); + } +} + +#if defined(_X360) +// Dump information on all nodes with PMC recording +static void DumpPMC(CVProfNode *pNode, bool &bPrintHeader, uint64 L2thresh = 1, + uint64 LHSthresh = 1) { + if (!pNode) return; + + uint64 l2 = pNode->GetL2CacheMisses(); + uint64 lhs = pNode->GetLoadHitStores(); + if (l2 > L2thresh && lhs > LHSthresh) { + // met threshold. + if (bPrintHeader) { + // print header + Msg(_T("-- 360 PMC information --\n")); + Msg(_T("Scope L2/call ") + _T("L2/frame LHS/call LHS/frame\n")); + Msg(_T("---------------------------------------------------- --------- ") + _T("--------- --------- ---------\n")); + + bPrintHeader = false; + } + + // print + float calls = pNode->GetTotalCalls(); + float frames = g_TotalFrames; + Msg(_T("%52.52s %9.2f %9.2f %9.2f %9.2f\n"), pNode->GetName(), l2 / calls, + l2 / frames, lhs / calls, lhs / frames); + } + + if (pNode->GetSibling()) { + DumpPMC(pNode->GetSibling(), bPrintHeader, L2thresh, LHSthresh); + } + + if (pNode->GetChild()) { + DumpPMC(pNode->GetChild(), bPrintHeader, L2thresh, LHSthresh); + } +} +#endif + +//------------------------------------- + +void CVProfile::OutputReport(int type, const tchar *pszStartNode, + int budgetGroupID) { + Msg(_T("******** BEGIN VPROF REPORT ********\n")); +#ifdef _MSC_VER +#if (_MSC_VER < 1300) + Msg(_T(" (note: this report exceeds the output capacity of MSVC debug ") + _T("window. Use console window or console log.) \n")); +#endif +#endif + + g_TotalFrames = max(NumFramesSampled() - 1, 1); + + if (NumFramesSampled() == 0 || GetTotalTimeSampled() == 0) + Msg(_T("No samples\n")); + else { + if (type & VPRT_SUMMARY) { + Msg(_T("-- Summary --\n")); + Msg(_T("%d frames sampled for %.2f seconds\n"), g_TotalFrames, + GetTotalTimeSampled() / 1000.0); + Msg(_T("Average %.2f fps, %.2f ms per frame\n"), + 1000.0 / (GetTotalTimeSampled() / g_TotalFrames), + GetTotalTimeSampled() / g_TotalFrames); + Msg(_T("Peak %.2f ms frame\n"), GetPeakFrameTime()); + + double timeAccountedFor = + 100.0 - (m_Root.GetTotalTimeLessChildren() / m_Root.GetTotalTime()); + Msg(_T("%.0f pct of time accounted for\n"), min(100.0, timeAccountedFor)); + Msg(_T("\n")); + } + + if (pszStartNode == NULL) { + pszStartNode = GetRoot()->GetName(); + } + + SumTimes(pszStartNode, budgetGroupID); + + // Dump the hierarchy + if (type & VPRT_HIERARCHY) { + Msg(_T("-- Hierarchical Call Graph --\n")); + if (pszStartNode == NULL) + g_pStartNode = NULL; + else + g_pStartNode = FindNode(GetRoot(), pszStartNode); + + DumpNodes((!g_pStartNode) ? GetRoot() : g_pStartNode, 0, false); + Msg(_T("\n")); + } + + if (type & VPRT_HIERARCHY_TIME_PER_FRAME_AND_COUNT_ONLY) { + Msg(_T("-- Hierarchical Call Graph --\n")); + if (pszStartNode == NULL) + g_pStartNode = NULL; + else + g_pStartNode = FindNode(GetRoot(), pszStartNode); + + DumpNodes((!g_pStartNode) ? GetRoot() : g_pStartNode, 0, true); + Msg(_T("\n")); + } + + int maxLen = (type & VPRT_LIST_TOP_ITEMS_ONLY) ? 25 : 999999; + + if (type & VPRT_LIST_BY_TIME) { + DumpSorted(_T("-- Profile scopes sorted by time (including children) --"), + GetTotalTimeSampled(), TimeCompare, maxLen); + Msg(_T("\n")); + } + if (type & VPRT_LIST_BY_TIME_LESS_CHILDREN) { + DumpSorted(_T("-- Profile scopes sorted by time (without children) --"), + GetTotalTimeSampled(), TimeLessChildrenCompare, maxLen); + Msg(_T("\n")); + } + if (type & VPRT_LIST_BY_AVG_TIME) { + DumpSorted( + _T("-- Profile scopes sorted by average time (including children) ") + _T("--"), + GetTotalTimeSampled(), AverageTimeCompare, maxLen); + Msg(_T("\n")); + } + if (type & VPRT_LIST_BY_AVG_TIME_LESS_CHILDREN) { + DumpSorted( + _T("-- Profile scopes sorted by average time (without children) --"), + GetTotalTimeSampled(), AverageTimeLessChildrenCompare, maxLen); + Msg(_T("\n")); + } + if (type & VPRT_LIST_BY_PEAK_TIME) { + DumpSorted(_T("-- Profile scopes sorted by peak --"), + GetTotalTimeSampled(), PeakCompare, maxLen); + Msg(_T("\n")); + } + if (type & VPRT_LIST_BY_PEAK_OVER_AVERAGE) { + DumpSorted( + _T("-- Profile scopes sorted by peak over average (including ") + _T("children) --"), + GetTotalTimeSampled(), PeakOverAverageCompare, maxLen); + Msg(_T("\n")); + } + + // TODO: Functions by time less children + // TODO: Functions by time averages + // TODO: Functions by peak + // TODO: Peak deviation from average + g_TimesLessChildren.clear(); + g_TimeSumsMap.clear(); + g_TimeSums.clear(); + +#ifdef _X360 + bool bPrintedHeader = true; + DumpPMC(FindNode(GetRoot(), pszStartNode), bPrintedHeader); +#endif + } + Msg(_T("******** END VPROF REPORT ********\n")); +} + +//============================================================================= + +CVProfile::CVProfile() + : m_Root(_T("Root"), 0, NULL, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, 0), + m_pCurNode(&m_Root), + m_nFrames(0), + m_enabled(0), // don't change this. if m_enabled is anything but zero + // coming out of this constructor, vprof will break. + m_pausedEnabledDepth(0), + m_fAtRoot(true) { +#ifdef VPROF_VTUNE_GROUP + m_GroupIDStackDepth = 1; + m_GroupIDStack[0] = 0; // VPROF_BUDGETGROUP_OTHER_UNACCOUNTED +#endif + + m_TargetThreadId = ThreadGetCurrentId(); + + // Go ahead and allocate 32 slots for budget group names + MEM_ALLOC_CREDIT(); + m_pBudgetGroups = new CVProfile::CBudgetGroup[32]; + m_nBudgetGroupNames = 0; + m_nBudgetGroupNamesAllocated = 32; + + // Add these here so that they will always be in the same order. + // VPROF_BUDGETGROUP_OTHER_UNACCOUNTED has to be FIRST!!!! + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_WORLD_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_DISPLACEMENT_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_GAME, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_PLAYER, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_NPCS, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_SERVER_ANIM, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_CLIENT_ANIMATION, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_PHYSICS, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_STATICPROP_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_MODEL_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_MODEL_FAST_PATH_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_LIGHTCACHE, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_BRUSHMODEL_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_SHADOW_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_DETAILPROP_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_PARTICLE_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_ROPES, BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_DLIGHT_RENDERING, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_OTHER_NETWORKING, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_OTHER_SOUND, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_OTHER_VGUI, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_OTHER_FILESYSTEM, + BUDGETFLAG_OTHER | BUDGETFLAG_SERVER); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_PREDICTION, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_INTERPOLATION, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_SWAP_BUFFERS, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_OCCLUSION, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_OVERLAYS, BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_TOOLS, + BUDGETFLAG_OTHER | BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_TEXTURE_CACHE, + BUDGETFLAG_CLIENT); + BudgetGroupNameToBudgetGroupID(VPROF_BUDGETGROUP_REPLAY, BUDGETFLAG_SERVER); + // BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DISP_HULLTRACES ); + + m_bPMEInit = false; + m_bPMEEnabled = false; + +#ifdef VPROF_VXCONSOLE_EXISTS + m_bTraceCompleteEvent = false; + m_iSuccessiveTraceIndex = 0; + m_ReportMode = VXCONSOLE_REPORT_TIME; + m_pReportScale[VXCONSOLE_REPORT_TIME] = 1000.0f; + m_pReportScale[VXCONSOLE_REPORT_L2CACHE_MISSES] = 1.0f; + m_pReportScale[VXCONSOLE_REPORT_LOAD_HIT_STORE] = 0.1f; + m_nFrameCount = 0; + m_nFramesRemaining = 1; + m_WorstCycles = 0; + m_WorstTraceFilename[0] = 0; + m_UpdateMode = 0; +#endif +#ifdef _X360 + m_iCPUTraceEnabled = kDisabled; +#endif +} + +CVProfile::~CVProfile() { Term(); } + +void CVProfile::FreeNodes_R(CVProfNode *pNode) { + CVProfNode *pNext; + for (CVProfNode *pChild = pNode->GetChild(); pChild; pChild = pNext) { + pNext = pChild->GetSibling(); + FreeNodes_R(pChild); + } + + if (pNode == GetRoot()) { + pNode->m_pChild = NULL; + } else { + delete pNode; + } +} + +void CVProfile::Term() { + int i; + for (i = 0; i < m_nBudgetGroupNames; i++) { + delete[] m_pBudgetGroups[i].m_pName; + } + delete[] m_pBudgetGroups; + m_nBudgetGroupNames = m_nBudgetGroupNamesAllocated = 0; + m_pBudgetGroups = NULL; + + int n; + for (n = 0; n < m_NumCounters; n++) { + delete[] m_CounterNames[n]; + m_CounterNames[n] = NULL; + } + m_NumCounters = 0; + + // Free the nodes. + FreeNodes_R(GetRoot()); +} + +#define COLORMIN 160 +#define COLORMAX 255 + +static int g_ColorLookup[4] = { + COLORMIN, + COLORMAX, + COLORMIN + (COLORMAX - COLORMIN) / 3, + COLORMIN + ((COLORMAX - COLORMIN) * 2) / 3, +}; + +#define GET_BIT(val, bitnum) (((val) >> (bitnum)) & 0x1) + +void CVProfile::GetBudgetGroupColor(int budgetGroupID, int &r, int &g, int &b, + int &a) { + budgetGroupID = budgetGroupID % (1 << 6); + + int index; + index = GET_BIT(budgetGroupID, 0) | (GET_BIT(budgetGroupID, 5) << 1); + r = g_ColorLookup[index]; + index = GET_BIT(budgetGroupID, 1) | (GET_BIT(budgetGroupID, 4) << 1); + g = g_ColorLookup[index]; + index = GET_BIT(budgetGroupID, 2) | (GET_BIT(budgetGroupID, 3) << 1); + b = g_ColorLookup[index]; + a = 255; +} + +// return -1 if it doesn't exist. +int CVProfile::FindBudgetGroupName(const tchar *pBudgetGroupName) { + int i; + for (i = 0; i < m_nBudgetGroupNames; i++) { + if (_tcsicmp(pBudgetGroupName, m_pBudgetGroups[i].m_pName) == 0) { + return i; + } + } + return -1; +} + +int CVProfile::AddBudgetGroupName(const tchar *pBudgetGroupName, + int budgetFlags) { + MEM_ALLOC_CREDIT(); + tchar *pNewString = new tchar[_tcslen(pBudgetGroupName) + 1]; + _tcscpy(pNewString, pBudgetGroupName); + if (m_nBudgetGroupNames + 1 > m_nBudgetGroupNamesAllocated) { + m_nBudgetGroupNamesAllocated *= 2; + m_nBudgetGroupNamesAllocated = + max(m_nBudgetGroupNames + 6, m_nBudgetGroupNamesAllocated); + + CBudgetGroup *pNew = new CBudgetGroup[m_nBudgetGroupNamesAllocated]; + for (int i = 0; i < m_nBudgetGroupNames; i++) pNew[i] = m_pBudgetGroups[i]; + + delete[] m_pBudgetGroups; + m_pBudgetGroups = pNew; + } + + m_pBudgetGroups[m_nBudgetGroupNames].m_pName = pNewString; + m_pBudgetGroups[m_nBudgetGroupNames].m_BudgetFlags = budgetFlags; + m_nBudgetGroupNames++; + if (m_pNumBudgetGroupsChangedCallBack) { + (*m_pNumBudgetGroupsChangedCallBack)(); + } + +#if defined(VPROF_VXCONSOLE_EXISTS) + // re-start with all the known budgets + VXProfileStart(); +#endif + return m_nBudgetGroupNames - 1; +} + +int CVProfile::BudgetGroupNameToBudgetGroupID(const tchar *pBudgetGroupName, + int budgetFlagsToORIn) { + int budgetGroupID = FindBudgetGroupName(pBudgetGroupName); + if (budgetGroupID == -1) { + budgetGroupID = AddBudgetGroupName(pBudgetGroupName, budgetFlagsToORIn); + } else { + m_pBudgetGroups[budgetGroupID].m_BudgetFlags |= budgetFlagsToORIn; + } + + return budgetGroupID; +} + +int CVProfile::BudgetGroupNameToBudgetGroupID(const tchar *pBudgetGroupName) { + return BudgetGroupNameToBudgetGroupID(pBudgetGroupName, BUDGETFLAG_OTHER); +} + +int CVProfile::GetNumBudgetGroups(void) { return m_nBudgetGroupNames; } + +void CVProfile::RegisterNumBudgetGroupsChangedCallBack( + void (*pCallBack)(void)) { + m_pNumBudgetGroupsChangedCallBack = pCallBack; +} + +void CVProfile::HideBudgetGroup(int budgetGroupID, bool bHide) { + if (budgetGroupID != -1) { + if (bHide) + m_pBudgetGroups[budgetGroupID].m_BudgetFlags |= BUDGETFLAG_HIDDEN; + else + m_pBudgetGroups[budgetGroupID].m_BudgetFlags &= ~BUDGETFLAG_HIDDEN; + } +} + +int *CVProfile::FindOrCreateCounter(const tchar *pName, + CounterGroup_t eCounterGroup) { + Assert(m_NumCounters + 1 < MAXCOUNTERS); + if (m_NumCounters + 1 >= MAXCOUNTERS || !InTargetThread()) { + static int dummy; + return &dummy; + } + int i; + for (i = 0; i < m_NumCounters; i++) { + if (_tcsicmp(m_CounterNames[i], pName) == 0) { + // found it! + return &m_Counters[i]; + } + } + + // NOTE: These get freed in ~CVProfile. + MEM_ALLOC_CREDIT(); + tchar *pNewName = new tchar[_tcslen(pName) + 1]; + _tcscpy(pNewName, pName); + m_Counters[m_NumCounters] = 0; + m_CounterGroups[m_NumCounters] = (char)eCounterGroup; + m_CounterNames[m_NumCounters++] = pNewName; + return &m_Counters[m_NumCounters - 1]; +} + +void CVProfile::ResetCounters(CounterGroup_t eCounterGroup) { + int i; + for (i = 0; i < m_NumCounters; i++) { + if (m_CounterGroups[i] == eCounterGroup) m_Counters[i] = 0; + } +} + +int CVProfile::GetNumCounters() const { return m_NumCounters; } + +const tchar *CVProfile::GetCounterName(int index) const { + Assert(index >= 0 && index < m_NumCounters); + return m_CounterNames[index]; +} + +int CVProfile::GetCounterValue(int index) const { + Assert(index >= 0 && index < m_NumCounters); + return m_Counters[index]; +} + +const tchar *CVProfile::GetCounterNameAndValue(int index, int &val) const { + Assert(index >= 0 && index < m_NumCounters); + val = m_Counters[index]; + return m_CounterNames[index]; +} + +CounterGroup_t CVProfile::GetCounterGroup(int index) const { + Assert(index >= 0 && index < m_NumCounters); + return (CounterGroup_t)m_CounterGroups[index]; +} + +#ifdef _X360 +void CVProfile::LatchMultiFrame(int64 cycles) { + if (cycles > m_WorstCycles) { + strncpy(m_WorstTraceFilename, GetCPUTraceFilename(), + sizeof(m_WorstTraceFilename)); + m_WorstCycles = cycles; + } +} + +void CVProfile::SpewWorstMultiFrame() { + CCycleCount cc(m_WorstCycles); + Msg("%s == %.3f msec\n", m_WorstTraceFilename, cc.GetMillisecondsF()); +} +#endif + +#ifdef DBGFLAG_VALIDATE + +#ifdef _WIN64 +#error the below is presumably broken on 64 bit +#endif // _WIN64 + +const int k_cSTLMapAllocOffset = 4; +#define GET_INTERNAL_MAP_ALLOC_PTR(pMap) \ + (*((void **)(((byte *)(pMap)) + k_cSTLMapAllocOffset))) +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our +//container) +//----------------------------------------------------------------------------- +void CVProfile::Validate(CValidator &validator, tchar *pchName) { + validator.Push(_T("CVProfile"), this, pchName); + + m_Root.Validate(validator, _T("m_Root")); + + for (int iBudgetGroup = 0; iBudgetGroup < m_nBudgetGroupNames; iBudgetGroup++) + validator.ClaimMemory(m_pBudgetGroups[iBudgetGroup].m_pName); + + validator.ClaimMemory(m_pBudgetGroups); + + // The std template map class allocates memory internally, but offer no way to + // get access to their pointer. Since this is for debug purposes only and the + // std template classes don't change, just look at the well-known offset. This + // is arguably sick and wrong, kind of like marrying a squirrel. + validator.ClaimMemory(GET_INTERNAL_MAP_ALLOC_PTR(&g_TimesLessChildren)); + validator.ClaimMemory(GET_INTERNAL_MAP_ALLOC_PTR(&g_TimeSumsMap)); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + +#endif diff --git a/tier0/win32consoleio.cpp b/tier0/win32consoleio.cpp new file mode 100644 index 0000000..20298ec --- /dev/null +++ b/tier0/win32consoleio.cpp @@ -0,0 +1,168 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Win32 Console API helpers + +#include "pch_tier0.h" +#include "win32consoleio.h" + +#if defined(_WIN32) + +#include +#include +#include + +#include "winlite.h" + +#endif // defined( _WIN32 ) + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// +// Attach a console to a Win32 GUI process and setup stdin, stdout & stderr +// along with the std::iostream (cout, cin, cerr) equivalents to read and +// write to and from that console +// +// 1. Ensure the handle associated with stdio is FILE_TYPE_UNKNOWN +// if it's anything else just return false. This supports cygwin +// style command shells like rxvt which setup pipes to processes +// they spawn +// +// 2. See if the Win32 function call AttachConsole exists in kernel32 +// It's a Windows 2000 and above call. If it does, call it and see +// if it succeeds in attaching to the console of the parent process. +// If that succeeds, return false (for no new console allocated). +// This supports someone typing the command from a normal windows +// command window and having the output go to the parent window. +// It's a little funny because a GUI app detaches so the command +// prompt gets intermingled with output from this process +// +// 3. If things get to here call AllocConsole which will pop open +// a new window and allow output to go to that window. The +// window will disappear when the process exists so if it's used +// for something like a help message then do something like getchar() +// from stdin to wait for a keypress. if AllocConsole is called +// true is returned. +// +// Return: true if AllocConsole() was used to pop open a new windows console +// +//----------------------------------------------------------------------------- +bool SetupWin32ConsoleIO() { +#if defined(_WIN32) + // Only useful on Windows platforms + + bool newConsole(false); + + if (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_UNKNOWN) { + HINSTANCE hInst = ::LoadLibrary("kernel32.dll"); + typedef BOOL(WINAPI * pAttachConsole_t)(DWORD); + pAttachConsole_t pAttachConsole( + (BOOL(_stdcall *)(DWORD))GetProcAddress(hInst, "AttachConsole")); + + if (!(pAttachConsole && (*pAttachConsole)((DWORD)-1))) { + newConsole = true; + AllocConsole(); + } + + *stdout = *_fdopen( + _open_osfhandle(reinterpret_cast(GetStdHandle(STD_OUTPUT_HANDLE)), + _O_TEXT), + "w"); + setvbuf(stdout, NULL, _IONBF, 0); + + *stdin = *_fdopen( + _open_osfhandle(reinterpret_cast(GetStdHandle(STD_INPUT_HANDLE)), + _O_TEXT), + "r"); + setvbuf(stdin, NULL, _IONBF, 0); + + *stderr = *_fdopen( + _open_osfhandle(reinterpret_cast(GetStdHandle(STD_ERROR_HANDLE)), + _O_TEXT), + "w"); + setvbuf(stdout, NULL, _IONBF, 0); + + std::ios_base::sync_with_stdio(); + } + + return newConsole; + +#else // defined( _WIN32 ) + + return false; + +#endif // defined( _WIN32 ) +} + +//----------------------------------------------------------------------------- +// Win32 Console Color API Helpers, originally from cmdlib. +// Retrieves the current console color attributes. +//----------------------------------------------------------------------------- +void InitWin32ConsoleColorContext(Win32ConsoleColorContext_t *pContext) { +#if PLATFORM_WINDOWS_PC + // Get the old background attributes. + CONSOLE_SCREEN_BUFFER_INFO oldInfo; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &oldInfo); + pContext->m_InitialColor = pContext->m_LastColor = + oldInfo.wAttributes & (FOREGROUND_RED | FOREGROUND_GREEN | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + pContext->m_BackgroundFlags = + oldInfo.wAttributes & (BACKGROUND_RED | BACKGROUND_GREEN | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + + pContext->m_BadColor = 0; + if (pContext->m_BackgroundFlags & BACKGROUND_RED) + pContext->m_BadColor |= FOREGROUND_RED; + if (pContext->m_BackgroundFlags & BACKGROUND_GREEN) + pContext->m_BadColor |= FOREGROUND_GREEN; + if (pContext->m_BackgroundFlags & BACKGROUND_BLUE) + pContext->m_BadColor |= FOREGROUND_BLUE; + if (pContext->m_BackgroundFlags & BACKGROUND_INTENSITY) + pContext->m_BadColor |= FOREGROUND_INTENSITY; +#else + pContext->m_InitialColor = 0; +#endif +} + +//----------------------------------------------------------------------------- +// Sets the active console foreground color. This function is smart enough to +// avoid setting the color to something that would be unreadable given +// the user's potentially customized background color. It leaves the +// background color unchanged. +// Returns: The console's previous foreground color. +//----------------------------------------------------------------------------- +uint16 SetWin32ConsoleColor(Win32ConsoleColorContext_t *pContext, int nRed, + int nGreen, int nBlue, int nIntensity) { +#if PLATFORM_WINDOWS_PC + uint16 ret = pContext->m_LastColor; + pContext->m_LastColor = 0; + if (nRed) pContext->m_LastColor |= FOREGROUND_RED; + if (nGreen) pContext->m_LastColor |= FOREGROUND_GREEN; + if (nBlue) pContext->m_LastColor |= FOREGROUND_BLUE; + if (nIntensity) pContext->m_LastColor |= FOREGROUND_INTENSITY; + + // Just use the initial color if there's a match... + if (pContext->m_LastColor == pContext->m_BadColor) + pContext->m_LastColor = static_cast(pContext->m_InitialColor); + + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), + pContext->m_LastColor | pContext->m_BackgroundFlags); + return ret; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Restore's the active foreground console color, without distributing the +// current background color. +//----------------------------------------------------------------------------- +void RestoreWin32ConsoleColor(Win32ConsoleColorContext_t *pContext, + uint16 prevColor) { +#if PLATFORM_WINDOWS_PC + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), + prevColor | pContext->m_BackgroundFlags); + pContext->m_LastColor = prevColor; +#endif +} diff --git a/tier1/characterset.cpp b/tier1/characterset.cpp new file mode 100644 index 0000000..321cad8 --- /dev/null +++ b/tier1/characterset.cpp @@ -0,0 +1,31 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "characterset.h" + +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: builds a simple lookup table of a group of important characters +// Input : *pParseGroup - pointer to the buffer for the group +// *pGroupString - null terminated list of characters to flag +//----------------------------------------------------------------------------- +void CharacterSetBuild( characterset_t *pSetBuffer, const char *pszSetString ) +{ + int i = 0; + + // Test our pointers + if ( !pSetBuffer || !pszSetString ) + return; + + memset( pSetBuffer->set, 0, sizeof(pSetBuffer->set) ); + + while ( pszSetString[i] ) + { + pSetBuffer->set[ pszSetString[i] ] = 1; + i++; + } + +} diff --git a/tier1/checksum_crc.cpp b/tier1/checksum_crc.cpp new file mode 100644 index 0000000..de543ac --- /dev/null +++ b/tier1/checksum_crc.cpp @@ -0,0 +1,145 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Generic CRC functions + +#include "checksum_crc.h" + +#include "tier0/platform.h" +#include "commonmacros.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define CRC32_INIT_VALUE 0xFFFFFFFFUL +#define CRC32_XOR_VALUE 0xFFFFFFFFUL + +#define NUM_BYTES 256 +static const CRC32_t pulCRCTable[NUM_BYTES] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + +void CRC32_Init(CRC32_t *pulCRC) { *pulCRC = CRC32_INIT_VALUE; } + +void CRC32_Final(CRC32_t *pulCRC) { *pulCRC ^= CRC32_XOR_VALUE; } + +CRC32_t CRC32_GetTableEntry(unsigned int slot) { + return pulCRCTable[(unsigned char)slot]; +} + +void CRC32_ProcessBuffer(CRC32_t *pulCRC, const void *pBuffer, ptrdiff_t nBuffer) { + CRC32_t ulCrc = *pulCRC; + unsigned char *pb = (unsigned char *)pBuffer; + unsigned int nFront; + ptrdiff_t nMain; + +JustAfew: + + switch (nBuffer) { + case 7: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 6: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 5: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 4: + ulCrc ^= LittleLong(*(CRC32_t *)pb); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + *pulCRC = ulCrc; + return; + + case 3: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 2: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 1: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 0: + *pulCRC = ulCrc; + return; + } + + // We may need to do some alignment work up front, and at the end, so that + // the main loop is aligned and only has to worry about 8 byte at a time. + // + // The low-order two bits of pb and nBuffer in total control the + // upfront work. + // + nFront = ((uintp)pb) & 3; + nBuffer -= nFront; + switch (nFront) { + case 3: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + case 2: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + case 1: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + } + + nMain = nBuffer >> 3; + while (nMain--) { + ulCrc ^= LittleLong(*(CRC32_t *)pb); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc ^= LittleLong(*(CRC32_t *)(pb + 4)); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + pb += 8; + } + + nBuffer &= 7; + goto JustAfew; +} diff --git a/tier1/checksum_md5.cpp b/tier1/checksum_md5.cpp new file mode 100644 index 0000000..d42cba2 --- /dev/null +++ b/tier1/checksum_md5.cpp @@ -0,0 +1,276 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "checksum_md5.h" + +#include +#include + +#include "tier0/basetypes.h" +#include "tier0/commonmacros.h" +#include "tier1/strtools.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// The four core functions - F1 is optimized somewhat +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) ((x) ^ (y) ^ (z)) +#define F4(x, y, z) ((y) ^ ((x) | ~(z))) + +// This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + (data), w = (w) << (s) | (w) >> (32 - s), w += x) + +//----------------------------------------------------------------------------- +// Purpose: The core of the MD5 algorithm, this alters an existing MD5 hash to +// reflect the addition of 16 longwords of new data. MD5Update blocks +// the data and converts bytes into longwords for this routine. +// Input : buf[4] - +// in[16] - +// Output : static void +//----------------------------------------------------------------------------- +#if (PLAT_BIG_ENDIAN == 1) +static void MD5Transform(unsigned int buf[4], unsigned int const in_big[16]) { + unsigned int in[16]; + for (int i = 0; i != 16; ++i) { + in[i] = LittleDWord(in_big[i]); + } +#else +static void MD5Transform(unsigned int buf[4], unsigned int const in[16]) { +#endif + unsigned int a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +//----------------------------------------------------------------------------- +// Purpose: Start MD5 accumulation. Set bit count to 0 and buffer to mysterious +// initialization constants. + +// Input : *ctx - +//----------------------------------------------------------------------------- +void MD5Init(MD5Context_t *ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Update context to reflect the concatenation of another buffer full +// of bytes. Input : *ctx - +// *buf - +// len - +//----------------------------------------------------------------------------- +void MD5Update(MD5Context_t *ctx, unsigned char const *buf, size_t len) { + unsigned int t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + (len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + // byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (unsigned int *)ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + // byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (unsigned int *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +//----------------------------------------------------------------------------- +// Purpose: Final wrapup - pad to 64-byte boundary with the bit pattern +// 1 0* (64-bit count of bits processed, MSB-first) +// Input : digest[MD5_DIGEST_LENGTH] - +// *ctx - +//----------------------------------------------------------------------------- +void MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5Context_t *ctx) { + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + // byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (unsigned int *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + // byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((unsigned int *)ctx->in)[14] = LittleDWord(ctx->bits[0]); + ((unsigned int *)ctx->in)[15] = LittleDWord(ctx->bits[1]); + + MD5Transform(ctx->buf, (unsigned int *)ctx->in); + // byteReverse((unsigned char *) ctx->buf, 4); +#if (PLAT_BIG_ENDIAN == 1) + static_assert(MD5_DIGEST_LENGTH == (sizeof(unsigned int) * 4)); + ((unsigned int *)digest)[0] = LittleDWord(ctx->buf[0]); + ((unsigned int *)digest)[1] = LittleDWord(ctx->buf[1]); + ((unsigned int *)digest)[2] = LittleDWord(ctx->buf[2]); + ((unsigned int *)digest)[3] = LittleDWord(ctx->buf[3]); +#else + memcpy(digest, ctx->buf, MD5_DIGEST_LENGTH); +#endif + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *hash - +// hashlen - +// Output : char +//----------------------------------------------------------------------------- +char *MD5_Print(unsigned char *hash, int hashlen) { + static char szReturn[64]; + + Assert(hashlen <= 32); + + V_binarytohex(hash, hashlen, szReturn, sizeof(szReturn)); + return szReturn; +} + +//----------------------------------------------------------------------------- +// Purpose: generate pseudo random number from a seed number +// Input : seed number +// Output : pseudo random number +//----------------------------------------------------------------------------- +unsigned int MD5_PseudoRandom(unsigned int nSeed) { + nSeed = LittleDWord(nSeed); + MD5Context_t ctx; + alignas(unsigned int) unsigned char digest[MD5_DIGEST_LENGTH]; // The MD5 Hash + + memset(&ctx, 0, sizeof(ctx)); + + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char *)&nSeed, sizeof(nSeed)); + MD5Final(digest, &ctx); + + return LittleDWord( + *(unsigned int *)(digest + 6)); // use 4 middle bytes for random value +} diff --git a/tier1/convar.cpp b/tier1/convar.cpp new file mode 100644 index 0000000..0066d8b --- /dev/null +++ b/tier1/convar.cpp @@ -0,0 +1,1278 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier1/convar.h" + +#include "basetypes.h" +#include "tier1/strtools.h" +#include "tier1/characterset.h" +#include "tier1/utlbuffer.h" +#include "tier1/tier1.h" +#include "tier1/convar_serverbounded.h" +#include "icvar.h" +#include "tier0/dbg.h" + +#if defined(_X360) +#include "xbox/xbox_console.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Comment this out when we release. +//#define ALLOW_DEVELOPMENT_CVARS +// This enables the l4d style of culling all cvars that are not marked +// FCVAR_RELEASE : #define CULL_ALL_CVARS_NOT_FCVAR_RELEASE + +//----------------------------------------------------------------------------- +// Statically constructed list of ConCommandBases, +// used for registering them with the ICVar interface +//----------------------------------------------------------------------------- +ConCommandBase *ConCommandBase::s_pConCommandBases = NULL; +IConCommandBaseAccessor *ConCommandBase::s_pAccessor = NULL; +static int s_nCVarFlag = 0; +static int s_nDLLIdentifier = + -1; // A unique identifier indicating which DLL this convar came from +static bool s_bRegistered = false; + +class CDefaultAccessor : public IConCommandBaseAccessor { + public: + virtual bool RegisterConCommandBase(ConCommandBase *pVar) { + // Link to engine's list instead + g_pCVar->RegisterConCommand(pVar); + return true; + } +}; + +static CDefaultAccessor s_DefaultAccessor; + +//----------------------------------------------------------------------------- +// Called by the framework to register ConCommandBases with the ICVar +//----------------------------------------------------------------------------- +void ConVar_Register(int nCVarFlag, IConCommandBaseAccessor *pAccessor) { + if (!g_pCVar || s_bRegistered) return; + + Assert(s_nDLLIdentifier < 0); + s_bRegistered = true; + s_nCVarFlag = nCVarFlag; + s_nDLLIdentifier = g_pCVar->AllocateDLLIdentifier(); + + ConCommandBase *pCur, *pNext; + + ConCommandBase::s_pAccessor = pAccessor ? pAccessor : &s_DefaultAccessor; + pCur = ConCommandBase::s_pConCommandBases; + while (pCur) { + pNext = pCur->m_pNext; + pCur->AddFlags(s_nCVarFlag); + pCur->Init(); + pCur = pNext; + } + + g_pCVar->AddSplitScreenConVars(); + g_pCVar->ProcessQueuedMaterialThreadConVarSets(); + + ConCommandBase::s_pConCommandBases = NULL; +} + +void ConVar_Unregister() { + if (!g_pCVar || !s_bRegistered) return; + + Assert(s_nDLLIdentifier >= 0); + + // Do this after unregister!!! + g_pCVar->RemoveSplitScreenConVars(s_nDLLIdentifier); + g_pCVar->UnregisterConCommands(s_nDLLIdentifier); + s_nDLLIdentifier = -1; + s_bRegistered = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Default constructor +//----------------------------------------------------------------------------- +ConCommandBase::ConCommandBase(void) { + m_bRegistered = false; + m_pszName = NULL; + m_pszHelpString = NULL; + + m_nFlags = 0; + m_pNext = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: The base console invoked command/cvar interface +// Input : *pName - name of variable/command +// *pHelpString - help text +// flags - flags +//----------------------------------------------------------------------------- +ConCommandBase::ConCommandBase(const char *pName, + const char *pHelpString /*=0*/, + int flags /*= 0*/) { + Create(pName, pHelpString, flags); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConCommandBase::~ConCommandBase(void) {} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConCommandBase::IsCommand(void) const { + // Assert( 0 ); This can't assert. . causes a recursive assert in + // Sys_Printf, etc. + return true; +} + +//----------------------------------------------------------------------------- +// Returns the DLL identifier +//----------------------------------------------------------------------------- +CVarDLLIdentifier_t ConCommandBase::GetDLLIdentifier() const { + return s_nDLLIdentifier; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pName - +// callback - +// *pHelpString - +// flags - +//----------------------------------------------------------------------------- +void ConCommandBase::Create(const char *pName, const char *pHelpString /*= 0*/, + int flags /*= 0*/) { + static const char empty_string[] = ""; + + m_bRegistered = false; + + // Name should be static data + Assert(pName); + m_pszName = pName; + m_pszHelpString = pHelpString ? pHelpString : empty_string; + + m_nFlags = flags; + +#ifdef ALLOW_DEVELOPMENT_CVARS + m_nFlags &= ~FCVAR_DEVELOPMENTONLY; +#endif + + if (!(m_nFlags & FCVAR_UNREGISTERED)) { + m_pNext = s_pConCommandBases; + s_pConCommandBases = this; + } else { + // It's unregistered + m_pNext = NULL; + } + + // If s_pAccessor is already set (this ConVar is not a global variable), + // register it. + if (s_pAccessor) { + Init(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Used internally by OneTimeInit to initialize. +//----------------------------------------------------------------------------- +void ConCommandBase::Init() { + if (s_pAccessor) { + s_pAccessor->RegisterConCommandBase(this); + } +} + +void ConCommandBase::Shutdown() { + if (g_pCVar) { + g_pCVar->UnregisterConCommand(this); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return name of the command/var +// Output : const char +//----------------------------------------------------------------------------- +const char *ConCommandBase::GetName(void) const { return m_pszName; } + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flag - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConCommandBase::IsFlagSet(int flag) const { + return (flag & m_nFlags) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flags - +//----------------------------------------------------------------------------- +void ConCommandBase::AddFlags(int flags) { + m_nFlags |= flags; + +#ifdef ALLOW_DEVELOPMENT_CVARS + m_nFlags &= ~FCVAR_DEVELOPMENTONLY; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: removes specified flags +//----------------------------------------------------------------------------- +void ConCommandBase::RemoveFlags(int flags) { m_nFlags &= ~flags; } + +// Returns current flags +int ConCommandBase::GetFlags() const { return m_nFlags; } + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const ConCommandBase +//----------------------------------------------------------------------------- +const ConCommandBase *ConCommandBase::GetNext(void) const { return m_pNext; } + +ConCommandBase *ConCommandBase::GetNext(void) { return m_pNext; } + +//----------------------------------------------------------------------------- +// Purpose: Copies string using local new/delete operators +// Input : *from - +// Output : char +//----------------------------------------------------------------------------- +char *ConCommandBase::CopyString(const char *from) { + intp len; + char *to; + + len = V_strlen(from); + if (len <= 0) { + to = new char[1]; + to[0] = 0; + } else { + to = new char[len + 1]; + V_strncpy(to, from, len + 1); + } + return to; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *ConCommandBase::GetHelpText(void) const { return m_pszHelpString; } + +//----------------------------------------------------------------------------- +// Purpose: Has this cvar been registered +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConCommandBase::IsRegistered(void) const { return m_bRegistered; } + +//----------------------------------------------------------------------------- +// +// Con Commands start here +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Global methods +//----------------------------------------------------------------------------- +static characterset_t s_BreakSet; +static bool s_bBuiltBreakSet = false; + +//----------------------------------------------------------------------------- +// Tokenizer class +//----------------------------------------------------------------------------- +CCommand::CCommand() { + if (!s_bBuiltBreakSet) { + s_bBuiltBreakSet = true; + CharacterSetBuild(&s_BreakSet, "{}()':"); + } + + Reset(); +} + +CCommand::CCommand(int nArgC, const char **ppArgV) { + Assert(nArgC > 0); + + if (!s_bBuiltBreakSet) { + s_bBuiltBreakSet = true; + CharacterSetBuild(&s_BreakSet, "{}()':"); + } + + Reset(); + + char *pBuf = m_pArgvBuffer; + char *pSBuf = m_pArgSBuffer; + m_nArgc = nArgC; + for (int i = 0; i < nArgC; ++i) { + m_ppArgv[i] = pBuf; + intp nLen = V_strlen(ppArgV[i]); + memcpy(pBuf, ppArgV[i], nLen + 1); + if (i == 0) { + m_nArgv0Size = nLen; + } + pBuf += nLen + 1; + + bool bContainsSpace = strchr(ppArgV[i], ' ') != NULL; + if (bContainsSpace) { + *pSBuf++ = '\"'; + } + memcpy(pSBuf, ppArgV[i], nLen); + pSBuf += nLen; + if (bContainsSpace) { + *pSBuf++ = '\"'; + } + + if (i != nArgC - 1) { + *pSBuf++ = ' '; + } + } +} + +void CCommand::Reset() { + m_nArgc = 0; + m_nArgv0Size = 0; + m_pArgSBuffer[0] = 0; +} + +characterset_t *CCommand::DefaultBreakSet() { return &s_BreakSet; } + +bool CCommand::Tokenize(const char *pCommand, characterset_t *pBreakSet) { + Reset(); + if (!pCommand) return false; + + // Use default break set + if (!pBreakSet) { + pBreakSet = &s_BreakSet; + } + + // Copy the current command into a temp buffer + // NOTE: This is here to avoid the pointers returned by DequeueNextCommand + // to become invalid by calling AddText. Is there a way we can avoid the + // memcpy? + intp nLen = V_strlen(pCommand); + if (nLen >= COMMAND_MAX_LENGTH - 1) { + Warning( + "CCommand::Tokenize: Encountered command which overflows the tokenizer " + "buffer.. Skipping!\n"); + return false; + } + + memcpy(m_pArgSBuffer, pCommand, nLen + 1); + + // Parse the current command into the current command buffer + CUtlBuffer bufParse(m_pArgSBuffer, nLen, + CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY); + intp nArgvBufferSize = 0; + while (bufParse.IsValid() && (m_nArgc < COMMAND_MAX_ARGC)) { + char *pArgvBuf = &m_pArgvBuffer[nArgvBufferSize]; + intp nMaxLen = (intp)COMMAND_MAX_LENGTH - nArgvBufferSize; + intp nStartGet = bufParse.TellGet(); + intp nSize = bufParse.ParseToken(pBreakSet, pArgvBuf, nMaxLen); + if (nSize < 0) break; + + // Check for overflow condition + if (nMaxLen == nSize) { + Reset(); + return false; + } + + if (m_nArgc == 1) { + // Deal with the case where the arguments were quoted + m_nArgv0Size = bufParse.TellGet(); + bool bFoundEndQuote = m_pArgSBuffer[m_nArgv0Size - 1] == '\"'; + if (bFoundEndQuote) { + --m_nArgv0Size; + } + m_nArgv0Size -= nSize; + Assert(m_nArgv0Size != 0); + + // The StartGet check is to handle this case: "foo"bar + // which will parse into 2 different args. ArgS should point to bar. + bool bFoundStartQuote = (m_nArgv0Size > nStartGet) && + (m_pArgSBuffer[m_nArgv0Size - 1] == '\"'); + Assert(bFoundEndQuote == bFoundStartQuote); + if (bFoundStartQuote) { + --m_nArgv0Size; + } + } + + m_ppArgv[m_nArgc++] = pArgvBuf; + if (m_nArgc >= COMMAND_MAX_ARGC) { + Warning( + "CCommand::Tokenize: Encountered command which overflows the " + "argument buffer.. Clamped!\n"); + } + + nArgvBufferSize += nSize + 1; + Assert(nArgvBufferSize <= COMMAND_MAX_LENGTH); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Helper function to parse arguments to commands. +//----------------------------------------------------------------------------- +const char *CCommand::FindArg(const char *pName) const { + int nArgC = ArgC(); + for (int i = 1; i < nArgC; i++) { + if (!V_stricmp(Arg(i), pName)) return (i + 1) < nArgC ? Arg(i + 1) : ""; + } + return 0; +} + +int CCommand::FindArgInt(const char *pName, int nDefaultVal) const { + const char *pVal = FindArg(pName); + if (pVal) + return atoi(pVal); + else + return nDefaultVal; +} + +//----------------------------------------------------------------------------- +// Default console command autocompletion function +//----------------------------------------------------------------------------- +int DefaultCompletionFunc(const char *partial, + char commands[COMMAND_COMPLETION_MAXITEMS] + [COMMAND_COMPLETION_ITEM_LENGTH]) { + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructs a console command +//----------------------------------------------------------------------------- +// ConCommand::ConCommand() +//{ +// m_bIsNewConCommand = true; +//} + +ConCommand::ConCommand(const char *pName, FnCommandCallbackV1_t callback, + const char *pHelpString /*= 0*/, int flags /*= 0*/, + FnCommandCompletionCallback completionFunc /*= 0*/) { + // Set the callback + m_fnCommandCallbackV1 = callback; + m_bUsingNewCommandCallback = false; + m_bUsingCommandCallbackInterface = false; + m_fnCompletionCallback = + completionFunc ? completionFunc : DefaultCompletionFunc; + m_bHasCompletionCallback = completionFunc != 0 ? true : false; + + // Setup the rest + BaseClass::Create(pName, pHelpString, flags); +} + +ConCommand::ConCommand(const char *pName, FnCommandCallback_t callback, + const char *pHelpString /*= 0*/, int flags /*= 0*/, + FnCommandCompletionCallback completionFunc /*= 0*/) { + // Set the callback + m_fnCommandCallback = callback; + m_bUsingNewCommandCallback = true; + m_fnCompletionCallback = + completionFunc ? completionFunc : DefaultCompletionFunc; + m_bHasCompletionCallback = completionFunc != 0 ? true : false; + m_bUsingCommandCallbackInterface = false; + + // Setup the rest + BaseClass::Create(pName, pHelpString, flags); +} + +ConCommand::ConCommand( + const char *pName, ICommandCallback *pCallback, + const char *pHelpString /*= 0*/, int flags /*= 0*/, + ICommandCompletionCallback *pCompletionCallback /*= 0*/) { + // Set the callback + m_pCommandCallback = pCallback; + m_bUsingNewCommandCallback = false; + m_pCommandCompletionCallback = pCompletionCallback; + m_bHasCompletionCallback = (pCompletionCallback != 0); + m_bUsingCommandCallbackInterface = true; + + // Setup the rest + BaseClass::Create(pName, pHelpString, flags); +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +ConCommand::~ConCommand(void) {} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this is a command +//----------------------------------------------------------------------------- +bool ConCommand::IsCommand(void) const { return true; } + +//----------------------------------------------------------------------------- +// Purpose: Invoke the function if there is one +//----------------------------------------------------------------------------- +void ConCommand::Dispatch(const CCommand &command) { + if (m_bUsingNewCommandCallback) { + if (m_fnCommandCallback) { + (*m_fnCommandCallback)(command); + return; + } + } else if (m_bUsingCommandCallbackInterface) { + if (m_pCommandCallback) { + m_pCommandCallback->CommandCallback(command); + return; + } + } else { + if (m_fnCommandCallbackV1) { + (*m_fnCommandCallbackV1)(); + return; + } + } + + // Command without callback!!! + AssertMsg1(0, "Encountered ConCommand '%s' without a callback!\n", GetName()); +} + +//----------------------------------------------------------------------------- +// Purpose: Calls the autocompletion method to get autocompletion suggestions +//----------------------------------------------------------------------------- +int ConCommand::AutoCompleteSuggest(const char *partial, + CUtlVector &commands) { + if (m_bUsingCommandCallbackInterface) { + if (!m_pCommandCompletionCallback) return 0; + return m_pCommandCompletionCallback->CommandCompletionCallback(partial, + commands); + } + + Assert(m_fnCompletionCallback); + if (!m_fnCompletionCallback) return 0; + + char rgpchCommands[COMMAND_COMPLETION_MAXITEMS] + [COMMAND_COMPLETION_ITEM_LENGTH]; + int iret = (m_fnCompletionCallback)(partial, rgpchCommands); + for (int i = 0; i < iret; ++i) { + CUtlString str = rgpchCommands[i]; + commands.AddToTail(str); + } + return iret; +} + +//----------------------------------------------------------------------------- +// Returns true if the console command can autocomplete +//----------------------------------------------------------------------------- +bool ConCommand::CanAutoComplete(void) { return m_bHasCompletionCallback; } + +//----------------------------------------------------------------------------- +// +// Console Variables +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Various constructors +//----------------------------------------------------------------------------- +ConVar::ConVar(const char *pName, const char *pDefaultValue, + int flags /* = 0 */) { + Create(pName, pDefaultValue, flags); +} + +ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString) { + Create(pName, pDefaultValue, flags, pHelpString); +} + +ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString, bool bMin, float fMin, bool bMax, + float fMax) { + Create(pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax); +} + +ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString, FnChangeCallback_t callback) { + Create(pName, pDefaultValue, flags, pHelpString, false, 0.0, false, 0.0, + callback); +} + +ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, + const char *pHelpString, bool bMin, float fMin, bool bMax, + float fMax, FnChangeCallback_t callback) { + Create(pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax, + callback); +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +ConVar::~ConVar(void) { + if (m_Value.m_pszString) { + delete[] m_Value.m_pszString; + m_Value.m_pszString = NULL; + } +} + +//----------------------------------------------------------------------------- +// Install a change callback (there shouldn't already be one....) +//----------------------------------------------------------------------------- +void ConVar::InstallChangeCallback(FnChangeCallback_t callback, + bool bInvoke /*=true*/) { + if (!callback) { + Warning("InstallChangeCallback called with NULL callback, ignoring!!!\n"); + return; + } + + if (m_pParent->m_fnChangeCallbacks.Find(callback) != + m_pParent->m_fnChangeCallbacks.InvalidIndex()) { + // Same ptr added twice, sigh... + Warning("InstallChangeCallback ignoring duplicate change callback!!!\n"); + return; + } + + m_pParent->m_fnChangeCallbacks.AddToTail(callback); + + // Call it immediately to set the initial value... + if (bInvoke) { + callback(this, m_Value.m_pszString, m_Value.m_fValue); + } +} + +void ConVar::RemoveChangeCallback(FnChangeCallback_t callback) { + m_pParent->m_fnChangeCallbacks.FindAndRemove(callback); +} + +bool ConVar::IsFlagSet(int flag) const { + return (flag & m_pParent->m_nFlags) ? true : false; +} + +int ConVar::GetFlags() const { return m_pParent->m_nFlags; } + +const char *ConVar::GetHelpText(void) const { + return m_pParent->m_pszHelpString; +} + +void ConVar::AddFlags(int flags) { + m_pParent->m_nFlags |= flags; + +#ifdef ALLOW_DEVELOPMENT_CVARS + m_pParent->m_nFlags &= ~FCVAR_DEVELOPMENTONLY; +#endif +} + +bool ConVar::IsRegistered(void) const { return m_pParent->m_bRegistered; } + +const char *ConVar::GetName(void) const { return m_pParent->m_pszName; } + +const char *ConVar::GetBaseName(void) const { return m_pParent->m_pszName; } + +int ConVar::GetSplitScreenPlayerSlot(void) const { + // Default implementation (certain FCVAR_USERINFO derive a new type of convar + // and set this) + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConVar::IsCommand(void) const { return false; } + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +//----------------------------------------------------------------------------- +void ConVar::Init() { BaseClass::Init(); } + +bool ConVar::InternalSetColorFromString(const char *value) { + bool bColor = false; + + // Try pulling RGBA color values out of the string + int nRGBA[4]; + int nParamsRead = sscanf(value, "%i %i %i %i", &(nRGBA[0]), &(nRGBA[1]), + &(nRGBA[2]), &(nRGBA[3])); + + if (nParamsRead >= 3) { + // This is probably a color! + if (nParamsRead == 3) { + // Assume they wanted full alpha + nRGBA[3] = 255; + } + + if (nRGBA[0] >= 0 && nRGBA[0] <= 255 && nRGBA[1] >= 0 && nRGBA[1] <= 255 && + nRGBA[2] >= 0 && nRGBA[2] <= 255 && nRGBA[3] >= 0 && nRGBA[3] <= 255) { + // This is definitely a color! + bColor = true; + + // Stuff all the values into each byte of our int + unsigned char *pColorElement = ((unsigned char *)&m_Value.m_nValue); + pColorElement[0] = static_cast(nRGBA[0]); + pColorElement[1] = static_cast(nRGBA[1]); + pColorElement[2] = static_cast(nRGBA[2]); + pColorElement[3] = static_cast(nRGBA[3]); + + // Copy that value into a float (even though this has little meaning) + m_Value.m_fValue = (float)(m_Value.m_nValue); + } + } + + return bColor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetValue(const char *value) { + if (IsFlagSet(FCVAR_MATERIAL_THREAD_MASK)) { + if (g_pCVar && !g_pCVar->IsMaterialThreadSetAllowed()) { + g_pCVar->QueueMaterialThreadSetValue(this, value); + return; + } + } + + char tempVal[32]; + + Assert(m_pParent == this); // Only valid for root convars. + + float flOldValue = m_Value.m_fValue; + const char *val = value ? value : ""; + + if (!InternalSetColorFromString(val)) { + // Not a color, do the standard thing + float fNewValue = (float)atof(value); + if (!IsFinite(fNewValue)) { + Warning("Warning: %s = '%s' is infinite, clamping value.\n", GetName(), + value); + fNewValue = FLT_MAX; + } + + if (ClampValue(fNewValue)) { + V_snprintf(tempVal, sizeof(tempVal), "%f", fNewValue); + val = tempVal; + } + + // Redetermine value + m_Value.m_fValue = fNewValue; + m_Value.m_nValue = (int)(m_Value.m_fValue); + } + + if (!(m_nFlags & FCVAR_NEVER_AS_STRING)) { + ChangeStringValue(val, flOldValue); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tempVal - +//----------------------------------------------------------------------------- +void ConVar::ChangeStringValue(const char *tempVal, float flOldValue) { + Assert(!(m_nFlags & FCVAR_NEVER_AS_STRING)); + + char *pszOldValue = (char *)stackalloc(m_Value.m_StringLength); + memcpy(pszOldValue, m_Value.m_pszString, m_Value.m_StringLength); + + intp len = V_strlen(tempVal) + 1; + + if (len > m_Value.m_StringLength) { + delete[] m_Value.m_pszString; + + m_Value.m_pszString = new char[len]; + m_Value.m_StringLength = len; + } + + memcpy(m_Value.m_pszString, tempVal, len); + + // Invoke any necessary callback function + for (intp i = 0; i < m_fnChangeCallbacks.Count(); ++i) { + m_fnChangeCallbacks[i](this, pszOldValue, flOldValue); + } + + if (g_pCVar) { + g_pCVar->CallGlobalChangeCallbacks(this, pszOldValue, flOldValue); + } + + stackfree(pszOldValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Check whether to clamp and then perform clamp +// Input : value - +// Output : Returns true if value changed +//----------------------------------------------------------------------------- +bool ConVar::ClampValue(float &value) { + if (m_bHasMin && (value < m_fMinVal)) { + value = m_fMinVal; + return true; + } + + if (m_bHasMax && (value > m_fMaxVal)) { + value = m_fMaxVal; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetFloatValue(float fNewValue) { + if (fNewValue == m_Value.m_fValue) return; + + if (IsFlagSet(FCVAR_MATERIAL_THREAD_MASK)) { + if (g_pCVar && !g_pCVar->IsMaterialThreadSetAllowed()) { + g_pCVar->QueueMaterialThreadSetValue(this, fNewValue); + return; + } + } + + Assert(m_pParent == this); // Only valid for root convars. + + // Check bounds + ClampValue(fNewValue); + + // Redetermine value + float flOldValue = m_Value.m_fValue; + m_Value.m_fValue = fNewValue; + m_Value.m_nValue = (int)m_Value.m_fValue; + + if (!(m_nFlags & FCVAR_NEVER_AS_STRING)) { + char tempVal[32]; + V_snprintf(tempVal, sizeof(tempVal), "%f", m_Value.m_fValue); + ChangeStringValue(tempVal, flOldValue); + } else { + Assert(m_fnChangeCallbacks.Count() == 0); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetIntValue(int nValue) { + if (nValue == m_Value.m_nValue) return; + + if (IsFlagSet(FCVAR_MATERIAL_THREAD_MASK)) { + if (g_pCVar && !g_pCVar->IsMaterialThreadSetAllowed()) { + g_pCVar->QueueMaterialThreadSetValue(this, nValue); + return; + } + } + + Assert(m_pParent == this); // Only valid for root convars. + + float fValue = (float)nValue; + if (ClampValue(fValue)) { + nValue = (int)(fValue); + } + + // Redetermine value + float flOldValue = m_Value.m_fValue; + m_Value.m_fValue = fValue; + m_Value.m_nValue = nValue; + + if (!(m_nFlags & FCVAR_NEVER_AS_STRING)) { + char tempVal[32]; + V_snprintf(tempVal, sizeof(tempVal), "%d", m_Value.m_nValue); + ChangeStringValue(tempVal, flOldValue); + } else { + Assert(m_fnChangeCallbacks.Count() == 0); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetColorValue(Color value) { + // Stuff color values into an int + int nValue; + + unsigned char *pColorElement = ((unsigned char *)&nValue); + pColorElement[0] = value[0]; + pColorElement[1] = value[1]; + pColorElement[2] = value[2]; + pColorElement[3] = value[3]; + + // Call the int internal set + InternalSetIntValue(nValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Private creation +//----------------------------------------------------------------------------- +void ConVar::Create(const char *pName, const char *pDefaultValue, + int flags /*= 0*/, const char *pHelpString /*= NULL*/, + bool bMin /*= false*/, float fMin /*= 0.0*/, + bool bMax /*= false*/, float fMax /*= false*/, + FnChangeCallback_t callback /*= NULL*/) { + static const char empty_string[] = ""; + + m_pParent = this; + + // Name should be static data + m_pszDefaultValue = pDefaultValue ? pDefaultValue : empty_string; + Assert(m_pszDefaultValue); + + m_bHasMin = bMin; + m_fMinVal = fMin; + m_bHasMax = bMax; + m_fMaxVal = fMax; + + if (callback) { + m_fnChangeCallbacks.AddToTail(callback); + } + + m_Value.m_StringLength = V_strlen(m_pszDefaultValue) + 1; + m_Value.m_pszString = new char[m_Value.m_StringLength]; + memcpy(m_Value.m_pszString, m_pszDefaultValue, m_Value.m_StringLength); + + if (!InternalSetColorFromString(m_Value.m_pszString)) { + m_Value.m_fValue = (float)atof(m_Value.m_pszString); + if (!IsFinite(m_Value.m_fValue)) { + Warning("ConVar(%s) defined with infinite float value (%s)\n", pName, + m_Value.m_pszString); + m_Value.m_fValue = FLT_MAX; + Assert(0); + } + + // Bounds Check, should never happen, if it does, no big deal + if (m_bHasMin && (m_Value.m_fValue < m_fMinVal)) { + Assert(0); + } + + if (m_bHasMax && (m_Value.m_fValue > m_fMaxVal)) { + Assert(0); + } + + m_Value.m_nValue = (int)m_Value.m_fValue; + } + + // If we're not tagged as cheat, archive or release then hide us. +#if CULL_ALL_CVARS_NOT_FCVAR_RELEASE + // FIXMEL4DTOMAINMERGE: will need to assess if this hides too many convars for + // TF and other projects in main + if (!(flags & + (FCVAR_CHEAT | FCVAR_ARCHIVE | FCVAR_RELEASE | FCVAR_USERINFO))) { + flags |= FCVAR_DEVELOPMENTONLY; + } +#else + +#endif + + BaseClass::Create(pName, pHelpString, flags); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::SetValue(const char *value) { + ConVar *var = (ConVar *)m_pParent; + var->InternalSetValue(value); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : value - +//----------------------------------------------------------------------------- +void ConVar::SetValue(float value) { + ConVar *var = (ConVar *)m_pParent; + var->InternalSetFloatValue(value); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : value - +//----------------------------------------------------------------------------- +void ConVar::SetValue(int value) { + ConVar *var = (ConVar *)m_pParent; + var->InternalSetIntValue(value); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : value - +//----------------------------------------------------------------------------- +void ConVar::SetValue(Color value) { + ConVar *var = (ConVar *)m_pParent; + var->InternalSetColorValue(value); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset to default value +//----------------------------------------------------------------------------- +void ConVar::Revert(void) { + // Force default value again + ConVar *var = (ConVar *)m_pParent; + var->SetValue(var->m_pszDefaultValue); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : minVal - +// Output : true if there is a min set +//----------------------------------------------------------------------------- +bool ConVar::GetMin(float &minVal) const { + minVal = m_pParent->m_fMinVal; + return m_pParent->m_bHasMin; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : maxVal - +//----------------------------------------------------------------------------- +bool ConVar::GetMax(float &maxVal) const { + maxVal = m_pParent->m_fMaxVal; + return m_pParent->m_bHasMax; +} + +float ConVar::GetMinValue() const { return m_pParent->m_fMinVal; } + +float ConVar::GetMaxValue() const { + return m_pParent->m_fMaxVal; + ; +} + +bool ConVar::HasMin() const { return m_pParent->m_bHasMin; } + +bool ConVar::HasMax() const { return m_pParent->m_bHasMax; } + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *ConVar::GetDefault(void) const { + return m_pParent->m_pszDefaultValue; +} + +void ConVar::SetDefault(const char *pszDefault) { + static const char empty_string[] = ""; + m_pszDefaultValue = pszDefault ? pszDefault : empty_string; + Assert(m_pszDefaultValue); +} + +//----------------------------------------------------------------------------- +// This version is simply used to make reading convars simpler. +// Writing convars isn't allowed in this mode +//----------------------------------------------------------------------------- +class CEmptyConVar : public ConVar { + public: + CEmptyConVar() : ConVar("", "0") {} + // Used for optimal read access + virtual void SetValue(const char *pValue) {} + virtual void SetValue(float flValue) {} + virtual void SetValue(int nValue) {} + virtual const char *GetName(void) const { return ""; } + virtual bool IsFlagSet(int nFlags) const { return false; } +}; + +static CEmptyConVar s_EmptyConVar; + +ConVarRef::ConVarRef(const char *pName) { Init(pName, false); } + +ConVarRef::ConVarRef(const char *pName, bool bIgnoreMissing) { + Init(pName, bIgnoreMissing); +} + +void ConVarRef::Init(const char *pName, bool bIgnoreMissing) { + m_pConVar = g_pCVar ? g_pCVar->FindVar(pName) : &s_EmptyConVar; + if (!m_pConVar) { + m_pConVar = &s_EmptyConVar; + } + m_pConVarState = static_cast(m_pConVar); + if (!IsValid()) { + static bool bFirst = true; + if (g_pCVar || bFirst) { + if (!bIgnoreMissing) { + Warning("ConVarRef %s doesn't point to an existing ConVar\n", pName); + } + bFirst = false; + } + } +} + +ConVarRef::ConVarRef(IConVar *pConVar) { + m_pConVar = pConVar ? pConVar : &s_EmptyConVar; + m_pConVarState = static_cast(m_pConVar); +} + +bool ConVarRef::IsValid() const { return m_pConVar != &s_EmptyConVar; } + +// Helper for splitscreen ConVars +SplitScreenConVarRef::SplitScreenConVarRef(const char *pName) { + Init(pName, false); +} + +SplitScreenConVarRef::SplitScreenConVarRef(const char *pName, + bool bIgnoreMissing) { + Init(pName, bIgnoreMissing); +} + +void SplitScreenConVarRef::Init(const char *pName, bool bIgnoreMissing) { + for (int i = 0; i < MAX_SPLITSCREEN_CLIENTS; ++i) { + cv_t &info = m_Info[i]; + char pchName[256]; + if (i != 0) { + V_snprintf(pchName, sizeof(pchName), "%s%d", pName, i + 1); + } else { + V_strncpy(pchName, pName, sizeof(pchName)); + } + + info.m_pConVar = g_pCVar ? g_pCVar->FindVar(pchName) : &s_EmptyConVar; + if (!info.m_pConVar) { + info.m_pConVar = &s_EmptyConVar; + if (i > 0) { + // Point at slot zero instead, in case we got in here with a non + // FCVAR_SS var... + info.m_pConVar = m_Info[0].m_pConVar; + } + } + info.m_pConVarState = static_cast(info.m_pConVar); + } + + if (!IsValid()) { + static bool bFirst = true; + if (g_pCVar || bFirst) { + if (!bIgnoreMissing) { + Warning("ConVarRef %s doesn't point to an existing ConVar\n", pName); + } + bFirst = false; + } + } +} + +SplitScreenConVarRef::SplitScreenConVarRef(IConVar *pConVar) { + cv_t &info = m_Info[0]; + info.m_pConVar = pConVar ? pConVar : &s_EmptyConVar; + info.m_pConVarState = static_cast(info.m_pConVar); + + for (int i = 1; i < MAX_SPLITSCREEN_CLIENTS; ++i) { + info = m_Info[i]; + char pchName[256]; + V_snprintf(pchName, sizeof(pchName), "%s%d", pConVar->GetName(), i + 1); + + info.m_pConVar = g_pCVar ? g_pCVar->FindVar(pchName) : &s_EmptyConVar; + if (!info.m_pConVar) { + // Point at slot zero instead, in case we got in here with a non + // FCVAR_SS var... + info.m_pConVar = m_Info[0].m_pConVar; + } + info.m_pConVarState = static_cast(info.m_pConVar); + } +} + +bool SplitScreenConVarRef::IsValid() const { + return m_Info[0].m_pConVar != &s_EmptyConVar; +} + +struct PrintConVarFlags_t { + int flag; + const char *desc; +}; + +static PrintConVarFlags_t g_PrintConVarFlags[] = { + {FCVAR_GAMEDLL, "game"}, + {FCVAR_CLIENTDLL, "client"}, + {FCVAR_ARCHIVE, "archive"}, + {FCVAR_NOTIFY, "notify"}, + {FCVAR_SPONLY, "singleplayer"}, + {FCVAR_NOT_CONNECTED, "notconnected"}, + {FCVAR_CHEAT, "cheat"}, + {FCVAR_REPLICATED, "replicated"}, + {FCVAR_SERVER_CAN_EXECUTE, "server_can_execute"}, + {FCVAR_CLIENTCMD_CAN_EXECUTE, "clientcmd_can_execute"}, + {FCVAR_USERINFO, "user"}, + {FCVAR_SS, "ss"}, + {FCVAR_SS_ADDED, "ss_added"}, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ConVar_AppendFlags(const ConCommandBase *var, char *buf, size_t bufsize) { + char append[128]; + + for (const PrintConVarFlags_t &info : g_PrintConVarFlags) { + if (var->IsFlagSet(info.flag)) { + V_snprintf(append, sizeof(append), " %s", info.desc); + V_strncat(buf, append, bufsize, COPY_ALL_CHARACTERS); + } + } +} + +static void AppendPrintf(char *buf, size_t bufsize, + PRINTF_FORMAT_STRING char const *fmt, ...) { + char scratch[1024]; + va_list argptr; + va_start(argptr, fmt); + _vsnprintf(scratch, sizeof(scratch) - 1, fmt, argptr); + va_end(argptr); + scratch[sizeof(scratch) - 1] = 0; + + V_strncat(buf, scratch, bufsize, COPY_ALL_CHARACTERS); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ConVar_PrintDescription(const ConCommandBase *pVar) { + bool bMin, bMax; + float fMin, fMax; + const char *pStr; + + Assert(pVar); + + Color clr; + clr.SetColor(255, 100, 100, 255); + + char outstr[4096]; + outstr[0] = 0; + + if (!pVar->IsCommand()) { + ConVar *var = (ConVar *)pVar; + const ConVar_ServerBounded *pBounded = + dynamic_cast(var); + + bMin = var->GetMin(fMin); + bMax = var->GetMax(fMax); + + const char *value = NULL; + char tempVal[32]; + + if (pBounded || var->IsFlagSet(FCVAR_NEVER_AS_STRING)) { + value = tempVal; + + int intVal = pBounded ? pBounded->GetInt() : var->GetInt(); + float floatVal = pBounded ? pBounded->GetFloat() : var->GetFloat(); + + if (fabs((float)intVal - floatVal) < 0.000001) { + V_snprintf(tempVal, sizeof(tempVal), "%d", intVal); + } else { + V_snprintf(tempVal, sizeof(tempVal), "%f", floatVal); + } + } else { + value = var->GetString(); + } + + AppendPrintf(outstr, sizeof(outstr), "\"%s\" = \"%s\"", var->GetName(), + value); + + if (V_stricmp(value, var->GetDefault())) { + AppendPrintf(outstr, sizeof(outstr), " ( def. \"%s\" )", + var->GetDefault()); + } + + if (bMin) { + AppendPrintf(outstr, sizeof(outstr), " min. %f", fMin); + } + if (bMax) { + AppendPrintf(outstr, sizeof(outstr), " max. %f", fMax); + } + + // Handle virtualized cvars. + if (pBounded && fabs(pBounded->GetFloat() - var->GetFloat()) > 0.0001f) { + AppendPrintf(outstr, sizeof(outstr), " [%.3f server clamped to %.3f]", + var->GetFloat(), pBounded->GetFloat()); + } + } else { + ConCommand *var = (ConCommand *)pVar; + + AppendPrintf(outstr, sizeof(outstr), "\"%s\" ", var->GetName()); + } + + ConVar_AppendFlags(pVar, outstr, sizeof(outstr)); + + pStr = pVar->GetHelpText(); + if (pStr && *pStr) { + ConMsg("%-80s - %.80s\n", outstr, pStr); + } else { + ConMsg("%-80s\n", outstr); + } +} diff --git a/tier1/exprevaluator.cpp b/tier1/exprevaluator.cpp new file mode 100644 index 0000000..3e983cd --- /dev/null +++ b/tier1/exprevaluator.cpp @@ -0,0 +1,429 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: ExprSimplifier builds a binary tree from an infix expression (in the +// form of a character array). Evaluates C style infix parenthetic logical +// expressions. Supports !, ||, &&, (). Symbols are resolved via callback. +// Syntax is $. $0 evaluates to false. $ evaluates to true, e.g: ( +// $1 || ( $FOO || $WHATEVER ) && !$BAR ) + +#include "tier1/exprevaluator.h" + +#include +#include "vstdlib/ikeyvaluessystem.h" +#include "tier1/convar.h" +#include "tier1/fmtstr.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Default conditional symbol handler callback. Symbols are the form $. +// Return true or false for the value of the symbol. +//----------------------------------------------------------------------------- +bool DefaultConditionalSymbolProc(const char *pKey) { + if (pKey[0] == '$') { + pKey++; + } + + if (!V_stricmp(pKey, "WIN32")) { + return IsPC(); + } + + if (!V_stricmp(pKey, "WINDOWS")) { + return IsPlatformWindowsPC(); + } + + if (!V_stricmp(pKey, "X360")) { + return IsX360(); + } + + if (!V_stricmp(pKey, "PS3")) { + return IsPS3(); + } + + if (!V_stricmp(pKey, "OSX")) { + return IsPlatformOSX(); + } + + if (!V_stricmp(pKey, "LINUX")) { + return IsPlatformLinux(); + } + + if (!V_stricmp(pKey, "POSIX")) { + return IsPlatformPosix(); + } + + if (!V_stricmp(pKey, "GAMECONSOLE")) { + return IsGameConsole(); + } + + if (!V_stricmp(pKey, "DEMO")) { +#if defined(_DEMO) + return true; +#else + return false; +#endif + } + + if (!V_stricmp(pKey, "LOWVIOLENCE")) { +#if defined(_LOWVIOLENCE) + return true; +#endif + // If it is not a LOWVIOLENCE binary build, then fall through + // and check if there was a run-time symbol installed for it + } + + // don't know it at compile time, so fall through to installed symbol values + return KeyValuesSystem()->GetKeyValuesExpressionSymbol(pKey); +} + +void DefaultConditionalErrorProc(const char *pReason) { + Warning("Conditional Error: %s\n", pReason); +} + +CExpressionEvaluator::CExpressionEvaluator() { + m_ExprTree = NULL; + m_CurToken = '\0'; + m_pExpression = NULL; + m_CurPosition = 0; + m_Identifier[0] = '\0'; + m_pGetSymbolProc = NULL; + m_pSyntaxErrorProc = NULL; + m_bSetup = false; +} + +CExpressionEvaluator::~CExpressionEvaluator() { FreeTree(m_ExprTree); } + +//----------------------------------------------------------------------------- +// Sets mCurToken to the next token in the input string. Skips all +// whitespace. +//----------------------------------------------------------------------------- +char CExpressionEvaluator::GetNextToken(void) { + // while whitespace, Increment CurrentPosition + while (m_pExpression[m_CurPosition] == ' ') ++m_CurPosition; + + // CurrentToken = Expression[CurrentPosition] + m_CurToken = m_pExpression[m_CurPosition++]; + + return m_CurToken; +} + +//----------------------------------------------------------------------------- +// Utility funcs +//----------------------------------------------------------------------------- +void CExpressionEvaluator::FreeNode(ExprNode *pNode) { delete pNode; } + +ExprNode *CExpressionEvaluator::AllocateNode(void) { return new ExprNode; } + +void CExpressionEvaluator::FreeTree(ExprTree &node) { + if (!node) return; + + FreeTree(node->left); + FreeTree(node->right); + FreeNode(node); + node = 0; +} + +bool CExpressionEvaluator::IsConditional(bool &bConditional, const char token) { + char nextchar = ' '; + if (token == OR_OP || token == AND_OP) { + // expect || or && + nextchar = m_pExpression[m_CurPosition++]; + if ((token & nextchar) == token) { + bConditional = true; + } else if (m_pSyntaxErrorProc) { + m_pSyntaxErrorProc( + CFmtStr("Bad expression operator: '%c%c', expected C style operator", + token, nextchar)); + return false; + } + } else { + bConditional = false; + } + + // valid + return true; +} + +bool CExpressionEvaluator::IsNotOp(const char token) { + if (token == NOT_OP) + return true; + else + return false; +} + +bool CExpressionEvaluator::IsIdentifierOrConstant(const char token) { + bool success = false; + if (token == '$') { + // store the entire identifier + int i = 0; + m_Identifier[i++] = token; + while ((isalnum(m_pExpression[m_CurPosition]) || + m_pExpression[m_CurPosition] == '_') && + i < MAX_IDENTIFIER_LEN) { + m_Identifier[i] = m_pExpression[m_CurPosition]; + ++m_CurPosition; + ++i; + } + + if (i < MAX_IDENTIFIER_LEN - 1) { + m_Identifier[i] = '\0'; + success = true; + } + } else { + if (isdigit(token)) { + int i = 0; + m_Identifier[i++] = token; + while (isdigit(m_pExpression[m_CurPosition]) && + (i < MAX_IDENTIFIER_LEN)) { + m_Identifier[i] = m_pExpression[m_CurPosition]; + ++m_CurPosition; + ++i; + } + if (i < MAX_IDENTIFIER_LEN - 1) { + m_Identifier[i] = '\0'; + success = true; + } + } + } + + return success; +} + +bool CExpressionEvaluator::MakeExprNode(ExprTree &tree, char token, Kind kind, + ExprTree left, ExprTree right) { + tree = AllocateNode(); + tree->left = left; + tree->right = right; + tree->kind = kind; + + switch (kind) { + case CONDITIONAL: + tree->data.cond = token; + break; + + case LITERAL: + if (isdigit(m_Identifier[0])) { + tree->data.value = (atoi(m_Identifier) != 0); + } else { + tree->data.value = m_pGetSymbolProc(m_Identifier); + } + break; + + case NOT: + break; + + default: + if (m_pSyntaxErrorProc) { + Assert(0); + m_pSyntaxErrorProc(CFmtStr("Logic Error in CExpressionEvaluator")); + } + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Makes a factor :: { } | . +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::MakeFactor(ExprTree &tree) { + if (m_CurToken == '(') { + // Get the next token + GetNextToken(); + + // Make an expression, setting Tree to point to it + if (!MakeExpression(tree)) { + return false; + } + } else if (IsIdentifierOrConstant(m_CurToken)) { + // Make a literal node, set Tree to point to it, set left/right children to + // NULL. + if (!MakeExprNode(tree, m_CurToken, LITERAL, NULL, NULL)) { + return false; + } + } else if (IsNotOp(m_CurToken)) { + // do nothing + return true; + } else { + // This must be a bad token + if (m_pSyntaxErrorProc) { + m_pSyntaxErrorProc(CFmtStr("Bad expression token: %c", m_CurToken)); + } + return false; + } + + // Get the next token + GetNextToken(); + return true; +} + +//----------------------------------------------------------------------------- +// Makes a term :: { }. +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::MakeTerm(ExprTree &tree) { + // Make a factor, setting Tree to point to it + if (!MakeFactor(tree)) { + return false; + } + + // while the next token is ! + while (IsNotOp(m_CurToken)) { + // Make an operator node, setting left child to Tree and right to NULL. + // (Tree points to new node) + if (!MakeExprNode(tree, m_CurToken, NOT, tree, NULL)) { + return false; + } + + // Get the next token. + GetNextToken(); + + // Make a factor, setting the right child of Tree to point to it. + if (!MakeFactor(tree->right)) { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Makes a complete expression :: { }. +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::MakeExpression(ExprTree &tree) { + // Make a term, setting Tree to point to it + if (!MakeTerm(tree)) { + return false; + } + + // while the next token is a conditional + while (1) { + bool bConditional = false; + bool bValid = IsConditional(bConditional, m_CurToken); + if (!bValid) { + return false; + } + + if (!bConditional) { + break; + } + + // Make a conditional node, setting left child to Tree and right to NULL. + // (Tree points to new node) + if (!MakeExprNode(tree, m_CurToken, CONDITIONAL, tree, NULL)) { + return false; + } + + // Get the next token. + GetNextToken(); + + // Make a term, setting the right child of Tree to point to it. + if (!MakeTerm(tree->right)) { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// returns true for success, false for failure +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::BuildExpression(void) { + // Get the first token, and build the tree. + GetNextToken(); + + return (MakeExpression(m_ExprTree)); +} + +//----------------------------------------------------------------------------- +// returns the value of the node after resolving all children +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::SimplifyNode(ExprTree &node) { + if (!node) return false; + + // Simplify the left and right children of this node + bool leftVal = SimplifyNode(node->left); + bool rightVal = SimplifyNode(node->right); + + // Simplify this node + switch (node->kind) { + case NOT: + // the child of '!' is always to the right + node->data.value = !rightVal; + break; + + case CONDITIONAL: + if (node->data.cond == AND_OP) { + node->data.value = leftVal && rightVal; + } else // OR_OP + { + node->data.value = leftVal || rightVal; + } + break; + + default: // LITERAL + break; + } + + // This node has beed resolved + node->kind = LITERAL; + return node->data.value; +} + +//----------------------------------------------------------------------------- +// Interface to solve a conditional expression. Returns false on failure, +// Result is undefined. +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::Evaluate(bool &bResult, const char *pInfixExpression, + GetSymbolProc_t pGetSymbolProc, + SyntaxErrorProc_t pSyntaxErrorProc) { + if (!pInfixExpression) { + return false; + } + + // for caller simplicity, we strip of any enclosing braces + // strip the bracketing [] if present + char szCleanToken[512]; + if (pInfixExpression[0] == '[') { + intp len = V_strlen(pInfixExpression); + + // SECURITY: Bail on input buffers that are too large, they're used for RCEs + // and we don't need to support them. + if (len + 1 > V_ARRAYSIZE(szCleanToken)) { + return false; + } + + // SECURIY: Because this starts one character late, it picks up the null + // termination from pInfixExpression. + V_strncpy(szCleanToken, pInfixExpression + 1, len); + len--; + if (szCleanToken[len - 1] == ']') { + szCleanToken[len - 1] = '\0'; + } + pInfixExpression = szCleanToken; + } + + // reset state + m_pExpression = pInfixExpression; + m_pGetSymbolProc = + pGetSymbolProc ? pGetSymbolProc : DefaultConditionalSymbolProc; + m_pSyntaxErrorProc = + pSyntaxErrorProc ? pSyntaxErrorProc : DefaultConditionalErrorProc; + m_ExprTree = 0; + m_CurPosition = 0; + m_CurToken = 0; + + // Building the expression tree will fail on bad syntax + bool bValid = BuildExpression(); + if (bValid) { + bResult = SimplifyNode(m_ExprTree); + } + + // don't leak + FreeTree(m_ExprTree); + m_ExprTree = NULL; + + return bValid; +} diff --git a/tier1/generichash.cpp b/tier1/generichash.cpp new file mode 100644 index 0000000..8562d9f --- /dev/null +++ b/tier1/generichash.cpp @@ -0,0 +1,404 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Variant Pearson Hash general purpose hashing algorithm described by +// Cargill in C++ Report 1994. Generates a 16-bit result. + +#include "generichash.h" + +#include +#include + +#include "tier0/basetypes.h" +#include "tier0/platform.h" +#include "tier0/dbg.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// +// Table of randomly shuffled values from 0-255 generated by: +// +//----------------------------------------------------------------------------- +/* +void MakeRandomValues() +{ + int i, j, r; + unsigned t; + srand( 0xdeadbeef ); + + for ( i = 0; i < 256; i++ ) + { + g_nRandomValues[i] = (unsigned )i; + } + + for (j = 0; j < 8; j++) + { + for (i = 0; i < 256; i++) + { + r = rand() & 0xff; + t = g_nRandomValues[i]; + g_nRandomValues[i] = g_nRandomValues[r]; + g_nRandomValues[r] = t; + } + } + + printf("static unsigned g_nRandomValues[256] =\n{\n"); + + for (i = 0; i < 256; i += 16) + { + printf("\t"); + for (j = 0; j < 16; j++) + printf(" %3d,", g_nRandomValues[i+j]); + printf("\n"); + } + printf("};\n"); +} +*/ + +static unsigned g_nRandomValues[256] = { + 238, 164, 191, 168, 115, 16, 142, 11, 213, 214, 57, 151, 248, 252, 26, + 198, 13, 105, 102, 25, 43, 42, 227, 107, 210, 251, 86, 66, 83, 193, + 126, 108, 131, 3, 64, 186, 192, 81, 37, 158, 39, 244, 14, 254, 75, + 30, 2, 88, 172, 176, 255, 69, 0, 45, 116, 139, 23, 65, 183, 148, + 33, 46, 203, 20, 143, 205, 60, 197, 118, 9, 171, 51, 233, 135, 220, + 49, 71, 184, 82, 109, 36, 161, 169, 150, 63, 96, 173, 125, 113, 67, + 224, 78, 232, 215, 35, 219, 79, 181, 41, 229, 149, 153, 111, 217, 21, + 72, 120, 163, 133, 40, 122, 140, 208, 231, 211, 200, 160, 182, 104, 110, + 178, 237, 15, 101, 27, 50, 24, 189, 177, 130, 187, 92, 253, 136, 100, + 212, 19, 174, 70, 22, 170, 206, 162, 74, 247, 5, 47, 32, 179, 117, + 132, 195, 124, 123, 245, 128, 236, 223, 12, 84, 54, 218, 146, 228, 157, + 94, 106, 31, 17, 29, 194, 34, 56, 134, 239, 246, 241, 216, 127, 98, + 7, 204, 154, 152, 209, 188, 48, 61, 87, 97, 225, 85, 90, 167, 155, + 112, 145, 114, 141, 93, 250, 4, 201, 156, 38, 89, 226, 196, 1, 235, + 44, 180, 159, 121, 119, 166, 190, 144, 10, 91, 76, 230, 221, 80, 207, + 55, 58, 53, 175, 8, 6, 52, 68, 242, 18, 222, 103, 249, 147, 129, + 138, 243, 28, 185, 62, 59, 240, 202, 234, 99, 77, 73, 199, 137, 95, + 165, +}; + +//----------------------------------------------------------------------------- +// String +//----------------------------------------------------------------------------- +unsigned FASTCALL HashString(const char *pszKey) { + const uint8 *k = (const uint8 *)pszKey; + unsigned even = 0, odd = 0, n; + + while ((n = *k++) != 0) { + even = g_nRandomValues[odd ^ n]; + if ((n = *k++) != 0) + odd = g_nRandomValues[even ^ n]; + else + break; + } + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// Case-insensitive string +//----------------------------------------------------------------------------- +unsigned FASTCALL HashStringCaseless(const char *pszKey) { + const uint8 *k = (const uint8 *)pszKey; + unsigned even = 0, odd = 0, n; + + while ((n = toupper(*k++)) != 0) { + even = g_nRandomValues[odd ^ n]; + if ((n = toupper(*k++)) != 0) + odd = g_nRandomValues[even ^ n]; + else + break; + } + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// 32 bit conventional case-insensitive string +//----------------------------------------------------------------------------- +unsigned FASTCALL HashStringCaselessConventional(const char *pszKey) { + unsigned hash = 0xAAAAAAAA; // Alternating 1's and 0's to maximize the effect + // of the later multiply and add + + for (; *pszKey; pszKey++) { + hash = ((hash << 5) + hash) + (uint8)tolower(*pszKey); + } + + return hash; +} + +//----------------------------------------------------------------------------- +// int hash +//----------------------------------------------------------------------------- +unsigned FASTCALL HashInt(const int n) { + unsigned even, odd; + odd = g_nRandomValues[(((unsigned)n >> 8) & 0xff)]; + even = g_nRandomValues[odd ^ ((unsigned)n >> 24)]; + odd = g_nRandomValues[even ^ ((unsigned)n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ ((unsigned)n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ ((unsigned)n & 0xff)]; + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// 4-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash4(const void *pKey) { + const uint32 *p = (const uint32 *)pKey; + unsigned even, odd, n; + n = *p; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// 8-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash8(const void *pKey) { + const uint32 *p = (const uint32 *)pKey; + unsigned even, odd, n; + n = *p; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p + 1); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// 12-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash12(const void *pKey) { + const uint32 *p = (const uint32 *)pKey; + unsigned even, odd, n; + n = *p; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p + 1); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p + 2); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// 16-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash16(const void *pKey) { + const uint32 *p = (const uint32 *)pKey; + unsigned even, odd, n; + n = *p; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p + 1); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p + 2); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p + 3); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ (n >> 8) & 0xff]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// Arbitrary fixed length hash +//----------------------------------------------------------------------------- +unsigned FASTCALL HashBlock(const void *pKey, unsigned size) { + const uint8 *k = (const uint8 *)pKey; + unsigned even = 0, odd = 0, n; + + while (size) { + --size; + n = *k++; + even = g_nRandomValues[odd ^ n]; + if (size) { + --size; + n = *k++; + odd = g_nRandomValues[even ^ n]; + } else + break; + } + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// Murmur hash +//----------------------------------------------------------------------------- +uint32 MurmurHash2(const void *key, int len, uint32 seed) { + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + + const uint32 m = 0x5bd1e995; + const int r = 24; + + // Initialize the hash to a 'random' value + + uint32 h = seed ^ len; + + // Mix 4 bytes at a time into the hash + + const unsigned char *data = (const unsigned char *)key; + + while (len >= 4) { + uint32 k = LittleDWord(*(uint32 *)data); + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + // Handle the last few bytes of the input array + + switch (len) { + case 3: + h ^= data[2] << 16; + case 2: + h ^= data[1] << 8; + case 1: + h ^= data[0]; + h *= m; + }; + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +#define TOLOWERU(c) ((uint32)((((c) >= 'A') && ((c) <= 'Z')) ? (c) + 32 : (c))) + +uint32 MurmurHash2LowerCase(char const *pString, uint32 nSeed) { + int nLen = (int)strlen(pString); + char *p = (char *)stackalloc(nLen + 1); + for (int i = 0; i < nLen; i++) { + p[i] = TOLOWERU(pString[i]); + } + return MurmurHash2(p, nLen, nSeed); +} + +//----------------------------------------------------------------------------- +// Murmur hash, 64 bit- endian neutral +//----------------------------------------------------------------------------- +uint64 MurmurHash64(const void *key, int len, uint32 seed) { + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + + const uint32 m = 0x5bd1e995; + const int r = 24; + + // Initialize the hash to a 'random' value + + uint32 h1 = seed ^ len; + uint32 h2 = 0; + + // Mix 4 bytes at a time into the hash + + const uint32 *data = (const uint32 *)key; + while (len >= 8) { + uint32 k1 = LittleDWord(*data++); + k1 *= m; + k1 ^= k1 >> r; + k1 *= m; + h1 *= m; + h1 ^= k1; + len -= 4; + + uint32 k2 = LittleDWord(*data++); + k2 *= m; + k2 ^= k2 >> r; + k2 *= m; + h2 *= m; + h2 ^= k2; + len -= 4; + } + + if (len >= 4) { + uint32 k1 = LittleDWord(*data++); + k1 *= m; + k1 ^= k1 >> r; + k1 *= m; + h1 *= m; + h1 ^= k1; + len -= 4; + } + + // Handle the last few bytes of the input array + switch (len) { + case 3: + h2 ^= ((uint8 *)data)[2] << 16; + case 2: + h2 ^= ((uint8 *)data)[1] << 8; + case 1: + h2 ^= ((uint8 *)data)[0]; + h2 *= m; + }; + + h1 ^= h2 >> 18; + h1 *= m; + h2 ^= h1 >> 22; + h2 *= m; + h1 ^= h2 >> 17; + h1 *= m; + h2 ^= h1 >> 19; + h2 *= m; + + uint64 h = h1; + + h = (h << 32) | h2; + + return h; +} diff --git a/tier1/interface.cpp b/tier1/interface.cpp new file mode 100644 index 0000000..d1f2d36 --- /dev/null +++ b/tier1/interface.cpp @@ -0,0 +1,625 @@ +// Copyright Valve Corporation, All rights reserved. + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#endif + +#if !defined(DONT_PROTECT_FILEIO_FUNCTIONS) +#define DONT_PROTECT_FILEIO_FUNCTIONS // for protected_things.h +#endif + +#if defined(PROTECTED_THINGS_ENABLE) +#undef PROTECTED_THINGS_ENABLE // from protected_things.h +#endif + +#ifdef _WIN32 +#include // getcwd +#endif + +#include "tier1/interface.h" +#include "basetypes.h" +#include "tier0/dbg.h" +#include "tier1/strtools.h" +#include "tier0/icommandline.h" +#include "tier0/dbg.h" +#include "tier0/stacktools.h" +#include "tier0/threadtools.h" + +#if defined(_X360) +#include "xbox/xbox_win32stubs.h" +#endif + +#ifdef _PS3 +#include "sys/prx.h" +#include "tier1/utlvector.h" +#include "ps3/ps3_platform.h" +#include "ps3/ps3_win32stubs.h" +#include "ps3/ps3_helpers.h" +#include "ps3_pathinfo.h" +#elif defined(POSIX) +#include "tier0/platform.h" +#endif // _PS3 + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ------------------------------------------------------------------------------------ +// // InterfaceReg. +// ------------------------------------------------------------------------------------ +// // +#ifdef POSIX +DLL_GLOBAL_EXPORT +#endif +InterfaceReg *s_pInterfaceRegs; + +InterfaceReg::InterfaceReg(InstantiateInterfaceFn fn, const char *pName) + : m_pName(pName) { + m_CreateFn = fn; + m_pNext = s_pInterfaceRegs; + s_pInterfaceRegs = this; +} + +// ------------------------------------------------------------------------------------ +// // CreateInterface. This is the primary exported function by a dll, +// referenced by name via dynamic binding that exposes an opqaue function +// pointer to the interface. +// +// We have the Internal variant so Sys_GetFactoryThis() returns the correct +// internal symbol under GCC/Linux/Mac as CreateInterface is DLL_EXPORT so its +// global so the loaders on those OS's pick exactly 1 of the CreateInterface +// symbols to be the one that is process wide and all Sys_GetFactoryThis() calls +// find that one, which doesn't work. Using the internal walkthrough here makes +// sure Sys_GetFactoryThis() has the dll specific symbol and GetProcAddress() +// returns the module specific function for CreateInterface again getting the +// dll specific symbol we need. +// ------------------------------------------------------------------------------------ +// // +void *CreateInterfaceInternal(const char *pName, int *pReturnCode) { + InterfaceReg *pCur; + + for (pCur = s_pInterfaceRegs; pCur; pCur = pCur->m_pNext) { + if (strcmp(pCur->m_pName, pName) == 0) { + if (pReturnCode) { + *pReturnCode = IFACE_OK; + } + return pCur->m_CreateFn(); + } + } + + if (pReturnCode) { + *pReturnCode = IFACE_FAILED; + } + return NULL; +} + +void *CreateInterface(const char *pName, int *pReturnCode) { + return CreateInterfaceInternal(pName, pReturnCode); +} + +#if defined(POSIX) && !defined(_PS3) +// Linux doesn't have this function so this emulates its functionality +void *GetModuleHandle(const char *name) { + void *handle; + + if (name == NULL) { + // hmm, how can this be handled under linux.... + // is it even needed? + return NULL; + } + + if ((handle = dlopen(name, RTLD_NOW)) == NULL) { + fprintf(stderr, "DLOPEN Error:%s\n", dlerror()); + // couldn't open this file + return NULL; + } + + // read "man dlopen" for details + // in short dlopen() inc a ref count + // so dec the ref count by performing the close + dlclose(handle); + return handle; +} +#endif + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#endif + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a function, given a module +// Input : pModuleName - module name +// *pName - proc name +//----------------------------------------------------------------------------- +static void *Sys_GetProcAddress(const char *pModuleName, const char *pName) { +#if defined(_PS3) + Assert(!"Unsupported, use HMODULE"); + return NULL; +#else // !_PS3 + HMODULE hModule = (HMODULE)GetModuleHandle(pModuleName); +#if defined(WIN32) + return (void *)GetProcAddress(hModule, pName); +#else // !WIN32 + return (void *)dlsym((void *)hModule, pName); +#endif // WIN32 +#endif // _PS3 +} + +static void *Sys_GetProcAddress(HMODULE hModule, const char *pName) { +#if defined(WIN32) + return (void *)GetProcAddress(hModule, pName); +#elif defined(_PS3) + PS3_LoadAppSystemInterface_Parameters_t *pPRX = + reinterpret_cast(hModule); + if (!pPRX) return NULL; + if (!strcmp(pName, CREATEINTERFACE_PROCNAME)) + return reinterpret_cast(pPRX->pfnCreateInterface); + Assert(!"Unknown PRX function requested!"); + return NULL; +#else + return (void *)dlsym((void *)hModule, pName); +#endif +} + +bool Sys_IsDebuggerPresent() { return Plat_IsInDebugSession(); } + +struct ThreadedLoadLibaryContext_t { + const char *m_pLibraryName; + HMODULE m_hLibrary; + DWORD m_nError; + ThreadedLoadLibaryContext_t() + : m_pLibraryName(NULL), m_hLibrary(0), m_nError(0) {} +}; + +#ifdef _WIN32 + +// wraps LoadLibraryEx() since 360 doesn't support that +static HMODULE InternalLoadLibrary(const char *pName) { +#if defined(_X360) + return LoadLibrary(pName); +#else + return LoadLibraryEx(pName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); +#endif +} +uint ThreadedLoadLibraryFunc(void *pParam) { + ThreadedLoadLibaryContext_t *pContext = (ThreadedLoadLibaryContext_t *)pParam; + pContext->m_hLibrary = InternalLoadLibrary(pContext->m_pLibraryName); + return 0; +} +#endif + +// global to propagate a library load error from thread into Sys_LoadModule +static DWORD g_nLoadLibraryError = 0; + +static HMODULE Sys_LoadLibraryGuts(const char *pLibraryName) { +#ifdef PLATFORM_PS3 + + PS3_LoadAppSystemInterface_Parameters_t *pPRX = + new PS3_LoadAppSystemInterface_Parameters_t; + V_memset(pPRX, 0, sizeof(PS3_LoadAppSystemInterface_Parameters_t)); + pPRX->cbSize = sizeof(PS3_LoadAppSystemInterface_Parameters_t); + int iResult = PS3_PrxLoad(pLibraryName, pPRX); + if (iResult < CELL_OK) { + delete pPRX; + return NULL; + } + return reinterpret_cast(pPRX); + +#else + + char str[1024]; + + // How to get a string out of a #define on the command line. + const char *pModuleExtension = DLL_EXT_STRING; + const char *pModuleAddition = pModuleExtension; + + V_strncpy(str, pLibraryName, sizeof(str)); + if (!V_stristr(str, pModuleExtension)) { + if (IsX360()) { + V_StripExtension(str, str, sizeof(str)); + } + V_strncat(str, pModuleAddition, sizeof(str)); + } + V_FixSlashes(str); + +#ifdef _WIN32 + ThreadedLoadLibraryFunc_t threadFunc = GetThreadedLoadLibraryFunc(); + if (!threadFunc) { + HMODULE retVal = InternalLoadLibrary(str); + if (retVal) { + StackToolsNotify_LoadedLibrary(str); + } +#if 0 // you can enable this block to help track down why a module isn't + // loading: + else + { +#ifdef _WINDOWS + char buf[1024]; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + 0, // Default language + (LPTSTR) buf, + 1023, + NULL // no insert arguments + ); + Warning( "Could not load %s: %s\n", str, buf ); +#endif + } +#endif + + return retVal; + } + + ThreadedLoadLibaryContext_t context; + context.m_pLibraryName = str; + context.m_hLibrary = 0; + + ThreadHandle_t h = CreateSimpleThread(ThreadedLoadLibraryFunc, &context); + +#ifdef _X360 + ThreadSetAffinity(h, XBOX_PROCESSOR_3); +#endif + + unsigned int nTimeout = 0; + while (WaitForSingleObject((HANDLE)h, nTimeout) == WAIT_TIMEOUT) { + nTimeout = threadFunc(); + } + + ReleaseThreadHandle(h); + + if (context.m_hLibrary) { + g_nLoadLibraryError = 0; + StackToolsNotify_LoadedLibrary(str); + } else { + g_nLoadLibraryError = context.m_nError; + } + + return context.m_hLibrary; + +#elif defined(POSIX) && !defined(_PS3) + HMODULE ret = (HMODULE)dlopen(str, RTLD_NOW); + if (!ret) { + const char *pError = dlerror(); + if (pError && (strstr(pError, "No such file") == 0) && + (strstr(pError, "image not found") == 0)) { + Msg(" failed to dlopen %s error=%s\n", str, pError); + } + } + + // if( ret ) + // StackToolsNotify_LoadedLibrary( str ); + + return ret; +#endif + +#endif +} + +static HMODULE Sys_LoadLibrary(const char *pLibraryName) { + // load a library. If a library suffix is set, look for the library first with + // that name + const char *pSuffix = NULL; + + if (CommandLine()->FindParm("-xlsp")) { + pSuffix = "_xlsp"; + } +#ifdef POSIX + else if (CommandLine()->FindParm("-valveinternal")) { + pSuffix = "_valveinternal"; + } +#endif +#ifdef IS_WINDOWS_PC + else if (CommandLine()->FindParm("-ds")) // windows DS bins + { + pSuffix = "_ds"; + } +#endif + if (pSuffix) { + char nameBuf[MAX_PATH]; + strcpy(nameBuf, pLibraryName); + char *pDot = strchr(nameBuf, '.'); + if (pDot) *pDot = 0; + V_strncat(nameBuf, pSuffix, sizeof(nameBuf), COPY_ALL_CHARACTERS); + HMODULE hRet = Sys_LoadLibraryGuts(nameBuf); + if (hRet) return hRet; + } + return Sys_LoadLibraryGuts(pLibraryName); +} + +//----------------------------------------------------------------------------- +// Purpose: Keeps a flag if the current dll/exe loaded any debug modules +// This flag can also get set if the current process discovers any other debug +// modules loaded by other dlls +//----------------------------------------------------------------------------- +static bool s_bRunningWithDebugModules = false; + +#ifdef IS_WINDOWS_PC +//----------------------------------------------------------------------------- +// Purpose: Construct a process-specific name for kernel object to track +// if any debug modules were loaded +//----------------------------------------------------------------------------- +static void DebugKernelMemoryObjectName(char *pszNameBuffer) { + sprintf(pszNameBuffer, "VALVE-MODULE-DEBUG-%08X", GetCurrentProcessId()); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Loads a DLL/component from disk and returns a handle to it +// Input : *pModuleName - filename of the component +// Output : opaque handle to the module (hides system dependency) +//----------------------------------------------------------------------------- +CSysModule *Sys_LoadModule(const char *pModuleName) { + // If using the Steam filesystem, either the DLL must be a minimum footprint + // file in the depot (MFP) or a filesystem GetLocalCopy() call must be made + // prior to the call to this routine. + HMODULE hDLL = NULL; + + char alteredFilename[MAX_PATH]; + if (IsPS3()) { + // PS3's load module *must* be fed extensions. If the extension is missing, + // add it. + if (!(strstr(pModuleName, ".sprx") || strstr(pModuleName, ".prx"))) { + strncpy(alteredFilename, pModuleName, MAX_PATH); + strncat(alteredFilename, DLL_EXT_STRING, + MAX_PATH - strlen(alteredFilename) - 1); + pModuleName = alteredFilename; + } + } + + if (!V_IsAbsolutePath(pModuleName)) { + // full path wasn't passed in, using the current working dir + char szAbsoluteModuleName[1024]; +#if defined(_PS3) + // getcwd not supported on ps3; use PRX PATCH path if patched + if (g_pPS3PathInfo->IsPatched()) { + V_snprintf(szAbsoluteModuleName, sizeof(szAbsoluteModuleName), + "%s/bin/%s", g_pPS3PathInfo->GamePatchBasePath(), pModuleName); + hDLL = Sys_LoadLibrary(szAbsoluteModuleName); + } + if (!hDLL) // use base PRX path + { + V_snprintf(szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/%s", + g_pPS3PathInfo->PrxPath(), pModuleName); + hDLL = Sys_LoadLibrary(szAbsoluteModuleName); + } +#else // !_PS3 + char szCwd[1024]; + _getcwd(szCwd, sizeof(szCwd)); + + if (IsX360()) { + int i = CommandLine()->FindParm("-basedir"); + if (i) strcpy(szCwd, CommandLine()->GetParm(i + 1)); + } + + size_t cCwd = strlen(szCwd); + if (cCwd != 0 && (szCwd[cCwd - 1] == '/' || szCwd[cCwd - 1] == '\\')) { + szCwd[cCwd - 1] = '\0'; + } + + cCwd = strlen(szCwd); + if (strstr(pModuleName, "bin/") == pModuleName || + (cCwd >= 3 && (szCwd[cCwd - 1] == 'n' && szCwd[cCwd - 2] == 'i' && + szCwd[cCwd - 3] == 'b'))) { + // don't make bin/bin path + V_snprintf(szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/%s", + szCwd, pModuleName); + } else { + V_snprintf(szAbsoluteModuleName, sizeof(szAbsoluteModuleName), + "%s/bin/%s", szCwd, pModuleName); + } + hDLL = Sys_LoadLibrary(szAbsoluteModuleName); +#endif // _PS3 + } + + if (!hDLL) { + // full path failed, let LoadLibrary() try to search the PATH now + hDLL = Sys_LoadLibrary(pModuleName); +#if defined(_DEBUG) + if (!hDLL) { +// So you can see what the error is in the debugger... +#if defined(_WIN32) && !defined(_X360) + char *lpMsgBuf; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR)&lpMsgBuf, 0, NULL); + + LocalFree((HLOCAL)lpMsgBuf); +#elif defined(_X360) + DWORD error = g_nLoadLibraryError ? g_nLoadLibraryError : GetLastError(); + Msg("Error(%d) - Failed to load %s:\n", error, pModuleName); +#elif defined(_PS3) + Msg("Failed to load %s:\n", pModuleName); +#else + Msg("Failed to load %s: %s\n", pModuleName, dlerror()); +#endif // _WIN32 + } +#endif // DEBUG + } + + // If running in the debugger, assume debug binaries are okay, otherwise they + // must run with -allowdebug + if (!IsGameConsole() && Sys_GetProcAddress(hDLL, "BuiltDebug")) { + if (hDLL && !CommandLine()->FindParm("-allowdebug") && + !Sys_IsDebuggerPresent()) { + Error("Module %s is a debug build\n", pModuleName); + } + + DevWarning("Module %s is a debug build\n", pModuleName); + + if (!s_bRunningWithDebugModules) { + s_bRunningWithDebugModules = true; + +#ifdef IS_WINDOWS_PC + char chMemoryName[MAX_PATH]; + DebugKernelMemoryObjectName(chMemoryName); + + (void)CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, + 1024, chMemoryName); + // Created a shared memory kernel object specific to process id + // Existence of this object indicates that we have debug modules loaded +#endif + } + } + + return reinterpret_cast(hDLL); +} + +//----------------------------------------------------------------------------- +// Purpose: Determine if any debug modules were loaded +//----------------------------------------------------------------------------- +bool Sys_RunningWithDebugModules() { + if (!s_bRunningWithDebugModules) { +#ifdef IS_WINDOWS_PC + char chMemoryName[MAX_PATH]; + DebugKernelMemoryObjectName(chMemoryName); + + HANDLE hObject = OpenFileMapping(FILE_MAP_READ, FALSE, chMemoryName); + if (hObject && hObject != INVALID_HANDLE_VALUE) { + CloseHandle(hObject); + s_bRunningWithDebugModules = true; + } +#endif + } + return s_bRunningWithDebugModules; +} + +//----------------------------------------------------------------------------- +// Purpose: Unloads a DLL/component from +// Input : *pModuleName - filename of the component +// Output : opaque handle to the module (hides system dependency) +//----------------------------------------------------------------------------- +void Sys_UnloadModule(CSysModule *pModule) { + if (!pModule) return; + + HMODULE hDLL = reinterpret_cast(pModule); + +#ifdef _WIN32 + FreeLibrary(hDLL); +#elif defined(_PS3) + PS3_PrxUnload(((PS3_PrxLoadParametersBase_t *)pModule)->sysPrxId); + delete (PS3_PrxLoadParametersBase_t *)pModule; +#elif defined(POSIX) + dlclose((void *)hDLL); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a function, given a module +// Input : module - windows HMODULE from Sys_LoadModule() +// *pName - proc name +// Output : factory for this module +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactory(CSysModule *pModule) { + if (!pModule) return NULL; + + HMODULE hDLL = reinterpret_cast(pModule); +#ifdef _WIN32 + return reinterpret_cast( + GetProcAddress(hDLL, CREATEINTERFACE_PROCNAME)); +#elif defined(_PS3) + return reinterpret_cast( + Sys_GetProcAddress(hDLL, CREATEINTERFACE_PROCNAME)); +#elif defined(POSIX) + // Linux gives this error: + //../public/interface.cpp: In function `IBaseInterface *(*Sys_GetFactory + //(CSysModule *)) (const char *, int *)': + //../public/interface.cpp:154: ISO C++ forbids casting between + // pointer-to-function and pointer-to-object + // + // so lets get around it :) + return (CreateInterfaceFn)(GetProcAddress((void *)hDLL, + CREATEINTERFACE_PROCNAME)); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of this module +// Output : interface_instance_t +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactoryThis(void) { return &CreateInterfaceInternal; } + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of the named module +// Input : *pModuleName - name of the module +// Output : interface_instance_t - instance of that module +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactory(const char *pModuleName) { +#ifdef _WIN32 + return static_cast( + Sys_GetProcAddress(pModuleName, CREATEINTERFACE_PROCNAME)); +#elif defined(_PS3) + Assert(0); + return NULL; +#elif defined(POSIX) + // see Sys_GetFactory( CSysModule *pModule ) for an explanation + return (CreateInterfaceFn)(Sys_GetProcAddress(pModuleName, + CREATEINTERFACE_PROCNAME)); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: get the interface for the specified module and version +// Input : +// Output : +//----------------------------------------------------------------------------- +bool Sys_LoadInterface(const char *pModuleName, + const char *pInterfaceVersionName, + CSysModule **pOutModule, void **pOutInterface) { + CSysModule *pMod = Sys_LoadModule(pModuleName); + if (!pMod) return false; + + CreateInterfaceFn fn = Sys_GetFactory(pMod); + if (!fn) { + Sys_UnloadModule(pMod); + return false; + } + + *pOutInterface = fn(pInterfaceVersionName, NULL); + if (!(*pOutInterface)) { + Sys_UnloadModule(pMod); + return false; + } + + if (pOutModule) *pOutModule = pMod; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Place this as a singleton at module scope (e.g.) and use it to get +// the factory from the specified module name. +// +// When the singleton goes out of scope (.dll unload if at module scope), +// then it'll call Sys_UnloadModule on the module so that the refcount is +// decremented and the .dll actually can unload from memory. +//----------------------------------------------------------------------------- +CDllDemandLoader::CDllDemandLoader(char const *pchModuleName) + : m_pchModuleName(pchModuleName), m_hModule(0), m_bLoadAttempted(false) {} + +CDllDemandLoader::~CDllDemandLoader() { Unload(); } + +CreateInterfaceFn CDllDemandLoader::GetFactory() { + if (!m_hModule && !m_bLoadAttempted) { + m_bLoadAttempted = true; + m_hModule = Sys_LoadModule(m_pchModuleName); + } + + if (!m_hModule) { + return NULL; + } + + return Sys_GetFactory(m_hModule); +} + +void CDllDemandLoader::Unload() { + if (m_hModule) { + Sys_UnloadModule(m_hModule); + m_hModule = 0; + } +} diff --git a/tier1/keyvalues.cpp b/tier1/keyvalues.cpp new file mode 100644 index 0000000..1d9bdd0 --- /dev/null +++ b/tier1/keyvalues.cpp @@ -0,0 +1,2989 @@ +// Copyright Valve Corporation, All rights reserved. + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" // for widechartomultibyte and multibytetowidechar +#elif defined(POSIX) +#include // wcslen() +#define _alloca alloca +#define _wtoi(arg) wcstol(arg, NULL, 10) +#define _wtoi64(arg) wcstoll(arg, NULL, 10) +#endif + +#include "keyvalues.h" +#include "filesystem.h" +#include "vstdlib/ikeyvaluessystem.h" + +#include "color.h" +#include +#include +#include "tier1/convar.h" +#include "tier0/dbg.h" +#include "tier0/mem.h" +#include "utlvector.h" +#include "utlbuffer.h" +#include "utlhash.h" +#include "vstdlib/vstrtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//////// VPROF? ////////////////// +// For an example of how to mark up this file with VPROF nodes, see +// changelist 702984. However, be aware that calls to FindKey and Init +// may occur outside of Vprof's usual hierarchy, which can cause strange +// duplicate KeyValues::FindKey nodes at the root level and other +// confusing effects. +////////////////////////////////// +// +// just needed for error messages +static const char *s_LastFileLoadingFrom = "unknown"; + +// Statics for the growable string table +intp (*KeyValues::s_pfGetSymbolForString)(const char *name, bool bCreate) = + &KeyValues::GetSymbolForStringClassic; +const char *(*KeyValues::s_pfGetStringForSymbol)(intp symbol) = + &KeyValues::GetStringForSymbolClassic; +CKeyValuesGrowableStringTable *KeyValues::s_pGrowableStringTable = NULL; + +#define KEYVALUES_TOKEN_SIZE 1024 +static char s_pTokenBuf[KEYVALUES_TOKEN_SIZE]; + +#define INTERNALWRITE(pData, len) InternalWrite(filesystem, f, pBuf, pData, len) + +#define MAKE_3_BYTES_FROM_1_AND_2(x1, x2) ((((uint16)x2) << 8) | (uint8)(x1)) +#define SPLIT_3_BYTES_INTO_1_AND_2(x1, x2, x3) \ + do { \ + x1 = (uint8)(x3); \ + x2 = (uint16)((x3) >> 8); \ + } while (0) + +CExpressionEvaluator g_ExpressionEvaluator; + +// a simple class to keep track of a stack of valid parsed symbols +const int MAX_ERROR_STACK = 64; +class CKeyValuesErrorStack { + public: + CKeyValuesErrorStack() + : m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0) { + m_errorStack[0] = 0; + } + + void SetFilename(const char *pFilename) { + m_pFilename = pFilename; + m_maxErrorIndex = 0; + } + + // entering a new keyvalues block, save state for errors + // Not save symbols instead of pointers because the pointers can move! + int Push(int symName) { + if (m_errorIndex < MAX_ERROR_STACK) { + m_errorStack[m_errorIndex] = symName; + } + m_errorIndex++; + m_maxErrorIndex = MAX(m_maxErrorIndex, (m_errorIndex - 1)); + return m_errorIndex - 1; + } + + // exiting block, error isn't in this block, remove. + void Pop() { + m_errorIndex--; + Assert(m_errorIndex >= 0); + } + + // Allows you to keep the same stack level, but change the name as you parse + // peers + void Reset(int stackLevel, int symName) { + Assert(stackLevel >= 0 && stackLevel < m_errorIndex); + m_errorStack[stackLevel] = symName; + } + + // Hit an error, report it and the parsing stack for context + void ReportError(const char *pError) { + Warning("KeyValues Error: %s in file %s\n", pError, m_pFilename); + for (int i = 0; i < m_maxErrorIndex; i++) { + if (m_errorStack[i] != INVALID_KEY_SYMBOL) { + if (i < m_errorIndex) { + Warning("%s, ", + KeyValuesSystem()->GetStringForSymbol(m_errorStack[i])); + } else { + Warning("(*%s*), ", + KeyValuesSystem()->GetStringForSymbol(m_errorStack[i])); + } + } + } + Warning("\n"); + } + + private: + int m_errorStack[MAX_ERROR_STACK]; + const char *m_pFilename; + int m_errorIndex; + int m_maxErrorIndex; +} g_KeyValuesErrorStack; + +// a simple helper that creates stack entries as it goes in & out of scope +class CKeyErrorContext { + public: + ~CKeyErrorContext() { g_KeyValuesErrorStack.Pop(); } + explicit CKeyErrorContext(int symName) { Init(symName); } + void Reset(int symName) { + g_KeyValuesErrorStack.Reset(m_stackLevel, symName); + } + + private: + void Init(int symName) { m_stackLevel = g_KeyValuesErrorStack.Push(symName); } + + int m_stackLevel; +}; + +// Uncomment this line to hit the ~CLeakTrack assert to see what's looking like +// it's leaking #define LEAKTRACK + +#ifdef LEAKTRACK + +class CLeakTrack { + public: + CLeakTrack() {} + ~CLeakTrack() { + if (keys.Count() != 0) { + Assert(0); + } + } + + struct kve { + KeyValues *kv; + char name[256]; + }; + + void AddKv(KeyValues *kv, char const *name) { + kve k; + V_strncpy(k.name, name ? name : "NULL", sizeof(k.name)); + k.kv = kv; + + keys.AddToTail(k); + } + + void RemoveKv(KeyValues *kv) { + int c = keys.Count(); + for (int i = 0; i < c; i++) { + if (keys[i].kv == kv) { + keys.Remove(i); + break; + } + } + } + + CUtlVector keys; +}; + +static CLeakTrack track; + +#define TRACK_KV_ADD(ptr, name) track.AddKv(ptr, name) +#define TRACK_KV_REMOVE(ptr) track.RemoveKv(ptr) + +#else + +#define TRACK_KV_ADD(ptr, name) +#define TRACK_KV_REMOVE(ptr) + +#endif + +//----------------------------------------------------------------------------- +// Purpose: An arbitrarily growable string table for KeyValues key names. +// See the comment in the header for more info. +//----------------------------------------------------------------------------- +class CKeyValuesGrowableStringTable { + public: + // Constructor + CKeyValuesGrowableStringTable() + : m_vecStrings((intp)0, (intp)512 * 1024), + m_hashLookup(2048, 0, 0, m_Functor, m_Functor) { + m_vecStrings.AddToTail('\0'); + } + + // Translates a string to an index + intp GetSymbolForString(const char *name, bool bCreate = true) { + AUTO_LOCK(m_mutex); + + // Put the current details into our hash functor + m_Functor.SetCurString(name); + m_Functor.SetCurStringBase((const char *)m_vecStrings.Base()); + + if (bCreate) { + bool bInserted = false; + UtlHashHandle_t hElement = m_hashLookup.Insert(-1, &bInserted); + if (bInserted) { + intp iIndex = m_vecStrings.AddMultipleToTail(V_strlen(name) + 1, name); + m_hashLookup[hElement] = iIndex; + } + + return m_hashLookup[hElement]; + } else { + UtlHashHandle_t hElement = m_hashLookup.Find(-1); + if (m_hashLookup.IsValidHandle(hElement)) + return m_hashLookup[hElement]; + else + return -1; + } + } + + // Translates an index back to a string + const char *GetStringForSymbol(intp symbol) { + return (const char *)m_vecStrings.Base() + symbol; + } + + private: + // A class plugged into CUtlHash that allows us to change the behavior of the + // table and store only the index in the table. + class CLookupFunctor { + public: + CLookupFunctor() : m_pchCurString(NULL), m_pchCurBase(NULL) {} + + // Sets what we are currently inserting or looking for. + void SetCurString(const char *pchCurString) { + m_pchCurString = pchCurString; + } + void SetCurStringBase(const char *pchCurBase) { m_pchCurBase = pchCurBase; } + + // The compare function. + bool operator()(intp nLhs, intp nRhs) const { + const char *pchLhs = nLhs > 0 ? m_pchCurBase + nLhs : m_pchCurString; + const char *pchRhs = nRhs > 0 ? m_pchCurBase + nRhs : m_pchCurString; + + return (0 == V_stricmp(pchLhs, pchRhs)); + } + + // The hash function. + unsigned int operator()(intp nItem) const { + return HashStringCaseless(m_pchCurString); + } + + private: + const char *m_pchCurString; + const char *m_pchCurBase; + }; + + CThreadFastMutex m_mutex; + CLookupFunctor m_Functor; + CUtlHash m_hashLookup; + CUtlVector m_vecStrings; +}; + +//----------------------------------------------------------------------------- +// Purpose: Sets whether the KeyValues system should use an arbitrarily growable +// string table. See the comment in the header for more info. +//----------------------------------------------------------------------------- +void KeyValues::SetUseGrowableStringTable(bool bUseGrowableTable) { + if (bUseGrowableTable) { + s_pfGetStringForSymbol = &(KeyValues::GetStringForSymbolGrowable); + s_pfGetSymbolForString = &(KeyValues::GetSymbolForStringGrowable); + + if (NULL == s_pGrowableStringTable) { + s_pGrowableStringTable = new CKeyValuesGrowableStringTable; + } + } else { + s_pfGetStringForSymbol = &(KeyValues::GetStringForSymbolClassic); + s_pfGetSymbolForString = &(KeyValues::GetSymbolForStringClassic); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Bodys of the function pointers used for interacting with the key +// name string table +//----------------------------------------------------------------------------- +intp KeyValues::GetSymbolForStringClassic(const char *name, bool bCreate) { + return KeyValuesSystem()->GetSymbolForString(name, bCreate); +} + +const char *KeyValues::GetStringForSymbolClassic(intp symbol) { + return KeyValuesSystem()->GetStringForSymbol(symbol); +} + +intp KeyValues::GetSymbolForStringGrowable(const char *name, bool bCreate) { + return s_pGrowableStringTable->GetSymbolForString(name, bCreate); +} + +const char *KeyValues::GetStringForSymbolGrowable(intp symbol) { + return s_pGrowableStringTable->GetStringForSymbol(symbol); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues(const char *setName) { + TRACK_KV_ADD(this, setName); + + Init(); + SetName(setName); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues(const char *setName, const char *firstKey, + const char *firstValue) { + TRACK_KV_ADD(this, setName); + + Init(); + SetName(setName); + SetString(firstKey, firstValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues(const char *setName, const char *firstKey, + const wchar_t *firstValue) { + TRACK_KV_ADD(this, setName); + + Init(); + SetName(setName); + SetWString(firstKey, firstValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues(const char *setName, const char *firstKey, + int firstValue) { + TRACK_KV_ADD(this, setName); + + Init(); + SetName(setName); + SetInt(firstKey, firstValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues(const char *setName, const char *firstKey, + const char *firstValue, const char *secondKey, + const char *secondValue) { + TRACK_KV_ADD(this, setName); + + Init(); + SetName(setName); + SetString(firstKey, firstValue); + SetString(secondKey, secondValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues(const char *setName, const char *firstKey, int firstValue, + const char *secondKey, int secondValue) { + TRACK_KV_ADD(this, setName); + + Init(); + SetName(setName); + SetInt(firstKey, firstValue); + SetInt(secondKey, secondValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize member variables +//----------------------------------------------------------------------------- +void KeyValues::Init() { + m_iKeyName = 0; + m_iKeyNameCaseSensitive1 = 0; + m_iKeyNameCaseSensitive2 = 0; + m_iDataType = TYPE_NONE; + + m_pSub = NULL; + m_pPeer = NULL; + m_pChain = NULL; + + m_sValue = NULL; + m_wsValue = NULL; + m_pValue = NULL; + + m_bHasEscapeSequences = 0; + m_pExpressionGetSymbolProc = nullptr; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +KeyValues::~KeyValues() { + TRACK_KV_REMOVE(this); + + RemoveEverything(); +} + +//----------------------------------------------------------------------------- +// Purpose: remove everything +//----------------------------------------------------------------------------- +void KeyValues::RemoveEverything() { + KeyValues *dat; + KeyValues *datNext = NULL; + for (dat = m_pSub; dat != NULL; dat = datNext) { + datNext = dat->m_pPeer; + dat->m_pPeer = NULL; + delete dat; + } + + for (dat = m_pPeer; dat && dat != this; dat = datNext) { + datNext = dat->m_pPeer; + dat->m_pPeer = NULL; + delete dat; + } + + delete[] m_sValue; + m_sValue = NULL; + delete[] m_wsValue; + m_wsValue = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *f - +//----------------------------------------------------------------------------- + +void KeyValues::RecursiveSaveToFile(CUtlBuffer &buf, int indentLevel) { + RecursiveSaveToFile(NULL, FILESYSTEM_INVALID_HANDLE, &buf, indentLevel); +} + +//----------------------------------------------------------------------------- +// Adds a chain... if we don't find stuff in this keyvalue, we'll look +// in the one we're chained to. +//----------------------------------------------------------------------------- + +void KeyValues::ChainKeyValue(KeyValues *pChain) { m_pChain = pChain; } + +//----------------------------------------------------------------------------- +// Purpose: Get the name of the current key section +//----------------------------------------------------------------------------- +const char *KeyValues::GetName(void) const { + return KeyValuesSystem()->GetStringForSymbol(MAKE_3_BYTES_FROM_1_AND_2( + m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2)); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the symbol name of the current key section +//----------------------------------------------------------------------------- +int KeyValues::GetNameSymbol() const { return m_iKeyName; } + +int KeyValues::GetNameSymbolCaseSensitive() const { + return MAKE_3_BYTES_FROM_1_AND_2(m_iKeyNameCaseSensitive1, + m_iKeyNameCaseSensitive2); +} + +//----------------------------------------------------------------------------- +// Purpose: Read a single token from buffer (0 terminated) +//----------------------------------------------------------------------------- +const char *KeyValues::ReadToken(CUtlBuffer &buf, bool &wasQuoted, + bool &wasConditional) { + wasQuoted = false; + wasConditional = false; + + if (!buf.IsValid()) return NULL; + + // eating white spaces and remarks loop + while (true) { + buf.EatWhiteSpace(); + if (!buf.IsValid()) return NULL; // file ends after reading whitespaces + + // stop if it's not a comment; a new token starts here + if (!buf.EatCPPComment()) break; + } + + const char *c = (const char *)buf.PeekGet(sizeof(char), 0); + if (!c) return NULL; + + // read quoted strings specially + if (*c == '\"') { + wasQuoted = true; + buf.GetDelimitedString(m_bHasEscapeSequences ? GetCStringCharConversion() + : GetNoEscCharConversion(), + s_pTokenBuf, KEYVALUES_TOKEN_SIZE); + return s_pTokenBuf; + } + + if (*c == '{' || *c == '}') { + // it's a control char, just add this one char and stop reading + s_pTokenBuf[0] = *c; + s_pTokenBuf[1] = 0; + buf.SeekGet(CUtlBuffer::SEEK_CURRENT, 1); + return s_pTokenBuf; + } + + // read in the token until we hit a whitespace or a control character + bool bReportedError = false; + bool bConditionalStart = false; + int nCount = 0; + while (const char *v = (const char *)buf.PeekGet(sizeof(char), 0)) { + // end of file + if (*v == 0) break; + + // break if any control character appears in non quoted tokens + if (*v == '"' || *v == '{' || *v == '}') break; + + if (*v == '[') { + bConditionalStart = true; + } + + if (*v == ']' && bConditionalStart) { + bConditionalStart = false; + wasConditional = true; + } + + // conditionals need to get tokenized as delimited by [] + // othwerwise break on whitespace + if (V_isspace(*v) && !bConditionalStart) break; + + if (nCount < (KEYVALUES_TOKEN_SIZE - 1)) { + s_pTokenBuf[nCount++] = *v; // add char to buffer + } else if (!bReportedError) { + bReportedError = true; + g_KeyValuesErrorStack.ReportError(" ReadToken overflow"); + } + + buf.SeekGet(CUtlBuffer::SEEK_CURRENT, 1); + } + s_pTokenBuf[nCount] = 0; + return s_pTokenBuf; +} + +//----------------------------------------------------------------------------- +// Purpose: if parser should translate escape sequences ( /n, /t etc), set to +// true +//----------------------------------------------------------------------------- +void KeyValues::UsesEscapeSequences(bool state) { + m_bHasEscapeSequences = state; +} + +//----------------------------------------------------------------------------- +// Purpose: Load keyValues from disk +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromFile(IBaseFileSystem *filesystem, + const char *resourceName, const char *pathID, + GetSymbolProc_t pfnEvaluateSymbolProc) { +#if defined(_WIN32) + Assert(IsGameConsole() || (_heapchk() == _HEAPOK)); +#endif + FileHandle_t f = filesystem->Open(resourceName, "rb", pathID); + if (!f) return false; + + s_LastFileLoadingFrom = (char *)resourceName; + + // load file into a null-terminated buffer + int fileSize = filesystem->Size(f); + unsigned bufSize = + ((IFileSystem *)filesystem)->GetOptimalReadSize(f, fileSize + 2); + + char *buffer = + (char *)((IFileSystem *)filesystem)->AllocOptimalReadBuffer(f, bufSize); + Assert(buffer); + + // read into local buffer + bool bRetOK = + (((IFileSystem *)filesystem)->ReadEx(buffer, bufSize, fileSize, f) != 0); + + filesystem->Close(f); // close file after reading + + if (bRetOK) { + buffer[fileSize] = 0; // null terminate file as EOF + buffer[fileSize + 1] = + 0; // double NULL terminating in case this is a unicode file + + CUtlBuffer utlBuffer; + if (IsGameConsole() && + (unsigned int)((unsigned char *)buffer)[0] == KV_BINARY_POOLED_FORMAT) { + // kv contents are compiled binary + utlBuffer.SetExternalBuffer(buffer, bufSize, fileSize, + CUtlBuffer::READ_ONLY); + } else { + // kv contents are text + intp nLen = V_strlen(buffer); + utlBuffer.SetExternalBuffer( + buffer, bufSize, nLen, + CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER); + } + + bRetOK = LoadFromBuffer(resourceName, utlBuffer, filesystem, pathID, + pfnEvaluateSymbolProc); + } + + ((IFileSystem *)filesystem)->FreeOptimalReadBuffer(buffer); + + return bRetOK; +} + +//----------------------------------------------------------------------------- +// Purpose: Save the keyvalues to disk +// Creates the path to the file if it doesn't exist +//----------------------------------------------------------------------------- +bool KeyValues::SaveToFile(IBaseFileSystem *filesystem, + const char *resourceName, const char *pathID) { + // create a write file + FileHandle_t f = filesystem->Open(resourceName, "wb", pathID); + + if (f == FILESYSTEM_INVALID_HANDLE) { + DevMsg("KeyValues::SaveToFile: couldn't open file \"%s\" in path \"%s\".\n", + resourceName ? resourceName : "NULL", pathID ? pathID : "NULL"); + return false; + } + + RecursiveSaveToFile(filesystem, f, NULL, 0); + filesystem->Close(f); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Write out a set of indenting +//----------------------------------------------------------------------------- +void KeyValues::WriteIndents(IBaseFileSystem *filesystem, FileHandle_t f, + CUtlBuffer *pBuf, int indentLevel) { + for (int i = 0; i < indentLevel; i++) { + INTERNALWRITE("\t", 1); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Write out a string where we convert the double quotes to backslash +// double quote +//----------------------------------------------------------------------------- +void KeyValues::WriteConvertedString(IBaseFileSystem *filesystem, + FileHandle_t f, CUtlBuffer *pBuf, + const char *pszString) { + // handle double quote chars within the string + // the worst possible case is that the whole string is quotes + intp len = V_strlen(pszString); + char *convertedString = (char *)alloca((len + 1) * sizeof(char) * 2); + intp j = 0; + for (int i = 0; i <= len; i++) { + if (pszString[i] == '\"') { + convertedString[j] = '\\'; + j++; + } else if (m_bHasEscapeSequences && pszString[i] == '\\') { + convertedString[j] = '\\'; + j++; + } + convertedString[j] = pszString[i]; + j++; + } + + INTERNALWRITE(convertedString, V_strlen(convertedString)); +} + +void KeyValues::InternalWrite(IBaseFileSystem *filesystem, FileHandle_t f, + CUtlBuffer *pBuf, const void *pData, intp len) { + if (filesystem) { + filesystem->Write(pData, len, f); + } + + if (pBuf) { + pBuf->Put(pData, len); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Save keyvalues from disk, if subkey values are detected, calls +// itself to save those +//----------------------------------------------------------------------------- +void KeyValues::RecursiveSaveToFile(IBaseFileSystem *filesystem, FileHandle_t f, + CUtlBuffer *pBuf, int indentLevel) { + // write header + WriteIndents(filesystem, f, pBuf, indentLevel); + INTERNALWRITE("\"", 1); + WriteConvertedString(filesystem, f, pBuf, GetName()); + INTERNALWRITE("\"\n", 2); + WriteIndents(filesystem, f, pBuf, indentLevel); + INTERNALWRITE("{\n", 2); + + // loop through all our keys writing them to disk + for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) { + if (dat->m_pSub) { + dat->RecursiveSaveToFile(filesystem, f, pBuf, indentLevel + 1); + } else { + // only write non-empty keys + + switch (dat->m_iDataType) { + case TYPE_STRING: { + if (dat->m_sValue && *(dat->m_sValue)) { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + WriteConvertedString(filesystem, f, pBuf, dat->GetName()); + INTERNALWRITE("\"\t\t\"", 4); + + WriteConvertedString(filesystem, f, pBuf, dat->m_sValue); + + INTERNALWRITE("\"\n", 2); + } + break; + } + case TYPE_WSTRING: { + if (dat->m_wsValue) { + static char buf[KEYVALUES_TOKEN_SIZE]; + // make sure we have enough space + int result = + V_UnicodeToUTF8(dat->m_wsValue, buf, KEYVALUES_TOKEN_SIZE); + if (result) { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + WriteConvertedString(filesystem, f, pBuf, buf); + + INTERNALWRITE("\"\n", 2); + } + } + break; + } + + case TYPE_INT: { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[32]; + V_snprintf(buf, sizeof(buf), "%d", dat->m_iValue); + + INTERNALWRITE(buf, V_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + + case TYPE_UINT64: { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[32]; + // write "0x" + 16 char 0-padded hex encoded 64 bit value + V_snprintf(buf, sizeof(buf), "0x%016llX", *((uint64 *)dat->m_sValue)); + + INTERNALWRITE(buf, V_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + + case TYPE_FLOAT: { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[48]; + V_snprintf(buf, sizeof(buf), "%f", dat->m_flValue); + + INTERNALWRITE(buf, V_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + case TYPE_COLOR: + DevMsg( + "KeyValues::RecursiveSaveToFile: TODO, missing code for " + "TYPE_COLOR.\n"); + break; + + default: + break; + } + } + } + + // write tail + WriteIndents(filesystem, f, pBuf, indentLevel); + INTERNALWRITE("}\n", 2); +} + +//----------------------------------------------------------------------------- +// Purpose: looks up a key by symbol name +//----------------------------------------------------------------------------- +KeyValues *KeyValues::FindKey(int keySymbol) const { + for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) { + if (dat->m_iKeyName == (uint32)keySymbol) return dat; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Find a keyValue, create it if it is not found. +// Set bCreate to true to create the key if it doesn't +// already exist (which ensures a valid pointer will be +// returned) +//----------------------------------------------------------------------------- +KeyValues *KeyValues::FindKey(const char *keyName, bool bCreate) { + // return the current key if a NULL subkey is asked for + if (!keyName || !keyName[0]) return this; + + // look for '/' characters deliminating sub fields + char szBuf[256]; + const char *subStr = strchr(keyName, '/'); + const char *searchStr = keyName; + + // pull out the substring if it exists + if (subStr) { + intp size = subStr - keyName; + V_memcpy(szBuf, keyName, size); + szBuf[size] = 0; + searchStr = szBuf; + } + + // lookup the symbol for the search string, + // we do not need the case-sensitive symbol at this time + // because if the key is found, then it will be found by case-insensitive + // lookup if the key is not found and needs to be created we will pass the + // actual searchStr and have the new KeyValues constructor get/create the + // case-sensitive symbol + HKeySymbol iSearchStr = + KeyValuesSystem()->GetSymbolForString(searchStr, bCreate); + if (iSearchStr == INVALID_KEY_SYMBOL) { + // not found, couldn't possibly be in key value list + return NULL; + } + + KeyValues *lastItem = NULL; + KeyValues *dat; + // find the searchStr in the current peer list + for (dat = m_pSub; dat != NULL; dat = dat->m_pPeer) { + lastItem = dat; // record the last item looked at (for if we need to append + // to the end of the list) + + // symbol compare + if (dat->m_iKeyName == (uint32)iSearchStr) { + break; + } + } + + if (!dat && m_pChain) { + dat = m_pChain->FindKey(keyName, false); + } + + // make sure a key was found + if (!dat) { + if (bCreate) { + // we need to create a new key + dat = new KeyValues(searchStr); + // Assert(dat != NULL); + + // insert new key at end of list + if (lastItem) { + lastItem->m_pPeer = dat; + } else { + m_pSub = dat; + } + dat->m_pPeer = NULL; + + // a key graduates to be a submsg as soon as it's m_pSub is set + // this should be the only place m_pSub is set + m_iDataType = TYPE_NONE; + } else { + return NULL; + } + } + + // if we've still got a subStr we need to keep looking deeper in the tree + if (subStr) { + // recursively chain down through the paths in the string + return dat->FindKey(subStr + 1, bCreate); + } + + return dat; +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new key, with an autogenerated name. +// Name is guaranteed to be an integer, of value 1 higher +// than the highest other integer key name +//----------------------------------------------------------------------------- +KeyValues *KeyValues::CreateNewKey() { + int newID = 1; + + // search for any key with higher values + for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) { + // case-insensitive string compare + int val = atoi(dat->GetName()); + if (newID <= val) { + newID = val + 1; + } + } + + char buf[12]; + V_snprintf(buf, sizeof(buf), "%d", newID); + + return CreateKey(buf); +} + +//----------------------------------------------------------------------------- +// Create a key +//----------------------------------------------------------------------------- +KeyValues *KeyValues::CreateKey(const char *keyName) { + // key wasn't found so just create a new one + KeyValues *dat = new KeyValues(keyName); + + dat->UsesEscapeSequences(m_bHasEscapeSequences != + 0); // use same format as parent does + + // add into subkey list + AddSubKey(dat); + + return dat; +} + +//----------------------------------------------------------------------------- +// Adds a subkey. Make sure the subkey isn't a child of some other keyvalues +//----------------------------------------------------------------------------- +void KeyValues::AddSubKey(KeyValues *pSubkey) { + // Make sure the subkey isn't a child of some other keyvalues + Assert(pSubkey->m_pPeer == NULL); + + // add into subkey list + if (m_pSub == NULL) { + m_pSub = pSubkey; + } else { + KeyValues *pTempDat = m_pSub; + while (pTempDat->GetNextKey() != NULL) { + pTempDat = pTempDat->GetNextKey(); + } + + pTempDat->SetNextKey(pSubkey); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a subkey from the list +//----------------------------------------------------------------------------- +void KeyValues::RemoveSubKey(KeyValues *subKey) { + if (!subKey) return; + + // check the list pointer + if (m_pSub == subKey) { + m_pSub = subKey->m_pPeer; + } else { + // look through the list + KeyValues *kv = m_pSub; + while (kv->m_pPeer) { + if (kv->m_pPeer == subKey) { + kv->m_pPeer = subKey->m_pPeer; + break; + } + + kv = kv->m_pPeer; + } + } + + subKey->m_pPeer = NULL; +} + +void KeyValues::InsertSubKey(int nIndex, KeyValues *pSubKey) { + // Sub key must be valid and not part of another chain + Assert(pSubKey && pSubKey->m_pPeer == NULL); + + if (nIndex == 0) { + pSubKey->m_pPeer = m_pSub; + m_pSub = pSubKey; + return; + } else { + int nCurrentIndex = 0; + for (KeyValues *pIter = GetFirstSubKey(); pIter != NULL; + pIter = pIter->GetNextKey()) { + ++nCurrentIndex; + if (nCurrentIndex == nIndex) { + pSubKey->m_pPeer = pIter->m_pPeer; + pIter->m_pPeer = pSubKey; + return; + } + } + // Index is out of range if we get here + Assert(0); + return; + } +} + +bool KeyValues::ContainsSubKey(KeyValues *pSubKey) { + for (KeyValues *pIter = GetFirstSubKey(); pIter != NULL; + pIter = pIter->GetNextKey()) { + if (pSubKey == pIter) { + return true; + } + } + return false; +} + +void KeyValues::SwapSubKey(KeyValues *pExistingSubkey, KeyValues *pNewSubKey) { + Assert(pExistingSubkey != NULL && pNewSubKey != NULL); + + // Make sure the new sub key isn't a child of some other keyvalues + Assert(pNewSubKey->m_pPeer == NULL); + + // Check the list pointer + if (m_pSub == pExistingSubkey) { + pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer; + pExistingSubkey->m_pPeer = NULL; + m_pSub = pNewSubKey; + } else { + // Look through the list + KeyValues *kv = m_pSub; + while (kv->m_pPeer) { + if (kv->m_pPeer == pExistingSubkey) { + pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer; + pExistingSubkey->m_pPeer = NULL; + kv->m_pPeer = pNewSubKey; + break; + } + + kv = kv->m_pPeer; + } + // Existing sub key should always be found, otherwise it's a bug in the + // calling code. + Assert(kv->m_pPeer != NULL); + } +} + +void KeyValues::ElideSubKey(KeyValues *pSubKey) { + // This pointer's "next" pointer needs to be fixed up when we elide the key + KeyValues **ppPointerToFix = &m_pSub; + for (KeyValues *pKeyIter = m_pSub; pKeyIter != NULL; + ppPointerToFix = &pKeyIter->m_pPeer, pKeyIter = pKeyIter->GetNextKey()) { + if (pKeyIter == pSubKey) { + if (pSubKey->m_pSub == NULL) { + // No children, simply remove the key + *ppPointerToFix = pSubKey->m_pPeer; + pSubKey->deleteThis(); + } else { + *ppPointerToFix = pSubKey->m_pSub; + // Attach the remainder of this chain to the last child of pSubKey + KeyValues *pChildIter = pSubKey->m_pSub; + while (pChildIter->m_pPeer != NULL) { + pChildIter = pChildIter->m_pPeer; + } + // Now points to the last child of pSubKey + pChildIter->m_pPeer = pSubKey->m_pPeer; + // Detach the node to be elided + pSubKey->m_pSub = NULL; + pSubKey->m_pPeer = NULL; + pSubKey->deleteThis(); + } + return; + } + } + // Key not found; that's caller error. + Assert(0); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the first subkey in the list +//----------------------------------------------------------------------------- +KeyValues *KeyValues::GetFirstSubKey() { return m_pSub; } + +//----------------------------------------------------------------------------- +// Purpose: Return the next subkey +//----------------------------------------------------------------------------- +KeyValues *KeyValues::GetNextKey() { return m_pPeer; } + +//----------------------------------------------------------------------------- +// Purpose: Sets this key's peer to the KeyValues passed in +//----------------------------------------------------------------------------- +void KeyValues::SetNextKey(KeyValues *pDat) { m_pPeer = pDat; } + +KeyValues *KeyValues::GetFirstTrueSubKey() { + KeyValues *pRet = m_pSub; + while (pRet && pRet->m_iDataType != TYPE_NONE) pRet = pRet->m_pPeer; + + return pRet; +} + +KeyValues *KeyValues::GetNextTrueSubKey() { + KeyValues *pRet = m_pPeer; + while (pRet && pRet->m_iDataType != TYPE_NONE) pRet = pRet->m_pPeer; + + return pRet; +} + +KeyValues *KeyValues::GetFirstValue() { + KeyValues *pRet = m_pSub; + while (pRet && pRet->m_iDataType == TYPE_NONE) pRet = pRet->m_pPeer; + + return pRet; +} + +KeyValues *KeyValues::GetNextValue() { + KeyValues *pRet = m_pPeer; + while (pRet && pRet->m_iDataType == TYPE_NONE) pRet = pRet->m_pPeer; + + return pRet; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the integer value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +int KeyValues::GetInt(const char *keyName, int defaultValue) { + KeyValues *dat = FindKey(keyName, false); + if (dat) { + switch (dat->m_iDataType) { + case TYPE_STRING: + return atoi(dat->m_sValue); + case TYPE_WSTRING: + return _wtoi(dat->m_wsValue); + case TYPE_FLOAT: + return (int)dat->m_flValue; + case TYPE_UINT64: + // can't convert, since it would lose data + Assert(0); + return 0; + // Truncated on x64. + case TYPE_INT: + case TYPE_PTR: + default: + return dat->m_iValue; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the integer value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +uint64 KeyValues::GetUint64(const char *keyName, uint64 defaultValue) { + KeyValues *dat = FindKey(keyName, false); + if (dat) { + switch (dat->m_iDataType) { + case TYPE_STRING: { + uint64 uiResult = 0ull; + sscanf(dat->m_sValue, "%llu", &uiResult); + return uiResult; + } + case TYPE_WSTRING: { + uint64 uiResult = 0ull; + swscanf(dat->m_wsValue, L"%llu", &uiResult); + return uiResult; + } + case TYPE_FLOAT: + return (int)dat->m_flValue; + case TYPE_UINT64: + return *((uint64 *)dat->m_sValue); + case TYPE_INT: + case TYPE_PTR: + default: + return dat->m_iValue; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the pointer value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +void *KeyValues::GetPtr(const char *keyName, void *defaultValue) { + KeyValues *dat = FindKey(keyName, false); + if (dat) { + switch (dat->m_iDataType) { + case TYPE_PTR: + return dat->m_pValue; + + case TYPE_WSTRING: + case TYPE_STRING: + case TYPE_FLOAT: + case TYPE_INT: + case TYPE_UINT64: + default: + return NULL; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the float value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +float KeyValues::GetFloat(const char *keyName, float defaultValue) { + KeyValues *dat = FindKey(keyName, false); + if (dat) { + switch (dat->m_iDataType) { + case TYPE_STRING: + return (float)atof(dat->m_sValue); + case TYPE_WSTRING: +#ifdef WIN32 + return (float)_wtof(dat->m_wsValue); // no wtof +#else + return (float)wcstof(dat->m_wsValue, (wchar_t **)NULL); +#endif + case TYPE_FLOAT: + return dat->m_flValue; + case TYPE_INT: + return (float)dat->m_iValue; + case TYPE_UINT64: + return (float)(*((uint64 *)dat->m_sValue)); + case TYPE_PTR: + default: + return 0.0f; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the string pointer of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +const char *KeyValues::GetString(const char *keyName, + const char *defaultValue) { + KeyValues *dat = FindKey(keyName, false); + if (dat) { + // convert the data to string form then return it + char buf[64]; + switch (dat->m_iDataType) { + case TYPE_FLOAT: + V_snprintf(buf, sizeof(buf), "%f", dat->m_flValue); + SetString(keyName, buf); + break; + case TYPE_INT: + V_snprintf(buf, sizeof(buf), "%d", dat->m_iValue); + SetString(keyName, buf); + break; + case TYPE_PTR: + V_snprintf(buf, sizeof(buf), "%lld", (int64)dat->m_pValue); + SetString(keyName, buf); + break; + case TYPE_UINT64: + V_snprintf(buf, sizeof(buf), "%llu", *((uint64 *)(dat->m_sValue))); + SetString(keyName, buf); + break; + + case TYPE_WSTRING: { + // convert the string to char *, set it for future use, and return it + char wideBuf[512]; + int result = V_UnicodeToUTF8(dat->m_wsValue, wideBuf, 512); + if (result) { + // note: this will copy wideBuf + SetString(keyName, wideBuf); + } else { + return defaultValue; + } + break; + } + case TYPE_STRING: + break; + default: + return defaultValue; + }; + + return dat->m_sValue; + } + return defaultValue; +} + +const wchar_t *KeyValues::GetWString(const char *keyName, + const wchar_t *defaultValue) { + KeyValues *dat = FindKey(keyName, false); + if (dat) { + wchar_t wbuf[64]; + switch (dat->m_iDataType) { + case TYPE_FLOAT: + swprintf(wbuf, V_ARRAYSIZE(wbuf), L"%f", dat->m_flValue); + SetWString(keyName, wbuf); + break; + case TYPE_INT: + swprintf(wbuf, V_ARRAYSIZE(wbuf), L"%d", dat->m_iValue); + SetWString(keyName, wbuf); + break; + case TYPE_PTR: + swprintf(wbuf, V_ARRAYSIZE(wbuf), L"%lld", (int64)dat->m_pValue); + SetWString(keyName, wbuf); + break; + case TYPE_UINT64: { + swprintf(wbuf, V_ARRAYSIZE(wbuf), L"%llu", + *((uint64 *)(dat->m_sValue))); + SetWString(keyName, wbuf); + } break; + + case TYPE_WSTRING: + break; + case TYPE_STRING: { + intp bufSize = V_strlen(dat->m_sValue) + 1; + wchar_t *pWBuf = new wchar_t[bufSize]; + int result = + V_UTF8ToUnicode(dat->m_sValue, pWBuf, bufSize * sizeof(wchar_t)); + if (result >= 0) // may be a zero length string + { + SetWString(keyName, pWBuf); + } else { + delete[] pWBuf; + return defaultValue; + } + delete[] pWBuf; + break; + } + default: + return defaultValue; + }; + + return (const wchar_t *)dat->m_wsValue; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a color +//----------------------------------------------------------------------------- +Color KeyValues::GetColor(const char *keyName, const Color &defaultColor) { + Color color = defaultColor; + KeyValues *dat = FindKey(keyName, false); + if (dat) { + if (dat->m_iDataType == TYPE_COLOR) { + color[0] = dat->m_Color[0]; + color[1] = dat->m_Color[1]; + color[2] = dat->m_Color[2]; + color[3] = dat->m_Color[3]; + } else if (dat->m_iDataType == TYPE_FLOAT) { + color[0] = (unsigned char)dat->m_flValue; + } else if (dat->m_iDataType == TYPE_INT) { + color[0] = (unsigned char)dat->m_iValue; + } else if (dat->m_iDataType == TYPE_STRING) { + // parse the colors out of the string + float a, b, c, d; + sscanf(dat->m_sValue, "%f %f %f %f", &a, &b, &c, &d); + color[0] = (unsigned char)a; + color[1] = (unsigned char)b; + color[2] = (unsigned char)c; + color[3] = (unsigned char)d; + } + } + return color; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets a color +//----------------------------------------------------------------------------- +void KeyValues::SetColor(const char *keyName, Color value) { + KeyValues *dat = FindKey(keyName, true); + + if (dat) { + dat->m_iDataType = TYPE_COLOR; + dat->m_Color[0] = value[0]; + dat->m_Color[1] = value[1]; + dat->m_Color[2] = value[2]; + dat->m_Color[3] = value[3]; + } +} + +void KeyValues::SetStringValue(char const *strValue) { + // delete the old value + delete[] m_sValue; + // make sure we're not storing the WSTRING - as we're converting over to + // STRING + delete[] m_wsValue; + m_wsValue = NULL; + + if (!strValue) { + // ensure a valid value + strValue = ""; + } + + // allocate memory for the new value and copy it in + intp len = V_strlen(strValue); + m_sValue = new char[len + 1]; + V_memcpy(m_sValue, strValue, len + 1); + + m_iDataType = TYPE_STRING; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the string value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetString(const char *keyName, const char *value) { + if (KeyValues *dat = FindKey(keyName, true)) { + dat->SetStringValue(value); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the string value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetWString(const char *keyName, const wchar_t *value) { + KeyValues *dat = FindKey(keyName, true); + if (dat) { + // delete the old value + delete[] dat->m_wsValue; + // make sure we're not storing the STRING - as we're converting over to + // WSTRING + delete[] dat->m_sValue; + dat->m_sValue = NULL; + + if (!value) { + // ensure a valid value + value = L""; + } + + // allocate memory for the new value and copy it in + intp len = V_wcslen(value); + dat->m_wsValue = new wchar_t[len + 1]; + V_memcpy(dat->m_wsValue, value, (len + 1) * sizeof(wchar_t)); + + dat->m_iDataType = TYPE_WSTRING; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the integer value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetInt(const char *keyName, int value) { + KeyValues *dat = FindKey(keyName, true); + + if (dat) { + dat->m_iValue = value; + dat->m_iDataType = TYPE_INT; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the integer value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetUint64(const char *keyName, uint64 value) { + KeyValues *dat = FindKey(keyName, true); + + if (dat) { + // delete the old value + delete[] dat->m_sValue; + // make sure we're not storing the WSTRING - as we're converting over to + // STRING + delete[] dat->m_wsValue; + dat->m_wsValue = NULL; + + dat->m_sValue = new char[sizeof(uint64)]; + *((uint64 *)dat->m_sValue) = value; + dat->m_iDataType = TYPE_UINT64; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the float value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetFloat(const char *keyName, float value) { + KeyValues *dat = FindKey(keyName, true); + + if (dat) { + dat->m_flValue = value; + dat->m_iDataType = TYPE_FLOAT; + } +} + +void KeyValues::SetName(const char *setName) { + HKeySymbol hCaseSensitiveKeyName = INVALID_KEY_SYMBOL, + hCaseInsensitiveKeyName = INVALID_KEY_SYMBOL; + hCaseSensitiveKeyName = KeyValuesSystem()->GetSymbolForStringCaseSensitive( + hCaseInsensitiveKeyName, setName); + + m_iKeyName = hCaseInsensitiveKeyName; + SPLIT_3_BYTES_INTO_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2, + hCaseSensitiveKeyName); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the pointer value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetPtr(const char *keyName, void *value) { + KeyValues *dat = FindKey(keyName, true); + + if (dat) { + dat->m_pValue = value; + dat->m_iDataType = TYPE_PTR; + } +} + +void KeyValues::RecursiveCopyKeyValues(KeyValues &src) { + // garymcthack - need to check this code for possible buffer overruns. + + m_iKeyName = src.m_iKeyName; + m_iKeyNameCaseSensitive1 = src.m_iKeyNameCaseSensitive1; + m_iKeyNameCaseSensitive2 = src.m_iKeyNameCaseSensitive2; + + if (!src.m_pSub) { + m_iDataType = src.m_iDataType; + char buf[256]; + switch (src.m_iDataType) { + case TYPE_NONE: + break; + case TYPE_STRING: + if (src.m_sValue) { + intp len = V_strlen(src.m_sValue) + 1; + m_sValue = new char[len]; + V_strncpy(m_sValue, src.m_sValue, len); + } + break; + case TYPE_INT: { + m_iValue = src.m_iValue; + V_snprintf(buf, sizeof(buf), "%d", m_iValue); + intp len = V_strlen(buf) + 1; + m_sValue = new char[len]; + V_strncpy(m_sValue, buf, len); + } break; + case TYPE_FLOAT: { + m_flValue = src.m_flValue; + V_snprintf(buf, sizeof(buf), "%f", m_flValue); + intp len = V_strlen(buf) + 1; + m_sValue = new char[len]; + V_strncpy(m_sValue, buf, len); + } break; + case TYPE_PTR: { + m_pValue = src.m_pValue; + } break; + case TYPE_UINT64: { + m_sValue = new char[sizeof(uint64)]; + V_memcpy(m_sValue, src.m_sValue, sizeof(uint64)); + } break; + case TYPE_COLOR: { + m_Color[0] = src.m_Color[0]; + m_Color[1] = src.m_Color[1]; + m_Color[2] = src.m_Color[2]; + m_Color[3] = src.m_Color[3]; + } break; + + default: { + // do nothing . .what the heck is this? + Assert(0); + } break; + } + } +#if 0 + KeyValues *pDst = this; + for ( KeyValues *pSrc = src.m_pSub; pSrc; pSrc = pSrc->m_pPeer ) + { + if ( pSrc->m_pSub ) + { + pDst->m_pSub = new KeyValues( pSrc->m_pSub->getName() ); + pDst->m_pSub->RecursiveCopyKeyValues( *pSrc->m_pSub ); + } + else + { + // copy non-empty keys + if ( pSrc->m_sValue && *(pSrc->m_sValue) ) + { + pDst->m_pPeer = new KeyValues( + } + } + } +#endif + + // Handle the immediate child + if (src.m_pSub) { + m_pSub = new KeyValues(NULL); + m_pSub->RecursiveCopyKeyValues(*src.m_pSub); + } + + // Handle the immediate peer + if (src.m_pPeer) { + m_pPeer = new KeyValues(NULL); + m_pPeer->RecursiveCopyKeyValues(*src.m_pPeer); + } +} + +KeyValues &KeyValues::operator=(KeyValues &src) { + RemoveEverything(); + Init(); // reset all values + RecursiveCopyKeyValues(src); + return *this; +} + +//----------------------------------------------------------------------------- +// Make a new copy of all subkeys, add them all to the passed-in keyvalues +//----------------------------------------------------------------------------- +void KeyValues::CopySubkeys(KeyValues *pParent) const { + // recursively copy subkeys + // Also maintain ordering.... + KeyValues *pPrev = NULL; + for (KeyValues *sub = m_pSub; sub != NULL; sub = sub->m_pPeer) { + // take a copy of the subkey + KeyValues *dat = sub->MakeCopy(); + + // add into subkey list + if (pPrev) { + pPrev->m_pPeer = dat; + } else { + pParent->m_pSub = dat; + } + dat->m_pPeer = NULL; + pPrev = dat; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Makes a copy of the whole key-value pair set +//----------------------------------------------------------------------------- +KeyValues *KeyValues::MakeCopy(void) const { + KeyValues *newKeyValue = new KeyValues(GetName()); + + // copy data + newKeyValue->m_iDataType = m_iDataType; + switch (m_iDataType) { + case TYPE_STRING: { + if (m_sValue) { + intp len = V_strlen(m_sValue); + Assert(!newKeyValue->m_sValue); + newKeyValue->m_sValue = new char[len + 1]; + V_memcpy(newKeyValue->m_sValue, m_sValue, len + 1); + } + } break; + case TYPE_WSTRING: { + if (m_wsValue) { + intp len = V_wcslen(m_wsValue); + newKeyValue->m_wsValue = new wchar_t[len + 1]; + V_memcpy(newKeyValue->m_wsValue, m_wsValue, + (len + 1) * sizeof(wchar_t)); + } + } break; + + case TYPE_INT: + newKeyValue->m_iValue = m_iValue; + break; + + case TYPE_FLOAT: + newKeyValue->m_flValue = m_flValue; + break; + + case TYPE_PTR: + newKeyValue->m_pValue = m_pValue; + break; + + case TYPE_COLOR: + newKeyValue->m_Color[0] = m_Color[0]; + newKeyValue->m_Color[1] = m_Color[1]; + newKeyValue->m_Color[2] = m_Color[2]; + newKeyValue->m_Color[3] = m_Color[3]; + break; + + case TYPE_UINT64: + newKeyValue->m_sValue = new char[sizeof(uint64)]; + V_memcpy(newKeyValue->m_sValue, m_sValue, sizeof(uint64)); + break; + }; + + // recursively copy subkeys + CopySubkeys(newKeyValue); + return newKeyValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Check if a keyName has no value assigned to it. +//----------------------------------------------------------------------------- +bool KeyValues::IsEmpty(const char *keyName) { + KeyValues *dat = FindKey(keyName, false); + if (!dat) return true; + + if (dat->m_iDataType == TYPE_NONE && dat->m_pSub == NULL) return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Clear out all subkeys, and the current value +//----------------------------------------------------------------------------- +void KeyValues::Clear(void) { + delete m_pSub; + m_pSub = NULL; + m_iDataType = TYPE_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the data type of the value stored in a keyName +//----------------------------------------------------------------------------- +KeyValues::types_t KeyValues::GetDataType(const char *keyName) { + KeyValues *dat = FindKey(keyName, false); + if (dat) return (types_t)dat->m_iDataType; + + return TYPE_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: Deletion, ensures object gets deleted from correct heap +//----------------------------------------------------------------------------- +void KeyValues::deleteThis() { delete this; } + +//----------------------------------------------------------------------------- +// Purpose: +// Input : includedKeys - +//----------------------------------------------------------------------------- +void KeyValues::AppendIncludedKeys(CUtlVector &includedKeys) { + // Append any included keys, too... + intp includeCount = includedKeys.Count(); + intp i; + for (i = 0; i < includeCount; i++) { + KeyValues *kv = includedKeys[i]; + Assert(kv); + + KeyValues *insertSpot = this; + while (insertSpot->GetNextKey()) { + insertSpot = insertSpot->GetNextKey(); + } + + insertSpot->SetNextKey(kv); + } +} + +void KeyValues::ParseIncludedKeys(char const *resourceName, + const char *filetoinclude, + IBaseFileSystem *pFileSystem, + const char *pPathID, + CUtlVector &includedKeys, + GetSymbolProc_t pfnEvaluateSymbolProc) { + Assert(resourceName); + Assert(filetoinclude); + Assert(pFileSystem); + + // Load it... + if (!pFileSystem) { + return; + } + + // Get relative subdirectory + char fullpath[512]; + V_strncpy(fullpath, resourceName, sizeof(fullpath)); + + // Strip off characters back to start or first / + intp len = V_strlen(fullpath); + while (true) { + if (len <= 0) { + break; + } + + if (fullpath[len - 1] == '\\' || fullpath[len - 1] == '/') { + break; + } + + // zero it + fullpath[len - 1] = 0; + --len; + } + + // Append included file + V_strncat(fullpath, filetoinclude, sizeof(fullpath), COPY_ALL_CHARACTERS); + + KeyValues *newKV = new KeyValues(fullpath); + + // CUtlSymbol save = s_CurrentFileSymbol; // did that had any use ??? + + newKV->UsesEscapeSequences(m_bHasEscapeSequences != + 0); // use same format as parent + + if (newKV->LoadFromFile(pFileSystem, fullpath, pPathID, + pfnEvaluateSymbolProc)) { + includedKeys.AddToTail(newKV); + } else { + DevMsg( + "KeyValues::ParseIncludedKeys: Couldn't load included keyvalue file " + "%s\n", + fullpath); + newKV->deleteThis(); + } + + // s_CurrentFileSymbol = save; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : baseKeys - +//----------------------------------------------------------------------------- +void KeyValues::MergeBaseKeys(CUtlVector &baseKeys) { + intp includeCount = baseKeys.Count(); + intp i; + for (i = 0; i < includeCount; i++) { + KeyValues *kv = baseKeys[i]; + Assert(kv); + + RecursiveMergeKeyValues(kv); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : baseKV - keyvalues we're basing ourselves on +//----------------------------------------------------------------------------- +void KeyValues::RecursiveMergeKeyValues(KeyValues *baseKV) { + // Merge ourselves + // we always want to keep our value, so nothing to do here + + // Now merge our children + for (KeyValues *baseChild = baseKV->m_pSub; baseChild != NULL; + baseChild = baseChild->m_pPeer) { + // for each child in base, see if we have a matching kv + + bool bFoundMatch = false; + + // If we have a child by the same name, merge those keys + for (KeyValues *newChild = m_pSub; newChild != NULL; + newChild = newChild->m_pPeer) { + if (!V_strcmp(baseChild->GetName(), newChild->GetName())) { + newChild->RecursiveMergeKeyValues(baseChild); + bFoundMatch = true; + break; + } + } + + // If not merged, append this key + if (!bFoundMatch) { + KeyValues *dat = baseChild->MakeCopy(); + Assert(dat); + AddSubKey(dat); + } + } +} + +//----------------------------------------------------------------------------- +// Returns whether a keyvalues conditional expression string evaluates to true +// or false +//----------------------------------------------------------------------------- +bool KeyValues::EvaluateConditional(const char *pExpressionString, + GetSymbolProc_t pfnEvaluateSymbolProc) { + // evaluate the infix expression, calling the symbol proc to resolve each + // symbol's value + bool bResult = false; + bool bValid = g_ExpressionEvaluator.Evaluate(bResult, pExpressionString, + pfnEvaluateSymbolProc); + if (!bValid) { + g_KeyValuesErrorStack.ReportError("KV Conditional Evaluation Error"); + } + + return bResult; +} + +// prevent two threads from entering this at the same time and trying to share +// the global error reporting and parse buffers +static CThreadFastMutex g_KVMutex; +//----------------------------------------------------------------------------- +// Read from a buffer... +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromBuffer(char const *resourceName, CUtlBuffer &buf, + IBaseFileSystem *pFileSystem, + const char *pPathID, + GetSymbolProc_t pfnEvaluateSymbolProc) { + AUTO_LOCK_FM(g_KVMutex); + + if (IsGameConsole()) { + // Let's not crash if the buffer is empty + unsigned char *pData = + buf.Size() > 0 ? (unsigned char *)buf.PeekGet() : NULL; + if (pData && (unsigned int)pData[0] == KV_BINARY_POOLED_FORMAT) { + // skip past binary marker + buf.GetUnsignedChar(); + // get the pool identifier, allows the fs to bind the expected string pool + unsigned int poolKey = buf.GetUnsignedInt(); + + RemoveEverything(); + Init(); + + return ReadAsBinaryPooledFormat(buf, pFileSystem, poolKey, + pfnEvaluateSymbolProc); + } + } + + KeyValues *pPreviousKey = NULL; + KeyValues *pCurrentKey = this; + CUtlVector includedKeys; + CUtlVector baseKeys; + bool wasQuoted; + bool wasConditional; + g_KeyValuesErrorStack.SetFilename(resourceName); + do { + bool bAccepted = true; + + // the first thing must be a key + const char *s = ReadToken(buf, wasQuoted, wasConditional); + if (!buf.IsValid() || !s) break; + + if (!wasQuoted && *s == '\0') { + // non quoted empty strings stop parsing + // quoted empty strings are allowed to support unnnamed KV sections + break; + } + + if (!V_stricmp(s, "#include")) // special include macro (not a key name) + { + s = ReadToken(buf, wasQuoted, wasConditional); + // Name of subfile to load is now in s + + if (!s || *s == 0) { + g_KeyValuesErrorStack.ReportError("#include is NULL "); + } else { + ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, includedKeys, + pfnEvaluateSymbolProc); + } + + continue; + } else if (!V_stricmp(s, "#base")) { + s = ReadToken(buf, wasQuoted, wasConditional); + // Name of subfile to load is now in s + + if (!s || *s == 0) { + g_KeyValuesErrorStack.ReportError("#base is NULL "); + } else { + ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, baseKeys, + pfnEvaluateSymbolProc); + } + + continue; + } + + if (!pCurrentKey) { + pCurrentKey = new KeyValues(s); + Assert(pCurrentKey); + + pCurrentKey->UsesEscapeSequences(m_bHasEscapeSequences != + 0); // same format has parent use + + if (pPreviousKey) { + pPreviousKey->SetNextKey(pCurrentKey); + } + } else { + pCurrentKey->SetName(s); + } + + // get the '{' + s = ReadToken(buf, wasQuoted, wasConditional); + + if (wasConditional) { + bAccepted = EvaluateConditional(s, pfnEvaluateSymbolProc); + + // Now get the '{' + s = ReadToken(buf, wasQuoted, wasConditional); + } + + if (s && *s == '{' && !wasQuoted) { + // header is valid so load the file + pCurrentKey->RecursiveLoadFromBuffer(resourceName, buf, + pfnEvaluateSymbolProc); + } else { + g_KeyValuesErrorStack.ReportError("LoadFromBuffer: missing {"); + } + + if (!bAccepted) { + if (pPreviousKey) { + pPreviousKey->SetNextKey(NULL); + } + pCurrentKey->Clear(); + } else { + pPreviousKey = pCurrentKey; + pCurrentKey = NULL; + } + } while (buf.IsValid()); + + AppendIncludedKeys(includedKeys); + { + // delete included keys! + for (intp i = includedKeys.Count() - 1; i > 0; i--) { + KeyValues *kv = includedKeys[i]; + kv->deleteThis(); + } + } + + MergeBaseKeys(baseKeys); + { + // delete base keys! + for (intp i = baseKeys.Count() - 1; i >= 0; i--) { + KeyValues *kv = baseKeys[i]; + kv->deleteThis(); + } + } + + g_KeyValuesErrorStack.SetFilename(""); + + return true; +} + +//----------------------------------------------------------------------------- +// Read from a buffer... +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromBuffer(char const *resourceName, const char *pBuffer, + IBaseFileSystem *pFileSystem, + const char *pPathID, + GetSymbolProc_t pfnEvaluateSymbolProc) { + if (!pBuffer) return true; + + if (IsGameConsole() && + (unsigned int)((unsigned char *)pBuffer)[0] == KV_BINARY_POOLED_FORMAT) { + // bad, got a binary compiled KV file through an unexpected text path + // not all paths support binary compiled kv, needs to get fixed + // need to have caller supply buffer length (strlen not valid), this + // interface change was never plumbed + Warning("ERROR! Binary compiled KV '%s' in an unexpected handler\n", + resourceName); + Assert(0); + return false; + } + + intp nLen = V_strlen(pBuffer); + CUtlBuffer buf(pBuffer, nLen, + CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER); + // Translate Unicode files into UTF-8 before proceeding + if (nLen > 2 && (uint8)pBuffer[0] == 0xFF && (uint8)pBuffer[1] == 0xFE) { + int nUTF8Len = V_UnicodeToUTF8((wchar_t *)(pBuffer + 2), NULL, 0); + char *pUTF8Buf = new char[nUTF8Len]; + V_UnicodeToUTF8((wchar_t *)(pBuffer + 2), pUTF8Buf, nUTF8Len); + buf.AssumeMemory(pUTF8Buf, nUTF8Len, nUTF8Len, + CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER); + } + return LoadFromBuffer(resourceName, buf, pFileSystem, pPathID, + pfnEvaluateSymbolProc); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void KeyValues::RecursiveLoadFromBuffer(char const *resourceName, + CUtlBuffer &buf, + GetSymbolProc_t pfnEvaluateSymbolProc) { + CKeyErrorContext errorReport(GetNameSymbolCaseSensitive()); + bool wasQuoted; + bool wasConditional; + // keep this out of the stack until a key is parsed + CKeyErrorContext errorKey(INVALID_KEY_SYMBOL); + while (1) { + bool bAccepted = true; + + // get the key name + const char *name = ReadToken(buf, wasQuoted, wasConditional); + + if (!name) // EOF stop reading + { + g_KeyValuesErrorStack.ReportError( + "RecursiveLoadFromBuffer: got EOF instead of keyname"); + break; + } + + if (!*name) // empty token, maybe "" or EOF + { + g_KeyValuesErrorStack.ReportError( + "RecursiveLoadFromBuffer: got empty keyname"); + break; + } + + if (*name == '}' && !wasQuoted) // top level closed, stop reading + break; + + // Always create the key; note that this could potentially + // cause some duplication, but that's what we want sometimes + KeyValues *dat = CreateKey(name); + + errorKey.Reset(dat->GetNameSymbolCaseSensitive()); + + // get the value + const char *value = ReadToken(buf, wasQuoted, wasConditional); + + if (wasConditional && value) { + bAccepted = EvaluateConditional(value, pfnEvaluateSymbolProc); + + // get the real value + value = ReadToken(buf, wasQuoted, wasConditional); + } + + if (!value) { + g_KeyValuesErrorStack.ReportError( + "RecursiveLoadFromBuffer: got NULL key"); + break; + } + + if (*value == '}' && !wasQuoted) { + g_KeyValuesErrorStack.ReportError( + "RecursiveLoadFromBuffer: got } in key"); + break; + } + + if (*value == '{' && !wasQuoted) { + // this isn't a key, it's a section + errorKey.Reset(INVALID_KEY_SYMBOL); + // sub value list + dat->RecursiveLoadFromBuffer(resourceName, buf, pfnEvaluateSymbolProc); + } else { + if (wasConditional) { + g_KeyValuesErrorStack.ReportError( + "RecursiveLoadFromBuffer: got conditional between key and value"); + break; + } + + if (dat->m_sValue) { + delete[] dat->m_sValue; + dat->m_sValue = NULL; + } + + intp len = V_strlen(value); + + // Here, let's determine if we got a float or an int.... + char *pIEnd; // pos where int scan ended + char *pFEnd; // pos where float scan ended + const char *pSEnd = value + len; // pos where token ends + + int ival = strtol(value, &pIEnd, 10); + float fval = (float)strtod(value, &pFEnd); + bool bOverflow = + (ival == LONG_MAX || ival == LONG_MIN) && errno == ERANGE; +#ifdef POSIX + // strtod supports hex representation in strings under posix but we DON'T + // want that support in keyvalues, so undo it here if needed + if (len > 1 && tolower(value[1]) == 'x') { + fval = 0.0f; + pFEnd = (char *)value; + } +#endif + + if (*value == 0) { + dat->m_iDataType = TYPE_STRING; + } else if ((18 == len) && (value[0] == '0') && (value[1] == 'x')) { + // an 18-byte value prefixed with "0x" (followed by 16 hex digits) is an + // int64 value + int64 retVal = 0; + for (int i = 2; i < 2 + 16; i++) { + char digit = value[i]; + if (digit >= 'a') + digit -= 'a' - ('9' + 1); + else if (digit >= 'A') + digit -= 'A' - ('9' + 1); + retVal = (retVal * 16) + (digit - '0'); + } + dat->m_sValue = new char[sizeof(uint64)]; + *((uint64 *)dat->m_sValue) = retVal; + dat->m_iDataType = TYPE_UINT64; + } else if ((pFEnd > pIEnd) && (pFEnd == pSEnd)) { + dat->m_flValue = fval; + dat->m_iDataType = TYPE_FLOAT; + } else if (pIEnd == pSEnd && !bOverflow) { + dat->m_iValue = ival; + dat->m_iDataType = TYPE_INT; + } else { + dat->m_iDataType = TYPE_STRING; + } + + if (dat->m_iDataType == TYPE_STRING) { + // copy in the string information + dat->m_sValue = new char[len + 1]; + V_memcpy(dat->m_sValue, value, len + 1); + } + + // Look ahead one token for a conditional tag + intp prevPos = buf.TellGet(); + const char *peek = ReadToken(buf, wasQuoted, wasConditional); + if (wasConditional) { + bAccepted = EvaluateConditional(peek, pfnEvaluateSymbolProc); + } else { + buf.SeekGet(CUtlBuffer::SEEK_HEAD, prevPos); + } + } + + if (!bAccepted) { + this->RemoveSubKey(dat); + dat->deleteThis(); + dat = NULL; + } + } +} + +// writes KeyValue as binary data to buffer +bool KeyValues::WriteAsBinary(CUtlBuffer &buffer) const { + if (buffer.IsText()) // must be a binary buffer + return false; + + if (!buffer.IsValid()) // must be valid, no overflows etc + return false; + + // Write subkeys: + + // loop through all our peers + for (const KeyValues *dat = this; dat != NULL; dat = dat->m_pPeer) { + // write type + buffer.PutUnsignedChar(dat->m_iDataType); + + // write name + buffer.PutString(dat->GetName()); + + // write type + switch (dat->m_iDataType) { + case TYPE_NONE: { + dat->m_pSub->WriteAsBinary(buffer); + break; + } + case TYPE_STRING: { + if (dat->m_sValue && *(dat->m_sValue)) { + buffer.PutString(dat->m_sValue); + } else { + buffer.PutString(""); + } + break; + } + case TYPE_WSTRING: { + unsigned short nLength = + dat->m_wsValue ? (unsigned short)V_wcslen(dat->m_wsValue) : 0; + buffer.PutShort(nLength); + for (int k = 0; k < nLength; ++k) { + buffer.PutShort((unsigned short)dat->m_wsValue[k]); + } + break; + } + + case TYPE_INT: { + buffer.PutInt(dat->m_iValue); + break; + } + + case TYPE_UINT64: { + buffer.PutInt64(*((int64 *)dat->m_sValue)); + break; + } + + case TYPE_FLOAT: { + buffer.PutFloat(dat->m_flValue); + break; + } + case TYPE_COLOR: { + buffer.PutUnsignedChar(dat->m_Color[0]); + buffer.PutUnsignedChar(dat->m_Color[1]); + buffer.PutUnsignedChar(dat->m_Color[2]); + buffer.PutUnsignedChar(dat->m_Color[3]); + break; + } + case TYPE_PTR: { +#if !defined(PLATFORM_64BITS) + buffer.PutUnsignedInt((unsigned int)dat->m_pValue); +#else + buffer.PutInt64((int64)dat->m_pValue); +#endif + break; + } + + default: + break; + } + } + + // write tail, marks end of peers + buffer.PutUnsignedChar(TYPE_NUMTYPES); + + return buffer.IsValid(); +} + +// read KeyValues from binary buffer, returns true if parsing was successful +bool KeyValues::ReadAsBinary(CUtlBuffer &buffer) { + if (buffer.IsText()) // must be a binary buffer + return false; + + if (!buffer.IsValid()) // must be valid, no overflows etc + return false; + + RemoveEverything(); // remove current content + Init(); // reset + + char token[KEYVALUES_TOKEN_SIZE]; + KeyValues *dat = this; + types_t type = (types_t)buffer.GetUnsignedChar(); + + // loop through all our peers + while (true) { + if (type == TYPE_NUMTYPES) break; // no more peers + + dat->m_iDataType = type; + + buffer.GetString(token, KEYVALUES_TOKEN_SIZE - 1); + token[KEYVALUES_TOKEN_SIZE - 1] = 0; + + dat->SetName(token); + + switch (type) { + case TYPE_NONE: { + dat->m_pSub = new KeyValues(""); + dat->m_pSub->ReadAsBinary(buffer); + break; + } + case TYPE_STRING: { + buffer.GetString(token, KEYVALUES_TOKEN_SIZE - 1); + token[KEYVALUES_TOKEN_SIZE - 1] = 0; + + intp len = V_strlen(token); + dat->m_sValue = new char[len + 1]; + V_memcpy(dat->m_sValue, token, len + 1); + + break; + } + case TYPE_WSTRING: { + int nLength = buffer.GetShort(); + + dat->m_wsValue = new wchar_t[nLength + 1]; + + for (int k = 0; k < nLength; ++k) { + dat->m_wsValue[k] = buffer.GetShort(); + } + dat->m_wsValue[nLength] = 0; + break; + } + + case TYPE_INT: { + dat->m_iValue = buffer.GetInt(); + break; + } + + case TYPE_UINT64: { + dat->m_sValue = new char[sizeof(uint64)]; + *((uint64 *)dat->m_sValue) = buffer.GetInt64(); + break; + } + + case TYPE_FLOAT: { + dat->m_flValue = buffer.GetFloat(); + break; + } + case TYPE_COLOR: { + dat->m_Color[0] = buffer.GetUnsignedChar(); + dat->m_Color[1] = buffer.GetUnsignedChar(); + dat->m_Color[2] = buffer.GetUnsignedChar(); + dat->m_Color[3] = buffer.GetUnsignedChar(); + break; + } + case TYPE_PTR: { +#if !defined(PLATFORM_64BITS) + dat->m_pValue = (void *)buffer.GetUnsignedInt(); +#else + dat->m_pValue = (void *)buffer.GetInt64(); +#endif + break; + } + + default: + break; + } + + if (!buffer.IsValid()) // error occured + return false; + + type = (types_t)buffer.GetUnsignedChar(); + + if (type == TYPE_NUMTYPES) break; + + // new peer follows + dat->m_pPeer = new KeyValues(""); + dat = dat->m_pPeer; + } + + return buffer.IsValid(); +} + +//----------------------------------------------------------------------------- +// Alternate dense binary format that pools all the strings, the xbox supports +// this during creation of each mod dir's zip processing of kv files. +//----------------------------------------------------------------------------- +bool KeyValues::ReadAsBinaryPooledFormat( + CUtlBuffer &buffer, IBaseFileSystem *pFileSystem, unsigned int poolKey, + GetSymbolProc_t pfnEvaluateSymbolProc) { + // xbox only support + if (!IsGameConsole()) { + Assert(0); + return false; + } + + if (buffer.IsText()) // must be a binary buffer + return false; + + if (!buffer.IsValid()) // must be valid, no overflows etc + return false; + + char token[KEYVALUES_TOKEN_SIZE]; + KeyValues *dat = this; + types_t type = (types_t)buffer.GetUnsignedChar(); + + // loop through all our peers + while (true) { + if (type == TYPE_NUMTYPES) break; // no more peers + + dat->m_iDataType = type; + + unsigned int stringKey = buffer.GetUnsignedInt(); + if (!((IFileSystem *)pFileSystem) + ->GetStringFromKVPool(poolKey, stringKey, token, sizeof(token))) + return false; + dat->SetName(token); + + switch (type) { + case TYPE_NONE: { + dat->m_pSub = new KeyValues(""); + if (!dat->m_pSub->ReadAsBinaryPooledFormat(buffer, pFileSystem, poolKey, + pfnEvaluateSymbolProc)) + return false; + break; + } + + case TYPE_STRING: { + unsigned int stringKeyI = buffer.GetUnsignedInt(); + if (!((IFileSystem *)pFileSystem) + ->GetStringFromKVPool(poolKey, stringKeyI, token, + sizeof(token))) + return false; + intp len = V_strlen(token); + dat->m_sValue = new char[len + 1]; + V_memcpy(dat->m_sValue, token, len + 1); + break; + } + + case TYPE_WSTRING: { + int nLength = buffer.GetShort(); + dat->m_wsValue = new wchar_t[nLength + 1]; + for (int k = 0; k < nLength; ++k) { + dat->m_wsValue[k] = buffer.GetShort(); + } + dat->m_wsValue[nLength] = 0; + break; + } + + case TYPE_INT: { + dat->m_iValue = buffer.GetInt(); + break; + } + + case TYPE_UINT64: { + dat->m_sValue = new char[sizeof(uint64)]; + *((uint64 *)dat->m_sValue) = buffer.GetInt64(); + break; + } + + case TYPE_FLOAT: { + dat->m_flValue = buffer.GetFloat(); + break; + } + + case TYPE_COLOR: { + dat->m_Color[0] = buffer.GetUnsignedChar(); + dat->m_Color[1] = buffer.GetUnsignedChar(); + dat->m_Color[2] = buffer.GetUnsignedChar(); + dat->m_Color[3] = buffer.GetUnsignedChar(); + break; + } + + case TYPE_PTR: { +#if !defined(PLATFORM_64BITS) + dat->m_pValue = (void *)buffer.GetUnsignedInt(); +#else + dat->m_pValue = (void *)buffer.GetInt64(); +#endif + break; + } + + case TYPE_COMPILED_INT_0: { + // only for dense storage purposes, flip back to preferred internal + // format + dat->m_iDataType = TYPE_INT; + dat->m_iValue = 0; + break; + } + + case TYPE_COMPILED_INT_1: { + // only for dense storage purposes, flip back to preferred internal + // format + dat->m_iDataType = TYPE_INT; + dat->m_iValue = 1; + break; + } + + case TYPE_COMPILED_INT_BYTE: { + // only for dense storage purposes, flip back to preferred internal + // format + dat->m_iDataType = TYPE_INT; + dat->m_iValue = buffer.GetChar(); + break; + } + + default: + break; + } + + if (!buffer.IsValid()) // error occured + return false; + + if (!buffer.GetBytesRemaining()) break; + + type = (types_t)buffer.GetUnsignedChar(); + if (type == TYPE_NUMTYPES) break; + + // new peer follows + dat->m_pPeer = new KeyValues(""); + dat = dat->m_pPeer; + } + + return buffer.IsValid(); +} + +#include "tier0/memdbgoff.h" + +//----------------------------------------------------------------------------- +// Purpose: memory allocator +//----------------------------------------------------------------------------- +void *KeyValues::operator new(size_t iAllocSize) { + MEM_ALLOC_CREDIT(); + return KeyValuesSystem()->AllocKeyValuesMemory(iAllocSize); +} + +void *KeyValues::operator new(size_t iAllocSize, int nBlockUse, + const char *pFileName, int nLine) { + MemAlloc_PushAllocDbgInfo(pFileName, nLine); + void *p = KeyValuesSystem()->AllocKeyValuesMemory(iAllocSize); + MemAlloc_PopAllocDbgInfo(); + return p; +} + +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: deallocator +//----------------------------------------------------------------------------- +void KeyValues::operator delete(void *pMem) { + KeyValuesSystem()->FreeKeyValuesMemory(pMem); +} + +void KeyValues::operator delete(void *pMem, int nBlockUse, + const char *pFileName, int nLine) { + KeyValuesSystem()->FreeKeyValuesMemory(pMem); +} + +void KeyValues::UnpackIntoStructure( + KeyValuesUnpackStructure const *pUnpackTable, void *pDest) { + uint8 *dest = (uint8 *)pDest; + while (pUnpackTable->m_pKeyName) { + uint8 *dest_field = dest + pUnpackTable->m_nFieldOffset; + KeyValues *find_it = FindKey(pUnpackTable->m_pKeyName); + switch (pUnpackTable->m_eDataType) { + case UNPACK_TYPE_FLOAT: { + float default_value = (pUnpackTable->m_pKeyDefault) + ? strtof(pUnpackTable->m_pKeyDefault, nullptr) + : 0.0F; + *((float *)dest_field) = + GetFloat(pUnpackTable->m_pKeyName, default_value); + break; + } break; + + case UNPACK_TYPE_VECTOR: { + Vector *dest_v = (Vector *)dest_field; + char const *src_string = + GetString(pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault); + if ((!src_string) || (sscanf(src_string, "%f %f %f", &(dest_v->x), + &(dest_v->y), &(dest_v->z)) != 3)) + dest_v->Init(0, 0, 0); + } break; + + case UNPACK_TYPE_FOUR_FLOATS: { + float *dest_f = (float *)dest_field; + char const *src_string = + GetString(pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault); + if ((!src_string) || (sscanf(src_string, "%f %f %f %f", dest_f, + dest_f + 1, dest_f + 2, dest_f + 3)) != 4) + memset(dest_f, 0, 4 * sizeof(float)); + } break; + + case UNPACK_TYPE_TWO_FLOATS: { + float *dest_f = (float *)dest_field; + char const *src_string = + GetString(pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault); + if ((!src_string) || + (sscanf(src_string, "%f %f", dest_f, dest_f + 1)) != 2) + memset(dest_f, 0, 2 * sizeof(float)); + } break; + + case UNPACK_TYPE_STRING: { + char *dest_s = (char *)dest_field; + char const *pDefault = ""; + if (pUnpackTable->m_pKeyDefault) { + pDefault = pUnpackTable->m_pKeyDefault; + } + strncpy(dest_s, GetString(pUnpackTable->m_pKeyName, pDefault), + pUnpackTable->m_nFieldSize); + } break; + + case UNPACK_TYPE_INT: { + int *dest_i = (int *)dest_field; + int default_int = 0; + if (pUnpackTable->m_pKeyDefault) + default_int = atoi(pUnpackTable->m_pKeyDefault); + *(dest_i) = GetInt(pUnpackTable->m_pKeyName, default_int); + } break; + + case UNPACK_TYPE_VECTOR_COLOR: { + Vector *dest_v = (Vector *)dest_field; + if (find_it) { + Color c = GetColor(pUnpackTable->m_pKeyName); + dest_v->x = static_cast(c.r()); + dest_v->y = static_cast(c.g()); + dest_v->z = static_cast(c.b()); + } else { + if (pUnpackTable->m_pKeyDefault) + sscanf(pUnpackTable->m_pKeyDefault, "%f %f %f", &(dest_v->x), + &(dest_v->y), &(dest_v->z)); + else + dest_v->Init(0, 0, 0); + } + *(dest_v) *= (1.0F / 255); + } + } + pUnpackTable++; + } +} + +//----------------------------------------------------------------------------- +// Helper function for processing a keyvalue tree for console resolution +// support. Alters key/values for easier console video resolution support. If +// running SD (640x480), the presence of "???_lodef" creates or slams "???". If +// running HD (1280x720), the presence of "???_hidef" creates or slams "???". +//----------------------------------------------------------------------------- +bool KeyValues::ProcessResolutionKeys(const char *pResString) { + if (!pResString) { + // not for pc, console only + return false; + } + + KeyValues *pSubKey = GetFirstSubKey(); + if (!pSubKey) { + // not a block + return false; + } + + for (; pSubKey != NULL; pSubKey = pSubKey->GetNextKey()) { + // recursively descend each sub block + pSubKey->ProcessResolutionKeys(pResString); + + // check to see if our substring is present + if (V_stristr(pSubKey->GetName(), pResString) != NULL) { + char normalKeyName[128]; + V_strncpy(normalKeyName, pSubKey->GetName(), sizeof(normalKeyName)); + + // substring must match exactly, otherwise keys like "_lodef" and + // "_lodef_wide" would clash. + char *pString = V_stristr(normalKeyName, pResString); + if (pString && !V_stricmp(pString, pResString)) { + *pString = '\0'; + + // find and delete the original key (if any) + KeyValues *pKey = FindKey(normalKeyName); + if (pKey) { + // remove the key + RemoveSubKey(pKey); + pKey->deleteThis(); + } + + // rename the marked key + pSubKey->SetName(normalKeyName); + } + } + } + + return true; +} + +// +// KeyValues merge operations +// + +void KeyValues::MergeFrom(KeyValues *kvMerge, + MergeKeyValuesOp_t eOp /* = MERGE_KV_ALL */) { + if (!kvMerge) return; + + switch (eOp) { + case MERGE_KV_ALL: + MergeFrom(kvMerge->FindKey("update"), MERGE_KV_UPDATE); + MergeFrom(kvMerge->FindKey("delete"), MERGE_KV_DELETE); + MergeFrom(kvMerge->FindKey("borrow"), MERGE_KV_BORROW); + return; + + case MERGE_KV_UPDATE: { + for (KeyValues *sub = kvMerge->GetFirstTrueSubKey(); sub; + sub = sub->GetNextTrueSubKey()) { + char const *szName = sub->GetName(); + + KeyValues *subStorage = this->FindKey(szName, false); + if (!subStorage) { + AddSubKey(sub->MakeCopy()); + } else { + subStorage->MergeFrom(sub, eOp); + } + } + for (KeyValues *val = kvMerge->GetFirstValue(); val; + val = val->GetNextValue()) { + char const *szName = val->GetName(); + + if (KeyValues *valStorage = this->FindKey(szName, false)) { + this->RemoveSubKey(valStorage); + valStorage->deleteThis(); + } + this->AddSubKey(val->MakeCopy()); + } + } + return; + + case MERGE_KV_BORROW: { + for (KeyValues *sub = kvMerge->GetFirstTrueSubKey(); sub; + sub = sub->GetNextTrueSubKey()) { + char const *szName = sub->GetName(); + + KeyValues *subStorage = this->FindKey(szName, false); + if (!subStorage) continue; + + subStorage->MergeFrom(sub, eOp); + } + for (KeyValues *val = kvMerge->GetFirstValue(); val; + val = val->GetNextValue()) { + char const *szName = val->GetName(); + + if (KeyValues *valStorage = this->FindKey(szName, false)) { + this->RemoveSubKey(valStorage); + valStorage->deleteThis(); + } else + continue; + + this->AddSubKey(val->MakeCopy()); + } + } + return; + + case MERGE_KV_DELETE: { + for (KeyValues *sub = kvMerge->GetFirstTrueSubKey(); sub; + sub = sub->GetNextTrueSubKey()) { + char const *szName = sub->GetName(); + if (KeyValues *subStorage = this->FindKey(szName, false)) { + subStorage->MergeFrom(sub, eOp); + } + } + for (KeyValues *val = kvMerge->GetFirstValue(); val; + val = val->GetNextValue()) { + char const *szName = val->GetName(); + + if (KeyValues *valStorage = this->FindKey(szName, false)) { + this->RemoveSubKey(valStorage); + valStorage->deleteThis(); + } + } + } + return; + } +} + +// +// KeyValues from string parsing +// + +static char const *ParseStringToken(char const *szStringVal, + char const **ppEndOfParse) { + // Eat whitespace + while (V_isspace(*szStringVal)) ++szStringVal; + + char const *pszResult = szStringVal; + + while (*szStringVal && !V_isspace(*szStringVal)) ++szStringVal; + + if (ppEndOfParse) { + *ppEndOfParse = szStringVal; + } + + return pszResult; +} + +KeyValues *KeyValues::FromString(char const *szName, char const *szStringVal, + char const **ppEndOfParse) { + if (!szName) szName = ""; + + if (!szStringVal) szStringVal = ""; + + KeyValues *kv = new KeyValues(szName); + if (!kv) return NULL; + + char chName[256] = {0}; + char chValue[256] = {0}; + + for (;;) { + char const *szEnd; + + char const *szVarValue = NULL; + char const *szVarName = ParseStringToken(szStringVal, &szEnd); + if (!*szVarName) break; + if (*szVarName == '}') { + szStringVal = szVarName + 1; + break; + } + V_strncpy(chName, szVarName, MIN((intp)sizeof(chName), szEnd - szVarName + 1)); + szVarName = chName; + szStringVal = szEnd; + + if (*szVarName == '{') { + szVarName = ""; + goto do_sub_key; + } + + szVarValue = ParseStringToken(szStringVal, &szEnd); + if (*szVarValue == '}') { + szStringVal = szVarValue + 1; + kv->SetString(szVarName, ""); + break; + } + V_strncpy(chValue, szVarValue, + MIN((intp)sizeof(chValue), szEnd - szVarValue + 1)); + szVarValue = chValue; + szStringVal = szEnd; + + if (*szVarValue == '{') { + goto do_sub_key; + } + + // Try to recognize some known types + if (char const *szInt = StringAfterPrefix(szVarValue, "#int#")) { + kv->SetInt(szVarName, atoi(szInt)); + } else if (!V_stricmp(szVarValue, "#empty#")) { + kv->SetString(szVarName, ""); + } else { + kv->SetString(szVarName, szVarValue); + } + continue; + + do_sub_key : { + KeyValues *pSubKey = KeyValues::FromString(szVarName, szStringVal, &szEnd); + if (pSubKey) { + kv->AddSubKey(pSubKey); + } + szStringVal = szEnd; + continue; + } + } + + if (ppEndOfParse) { + *ppEndOfParse = szStringVal; + } + + return kv; +} + +// +// KeyValues dumping implementation +// +bool KeyValues::Dump(IKeyValuesDumpContext *pDump, int nIndentLevel /* = 0 */) { + if (!pDump->KvBeginKey(this, nIndentLevel)) return false; + + // Dump values + for (KeyValues *val = GetFirstValue(); val; val = val->GetNextValue()) { + if (!pDump->KvWriteValue(val, nIndentLevel + 1)) return false; + } + + // Dump subkeys + for (KeyValues *sub = GetFirstTrueSubKey(); sub; + sub = sub->GetNextTrueSubKey()) { + if (!sub->Dump(pDump, nIndentLevel + 1)) return false; + } + + return pDump->KvEndKey(this, nIndentLevel); +} + +bool IKeyValuesDumpContextAsText::KvBeginKey(KeyValues *pKey, + int nIndentLevel) { + if (pKey) { + return KvWriteIndent(nIndentLevel) && KvWriteText(pKey->GetName()) && + KvWriteText(" {\n"); + } else { + return KvWriteIndent(nIndentLevel) && KvWriteText("<< NULL >>\n"); + } +} + +bool IKeyValuesDumpContextAsText::KvWriteValue(KeyValues *val, + int nIndentLevel) { + if (!val) { + return KvWriteIndent(nIndentLevel) && KvWriteText("<< NULL >>\n"); + } + + if (!KvWriteIndent(nIndentLevel)) return false; + + if (!KvWriteText(val->GetName())) return false; + + if (!KvWriteText(" ")) return false; + + switch (val->GetDataType()) { + case KeyValues::TYPE_STRING: { + if (!KvWriteText(val->GetString())) return false; + } break; + + case KeyValues::TYPE_INT: { + int n = val->GetInt(); + char *chBuffer = (char *)stackalloc(128); + V_snprintf(chBuffer, 128, "int( %d = 0x%X )", n, n); + if (!KvWriteText(chBuffer)) return false; + } break; + + case KeyValues::TYPE_FLOAT: { + float fl = val->GetFloat(); + char *chBuffer = (char *)stackalloc(128); + V_snprintf(chBuffer, 128, "float( %f )", fl); + if (!KvWriteText(chBuffer)) return false; + } break; + + case KeyValues::TYPE_PTR: { + void *ptr = val->GetPtr(); + char *chBuffer = (char *)stackalloc(128); + V_snprintf(chBuffer, 128, "ptr( 0x%p )", ptr); + if (!KvWriteText(chBuffer)) return false; + } break; + + case KeyValues::TYPE_WSTRING: { + wchar_t const *wsz = val->GetWString(); + intp nLen = V_wcslen(wsz); + intp numBytes = nLen * 2 + 64; + char *chBuffer = (char *)stackalloc(numBytes); + V_snprintf(chBuffer, numBytes, "%ls [wstring, len = %zi]", wsz, nLen); + if (!KvWriteText(chBuffer)) return false; + } break; + + case KeyValues::TYPE_UINT64: { + uint64 n = val->GetUint64(); + char *chBuffer = (char *)stackalloc(128); + V_snprintf(chBuffer, 128, "u64( %lld = 0x%llX )", n, n); + if (!KvWriteText(chBuffer)) return false; + } break; + + default: + break; +#if 0 // this code was accidentally stubbed out by a mis-integration in + // CL722860; it hasn't been tested + { + int n = val->GetDataType(); + char *chBuffer = ( char * ) stackalloc( 128 ); + V_snprintf( chBuffer, 128, "??kvtype[%d]", n ); + if ( !KvWriteText( chBuffer ) ) + return false; + } + break; +#endif + } + + return KvWriteText("\n"); +} + +bool IKeyValuesDumpContextAsText::KvEndKey(KeyValues *pKey, int nIndentLevel) { + if (pKey) { + return KvWriteIndent(nIndentLevel) && KvWriteText("}\n"); + } else { + return true; + } +} + +bool IKeyValuesDumpContextAsText::KvWriteIndent(int nIndentLevel) { + int numIndentBytes = (nIndentLevel * 2 + 1); + char *pchIndent = (char *)stackalloc(numIndentBytes); + memset(pchIndent, ' ', numIndentBytes - 1); + pchIndent[numIndentBytes - 1] = 0; + return KvWriteText(pchIndent); +} + +bool CKeyValuesDumpContextAsDevMsg::KvBeginKey(KeyValues *pKey, + int nIndentLevel) { + static ConVarRef r_developer("developer"); + if (r_developer.IsValid() && r_developer.GetInt() < m_nDeveloperLevel) + // If "developer" is not the correct level, then avoid evaluating KeyValues + // tree early + return false; + else + return IKeyValuesDumpContextAsText::KvBeginKey(pKey, nIndentLevel); +} + +bool CKeyValuesDumpContextAsDevMsg::KvWriteText(char const *szText) { + if (m_nDeveloperLevel > 0) { + DevMsg("%s", szText); + } else { + Msg("%s", szText); + } + return true; +} diff --git a/tier1/mempool.cpp b/tier1/mempool.cpp new file mode 100644 index 0000000..fb683d4 --- /dev/null +++ b/tier1/mempool.cpp @@ -0,0 +1,289 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier1/mempool.h" + +#include +#include +#include + +#include "tier0/dbg.h" +#include "tier1/strtools.h" + +#ifndef _PS3 +#include +#endif + +// Should be last include +#include "tier0/memdbgon.h" + +MemoryPoolReportFunc_t CUtlMemoryPool::g_ReportFunc = 0; + +//----------------------------------------------------------------------------- +// Error reporting... (debug only) +//----------------------------------------------------------------------------- + +void CUtlMemoryPool::SetErrorReportFunc(MemoryPoolReportFunc_t func) { + g_ReportFunc = func; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CUtlMemoryPool::CUtlMemoryPool(int blockSize, int numElements, int growMode, + const char *pszAllocOwner, unsigned short nAlignment) { +#ifdef _X360 + if (numElements > 0 && growMode != GROW_NONE) { + numElements = 1; + } +#endif + + m_nAlignment = (nAlignment != 0) ? nAlignment : 1; + Assert(IsPowerOfTwo(m_nAlignment)); + m_BlockSize = blockSize < sizeof(void *) ? sizeof(void *) : blockSize; + m_BlockSize = AlignValue(m_BlockSize, m_nAlignment); + m_BlocksPerBlob = numElements; + m_PeakAlloc = 0; + m_GrowMode = growMode; + if (!pszAllocOwner) { + pszAllocOwner = __FILE__; + } + m_pszAllocOwner = pszAllocOwner; + Init(); + AddNewBlob(); +} + +//----------------------------------------------------------------------------- +// Purpose: Frees the memory contained in the mempool, and invalidates it for +// any further use. +// Input : *memPool - the mempool to shutdown +//----------------------------------------------------------------------------- +CUtlMemoryPool::~CUtlMemoryPool() { + if (m_BlocksAllocated > 0) { + ReportLeaks(); + } + Clear(); +} + +//----------------------------------------------------------------------------- +// Resets the pool +//----------------------------------------------------------------------------- +void CUtlMemoryPool::Init() { + m_NumBlobs = 0; + m_BlocksAllocated = 0; + m_pHeadOfFreeList = 0; + m_BlobHead.m_pNext = m_BlobHead.m_pPrev = &m_BlobHead; +} + +//----------------------------------------------------------------------------- +// Frees everything +//----------------------------------------------------------------------------- +void CUtlMemoryPool::Clear() { + // Free everything.. + CBlob *pNext; + for (CBlob *pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; pCur = pNext) { + pNext = pCur->m_pNext; + free(pCur); + } + Init(); +} + +//----------------------------------------------------------------------------- +// Is an allocation within the pool? +//----------------------------------------------------------------------------- +bool CUtlMemoryPool::IsAllocationWithinPool(void *pMem) const { + // Free everything.. + for (CBlob *pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; + pCur = pCur->m_pNext) { + // Is the allocation within the blob? + if ((pMem < pCur->m_Data) || (pMem >= pCur->m_Data + pCur->m_NumBytes)) + continue; + + // Make sure the allocation is on a block boundary + intp nOffset = (intp)pMem - (intp)pCur->m_Data; + return (nOffset % m_BlockSize) == 0; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Reports memory leaks +//----------------------------------------------------------------------------- +void CUtlMemoryPool::ReportLeaks() { + if (!g_ReportFunc) return; + + g_ReportFunc("Memory leak: mempool blocks left in memory: %d\n", + m_BlocksAllocated); + +#ifdef _DEBUG + // walk and destroy the free list so it doesn't intefere in the scan + while (m_pHeadOfFreeList != NULL) { + void *next = *((void **)m_pHeadOfFreeList); + memset(m_pHeadOfFreeList, 0, m_BlockSize); + m_pHeadOfFreeList = next; + } + + g_ReportFunc("Dumping memory: \'"); + + for (CBlob *pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; + pCur = pCur->m_pNext) { + // scan the memory block and dump the leaks + char *scanPoint = (char *)pCur->m_Data; + char *scanEnd = pCur->m_Data + pCur->m_NumBytes; + bool needSpace = false; + + while (scanPoint < scanEnd) { + // search for and dump any strings + if ((unsigned)(*scanPoint + 1) <= 256 && isprint(*scanPoint)) { + g_ReportFunc("%c", *scanPoint); + needSpace = true; + } else if (needSpace) { + needSpace = false; + g_ReportFunc(" "); + } + + scanPoint++; + } + } + + g_ReportFunc("\'\n"); +#endif // _DEBUG +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CUtlMemoryPool::AddNewBlob() { + MEM_ALLOC_CREDIT_(m_pszAllocOwner); + + int sizeMultiplier; + + if (m_GrowMode == GROW_SLOW) { + sizeMultiplier = 1; + } else { + if (m_GrowMode == GROW_NONE) { + // Can only have one allocation when we're in this mode + if (m_NumBlobs != 0) { + Assert(!"CUtlMemoryPool::AddNewBlob: mode == GROW_NONE"); + return; + } + } + + // GROW_FAST and GROW_NONE use this. + sizeMultiplier = m_NumBlobs + 1; + } + + // maybe use something other than malloc? + int nElements = m_BlocksPerBlob * sizeMultiplier; + int blobSize = m_BlockSize * nElements; + CBlob *pBlob = + (CBlob *)malloc(sizeof(CBlob) - 1 + blobSize + (m_nAlignment - 1)); + Assert(pBlob); + + // Link it in at the end of the blob list. + pBlob->m_NumBytes = blobSize; + pBlob->m_pNext = &m_BlobHead; + pBlob->m_pPrev = pBlob->m_pNext->m_pPrev; + pBlob->m_pNext->m_pPrev = pBlob->m_pPrev->m_pNext = pBlob; + + // setup the free list + m_pHeadOfFreeList = AlignValue(pBlob->m_Data, m_nAlignment); + Assert(m_pHeadOfFreeList); + + void **newBlob = (void **)m_pHeadOfFreeList; + for (int j = 0; j < nElements - 1; j++) { + newBlob[0] = (char *)newBlob + m_BlockSize; + newBlob = (void **)newBlob[0]; + } + + // null terminate list + newBlob[0] = NULL; + m_NumBlobs++; +} + +void *CUtlMemoryPool::Alloc() { return Alloc(m_BlockSize); } + +void *CUtlMemoryPool::AllocZero() { return AllocZero(m_BlockSize); } + +//----------------------------------------------------------------------------- +// Purpose: Allocs a single block of memory from the pool. +// Input : amount - +//----------------------------------------------------------------------------- +void *CUtlMemoryPool::Alloc(size_t amount) { + void *returnBlock; + + if (amount > (size_t)m_BlockSize) return NULL; + + if (!m_pHeadOfFreeList) { + // returning NULL is fine in GROW_NONE + if (m_GrowMode == GROW_NONE && m_NumBlobs > 0) { + // Assert( !"CUtlMemoryPool::Alloc: tried to make new blob with GROW_NONE" + // ); + return NULL; + } + + // overflow + AddNewBlob(); + + // still failure, error out + if (!m_pHeadOfFreeList) { + Assert(!"CUtlMemoryPool::Alloc: ran out of memory"); + return NULL; + } + } + m_BlocksAllocated++; + m_PeakAlloc = MAX(m_PeakAlloc, m_BlocksAllocated); + + returnBlock = m_pHeadOfFreeList; + + // move the pointer the next block + m_pHeadOfFreeList = *((void **)m_pHeadOfFreeList); + + return returnBlock; +} + +//----------------------------------------------------------------------------- +// Purpose: Allocs a single block of memory from the pool, zeroes the memory +// before returning Input : amount - +//----------------------------------------------------------------------------- +void *CUtlMemoryPool::AllocZero(size_t amount) { + void *mem = Alloc(amount); + if (mem) { + memset(mem, 0x00, amount); + } + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: Frees a block of memory +// Input : *memBlock - the memory to free +//----------------------------------------------------------------------------- +void CUtlMemoryPool::Free(void *memBlock) { + if (!memBlock) return; // trying to delete NULL pointer, ignore + +#ifdef _DEBUG + // check to see if the memory is from the allocated range + bool bOK = false; + for (CBlob *pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; + pCur = pCur->m_pNext) { + if (memBlock >= pCur->m_Data && + (char *)memBlock < (pCur->m_Data + pCur->m_NumBytes)) { + bOK = true; + } + } + Assert(bOK); +#endif // _DEBUG + +#ifdef _DEBUG + // invalidate the memory + memset(memBlock, 0xDD, m_BlockSize); +#endif + + m_BlocksAllocated--; + + // make the block point to the first item in the list + *((void **)memBlock) = m_pHeadOfFreeList; + + // the list head is now the new block + m_pHeadOfFreeList = memBlock; +} diff --git a/tier1/memstack.cpp b/tier1/memstack.cpp new file mode 100644 index 0000000..f92acdb --- /dev/null +++ b/tier1/memstack.cpp @@ -0,0 +1,408 @@ +// Copyright Valve Corporation, All rights reserved. + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#define VA_COMMIT_FLAGS MEM_COMMIT +#define VA_RESERVE_FLAGS MEM_RESERVE +#elif defined(_X360) +#define VA_COMMIT_FLAGS (MEM_COMMIT | MEM_NOZERO | MEM_LARGE_PAGES) +#define VA_RESERVE_FLAGS (MEM_RESERVE | MEM_LARGE_PAGES) +#elif defined(_PS3) +#include "sys/memory.h" +#include "sys/mempool.h" +#include "sys/process.h" +#include +#endif + +#include "tier0/dbg.h" +#include "memstack.h" +#include "utlmap.h" +#include "tier0/memdbgon.h" + +#ifdef _WIN32 +#pragma warning(disable : 4073) +#pragma init_seg(lib) +#endif + +// TODO: Register CMemoryStacks with g_pMemAlloc, so it can spew a +// summary +static volatile bool bSpewAllocations = false; + +//----------------------------------------------------------------------------- + +MEMALLOC_DEFINE_EXTERNAL_TRACKING(CMemoryStack); + +//----------------------------------------------------------------------------- + +CMemoryStack::CMemoryStack() + : m_pBase(NULL), + m_pNextAlloc(NULL), + m_pAllocLimit(NULL), + m_pCommitLimit(NULL), + m_alignment(16), +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + m_commitSize(0), + m_minCommit(0), +#ifdef _PS3 + m_pVirtualMemorySection(NULL), +#endif +#endif + m_maxSize(0), + m_bRegisteredAllocation(false), + m_bPhysical(false) { + m_pszAllocOwner = _strdup("CMemoryStack unattributed"); +} + +//------------------------------------- + +CMemoryStack::~CMemoryStack() { + if (m_pBase) Term(); + free(m_pszAllocOwner); +} + +//------------------------------------- + +bool CMemoryStack::Init(const char *pszAllocOwner, size_t maxSize, + size_t commitSize, size_t initialCommit, + unsigned alignment) { + Assert(!m_pBase); + + m_bPhysical = false; + + m_maxSize = maxSize; + m_alignment = AlignValue(alignment, 4); + + Assert(m_alignment == alignment); + Assert(m_maxSize > 0); + + SetAllocOwner(pszAllocOwner); + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + +#ifdef _PS3 + // Memory can only be committed in page-size increments on PS3 + static const unsigned PS3_PAGE_SIZE = 64 * 1024; + if (commitSize < PS3_PAGE_SIZE) commitSize = PS3_PAGE_SIZE; +#endif + + if (commitSize != 0) { + m_commitSize = commitSize; + } + + unsigned pageSize; + +#ifdef _PS3 + pageSize = PS3_PAGE_SIZE; +#elif defined(_X360) + pageSize = 64 * 1024; +#else + SYSTEM_INFO sysInfo; + GetNativeSystemInfo(&sysInfo); + Assert(!(sysInfo.dwPageSize & (sysInfo.dwPageSize - 1))); + pageSize = sysInfo.dwPageSize; +#endif + + if (m_commitSize == 0) { + m_commitSize = pageSize; + } else { + m_commitSize = AlignValue(m_commitSize, pageSize); + } + + m_maxSize = AlignValue(m_maxSize, m_commitSize); + + Assert(m_maxSize % pageSize == 0 && m_commitSize % pageSize == 0 && + m_commitSize <= m_maxSize); + +#ifdef _WIN32 + m_pBase = (unsigned char *)VirtualAlloc(NULL, m_maxSize, VA_RESERVE_FLAGS, + PAGE_NOACCESS); +#else + m_pVirtualMemorySection = + g_pMemAlloc->AllocateVirtualMemorySection(m_maxSize); + if (!m_pVirtualMemorySection) { + Warning("AllocateVirtualMemorySection failed( size=%d )\n", m_maxSize); + Assert(0); + m_pBase = NULL; + } else { + m_pBase = (byte *)m_pVirtualMemorySection->GetBaseAddress(); + } +#endif + if (!m_pBase) { +#if !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->OutOfMemory(); +#endif + return false; + } + m_pCommitLimit = m_pNextAlloc = m_pBase; + + if (initialCommit) { + initialCommit = AlignValue(initialCommit, m_commitSize); + Assert(initialCommit <= m_maxSize); + bool bInitialCommitSucceeded = false; +#ifdef _WIN32 + bInitialCommitSucceeded = !!VirtualAlloc(m_pCommitLimit, initialCommit, + VA_COMMIT_FLAGS, PAGE_READWRITE); +#else + m_pVirtualMemorySection->CommitPages(m_pCommitLimit, initialCommit); + bInitialCommitSucceeded = true; +#endif + if (!bInitialCommitSucceeded) { +#if !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->OutOfMemory(initialCommit); +#endif + return false; + } + m_minCommit = initialCommit; + m_pCommitLimit += initialCommit; + RegisterAllocation(); + } + +#else + m_pBase = (byte *)MemAlloc_AllocAligned(m_maxSize, alignment ? alignment : 1); + m_pNextAlloc = m_pBase; + m_pCommitLimit = m_pBase + m_maxSize; +#endif + + m_pAllocLimit = m_pBase + m_maxSize; + + return (m_pBase != NULL); +} + +//------------------------------------- + +#ifdef _GAMECONSOLE +bool CMemoryStack::InitPhysical(const char *pszAllocOwner, size_t size, + size_t nBaseAddrAlignment, uint alignment, + uint32 nFlags) { + m_bPhysical = true; + + m_maxSize = m_commitSize = size; + m_alignment = AlignValue(alignment, 4); + + SetAllocOwner(pszAllocOwner); + +#ifdef _X360 + int flags = PAGE_READWRITE | nFlags; + if (size >= 16 * 1024 * 1024) { + flags |= MEM_16MB_PAGES; + } else { + flags |= MEM_LARGE_PAGES; + } + m_pBase = (unsigned char *)XPhysicalAlloc(m_maxSize, MAXULONG_PTR, + nBaseAddrAlignment, flags); +#elif defined(_PS3) + m_pBase = (byte *)nFlags; + m_pBase = (byte *)AlignValue((uintp)m_pBase, m_alignment); +#else +#pragma error +#endif + + Assert(m_pBase); + m_pNextAlloc = m_pBase; + m_pCommitLimit = m_pBase + m_maxSize; + m_pAllocLimit = m_pBase + m_maxSize; + + RegisterAllocation(); + return (m_pBase != NULL); +} +#endif + +//------------------------------------- + +void CMemoryStack::Term() { + FreeAll(); + if (m_pBase) { +#ifdef _GAMECONSOLE + if (m_bPhysical) { +#if defined(_X360) + XPhysicalFree(m_pBase); +#elif defined(_PS3) +#else +#pragma error +#endif + m_pCommitLimit = m_pBase = NULL; + m_maxSize = 0; + RegisterDeallocation(true); + m_bPhysical = false; + return; + } +#endif // _GAMECONSOLE + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE +#if defined(_WIN32) + VirtualFree(m_pBase, 0, MEM_RELEASE); +#else + m_pVirtualMemorySection->Release(); + m_pVirtualMemorySection = NULL; +#endif +#else + MemAlloc_FreeAligned(m_pBase); +#endif + m_pCommitLimit = m_pBase = NULL; + m_maxSize = 0; + RegisterDeallocation(true); + } +} + +//------------------------------------- + +intp CMemoryStack::GetSize() { + if (m_bPhysical) return m_maxSize; + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + return m_pCommitLimit - m_pBase; +#else + return m_maxSize; +#endif +} + +//------------------------------------- + +bool CMemoryStack::CommitTo(byte *pNextAlloc) RESTRICT { + if (m_bPhysical) { + return NULL; + } + +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + unsigned char *pNewCommitLimit = AlignValue(pNextAlloc, m_commitSize); + ptrdiff_t commitSize = pNewCommitLimit - m_pCommitLimit; + + if (m_pCommitLimit + commitSize > m_pAllocLimit) { + return false; + } + + if (pNewCommitLimit > m_pCommitLimit) { + RegisterDeallocation(false); + bool bAllocationSucceeded = false; +#ifdef _WIN32 + bAllocationSucceeded = !!VirtualAlloc(m_pCommitLimit, commitSize, + VA_COMMIT_FLAGS, PAGE_READWRITE); +#else + bAllocationSucceeded = + m_pVirtualMemorySection->CommitPages(m_pCommitLimit, commitSize); +#endif + if (!bAllocationSucceeded) { +#if !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->OutOfMemory(commitSize); +#endif + return false; + } + m_pCommitLimit = pNewCommitLimit; + RegisterAllocation(); + } else if (pNewCommitLimit < m_pCommitLimit) { + if (m_pNextAlloc > pNewCommitLimit) { + Warning("ATTEMPTED TO DECOMMIT OWNED MEMORY STACK SPACE\n"); + pNewCommitLimit = AlignValue(m_pNextAlloc, m_commitSize); + } + + if (pNewCommitLimit < m_pCommitLimit) { + RegisterDeallocation(false); + ptrdiff_t decommitSize = m_pCommitLimit - pNewCommitLimit; +#ifdef _WIN32 + VirtualFree(pNewCommitLimit, decommitSize, MEM_DECOMMIT); +#else + m_pVirtualMemorySection->DecommitPages(pNewCommitLimit, decommitSize); +#endif + m_pCommitLimit = pNewCommitLimit; + RegisterAllocation(); + } + } + + return true; +#else + return false; +#endif +} + +// Identify the owner of this memory stack's memory +void CMemoryStack::SetAllocOwner(const char *pszAllocOwner) { + if (!pszAllocOwner || !V_strcmp(m_pszAllocOwner, pszAllocOwner)) return; + free(m_pszAllocOwner); + m_pszAllocOwner = _strdup(pszAllocOwner); +} + +void CMemoryStack::RegisterAllocation() { + // 'physical' allocations on PS3 come from RSX local memory, so we don't count + // them here: + if (IsPS3() && m_bPhysical) return; + + if (GetSize()) { + if (m_bRegisteredAllocation) + Warning( + "CMemoryStack: ERROR - mismatched " + "RegisterAllocation/RegisterDeallocation!\n"); + + // NOTE: we deliberately don't use MemAlloc_RegisterExternalAllocation. + // CMemoryStack needs to bypass 'GetActualDbgInfo' due to the way it + // allocates memory: there's just one representative memory address + // (m_pBase), it grows at unpredictable times (in CommitTo, not every Alloc + // call) and it is freed en-masse (instead of freeing each individual + // allocation). + MemAlloc_RegisterAllocation(m_pszAllocOwner, 0, GetSize(), GetSize(), 0); + } + m_bRegisteredAllocation = true; + + // Temp memorystack spew: very useful when we crash out of memory + if (IsGameConsole() && bSpewAllocations) + Msg("CMemoryStack: %4.1fMB (%s)\n", GetSize() / (float)(1024 * 1024), + m_pszAllocOwner); +} + +void CMemoryStack::RegisterDeallocation(bool bShouldSpewSize) { + // 'physical' allocations on PS3 come from RSX local memory, so we don't count + // them here: + if (IsPS3() && m_bPhysical) return; + + if (GetSize()) { + if (!m_bRegisteredAllocation) + Warning( + "CMemoryStack: ERROR - mismatched " + "RegisterAllocation/RegisterDeallocation!\n"); + MemAlloc_RegisterDeallocation(m_pszAllocOwner, 0, GetSize(), GetSize(), 0); + } + m_bRegisteredAllocation = false; + + // Temp memorystack spew: very useful when we crash out of memory + if (bShouldSpewSize && IsGameConsole() && bSpewAllocations) + Msg("CMemoryStack: %4.1fMB (%s)\n", GetSize() / (float)(1024 * 1024), + m_pszAllocOwner); +} + +//------------------------------------- + +void CMemoryStack::FreeToAllocPoint(MemoryStackMark_t mark, bool bDecommit) { + mark = AlignValue(mark, m_alignment); + byte *pAllocPoint = m_pBase + mark; + + Assert(pAllocPoint >= m_pBase && pAllocPoint <= m_pNextAlloc); + if (pAllocPoint >= m_pBase && pAllocPoint <= m_pNextAlloc) { + m_pNextAlloc = pAllocPoint; +#ifdef MEMSTACK_VIRTUAL_MEMORY_AVAILABLE + if (bDecommit && !m_bPhysical) { + CommitTo(MAX(m_pNextAlloc, (m_pBase + m_minCommit))); + } +#endif + } +} + +//------------------------------------- + +void CMemoryStack::FreeAll(bool bDecommit) { + if (m_pBase && (m_pBase < m_pCommitLimit)) { + FreeToAllocPoint(0, bDecommit); + } +} + +//------------------------------------- + +void CMemoryStack::Access(void **ppRegion, intp *pBytes) { + *ppRegion = m_pBase; + *pBytes = (m_pNextAlloc - m_pBase); +} + +//------------------------------------- + +void CMemoryStack::PrintContents() { + Msg("Total used memory: %zi\n", GetUsed()); + Msg("Total committed memory: %zi\n", GetSize()); +} diff --git a/tier1/splitstring.cpp b/tier1/splitstring.cpp new file mode 100644 index 0000000..0f4f52a --- /dev/null +++ b/tier1/splitstring.cpp @@ -0,0 +1,76 @@ +// Copyright Valve Corporation. All Rights Reserved. + +#include "strtools.h" + +#include "utlvector.h" + +CSplitString::CSplitString(const char *pString, const char **pSeparators, + intp nSeparators) { + Construct(pString, pSeparators, nSeparators); +}; + +CSplitString::CSplitString(const char *pString, const char *pSeparator) { + Construct(pString, &pSeparator, 1); +} + +CSplitString::~CSplitString() { + if (m_szBuffer) delete[] m_szBuffer; +} + +void CSplitString::Construct(const char *pString, const char **pSeparators, + intp nSeparators) { + ////////////////////////////////////////////////////////////////////////// + // make a duplicate of the original string. We'll use pieces of this duplicate + // to tokenize the string and create NULL-terminated tokens of the original + // string + // + intp nOriginalStringLength = V_strlen(pString); + m_szBuffer = new char[nOriginalStringLength + 1]; + memcpy(m_szBuffer, pString, nOriginalStringLength + 1); + + this->Purge(); + const char *pCurPos = pString; + while (1) { + intp iFirstSeparator = -1; + const char *pFirstSeparator = 0; + for (intp i = 0; i < nSeparators; i++) { + const char *pTest = V_stristr(pCurPos, pSeparators[i]); + if (pTest && (!pFirstSeparator || pTest < pFirstSeparator)) { + iFirstSeparator = i; + pFirstSeparator = pTest; + } + } + + if (pFirstSeparator) { + // Split on this separator and continue on. + intp separatorLen = V_strlen(pSeparators[iFirstSeparator]); + if (pFirstSeparator > pCurPos) { + ////////////////////////////////////////////////////////////////////////// + /// Cut the token out of the duplicate string + char *pTokenInDuplicate = m_szBuffer + (pCurPos - pString); + intp nTokenLength = pFirstSeparator - pCurPos; + Assert(nTokenLength > 0 && + !memcmp(pTokenInDuplicate, pCurPos, nTokenLength)); + pTokenInDuplicate[nTokenLength] = '\0'; + + this->AddToTail( pTokenInDuplicate /*AllocString( pCurPos, pFirstSeparator-pCurPos )*/ ); + } + + pCurPos = pFirstSeparator + separatorLen; + } else { + // Copy the rest of the string + if (intp nTokenLength = V_strlen(pCurPos)) { + ////////////////////////////////////////////////////////////////////////// + // There's no need to cut this token, because there's no separator after + // it. just add its copy in the buffer to the tail + char *pTokenInDuplicate = m_szBuffer + (pCurPos - pString); + Assert(!memcmp(pTokenInDuplicate, pCurPos, nTokenLength)); + + this->AddToTail(pTokenInDuplicate /*AllocString( pCurPos, -1 )*/); + } + return; + } + } +} + +void CSplitString::PurgeAndDeleteElements() { Purge(); } diff --git a/tier1/stringpool.cpp b/tier1/stringpool.cpp new file mode 100644 index 0000000..dca469b --- /dev/null +++ b/tier1/stringpool.cpp @@ -0,0 +1,364 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "stringpool.h" + +#include "convar.h" +#include "tier0/dbg.h" +#include "tier1/strtools.h" +#include "generichash.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Comparison function for string sorted associative data structures +//----------------------------------------------------------------------------- + +bool StrLessInsensitive(const char *const &pszLeft, + const char *const &pszRight) { + return (V_stricmp(pszLeft, pszRight) < 0); +} + +bool StrLessSensitive(const char *const &pszLeft, const char *const &pszRight) { + return (V_strcmp(pszLeft, pszRight) < 0); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CStringPool::CStringPool(StringPoolCase_t caseSensitivity) + : m_Strings(32, 256, + caseSensitivity == StringPoolCaseInsensitive + ? StrLessInsensitive + : StrLessSensitive) {} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CStringPool::~CStringPool() { FreeAll(); } + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +unsigned int CStringPool::Count() const { return m_Strings.Count(); } + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CStringPool::Find(const char *pszValue) { + unsigned short i = m_Strings.Find(pszValue); + if (m_Strings.IsValidIndex(i)) return m_Strings[i]; + + return NULL; +} + +const char *CStringPool::Allocate(const char *pszValue) { + char *pszNew; + + unsigned short i = m_Strings.Find(pszValue); + bool bNew = (i == m_Strings.InvalidIndex()); + + if (!bNew) return m_Strings[i]; + + pszNew = _strdup(pszValue); + m_Strings.Insert(pszNew); + + return pszNew; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void CStringPool::FreeAll() { + unsigned short i = m_Strings.FirstInorder(); + while (i != m_Strings.InvalidIndex()) { + free((void *)m_Strings[i]); + i = m_Strings.NextInorder(i); + } + m_Strings.RemoveAll(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CCountedStringPool::CCountedStringPool(StringPoolCase_t caseSensitivity) { + MEM_ALLOC_CREDIT(); + m_HashTable.EnsureCount(HASH_TABLE_SIZE); + + for (int i = 0; i < m_HashTable.Count(); i++) { + m_HashTable[i] = INVALID_ELEMENT; + } + + m_FreeListStart = INVALID_ELEMENT; + m_Elements.AddToTail(); + m_Elements[0].pString = NULL; + m_Elements[0].nReferenceCount = 0; + m_Elements[0].nNextElement = INVALID_ELEMENT; + + m_caseSensitivity = caseSensitivity; +} + +CCountedStringPool::~CCountedStringPool() { FreeAll(); } + +void CCountedStringPool::FreeAll() { + int i; + + // Reset the hash table: + for (i = 0; i < m_HashTable.Count(); i++) { + m_HashTable[i] = INVALID_ELEMENT; + } + + // Blow away the free list: + m_FreeListStart = INVALID_ELEMENT; + + for (i = 0; i < m_Elements.Count(); i++) { + if (m_Elements[i].pString) { + delete[] m_Elements[i].pString; + m_Elements[i].pString = NULL; + m_Elements[i].nReferenceCount = 0; + m_Elements[i].nNextElement = INVALID_ELEMENT; + } + } + + // Remove all but the invalid element: + m_Elements.RemoveAll(); + m_Elements.AddToTail(); + m_Elements[0].pString = NULL; + m_Elements[0].nReferenceCount = 0; + m_Elements[0].nNextElement = INVALID_ELEMENT; +} + +unsigned CCountedStringPool::Hash(const char *pszKey) { + if (m_caseSensitivity == StringPoolCaseInsensitive) { + return HashStringCaseless(pszKey); + } + return HashString(pszKey); +} + +unsigned short CCountedStringPool::FindStringHandle(const char *pIntrinsic) { + if (pIntrinsic == NULL) return INVALID_ELEMENT; + + unsigned short nHashBucketIndex = (Hash(pIntrinsic) % HASH_TABLE_SIZE); + unsigned short nCurrentBucket = m_HashTable[nHashBucketIndex]; + + // Does the bucket already exist? + if (nCurrentBucket != INVALID_ELEMENT) { + for (; nCurrentBucket != INVALID_ELEMENT; + nCurrentBucket = m_Elements[nCurrentBucket].nNextElement) { + if (!V_stricmp(pIntrinsic, m_Elements[nCurrentBucket].pString)) { + return nCurrentBucket; + } + } + } + + return 0; +} + +char *CCountedStringPool::FindString(const char *pIntrinsic) { + if (pIntrinsic == NULL) return NULL; + + // Yes, this will be NULL on failure. + return m_Elements[FindStringHandle(pIntrinsic)].pString; +} + +unsigned short CCountedStringPool::ReferenceStringHandle( + const char *pIntrinsic) { + if (pIntrinsic == NULL) return INVALID_ELEMENT; + + unsigned short nHashBucketIndex = (Hash(pIntrinsic) % HASH_TABLE_SIZE); + unsigned short nCurrentBucket = m_HashTable[nHashBucketIndex]; + + // Does the bucket already exist? + if (nCurrentBucket != INVALID_ELEMENT) { + for (; nCurrentBucket != INVALID_ELEMENT; + nCurrentBucket = m_Elements[nCurrentBucket].nNextElement) { + if (!V_stricmp(pIntrinsic, m_Elements[nCurrentBucket].pString)) { + // Anyone who hits 65k references is permanant + if (m_Elements[nCurrentBucket].nReferenceCount < MAX_REFERENCE) { + m_Elements[nCurrentBucket].nReferenceCount++; + } + return nCurrentBucket; + } + } + } + + if (m_FreeListStart != INVALID_ELEMENT) { + nCurrentBucket = m_FreeListStart; + m_FreeListStart = m_Elements[nCurrentBucket].nNextElement; + } else { + nCurrentBucket = (unsigned short)m_Elements.AddToTail(); + } + + m_Elements[nCurrentBucket].nReferenceCount = 1; + + // Insert at the beginning of the bucket: + m_Elements[nCurrentBucket].nNextElement = m_HashTable[nHashBucketIndex]; + m_HashTable[nHashBucketIndex] = nCurrentBucket; + + m_Elements[nCurrentBucket].pString = new char[V_strlen(pIntrinsic) + 1]; + V_strcpy(m_Elements[nCurrentBucket].pString, pIntrinsic); + + return nCurrentBucket; +} + +char *CCountedStringPool::ReferenceString(const char *pIntrinsic) { + if (!pIntrinsic) return NULL; + + return m_Elements[ReferenceStringHandle(pIntrinsic)].pString; +} + +void CCountedStringPool::DereferenceString(const char *pIntrinsic) { + // If we get a NULL pointer, just return + if (!pIntrinsic) return; + + unsigned short nHashBucketIndex = + static_cast(Hash(pIntrinsic) % m_HashTable.Count()); + unsigned short nCurrentBucket = m_HashTable[nHashBucketIndex]; + + // If there isn't anything in the bucket, just return. + if (nCurrentBucket == INVALID_ELEMENT) return; + + for (unsigned short previous = INVALID_ELEMENT; + nCurrentBucket != INVALID_ELEMENT; + nCurrentBucket = m_Elements[nCurrentBucket].nNextElement) { + if (!V_stricmp(pIntrinsic, m_Elements[nCurrentBucket].pString)) { + // Anyone who hits 65k references is permanant + if (m_Elements[nCurrentBucket].nReferenceCount < MAX_REFERENCE) { + m_Elements[nCurrentBucket].nReferenceCount--; + } + + if (m_Elements[nCurrentBucket].nReferenceCount == 0) { + if (previous == INVALID_ELEMENT) { + m_HashTable[nHashBucketIndex] = + m_Elements[nCurrentBucket].nNextElement; + } else { + m_Elements[previous].nNextElement = + m_Elements[nCurrentBucket].nNextElement; + } + + delete[] m_Elements[nCurrentBucket].pString; + m_Elements[nCurrentBucket].pString = NULL; + m_Elements[nCurrentBucket].nReferenceCount = 0; + + m_Elements[nCurrentBucket].nNextElement = m_FreeListStart; + m_FreeListStart = nCurrentBucket; + break; + } + } + + previous = nCurrentBucket; + } +} + +char *CCountedStringPool::HandleToString(unsigned short handle) { + return m_Elements[handle].pString; +} + +void CCountedStringPool::SpewStrings() { + int i; + for (i = 0; i < m_Elements.Count(); i++) { + char *string; + string = m_Elements[i].pString; + Msg("String %d: ref:%d %s\n", i, m_Elements[i].nReferenceCount, + string == NULL ? "EMPTY - ok for slot zero only!" : string); + } + + Msg("\n%d total counted strings.", m_Elements.Count()); +} + +#ifdef _DEBUG +CON_COMMAND(test_stringpool, "Tests the class CStringPool") { + CStringPool pool; + + Assert(pool.Count() == 0); + + pool.Allocate("test"); + Assert(pool.Count() == 1); + + pool.Allocate("test"); + Assert(pool.Count() == 1); + + pool.Allocate("test2"); + Assert(pool.Count() == 2); + + Assert(pool.Find("test2") != NULL); + Assert(pool.Find("TEST") != NULL); + Assert(pool.Find("Test2") != NULL); + Assert(pool.Find("test") != NULL); + + pool.FreeAll(); + Assert(pool.Count() == 0); + + Msg("Pass."); +} +#endif + +#define STRING_POOL_VERSION MAKEID('C', 'S', 'P', '1') +#define MAX_STRING_SAVE 1024 + +bool CCountedStringPool::SaveToBuffer(CUtlBuffer &buffer) { + if (m_Elements.Count() <= 1) { + // pool is empty, saving nothing + // caller can check put position of buffer to detect + return true; + } + + // signature/version + buffer.PutInt(STRING_POOL_VERSION); + + buffer.PutUnsignedShort(m_FreeListStart); + + buffer.PutInt(m_HashTable.Count()); + for (intp i = 0; i < m_HashTable.Count(); i++) { + buffer.PutUnsignedShort(m_HashTable[i]); + } + + buffer.PutInt(m_Elements.Count()); + for (intp i = 1; i < m_Elements.Count(); i++) { + buffer.PutUnsignedShort(m_Elements[i].nNextElement); + buffer.PutUnsignedChar(m_Elements[i].nReferenceCount); + + const char *pString = m_Elements[i].pString; + Assert(pString); + + if (strlen(pString) >= MAX_STRING_SAVE) { + return false; + } + buffer.PutString(pString); + } + + return buffer.IsValid(); +} + +bool CCountedStringPool::RestoreFromBuffer(CUtlBuffer &buffer) { + int signature = buffer.GetInt(); + if (signature != STRING_POOL_VERSION) { + // wrong version + return false; + } + + FreeAll(); + + m_FreeListStart = buffer.GetUnsignedShort(); + + intp hashCount = buffer.GetInt(); + m_HashTable.SetCount(hashCount); + + for (intp i = 0; i < hashCount; i++) { + m_HashTable[i] = buffer.GetUnsignedShort(); + } + + intp tableCount = buffer.GetInt(); + if (tableCount > 1) { + m_Elements.AddMultipleToTail(tableCount - 1); + } + + char tempString[MAX_STRING_SAVE]; + for (intp i = 1; i < tableCount; i++) { + m_Elements[i].nNextElement = buffer.GetUnsignedShort(); + m_Elements[i].nReferenceCount = buffer.GetUnsignedChar(); + buffer.GetString(tempString, sizeof(tempString)); + m_Elements[i].pString = _strdup(tempString); + } + + return buffer.IsValid(); +} diff --git a/tier1/strtools.cpp b/tier1/strtools.cpp new file mode 100644 index 0000000..d18bf4f --- /dev/null +++ b/tier1/strtools.cpp @@ -0,0 +1,2221 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: String Tools + +// These are redefined in the project settings to prevent anyone from using +// them. We in this module are of a higher caste and thus are privileged in +// their use. +#ifdef strncpy +#undef strncpy +#endif + +#ifdef _snprintf +#undef _snprintf +#endif + +#if defined(sprintf) +#undef sprintf +#endif + +#if defined(vsprintf) +#undef vsprintf +#endif + +#ifdef _vsnprintf +#ifdef _WIN32 +#undef _vsnprintf +#endif +#endif + +#ifdef vsnprintf +#ifndef _WIN32 +#undef vsnprintf +#endif +#endif + +#if defined(strcat) +#undef strcat +#endif + +#ifdef strncat +#undef strncat +#endif + +// NOTE: I have to include stdio + stdarg first so vsnprintf gets compiled in +#include +#include + +#include "tier0/basetypes.h" +#include "tier0/platform.h" + +#ifdef stricmp +#undef stricmp +#endif + +#ifdef POSIX + +#ifndef _PS3 +#include +#endif // _PS3 + +#include +#include +#include +#define stricmp strcasecmp +#elif _WIN32 +#include +#if !defined(_X360) +#include "winlite.h" +#endif +#endif + +#ifdef _WIN32 +#ifndef CP_UTF8 +#define CP_UTF8 65001 +#endif +#endif +#include "tier0/dbg.h" +#include "tier1/strtools.h" +#include +#include +#include "tier1/utldict.h" +#if defined(_X360) +#include "xbox/xbox_win32stubs.h" +#elif defined(_PS3) +#include "ps3_pathinfo.h" +#include // for UCS-2 to UTF-8 conversion +#endif +#include "tier0/vprof.h" +#include "tier0/memdbgon.h" + +#ifndef NDEBUG +static volatile char const *pDebugString; +#define DEBUG_LINK_CHECK pDebugString = "tier1.lib built debug!" +#else +#define DEBUG_LINK_CHECK +#endif + +void _V_memset(void *dest, int fill, intp count) { + DEBUG_LINK_CHECK; + Assert(count >= 0); + + memset(dest, fill, count); +} + +void _V_memcpy(void *dest, const void *src, intp count) { + Assert(count >= 0); + + memcpy(dest, src, count); +} + +void _V_memmove(void *dest, const void *src, intp count) { + Assert(count >= 0); + + memmove(dest, src, count); +} + +int _V_memcmp(const void *m1, const void *m2, intp count) { + DEBUG_LINK_CHECK; + Assert(count >= 0); + + return memcmp(m1, m2, count); +} + +intp _V_strlen(const char *str) { + AssertValidStringPtr(str); +#ifdef POSIX + if (!str) return 0; +#endif + return strlen(str); +} + +void _V_strcpy(char *dest, const char *src) { + DEBUG_LINK_CHECK; + AssertValidStringPtr(src); + + strcpy(dest, src); +} + +intp _V_wcslen(const wchar_t *pwch) { return wcslen(pwch); } + +char *_V_strrchr(const char *s, char c) { + AssertValidStringPtr(s); + intp len = V_strlen(s); + s += len; + while (len--) + if (*--s == c) return (char *)s; + return 0; +} + +int _V_strcmp(const char *s1, const char *s2) { + AssertValidStringPtr(s1); + AssertValidStringPtr(s2); + VPROF_2("V_strcmp", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, + BUDGETFLAG_ALL); + + return strcmp(s1, s2); +} + +int _V_wcscmp(const wchar_t *s1, const wchar_t *s2) { + while (1) { + if (*s1 != *s2) return -1; // strings not equal + if (!*s1) return 0; // strings are equal + s1++; + s2++; + } + + return -1; +} + +#define TOLOWERC(x) ((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 32) : (x)) + +int _V_stricmp(const char *s1, const char *s2) { + VPROF_2("V_stricmp", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, + BUDGETFLAG_ALL); +#ifdef POSIX + if (s1 == NULL && s2 == NULL) return 0; + if (s1 == NULL) return -1; + if (s2 == NULL) return 1; + + return stricmp(s1, s2); +#else + uint8 const *pS1 = (uint8 const *)s1; + uint8 const *pS2 = (uint8 const *)s2; + for (;;) { + int c1 = *(pS1++); + int c2 = *(pS2++); + if (c1 == c2) { + if (!c1) return 0; + } else { + if (!c2) { + return c1 - c2; + } + c1 = TOLOWERC(c1); + c2 = TOLOWERC(c2); + if (c1 != c2) { + return c1 - c2; + } + } + c1 = *(pS1++); + c2 = *(pS2++); + if (c1 == c2) { + if (!c1) return 0; + } else { + if (!c2) { + return c1 - c2; + } + c1 = TOLOWERC(c1); + c2 = TOLOWERC(c2); + if (c1 != c2) { + return c1 - c2; + } + } + } +#endif +} + +// A special high-performance case-insensitive compare function +// returns 0 if strings match exactly +// returns >0 if strings match in a case-insensitive way, but do not match +// exactly returns <0 if strings do not match even in a case-insensitive way +int _V_stricmp_NegativeForUnequal(const char *s1, const char *s2) { + VPROF_2("V_stricmp", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, + BUDGETFLAG_ALL); + uint8 const *pS1 = (uint8 const *)s1; + uint8 const *pS2 = (uint8 const *)s2; + int iExactMatchResult = 1; + for (;;) { + int c1 = *(pS1++); + int c2 = *(pS2++); + if (c1 == c2) { + // strings are case-insensitive equal, coerce accumulated + // case-difference to 0/1 and return it + if (!c1) return !iExactMatchResult; + } else { + if (!c2) { + // c2=0 and != c1 => not equal + return -1; + } + iExactMatchResult = 0; + c1 = TOLOWERC(c1); + c2 = TOLOWERC(c2); + if (c1 != c2) { + // strings are not equal + return -1; + } + } + c1 = *(pS1++); + c2 = *(pS2++); + if (c1 == c2) { + // strings are case-insensitive equal, coerce accumulated + // case-difference to 0/1 and return it + if (!c1) return !iExactMatchResult; + } else { + if (!c2) { + // c2=0 and != c1 => not equal + return -1; + } + iExactMatchResult = 0; + c1 = TOLOWERC(c1); + c2 = TOLOWERC(c2); + if (c1 != c2) { + // strings are not equal + return -1; + } + } + } +} + +char *_V_strstr(const char *s1, const char *search) { + AssertValidStringPtr(s1); + AssertValidStringPtr(search); + +#if defined(_X360) + return (char *)strstr((char *)s1, search); +#else + return (char *)strstr(s1, search); +#endif +} + +char *_V_strupr(char *start) { + AssertValidStringPtr(start); + return _strupr(start); +} + +char *_V_strlower(char *start) { + AssertValidStringPtr(start); + return _strlwr(start); +} + +wchar_t *_V_wcsupr(const char *file, int line, wchar_t *start) { + return _wcsupr(start); +} + +wchar_t *_V_wcslower(const char *file, int line, wchar_t *start) { + return _wcslwr(start); +} + +int V_strncmp(const char *s1, const char *s2, intp count) { + Assert(count >= 0); + VPROF_2("V_strcmp", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, + BUDGETFLAG_ALL); + + while (count-- > 0) { + if (*s1 != *s2) return *s1 < *s2 ? -1 : 1; // string different + if (*s1 == '\0') return 0; // null terminator hit - strings the same + s1++; + s2++; + } + + return 0; // count characters compared the same +} + +char *V_strnlwr(char *s, size_t count) { + char *pRet = s; + if (!s || !count) return s; + + while (--count > 0) { + if (!*s) return pRet; // reached end of string + + *s = static_cast(tolower(*s)); + ++s; + } + + *s = 0; // null-terminate original string at "count-1" + return pRet; +} + +int V_strncasecmp(const char *s1, const char *s2, intp n) { + Assert(n >= 0); + AssertValidStringPtr(s1); + AssertValidStringPtr(s2); + VPROF_2("V_strcmp", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, + BUDGETFLAG_ALL); + + while (n-- > 0) { + int c1 = *s1++; + int c2 = *s2++; + + if (c1 != c2) { + if (c1 >= 'a' && c1 <= 'z') c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') c2 -= ('a' - 'A'); + if (c1 != c2) return c1 < c2 ? -1 : 1; + } + if (c1 == '\0') return 0; // null terminator hit - strings the same + } + + return 0; // n characters compared the same +} + +int V_strcasecmp(const char *s1, const char *s2) { + AssertValidStringPtr(s1); + AssertValidStringPtr(s2); + VPROF_2("V_strcmp", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, + BUDGETFLAG_ALL); + + return V_stricmp(s1, s2); +} + +int V_strnicmp(const char *s1, const char *s2, intp n) { + DEBUG_LINK_CHECK; + Assert(n >= 0); + AssertValidStringPtr(s1); + AssertValidStringPtr(s2); + + return V_strncasecmp(s1, s2, n); +} + +const char *StringAfterPrefix(const char *str, const char *prefix) { + AssertValidStringPtr(str); + AssertValidStringPtr(prefix); + do { + if (!*prefix) return str; + } while (tolower(*str++) == tolower(*prefix++)); + return NULL; +} + +const char *StringAfterPrefixCaseSensitive(const char *str, + const char *prefix) { + AssertValidStringPtr(str); + AssertValidStringPtr(prefix); + do { + if (!*prefix) return str; + } while (*str++ == *prefix++); + return NULL; +} + +uint64 V_atoui64(const char *str) { + AssertValidStringPtr(str); + + uint64 val; + uint64 c; + + Assert(str); + + val = 0; + + // + // check for hex + // + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + str += 2; + while (1) { + c = *str++; + if (c >= '0' && c <= '9') + val = (val << 4) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val << 4) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val << 4) + c - 'A' + 10; + else + return val; + } + } + + // + // check for character + // + if (str[0] == '\'') { + return str[1]; + } + + // + // assume decimal + // + while (1) { + c = *str++; + if (c < '0' || c > '9') return val; + val = val * 10 + c - '0'; + } + + return 0; +} + +int64 V_atoi64(const char *str) { + AssertValidStringPtr(str); + + int64 val; + int64 sign; + int64 c; + + Assert(str); + if (*str == '-') { + sign = -1; + str++; + } else + sign = 1; + + val = 0; + + // + // check for hex + // + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + str += 2; + while (1) { + c = *str++; + if (c >= '0' && c <= '9') + val = (val << 4) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val << 4) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val << 4) + c - 'A' + 10; + else + return val * sign; + } + } + + // + // check for character + // + if (str[0] == '\'') { + return sign * str[1]; + } + + // + // assume decimal + // + while (1) { + c = *str++; + if (c < '0' || c > '9') return val * sign; + val = val * 10 + c - '0'; + } + + return 0; +} + +int V_atoi(const char *str) { return (int)V_atoi64(str); } + +float V_atof(const char *str) { + DEBUG_LINK_CHECK; + AssertValidStringPtr(str); + float val; + int sign; + int c; + int decimal, total; + + if (*str == '-') { + sign = -1; + str++; + } else + sign = 1; + + val = 0; + + // + // check for hex + // + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + str += 2; + while (1) { + c = *str++; + if (c >= '0' && c <= '9') + val = (val * 16) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val * 16) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val * 16) + c - 'A' + 10; + else + return val * sign; + } + } + + // + // check for character + // + if (str[0] == '\'') { + return static_cast(sign * str[1]); + } + + // + // assume decimal + // + decimal = -1; + total = 0; + int exponent = 0; + while (1) { + c = *str++; + if (c == '.') { + decimal = total; + continue; + } + if (c < '0' || c > '9') { + if (c == 'e') { + exponent = V_atoi(str); + } + break; + } + val = val * 10 + c - '0'; + total++; + } + + if (exponent != 0) { + val *= powf(10.0F, static_cast(exponent)); + } + if (decimal == -1) return val * sign; + while (total > decimal) { + val /= 10; + total--; + } + + return val * sign; +} + +//----------------------------------------------------------------------------- +// Normalizes a float string in place. +// +// (removes leading zeros, trailing zeros after the decimal point, and the +// decimal point itself where possible) +//----------------------------------------------------------------------------- +void V_normalizeFloatString(char *pFloat) { + // If we have a decimal point, remove trailing zeroes: + if (strchr(pFloat, '.')) { + intp len = V_strlen(pFloat); + + while (len > 1 && pFloat[len - 1] == '0') { + pFloat[len - 1] = '\0'; + len--; + } + + if (len > 1 && pFloat[len - 1] == '.') { + pFloat[len - 1] = '\0'; + len--; + } + } + + // TODO: Strip leading zeros +} + +FORCEINLINE unsigned char tolower_fast(unsigned char c) { + if ((c >= 'A') && (c <= 'Z')) return c + ('a' - 'A'); + return c; +} + +//----------------------------------------------------------------------------- +// Finds a string in another string with a case insensitive test +//----------------------------------------------------------------------------- +char const *V_stristr(char const *pStr, char const *pSearch) { + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) return 0; + + char const *pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) { + // Skip over non-matches + if (tolower_fast((unsigned char)*pLetter) == + tolower_fast((unsigned char)*pSearch)) { + // Check for match + char const *pMatch = pLetter + 1; + char const *pTest = pSearch + 1; + while (*pTest != 0) { + // We've run off the end; don't bother. + if (*pMatch == 0) return 0; + + if (tolower_fast((unsigned char)*pMatch) != + tolower_fast((unsigned char)*pTest)) + break; + + ++pMatch; + ++pTest; + } + + // Found a match! + if (*pTest == 0) return pLetter; + } + + ++pLetter; + } + + return 0; +} + +char *V_stristr(char *pStr, char const *pSearch) { + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + return (char *)V_stristr((char const *)pStr, pSearch); +} + +//----------------------------------------------------------------------------- +// Finds a string in another string with a case insensitive test w/ length +// validation +//----------------------------------------------------------------------------- +char const *V_strnistr(char const *pStr, char const *pSearch, intp n) { + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) return 0; + + char const *pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) { + if (n <= 0) return 0; + + // Skip over non-matches + if (tolower_fast(*pLetter) == tolower_fast(*pSearch)) { + intp n1 = n - 1; + + // Check for match + char const *pMatch = pLetter + 1; + char const *pTest = pSearch + 1; + while (*pTest != 0) { + if (n1 <= 0) return 0; + + // We've run off the end; don't bother. + if (*pMatch == 0) return 0; + + if (tolower_fast(*pMatch) != tolower_fast(*pTest)) break; + + ++pMatch; + ++pTest; + --n1; + } + + // Found a match! + if (*pTest == 0) return pLetter; + } + + ++pLetter; + --n; + } + + return 0; +} + +const char *V_strnchr(const char *pStr, char c, intp n) { + char const *pLetter = pStr; + char const *pLast = pStr + n; + + // Check the entire string + while ((pLetter < pLast) && (*pLetter != 0)) { + if (*pLetter == c) return pLetter; + ++pLetter; + } + return NULL; +} + +void V_strncpy(char *pDest, char const *pSrc, intp maxLen) { + Assert(maxLen >= 0); + AssertValidStringPtr(pSrc); + + DEBUG_LINK_CHECK; + + strncpy(pDest, pSrc, maxLen); //-V781 + if (maxLen > 0) { + pDest[maxLen - 1] = '\0'; + } +} + +void V_wcsncpy(wchar_t *pDest, wchar_t const *pSrc, intp maxLenInBytes) { + Assert(maxLenInBytes >= 0); + AssertValidReadPtr(pSrc); + + intp maxLen = maxLenInBytes / sizeof(wchar_t); + + wcsncpy(pDest, pSrc, maxLen); + if (maxLen) { + pDest[maxLen - 1] = L'\0'; + } +} + +int V_snwprintf(wchar_t *pDest, int maxLenInNumWideCharacters, + PRINTF_FORMAT_STRING const wchar_t *pFormat, ...) { + Assert(maxLenInNumWideCharacters >= 0); + AssertValidReadPtr(pFormat); + + va_list marker; + + va_start(marker, pFormat); +#ifdef _WIN32 + int len = _vsnwprintf(pDest, maxLenInNumWideCharacters, pFormat, marker); +#elif POSIX + int len = vswprintf(pDest, maxLenInNumWideCharacters, pFormat, marker); +#else +#error "define vsnwprintf type." +#endif + va_end(marker); + + // Len < 0 represents an overflow + if ((len < 0) || + (maxLenInNumWideCharacters > 0 && len >= maxLenInNumWideCharacters)) { + len = maxLenInNumWideCharacters; + pDest[maxLenInNumWideCharacters - 1] = 0; + } + + return len; +} + +int V_snprintf(char *pDest, int maxLen, + PRINTF_FORMAT_STRING char const *pFormat, ...) { + Assert(maxLen >= 0); + AssertValidStringPtr(pFormat); + + va_list marker; + + va_start(marker, pFormat); +#ifdef _WIN32 + int len = _vsnprintf(pDest, maxLen, pFormat, marker); +#elif POSIX + int len = vsnprintf(pDest, maxLen, pFormat, marker); +#else +#error "define vsnprintf type." +#endif + va_end(marker); + + // Len < 0 represents an overflow + if (len < 0) { + len = maxLen; + pDest[maxLen - 1] = 0; + } + + return len; +} + +int V_vsnprintf(char *pDest, int maxLen, char const *pFormat, va_list params) { + Assert(maxLen > 0); + AssertValidStringPtr(pFormat); + + int len = _vsnprintf(pDest, maxLen, pFormat, params); + + if (len < 0) { + len = maxLen; + pDest[maxLen - 1] = 0; + } + + return len; +} + +int V_vsnprintfRet(char *pDest, int maxLen, const char *pFormat, va_list params, + bool *pbTruncated) { + Assert(maxLen > 0); + AssertValidStringPtr(pFormat); + + int len = _vsnprintf(pDest, maxLen, pFormat, params); + + if (pbTruncated) { + *pbTruncated = len < 0; + } + + if (len < 0) { + len = maxLen; + pDest[maxLen - 1] = 0; + } + + return len; +} + +//----------------------------------------------------------------------------- +// Purpose: If COPY_ALL_CHARACTERS == max_chars_to_copy then we try to add the +// whole pSrc to the end of pDest, otherwise +// we copy only as many characters as are specified in max_chars_to_copy (or +// the # of characters in pSrc if thats's less). +// Input : *pDest - destination buffer +// *pSrc - string to append +// destBufferSize - sizeof the buffer pointed to by pDest +// max_chars_to_copy - COPY_ALL_CHARACTERS in pSrc or max # to copy +// Output : char * the copied buffer +//----------------------------------------------------------------------------- +char *V_strncat(char *pDest, const char *pSrc, size_t maxLenInBytes, + intp max_chars_to_copy) { + DEBUG_LINK_CHECK; + size_t charstocopy = (size_t)0; + + AssertValidStringPtr(pDest); + AssertValidStringPtr(pSrc); + + size_t len = strlen(pDest); + size_t srclen = strlen(pSrc); + if (max_chars_to_copy <= COPY_ALL_CHARACTERS) { + charstocopy = srclen; + } else { + charstocopy = (size_t)MIN(max_chars_to_copy, (intp)srclen); + } + + if (len + charstocopy >= maxLenInBytes) { + charstocopy = maxLenInBytes - len - 1; + } + + if (!charstocopy) { + return pDest; + } + + char *pOut = strncat(pDest, pSrc, charstocopy); + pOut[maxLenInBytes - 1] = '\0'; + return pOut; +} + +//----------------------------------------------------------------------------- +// Purpose: If COPY_ALL_CHARACTERS == max_chars_to_copy then we try to add the +// whole pSrc to the end of pDest, otherwise +// we copy only as many characters as are specified in max_chars_to_copy (or +// the # of characters in pSrc if thats's less). +// Input : *pDest - destination buffer +// *pSrc - string to append +// maxLenInCharacters - sizeof the buffer in characters +// pointed to by pDest +// max_chars_to_copy - COPY_ALL_CHARACTERS in pSrc or max # to copy +// Output : char * the copied buffer +//----------------------------------------------------------------------------- +wchar_t *V_wcsncat(wchar_t *pDest, const wchar_t *pSrc, size_t maxLenInBytes, + intp max_chars_to_copy) { + DEBUG_LINK_CHECK; + size_t charstocopy = (size_t)0; + + intp maxLenInCharacters = maxLenInBytes / sizeof(wchar_t); + + size_t len = wcslen(pDest); + size_t srclen = wcslen(pSrc); + if (max_chars_to_copy <= COPY_ALL_CHARACTERS) { + charstocopy = srclen; + } else { + charstocopy = (size_t)MIN(max_chars_to_copy, (intp)srclen); + } + + if (len + charstocopy >= (size_t)maxLenInCharacters) { + charstocopy = maxLenInCharacters - len - 1; + } + + if (!charstocopy) { + return pDest; + } + + wchar_t *pOut = wcsncat(pDest, pSrc, charstocopy); + pOut[maxLenInCharacters - 1] = L'\0'; + return pOut; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts value into x.xx MB/ x.xx KB, x.xx bytes format, including +// commas Input : value - +// 2 - +// false - +// Output : char +//----------------------------------------------------------------------------- +#define NUM_PRETIFYMEM_BUFFERS 8 +char *V_pretifymem(float value, int digitsafterdecimal /*= 2*/, + bool usebinaryonek /*= false*/) { + static char output[NUM_PRETIFYMEM_BUFFERS][32]; + static int current; + + float onekb = usebinaryonek ? 1024.0f : 1000.0f; + float onemb = onekb * onekb; + + char *out = output[current]; + current = (current + 1) & (NUM_PRETIFYMEM_BUFFERS - 1); + + char suffix[8]; + + // First figure out which bin to use + if (value > onemb) { + value /= onemb; + V_snprintf(suffix, sizeof(suffix), " MB"); + } else if (value > onekb) { + value /= onekb; + V_snprintf(suffix, sizeof(suffix), " KB"); + } else { + V_snprintf(suffix, sizeof(suffix), " bytes"); + } + + char val[32]; + + // Clamp to >= 0 + digitsafterdecimal = MAX(digitsafterdecimal, 0); + + // If it's basically integral, don't do any decimals + if (FloatMakePositive(value - (int)value) < 0.00001f) { + V_snprintf(val, sizeof(val), "%i%s", (int)value, suffix); + } else { + char fmt[32]; + + // Otherwise, create a format string for the decimals + V_snprintf(fmt, sizeof(fmt), "%%.%if%s", digitsafterdecimal, suffix); + V_snprintf(val, sizeof(val), fmt, value); + } + + // Copy from in to out + char *i = val; + char *o = out; + + // Search for decimal or if it was integral, find the space after the raw + // number + char *dot = strchr(i, '.'); + if (!dot) { + dot = strchr(i, ' '); + } + + // Compute position of dot + intp pos = dot - i; + // Don't put a comma if it's <= 3 long + pos -= 3; + + while (*i) { + // If pos is still valid then insert a comma every third digit, except if we + // would be + // putting one in the first spot + if (pos >= 0 && !(pos % 3)) { + // Never in first spot + if (o != out) { + *o++ = ','; + } + } + + // Count down comma position + pos--; + + // Copy rest of data as normal + *o++ = *i++; + } + + // Terminate + *o = 0; + + return out; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a string representation of an integer with commas +// separating the 1000s (ie, 37,426,421) +// Input : value - Value to convert +// Output : Pointer to a static buffer containing the output +//----------------------------------------------------------------------------- +#define NUM_PRETIFYNUM_BUFFERS 8 +char *V_pretifynum(int64 value) { + static char output[NUM_PRETIFYMEM_BUFFERS][32]; + static int current; + + char *out = output[current]; + current = (current + 1) & (NUM_PRETIFYMEM_BUFFERS - 1); + + *out = 0; + + // Render the leading -, if necessary + if (value < 0) { + char *pchRender = out + V_strlen(out); + V_snprintf(pchRender, 32, "-"); + value = -value; + } + + // Render quadrillions + if (value >= 1000000000000000ll) { + char *pchRender = out + V_strlen(out); + V_snprintf(pchRender, 32, "%d,", (int)(value / 1000000000000000ll)); + } + + // Render trillions + if (value >= 1000000000000ll) { + char *pchRender = out + V_strlen(out); + V_snprintf(pchRender, 32, "%d,", (int)(value / 1000000000000ll)); + } + + // Render billions + if (value >= 1000000000) { + char *pchRender = out + V_strlen(out); + V_snprintf(pchRender, 32, "%d,", (int)(value / 1000000000)); + } + + // Render millions + if (value >= 1000000) { + char *pchRender = out + V_strlen(out); + if (value >= 1000000000) + V_snprintf(pchRender, 32, "%03d,", (int)(value / 1000000) % 1000); + else + V_snprintf(pchRender, 32, "%d,", (int)(value / 1000000) % 1000); + } + + // Render thousands + if (value >= 1000) { + char *pchRender = out + V_strlen(out); + if (value >= 1000000) + V_snprintf(pchRender, 32, "%03d,", (int)(value / 1000) % 1000); + else + V_snprintf(pchRender, 32, "%d,", (int)(value / 1000) % 1000); + } + + // Render units + char *pchRender = out + V_strlen(out); + if (value > 1000) + V_snprintf(pchRender, 32, "%03d", (int)(value % 1000)); + else + V_snprintf(pchRender, 32, "%d", (int)(value % 1000)); + + return out; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the 4 bit nibble for a hex character +// Input : c - +// Output : unsigned char +//----------------------------------------------------------------------------- +static unsigned char V_nibble(char c) { + if ((c >= '0') && (c <= '9')) { + return (unsigned char)(c - '0'); + } + + if ((c >= 'A') && (c <= 'F')) { + return (unsigned char)(c - 'A' + 0x0a); + } + + if ((c >= 'a') && (c <= 'f')) { + return (unsigned char)(c - 'a' + 0x0a); + } + + return '0'; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *in - +// numchars - +// *out - +// maxoutputbytes - +//----------------------------------------------------------------------------- +void V_hextobinary(char const *in, intp numchars, byte *out, + intp maxoutputbytes) { + intp len = V_strlen(in); + numchars = MIN(len, numchars); + // Make sure it's even + numchars = (numchars) & ~0x1; + + // Must be an even # of input characters (two chars per output byte) + Assert(numchars >= 2); + + memset(out, 0x00, maxoutputbytes); + + byte *p; + intp i; + + p = out; + for (i = 0; (i < numchars) && ((p - out) < maxoutputbytes); i += 2, p++) { + *p = (V_nibble(in[i]) << 4) | V_nibble(in[i + 1]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *in - +// inputbytes - +// *out - +// outsize - +//----------------------------------------------------------------------------- +void V_binarytohex(const byte *in, intp inputbytes, char *out, intp outsize) { + Assert(outsize >= 1); + char doublet[10]; + intp i; + + out[0] = 0; + + for (i = 0; i < inputbytes; i++) { + unsigned char c = in[i]; + V_snprintf(doublet, sizeof(doublet), "%02x", c); + V_strncat(out, doublet, outsize, COPY_ALL_CHARACTERS); + } +} + +#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') + +//----------------------------------------------------------------------------- +// Purpose: Extracts the base name of a file (no path, no extension, assumes '/' +// or '\' as path separator) Input : *in - +// *out - +// maxlen - +//----------------------------------------------------------------------------- +void V_FileBase(const char *in, char *out, intp maxlen) { + Assert(maxlen >= 1); + Assert(in); + Assert(out); + + if (!in || !in[0]) { + *out = 0; + return; + } + + intp len, start, end; + + len = V_strlen(in); + + // scan backward for '.' + end = len - 1; + while (end && in[end] != '.' && !PATHSEPARATOR(in[end])) { + end--; + } + + if (in[end] != '.') // no '.', copy to end + { + end = len - 1; + } else { + end--; // Found ',', copy to left of '.' + } + + // Scan backward for '/' + start = len - 1; + while (start >= 0 && !PATHSEPARATOR(in[start])) { + start--; + } + + if (start < 0 || !PATHSEPARATOR(in[start])) { + start = 0; + } else { + start++; + } + + // Length of new sting + len = end - start + 1; + + intp maxcopy = MIN(len + 1, maxlen); + + // Copy partial string + V_strncpy(out, &in[start], maxcopy); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *ppath - +//----------------------------------------------------------------------------- +void V_StripTrailingSlash(char *ppath) { + Assert(ppath); + + intp len = V_strlen(ppath); + if (len > 0) { + if (PATHSEPARATOR(ppath[len - 1])) { + ppath[len - 1] = 0; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *in - +// *out - +// outSize - +//----------------------------------------------------------------------------- +void V_StripExtension(const char *in, char *out, intp outSize) { + // Find the last dot. If it's followed by a dot or a slash, then it's part of + // a directory specifier like ../../somedir/./blah. + + // scan backward for '.' + intp end = V_strlen(in) - 1; + while (end > 0 && in[end] != '.' && !PATHSEPARATOR(in[end])) { + --end; + } + + if (end > 0 && !PATHSEPARATOR(in[end]) && end < outSize) { + intp nChars = MIN(end, outSize - 1); + if (out != in) { + memcpy(out, in, nChars); + } + out[nChars] = 0; + } else { + // nothing found + if (out != in) { + V_strncpy(out, in, outSize); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *path - +// *extension - +// pathStringLength - +//----------------------------------------------------------------------------- +void V_DefaultExtension(char *path, const char *extension, + intp pathStringLength) { + Assert(path); + Assert(pathStringLength >= 1); + Assert(extension); + + char *src; + + // if path doesn't have a .EXT, append extension + // (extension should include the .) + src = path + V_strlen(path) - 1; + + while (!PATHSEPARATOR(*src) && (src > path)) { + if (*src == '.') { + // it has an extension + return; + } + src--; + } + + // Concatenate the desired extension + char pTemp[MAX_PATH]; + if (extension[0] != '.') { + pTemp[0] = '.'; + V_strncpy(&pTemp[1], extension, sizeof(pTemp) - 1); + extension = pTemp; + } + V_strncat(path, extension, pathStringLength, COPY_ALL_CHARACTERS); +} + +//----------------------------------------------------------------------------- +// Purpose: Force extension... +// Input : *path - +// *extension - +// pathStringLength - +//----------------------------------------------------------------------------- +void V_SetExtension(char *path, const char *extension, intp pathStringLength) { + V_StripExtension(path, path, pathStringLength); + V_DefaultExtension(path, extension, pathStringLength); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove final filename from string +// Input : *path - +// Output : void V_StripFilename +//----------------------------------------------------------------------------- +void V_StripFilename(char *path) { + intp length; + + length = V_strlen(path) - 1; + if (length <= 0) return; + + while (length > 0 && !PATHSEPARATOR(path[length])) { + length--; + } + + path[length] = 0; +} + +#ifdef _WIN32 +#define CORRECT_PATH_SEPARATOR '\\' +#define INCORRECT_PATH_SEPARATOR '/' +#elif POSIX +#define CORRECT_PATH_SEPARATOR '/' +#define INCORRECT_PATH_SEPARATOR '\\' +#endif + +//----------------------------------------------------------------------------- +// Purpose: Changes all '/' or '\' characters into separator +// Input : *pname - +// separator - +//----------------------------------------------------------------------------- +void V_FixSlashes(char *pname, char separator /* = CORRECT_PATH_SEPARATOR */) { + while (*pname) { + if (*pname == INCORRECT_PATH_SEPARATOR || + *pname == CORRECT_PATH_SEPARATOR) { + *pname = separator; + } + pname++; + } +} + +//----------------------------------------------------------------------------- +// Purpose: This function fixes cases of filenames like materials\\blah.vmt or +// somepath\otherpath\\ and removes the extra double slash. +//----------------------------------------------------------------------------- +void V_FixDoubleSlashes(char *pStr) { + intp len = V_strlen(pStr); + + for (intp i = 1; i < len - 1; i++) { + if ((pStr[i] == '/' || pStr[i] == '\\') && + (pStr[i + 1] == '/' || pStr[i + 1] == '\\')) { + // This means there's a double slash somewhere past the start of the + // filename. That can happen in Hammer if they use a material in the root + // directory. You'll get a filename that looks like 'materials\\blah.vmt' + V_memmove(&pStr[i], &pStr[i + 1], len - i); + --len; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Strip off the last directory from dirName +// Input : *dirName - +// maxlen - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool V_StripLastDir(char *dirName, intp maxlen) { + if (dirName[0] == 0 || !V_stricmp(dirName, "./") || + !V_stricmp(dirName, ".\\")) + return false; + + intp len = V_strlen(dirName); + + Assert(len < maxlen); + + // skip trailing slash + if (PATHSEPARATOR(dirName[len - 1])) { + len--; + } + + bool bHitColon = false; + while (len > 0) { + if (PATHSEPARATOR(dirName[len - 1])) { + dirName[len] = 0; + V_FixSlashes(dirName, CORRECT_PATH_SEPARATOR); + return true; + } else if (dirName[len - 1] == ':') { + bHitColon = true; + } + + len--; + } + + // If we hit a drive letter, then we're done. + // Ex: If they passed in c:\, then V_StripLastDir should return "" and false. + if (bHitColon) { + dirName[0] = 0; + return false; + } + + // Allow it to return an empty string and true. This can happen if something + // like "tf2/" is passed in. The correct behavior is to strip off the last + // directory ("tf2") and return true. + if (len == 0) { + V_snprintf(dirName, maxlen, ".%c", CORRECT_PATH_SEPARATOR); + return true; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a pointer to the beginning of the unqualified file name +// (no path information) +// Input: in - file name (may be unqualified, relative or absolute path) +// Output: pointer to unqualified file name +//----------------------------------------------------------------------------- +const char *V_UnqualifiedFileName(const char *in) { + if (!in || !in[0]) return in; + + // back up until the character after the first path separator we find, + // or the beginning of the string + const char *out = in + strlen(in) - 1; + while ((out > in) && (!PATHSEPARATOR(*(out - 1)))) out--; + return out; +} + +char *V_UnqualifiedFileName(char *in) { + return const_cast( + V_UnqualifiedFileName(const_cast(in))); +} + +//----------------------------------------------------------------------------- +// Purpose: Composes a path and filename together, inserting a path separator +// if need be +// Input: path - path to use +// filename - filename to use +// dest - buffer to compose result in +// destSize - size of destination buffer +//----------------------------------------------------------------------------- +void V_ComposeFileName(const char *path, const char *filename, char *dest, + intp destSize) { + V_strncpy(dest, path, destSize); + V_FixSlashes(dest); + V_AppendSlash(dest, destSize); + V_strncat(dest, filename, destSize, COPY_ALL_CHARACTERS); + V_FixSlashes(dest); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *path - +// *dest - +// destSize - +// Output : void V_ExtractFilePath +//----------------------------------------------------------------------------- +bool V_ExtractFilePath(const char *path, char *dest, intp destSize) { + Assert(destSize >= 1); + if (destSize < 1) { + return false; + } + + // Last char + intp len = V_strlen(path); + const char *src = path + (len ? len - 1 : 0); + + // back up until a \ or the start + while (src != path && !PATHSEPARATOR(*(src - 1))) { + src--; + } + + intp copysize = MIN(src - path, destSize - 1); + memcpy(dest, path, copysize); + dest[copysize] = 0; + + return copysize != 0 ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *path - +// *dest - +// destSize - +// Output : void V_ExtractFileExtension +//----------------------------------------------------------------------------- +void V_ExtractFileExtension(const char *path, char *dest, intp destSize) { + *dest = 0; + const char *extension = V_GetFileExtension(path); + if (NULL != extension) V_strncpy(dest, extension, destSize); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a pointer to the file extension within a file name string +// Input: in - file name +// Output: pointer to beginning of extension (after the "."), or NULL +// if there is no extension +//----------------------------------------------------------------------------- +const char *V_GetFileExtension(const char *path) { + const char *src; + + src = path + strlen(path) - 1; + + // + // back up until a . or the start + // + while (src != path && !PATHSEPARATOR(*src) && *(src - 1) != '.') src--; + + // check to see if the '.' is part of a pathname + if (src == path || PATHSEPARATOR(*src)) { + return NULL; // no extension + } + + return src; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a pointer to the filename part of a path string +// Input: in - file name +// Output: pointer to beginning of filename (after the "/"). If there were +// no /, +// output is identical to input +//----------------------------------------------------------------------------- +const char *V_GetFileName(const char *pPath) { + if (!pPath || !pPath[0]) return pPath; + + const char *pSrc; + pSrc = pPath + strlen(pPath) - 1; + + // back up until a / or the start + while (pSrc > pPath && !PATHSEPARATOR(*(pSrc - 1))) --pSrc; + + return pSrc; +} + +bool V_RemoveDotSlashes(char *pFilename, char separator) { + // Remove '//' or '\\' + char *pIn = pFilename; + char *pOut = pFilename; + bool bPrevPathSep = false; + while (*pIn) { + bool bIsPathSep = PATHSEPARATOR(*pIn); + if (!bIsPathSep || !bPrevPathSep) { + *pOut++ = *pIn; + } + bPrevPathSep = bIsPathSep; + ++pIn; + } + *pOut = 0; + + // Get rid of "./"'s + pIn = pFilename; + pOut = pFilename; + while (*pIn) { + // The logic on the second line is preventing it from screwing up "../" + if (pIn[0] == '.' && PATHSEPARATOR(pIn[1]) && + (pIn == pFilename || pIn[-1] != '.')) { + pIn += 2; + } else { + *pOut = *pIn; + ++pIn; + ++pOut; + } + } + *pOut = 0; + + // Get rid of a trailing "/." (needless). + intp len = V_strlen(pFilename); + if (len > 2 && pFilename[len - 1] == '.' && + PATHSEPARATOR(pFilename[len - 2])) { + pFilename[len - 2] = 0; + } + + // Each time we encounter a "..", back up until we've read the previous + // directory name, then get rid of it. + pIn = pFilename; + while (*pIn) { + if (pIn[0] == '.' && pIn[1] == '.' && + (pIn == pFilename || + PATHSEPARATOR(pIn[-1])) && // Preceding character must be a slash. + (pIn[2] == 0 || + PATHSEPARATOR(pIn[2]))) // Following character must be a slash or the + // end of the string. + { + char *pEndOfDots = pIn + 2; + char *pStart = pIn - 2; + + // Ok, now scan back for the path separator that starts the preceding + // directory. + while (1) { + if (pStart < pFilename) return false; + + if (PATHSEPARATOR(*pStart)) break; + + --pStart; + } + + // Now slide the string down to get rid of the previous directory and the + // ".." + memmove(pStart, pEndOfDots, strlen(pEndOfDots) + 1); + + // Start over. + pIn = pFilename; + } else { + ++pIn; + } + } + + V_FixSlashes(pFilename, separator); + return true; +} + +void V_AppendSlash(char *pStr, intp strSize) { + intp len = V_strlen(pStr); + if (len > 0 && !PATHSEPARATOR(pStr[len - 1])) { + if (len + 1 >= strSize) + Error("V_AppendSlash: ran out of space on %s.", pStr); + + pStr[len] = CORRECT_PATH_SEPARATOR; + pStr[len + 1] = 0; + } +} + +void V_MakeAbsolutePath(char *pOut, int outLen, const char *pPath, + const char *pStartingDir) { + if (V_IsAbsolutePath(pPath)) { + // pPath is not relative.. just copy it. + V_strncpy(pOut, pPath, outLen); + } else { + // Make sure the starting directory is absolute.. + if (pStartingDir && V_IsAbsolutePath(pStartingDir)) { + V_strncpy(pOut, pStartingDir, outLen); + } else { +#ifdef _PS3 + { V_strncpy(pOut, g_pPS3PathInfo->GameImagePath(), outLen); } +#else + { + if (!_getcwd(pOut, outLen)) + Error("V_MakeAbsolutePath: _getcwd failed."); + } +#endif + + if (pStartingDir) { + V_AppendSlash(pOut, outLen); + V_strncat(pOut, pStartingDir, outLen, COPY_ALL_CHARACTERS); + } + } + + // Concatenate the paths. + V_AppendSlash(pOut, outLen); + V_strncat(pOut, pPath, outLen, COPY_ALL_CHARACTERS); + } + + if (!V_RemoveDotSlashes(pOut)) + Error("V_MakeAbsolutePath: tried to \"..\" past the root."); + + V_FixSlashes(pOut); +} + +//----------------------------------------------------------------------------- +// Makes a relative path +//----------------------------------------------------------------------------- +bool V_MakeRelativePath(const char *pFullPath, const char *pDirectory, + char *pRelativePath, intp nBufLen) { + pRelativePath[0] = 0; + + const char *pPath = pFullPath; + const char *pDir = pDirectory; + + // Strip out common parts of the path + const char *pLastCommonPath = NULL; + const char *pLastCommonDir = NULL; + while (*pPath && + (tolower(*pPath) == tolower(*pDir) || + (PATHSEPARATOR(*pPath) && (PATHSEPARATOR(*pDir) || (*pDir == 0))))) { + if (PATHSEPARATOR(*pPath)) { + pLastCommonPath = pPath + 1; + pLastCommonDir = pDir + 1; + } + if (*pDir == 0) { + --pLastCommonDir; + break; + } + ++pDir; + ++pPath; + } + + // Nothing in common + if (!pLastCommonPath) return false; + + // For each path separator remaining in the dir, need a ../ + intp nOutLen = 0; + bool bLastCharWasSeparator = true; + for (; *pLastCommonDir; ++pLastCommonDir) { + if (PATHSEPARATOR(*pLastCommonDir)) { + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; + bLastCharWasSeparator = true; + } else { + bLastCharWasSeparator = false; + } + } + + // Deal with relative paths not specified with a trailing slash + if (!bLastCharWasSeparator) { + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; + } + + // Copy the remaining part of the relative path over, fixing the path + // separators + for (; *pLastCommonPath; ++pLastCommonPath) { + if (PATHSEPARATOR(*pLastCommonPath)) { + pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; + } else { + pRelativePath[nOutLen++] = *pLastCommonPath; + } + + // Check for overflow + if (nOutLen == nBufLen - 1) break; + } + + pRelativePath[nOutLen] = 0; + return true; +} + +//----------------------------------------------------------------------------- +// small helper function shared by lots of modules +//----------------------------------------------------------------------------- +bool V_IsAbsolutePath(const char *pStr) { + bool bIsAbsolute = + (pStr[0] && pStr[1] == ':') || pStr[0] == '/' || pStr[0] == '\\'; + if (IsX360() && !bIsAbsolute) { + bIsAbsolute = (V_stristr(pStr, ":") != NULL); + } + return bIsAbsolute; +} + +//----------------------------------------------------------------------------- +// Fixes up a file name, removing dot slashes, fixing slashes, converting to +// lowercase, etc. +//----------------------------------------------------------------------------- +void V_FixupPathName(char *pOut, size_t nOutLen, const char *pPath) { + V_strncpy(pOut, pPath, nOutLen); + V_FixSlashes(pOut); + V_RemoveDotSlashes(pOut); + V_FixDoubleSlashes(pOut); + V_strlower(pOut); +} + +// Copies at most nCharsToCopy bytes from pIn into pOut. +// Returns false if it would have overflowed pOut's buffer. +static bool CopyToMaxChars(char *pOut, intp outSize, const char *pIn, + intp nCharsToCopy) { + if (outSize == 0) return false; + + intp iOut = 0; + while (*pIn && nCharsToCopy > 0) { + if (iOut == (outSize - 1)) { + pOut[iOut] = 0; + return false; + } + pOut[iOut] = *pIn; + ++iOut; + ++pIn; + --nCharsToCopy; + } + + pOut[iOut] = 0; + return true; +} + +// Returns true if it completed successfully. +// If it would overflow pOut, it fills as much as it can and returns false. +bool V_StrSubst(const char *pIn, const char *pMatch, const char *pReplaceWith, + char *pOut, intp outLen, bool bCaseSensitive) { + intp replaceFromLen = V_strlen(pMatch); + intp replaceToLen = V_strlen(pReplaceWith); + + const char *pInStart = pIn; + char *pOutPos = pOut; + pOutPos[0] = 0; + + while (1) { + intp nRemainingOut = outLen - (pOutPos - pOut); + + const char *pTestPos = (bCaseSensitive ? strstr(pInStart, pMatch) + : V_stristr(pInStart, pMatch)); + if (pTestPos) { + // Found an occurence of pMatch. First, copy whatever leads up to the + // string. + intp copyLen = pTestPos - pInStart; + if (!CopyToMaxChars(pOutPos, nRemainingOut, pInStart, copyLen)) + return false; + + // Did we hit the end of the output string? + if (copyLen > nRemainingOut - 1) return false; + + pOutPos += strlen(pOutPos); + nRemainingOut = outLen - (pOutPos - pOut); + + // Now add the replacement string. + if (!CopyToMaxChars(pOutPos, nRemainingOut, pReplaceWith, replaceToLen)) + return false; + + pInStart += copyLen + replaceFromLen; + pOutPos += replaceToLen; + } else { + // We're at the end of pIn. Copy whatever remains and get out. + intp copyLen = V_strlen(pInStart); + V_strncpy(pOutPos, pInStart, nRemainingOut); + return (copyLen <= nRemainingOut - 1); + } + } +} + +char *AllocString(const char *pStr, intp nMaxChars) { + intp allocLen = V_strlen(pStr); + if (nMaxChars == -1) + allocLen += 1; + else + allocLen = min(allocLen, nMaxChars) + 1; + + char *pOut = new char[allocLen]; + V_strncpy(pOut, pStr, allocLen); + return pOut; +} + +void V_SplitString2(const char *pString, const char **pSeparators, + intp nSeparators, CUtlVector &outStrings) { + outStrings.Purge(); + const char *pCurPos = pString; + while (1) { + intp iFirstSeparator = -1; + const char *pFirstSeparator = 0; + for (intp i = 0; i < nSeparators; i++) { + const char *pTest = V_stristr(pCurPos, pSeparators[i]); + if (pTest && (!pFirstSeparator || pTest < pFirstSeparator)) { + iFirstSeparator = i; + pFirstSeparator = pTest; + } + } + + if (pFirstSeparator) { + // Split on this separator and continue on. + intp separatorLen = V_strlen(pSeparators[iFirstSeparator]); + if (pFirstSeparator > pCurPos) { + outStrings.AddToTail(AllocString(pCurPos, pFirstSeparator - pCurPos)); + } + + pCurPos = pFirstSeparator + separatorLen; + } else { + // Copy the rest of the string + if (strlen(pCurPos)) { + outStrings.AddToTail(AllocString(pCurPos, -1)); + } + return; + } + } +} + +void V_SplitString(const char *pString, const char *pSeparator, + CUtlVector &outStrings) { + V_SplitString2(pString, &pSeparator, 1, outStrings); +} + +bool V_GetCurrentDirectory(char *pOut, int maxLen) { +#if defined(_PS3) + Assert(0); + return false; // not supported +#else // !_PS3 + return _getcwd(pOut, maxLen) == pOut; +#endif // _PS3 +} + +bool V_SetCurrentDirectory(const char *pDirName) { +#if defined(_PS3) + Assert(0); + return false; // not supported +#else // !_PS3 + return _chdir(pDirName) == 0; +#endif // _PS3 +} + +// This function takes a slice out of pStr and stores it in pOut. +// It follows the Python slice convention: +// Negative numbers wrap around the string (-1 references the last character). +// Numbers are clamped to the end of the string. +void V_StrSlice(const char *pStr, intp firstChar, intp lastCharNonInclusive, + char *pOut, intp outSize) { + if (outSize == 0) return; + + intp length = V_strlen(pStr); + + // Fixup the string indices. + if (firstChar < 0) { + firstChar = length - (-firstChar % length); + } else if (firstChar >= length) { + pOut[0] = 0; + return; + } + + if (lastCharNonInclusive < 0) { + lastCharNonInclusive = length - (-lastCharNonInclusive % length); + } else if (lastCharNonInclusive > length) { + lastCharNonInclusive %= length; + } + + if (lastCharNonInclusive <= firstChar) { + pOut[0] = 0; + return; + } + + intp copyLen = lastCharNonInclusive - firstChar; + if (copyLen <= (outSize - 1)) { + memcpy(pOut, &pStr[firstChar], copyLen); + pOut[copyLen] = 0; + } else { + memcpy(pOut, &pStr[firstChar], outSize - 1); + pOut[outSize - 1] = 0; + } +} + +void V_StrLeft(const char *pStr, intp nChars, char *pOut, intp outSize) { + if (nChars == 0) { + if (outSize != 0) pOut[0] = 0; + + return; + } + + V_StrSlice(pStr, 0, nChars, pOut, outSize); +} + +void V_StrRight(const char *pStr, intp nChars, char *pOut, intp outSize) { + intp len = V_strlen(pStr); + if (nChars >= len) { + V_strncpy(pOut, pStr, outSize); + } else { + V_StrSlice(pStr, -nChars, V_strlen(pStr), pOut, outSize); + } +} + +//----------------------------------------------------------------------------- +// Convert multibyte to wchar + back +//----------------------------------------------------------------------------- +void V_strtowcs(const char *pString, int nInSize, wchar_t *pWString, + int nOutSize) { +#ifdef _WIN32 + if (!MultiByteToWideChar(CP_UTF8, 0, pString, nInSize, pWString, nOutSize)) { + *pWString = L'\0'; + } +#elif POSIX + if (mbstowcs(pWString, pString, nOutSize / sizeof(wchar_t)) <= 0) { + *pWString = 0; + } +#endif +} + +void V_wcstostr(const wchar_t *pWString, int nInSize, char *pString, + int nOutSize) { +#ifdef _WIN32 + if (!WideCharToMultiByte(CP_UTF8, 0, pWString, nInSize, pString, nOutSize, + NULL, NULL)) { + *pString = '\0'; + } +#elif POSIX + if (wcstombs(pString, pWString, nOutSize) <= 0) { + *pString = '\0'; + } +#endif +} + +//-------------------------------------------------------------------------------- +// backslashification +//-------------------------------------------------------------------------------- + +static char s_BackSlashMap[] = "\tt\nn\rr\"\"\\\\"; + +char *V_AddBackSlashesToSpecialChars(char const *pSrc) { + // first, count how much space we are going to need + intp nSpaceNeeded = 0; + for (char const *pScan = pSrc; *pScan; pScan++) { + nSpaceNeeded++; + for (char const *pCharSet = s_BackSlashMap; *pCharSet; pCharSet += 2) { + if (*pCharSet == *pScan) nSpaceNeeded++; // we need to store a bakslash + } + } + char *pRet = new char[nSpaceNeeded + 1]; // +1 for null + char *pOut = pRet; + + for (char const *pScan = pSrc; *pScan; pScan++) { + bool bIsSpecial = false; + for (char const *pCharSet = s_BackSlashMap; *pCharSet; pCharSet += 2) { + if (*pCharSet == *pScan) { + *(pOut++) = '\\'; + *(pOut++) = pCharSet[1]; + bIsSpecial = true; + break; + } + } + if (!bIsSpecial) { + *(pOut++) = *pScan; + } + } + *(pOut++) = 0; + return pRet; +} + +void V_StringToIntArray(int *pVector, intp count, const char *pString) { + char *pstr, *pfront, tempString[128]; + intp j; + + V_strncpy(tempString, pString, sizeof(tempString)); + pstr = pfront = tempString; + + for (j = 0; j < count; j++) // lifted from pr_edict.c + { + pVector[j] = atoi(pfront); + + while (*pstr && *pstr != ' ') pstr++; + if (!*pstr) break; + pstr++; + pfront = pstr; + } + + for (j++; j < count; j++) { + pVector[j] = 0; + } +} + +void V_StringToColor32(color32 *color, const char *pString) { + int tmp[4]; + V_StringToIntArray(tmp, 4, pString); + color->r = static_cast<::byte>(tmp[0]); + color->g = static_cast<::byte>(tmp[1]); + color->b = static_cast<::byte>(tmp[2]); + color->a = static_cast<::byte>(tmp[3]); +} + +// 3d memory copy +void CopyMemory3D(void *pDest, void const *pSrc, intp nNumCols, intp nNumRows, + intp nNumSlices, // dimensions of copy + intp nSrcBytesPerRow, + intp nSrcBytesPerSlice, // strides for source. + intp nDestBytesPerRow, + intp nDestBytesPerSlice // strides for dest +) { + if (nNumSlices && nNumRows && nNumCols) { + uint8 *pDestAdr = reinterpret_cast(pDest); + uint8 const *pSrcAdr = reinterpret_cast(pSrc); + // first check for optimized cases + if ((nNumCols == nSrcBytesPerRow) && + (nNumCols == nDestBytesPerRow)) // no row-to-row stride? + { + intp n2DSize = nNumCols * nNumRows; + if (nSrcBytesPerSlice == nDestBytesPerSlice) // can we do one memcpy? + { + memcpy(pDestAdr, pSrcAdr, n2DSize * nNumSlices); + } else { + // there might be some slice-to-slice stride + do { + memcpy(pDestAdr, pSrcAdr, n2DSize); + pDestAdr += nDestBytesPerSlice; + pSrcAdr += nSrcBytesPerSlice; + } while (nNumSlices--); + } + } else { + // there is row-by-row stride - we have to do the full nested loop + do { + intp nRowCtr = nNumRows; + uint8 const *pSrcRow = pSrcAdr; + uint8 *pDestRow = pDestAdr; + do { + memcpy(pDestRow, pSrcRow, nNumCols); + pDestRow += nDestBytesPerRow; + pSrcRow += nSrcBytesPerRow; + } while (--nRowCtr); + pSrcAdr += nSrcBytesPerSlice; + pDestAdr += nDestBytesPerSlice; + } while (nNumSlices--); + } + } +} + +void V_TranslateLineFeedsToUnix(char *pStr) { + char *pIn = pStr; + char *pOut = pStr; + while (*pIn) { + if (pIn[0] == '\r' && pIn[1] == '\n') { + ++pIn; + } + *pOut++ = *pIn++; + } + *pOut = 0; +} + +static char s_hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + +int HexToValue(char hex) { + if (hex >= '0' && hex <= '9') { + return hex - '0'; + } + if (hex >= 'A' && hex <= 'F') { + return hex - 'A' + 10; + } + if (hex >= 'a' && hex <= 'f') { + return hex - 'a' + 10; + } + // report error here + return -1; +} + +bool V_StringToBin(const char *pString, void *pBin, size_t nBinSize) { + if ((size_t)V_strlen(pString) != nBinSize * 2) { + return false; + } + + for (size_t i = 0; i < nBinSize; ++i) { + int high = HexToValue(pString[i * 2 + 0]); + int low = HexToValue(pString[i * 2 + 1]); + if (high < 0 || low < 0) { + return false; + } + + ((uint8 *)pBin)[i] = uint8((high << 4) | low); + } + return true; +} + +bool V_BinToString(char *pString, void *pBin, size_t nBinSize) { + for (size_t i = 0; i < nBinSize; ++i) { + pString[i * 2 + 0] = s_hex[((uint8 *)pBin)[i] >> 4]; + pString[i * 2 + 1] = s_hex[((uint8 *)pBin)[i] & 0xF]; + } + pString[nBinSize * 2] = '\0'; + return true; +} + +// The following characters are not allowed to begin a line for Asian language +// line-breaking purposes. They include the right parenthesis/bracket, space +// character, period, exclamation, question mark, and a number of +// language-specific characters for Chinese, Japanese, and Korean +static const wchar_t wszCantBeginLine[] = { + 0x0020, 0x0021, 0x0025, 0x0029, 0x002c, 0x002e, 0x003a, 0x003b, 0x003e, + 0x003f, 0x005d, 0x007d, 0x00a2, 0x00a8, 0x00b0, 0x00b7, 0x00bb, 0x02c7, + 0x02c9, 0x2010, 0x2013, 0x2014, 0x2015, 0x2016, 0x2019, 0x201d, 0x201e, + 0x201f, 0x2020, 0x2021, 0x2022, 0x2025, 0x2026, 0x2027, 0x203a, 0x203c, + 0x2047, 0x2048, 0x2049, 0x2103, 0x2236, 0x2574, 0x3001, 0x3002, 0x3003, + 0x3005, 0x3006, 0x3009, 0x300b, 0x300d, 0x300f, 0x3011, 0x3015, 0x3017, + 0x3019, 0x301b, 0x301c, 0x301e, 0x301f, 0x303b, 0x3041, 0x3043, 0x3045, + 0x3047, 0x3049, 0x3063, 0x3083, 0x3085, 0x3087, 0x308e, 0x3095, 0x3096, + 0x30a0, 0x30a1, 0x30a3, 0x30a5, 0x30a7, 0x30a9, 0x30c3, 0x30e3, 0x30e5, + 0x30e7, 0x30ee, 0x30f5, 0x30f6, 0x30fb, 0x30fd, 0x30fe, 0x30fc, 0x31f0, + 0x31f1, 0x31f2, 0x31f3, 0x31f4, 0x31f5, 0x31f6, 0x31f7, 0x31f8, 0x31f9, + 0x31fa, 0x31fb, 0x31fc, 0x31fd, 0x31fe, 0x31ff, 0xfe30, 0xfe31, 0xfe32, + 0xfe33, 0xfe36, 0xfe38, 0xfe3a, 0xfe3c, 0xfe3e, 0xfe40, 0xfe42, 0xfe44, + 0xfe4f, 0xfe50, 0xfe51, 0xfe52, 0xfe53, 0xfe54, 0xfe55, 0xfe56, 0xfe57, + 0xfe58, 0xfe5a, 0xfe5c, 0xfe5e, 0xff01, 0xff02, 0xff05, 0xff07, 0xff09, + 0xff0c, 0xff0e, 0xff1a, 0xff1b, 0xff1f, 0xff3d, 0xff40, 0xff5c, 0xff5d, + 0xff5e, 0xff60, 0xff64}; + +// The following characters are not allowed to end a line for Asian Language +// line-breaking purposes. They include left parenthesis/bracket, currency +// symbols, and an number of language-specific characters for Chinese, Japanese, +// and Korean +static const wchar_t wszCantEndLine[] = { + 0x0024, 0x0028, 0x002a, 0x003c, 0x005b, 0x005c, 0x007b, 0x00a3, 0x00a5, + 0x00ab, 0x00ac, 0x00b7, 0x02c6, 0x2018, 0x201c, 0x201f, 0x2035, 0x2039, + 0x3005, 0x3007, 0x3008, 0x300a, 0x300c, 0x300e, 0x3010, 0x3014, 0x3016, + 0x3018, 0x301a, 0x301d, 0xfe34, 0xfe35, 0xfe37, 0xfe39, 0xfe3b, 0xfe3d, + 0xfe3f, 0xfe41, 0xfe43, 0xfe59, 0xfe5b, 0xfe5d, 0xff04, 0xff08, 0xff0e, + 0xff3b, 0xff5b, 0xff5f, 0xffe1, 0xffe5, 0xffe6}; + +// Can't break between some repeated punctuation patterns ("--", "...", "") +static const wchar_t wszCantBreakRepeated[] = {0x002d, 0x002e, 0x3002}; + +bool AsianWordWrap::CanEndLine(wchar_t wcCandidate) { + for (intp i = 0; i < SIZE_OF_ARRAY(wszCantEndLine); ++i) { + if (wcCandidate == wszCantEndLine[i]) return false; + } + + return true; +} + +bool AsianWordWrap::CanBeginLine(wchar_t wcCandidate) { + for (intp i = 0; i < SIZE_OF_ARRAY(wszCantBeginLine); ++i) { + if (wcCandidate == wszCantBeginLine[i]) return false; + } + + return true; +} + +bool AsianWordWrap::CanBreakRepeated(wchar_t wcCandidate) { + for (intp i = 0; i < SIZE_OF_ARRAY(wszCantBreakRepeated); ++i) { + if (wcCandidate == wszCantBreakRepeated[i]) return false; + } + + return true; +} + +#if defined(_PS3) || defined(LINUX) +inline int __cdecl iswascii(wchar_t c) { + return ((unsigned)(c) < 0x80); +} // not defined in wctype.h on the PS3 +#endif + +// Used to determine if we can break a line between the first two characters +// passed +bool AsianWordWrap::CanBreakAfter(const wchar_t *wsz) { + if (wsz == NULL || wsz[0] == '\0' || wsz[1] == '\0') { + return false; + } + + wchar_t first_char = wsz[0]; + wchar_t second_char = wsz[1]; + if ((iswascii(first_char) && + iswascii(second_char)) // If not both CJK, return early + || (iswalnum(first_char) && + iswalnum(second_char))) // both characters are alphanumeric - Don't + // split a number or a word! + { + return false; + } + + if (!CanEndLine(first_char)) { + return false; + } + + if (!CanBeginLine(second_char)) { + return false; + } + + // don't allow line wrapping in the middle of "--" or "..." + if ((first_char == second_char) && (!CanBreakRepeated(first_char))) { + return false; + } + + // If no rules would prevent us from breaking, assume it's safe to break here + return true; +} + +// We use this function to determine where it is permissible to break lines +// of text while wrapping them. On some platforms, the native iswspace() +// function returns FALSE for the "non-breaking space" characters 0x00a0 and +// 0x202f, and so we don't break on them. On others (including the X360 and PC), +// iswspace returns TRUE for them. We get rid of the platform dependency by +// defining this wrapper which returns false for   and calls through to the +// library function for everything else. +int isbreakablewspace(wchar_t ch) { + // 0x00a0 and 0x202f are the wide and narrow non-breaking space UTF-16 values, + // respectively + return ch != 0x00a0 && ch != 0x202f && iswspace(ch); +} diff --git a/tier1/tier1.cpp b/tier1/tier1.cpp new file mode 100644 index 0000000..ff9c2d7 --- /dev/null +++ b/tier1/tier1.cpp @@ -0,0 +1,22 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: A higher level link library for general use in the game and tools. + +#include "tier1/tier1.h" + +#include "tier0/dbg.h" +#include "interfaces/interfaces.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Call this to connect to all tier 1 libraries. +// It's up to the caller to check the globals it cares about to see if ones are +// missing +//----------------------------------------------------------------------------- +void ConnectTier1Libraries(CreateInterfaceFn *pFactoryList, int nFactoryCount) { + ConnectInterfaces(pFactoryList, nFactoryCount); +} + +void DisconnectTier1Libraries() { DisconnectInterfaces(); } diff --git a/tier1/utlbuffer.cpp b/tier1/utlbuffer.cpp new file mode 100644 index 0000000..2bea02f --- /dev/null +++ b/tier1/utlbuffer.cpp @@ -0,0 +1,1445 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Serialization buffer. + +#include "utlbuffer.h" + +#include +#include +#include +#include +#include + +#include "tier1/strtools.h" +#include "tier1/characterset.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Character conversions for C strings +//----------------------------------------------------------------------------- +class CUtlCStringConversion : public CUtlCharConversion { + public: + CUtlCStringConversion(char nEscapeChar, const char *pDelimiter, intp nCount, + ConversionArray_t *pArray); + + // Finds a conversion for the passed-in string, returns length + virtual char FindConversion(const char *pString, intp *pLength); + + private: + char m_pConversion[256]; +}; + +//----------------------------------------------------------------------------- +// Character conversions for no-escape sequence strings +//----------------------------------------------------------------------------- +class CUtlNoEscConversion : public CUtlCharConversion { + public: + CUtlNoEscConversion(char nEscapeChar, const char *pDelimiter, intp nCount, + ConversionArray_t *pArray) + : CUtlCharConversion(nEscapeChar, pDelimiter, nCount, pArray) {} + + // Finds a conversion for the passed-in string, returns length + virtual char FindConversion(const char *pString, intp *pLength) { + *pLength = 0; + return 0; + } +}; + +//----------------------------------------------------------------------------- +// List of character conversions +//----------------------------------------------------------------------------- +BEGIN_CUSTOM_CHAR_CONVERSION(CUtlCStringConversion, s_StringCharConversion, + "\"", '\\'){'\n', "n"}, + {'\t', "t"}, {'\v', "v"}, {'\b', "b"}, {'\r', "r"}, {'\f', "f"}, + {'\a', "a"}, {'\\', "\\"}, {'\?', "\?"}, {'\'', "\'"}, {'\"', "\""}, + END_CUSTOM_CHAR_CONVERSION(CUtlCStringConversion, s_StringCharConversion, + "\"", '\\') + + CUtlCharConversion *GetCStringCharConversion() { + return &s_StringCharConversion; +} + +BEGIN_CUSTOM_CHAR_CONVERSION(CUtlNoEscConversion, s_NoEscConversion, "\"", + 0x7F){0x7F, ""}, + END_CUSTOM_CHAR_CONVERSION(CUtlNoEscConversion, s_NoEscConversion, "\"", + 0x7F) + + CUtlCharConversion *GetNoEscCharConversion() { + return &s_NoEscConversion; +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CUtlCStringConversion::CUtlCStringConversion(char nEscapeChar, + const char *pDelimiter, intp nCount, + ConversionArray_t *pArray) + : CUtlCharConversion(nEscapeChar, pDelimiter, nCount, pArray) { + memset(m_pConversion, 0x0, sizeof(m_pConversion)); + for (intp i = 0; i < nCount; ++i) { + m_pConversion[(unsigned char)(pArray[i].m_pReplacementString[0])] = + pArray[i].m_nActualChar; + } +} + +// Finds a conversion for the passed-in string, returns length +char CUtlCStringConversion::FindConversion(const char *pString, intp *pLength) { + char c = m_pConversion[(unsigned char)(pString[0])]; + *pLength = (c != '\0') ? 1 : 0; + return c; +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CUtlCharConversion::CUtlCharConversion(char nEscapeChar, const char *pDelimiter, + intp nCount, ConversionArray_t *pArray) { + m_nEscapeChar = nEscapeChar; + m_pDelimiter = pDelimiter; + m_nCount = nCount; + m_nDelimiterLength = V_strlen(pDelimiter); + m_nMaxConversionLength = 0; + + memset(m_pReplacements, 0, sizeof(m_pReplacements)); + + for (intp i = 0; i < nCount; ++i) { + m_pList[i] = pArray[i].m_nActualChar; + ConversionInfo_t &info = m_pReplacements[(unsigned char)(m_pList[i])]; + Assert(info.m_pReplacementString == 0); + info.m_pReplacementString = pArray[i].m_pReplacementString; + info.m_nLength = V_strlen(info.m_pReplacementString); + if (info.m_nLength > m_nMaxConversionLength) { + m_nMaxConversionLength = info.m_nLength; + } + } +} + +//----------------------------------------------------------------------------- +// Escape character + delimiter +//----------------------------------------------------------------------------- +char CUtlCharConversion::GetEscapeChar() const { return m_nEscapeChar; } + +const char *CUtlCharConversion::GetDelimiter() const { return m_pDelimiter; } + +intp CUtlCharConversion::GetDelimiterLength() const { + return m_nDelimiterLength; +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +const char *CUtlCharConversion::GetConversionString(char c) const { + return m_pReplacements[(unsigned char)c].m_pReplacementString; +} + +intp CUtlCharConversion::GetConversionLength(char c) const { + return m_pReplacements[(unsigned char)c].m_nLength; +} + +intp CUtlCharConversion::MaxConversionLength() const { + return m_nMaxConversionLength; +} + +//----------------------------------------------------------------------------- +// Finds a conversion for the passed-in string, returns length +//----------------------------------------------------------------------------- +char CUtlCharConversion::FindConversion(const char *pString, intp *pLength) { + for (intp i = 0; i < m_nCount; ++i) { + if (!V_strcmp(pString, m_pReplacements[(unsigned char)(m_pList[i])] + .m_pReplacementString)) { + *pLength = m_pReplacements[(unsigned char)(m_pList[i])].m_nLength; + return m_pList[i]; + } + } + + *pLength = 0; + return '\0'; +} + +//----------------------------------------------------------------------------- +// constructors +//----------------------------------------------------------------------------- +CUtlBuffer::CUtlBuffer(intp growSize, intp initSize, int nFlags) : m_Error(0) { + MEM_ALLOC_CREDIT(); + m_Memory.Init(growSize, initSize); + m_Get = 0; + m_Put = 0; + m_nTab = 0; + m_nOffset = 0; + m_Flags = static_cast(nFlags); + m_Reserved = 0; + if ((initSize != 0) && !IsReadOnly()) { + m_nMaxPut = -1; + AddNullTermination(m_Put); + } else { + m_nMaxPut = 0; + } + SetOverflowFuncs(&CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow); +} + +CUtlBuffer::CUtlBuffer(const void *pBuffer, intp nSize, int nFlags) + : m_Memory((unsigned char *)pBuffer, nSize), m_Error(0) { + Assert(nSize != 0); + + m_Get = 0; + m_Put = 0; + m_nTab = 0; + m_nOffset = 0; + m_Flags = static_cast(nFlags); + m_Reserved = 0; + if (IsReadOnly()) { + m_nMaxPut = m_Put = nSize; + } else { + m_nMaxPut = -1; + AddNullTermination(m_Put); + } + SetOverflowFuncs(&CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow); +} + +//----------------------------------------------------------------------------- +// Modifies the buffer to be binary or text; Blows away the buffer and the +// CONTAINS_CRLF value. +//----------------------------------------------------------------------------- +void CUtlBuffer::SetBufferType(bool bIsText, bool bContainsCRLF) { +#ifdef _DEBUG + // If the buffer is empty, there is no opportunity for this stuff to fail + if (TellMaxPut() != 0) { + if (IsText()) { + if (bIsText) { + Assert(ContainsCRLF() == bContainsCRLF); + } else { + Assert(ContainsCRLF()); + } + } else { + if (bIsText) { + Assert(bContainsCRLF); + } + } + } +#endif + + if (bIsText) { + m_Flags |= TEXT_BUFFER; + } else { + m_Flags &= ~TEXT_BUFFER; + } + if (bContainsCRLF) { + m_Flags |= CONTAINS_CRLF; + } else { + m_Flags &= ~CONTAINS_CRLF; + } +} + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +void CUtlBuffer::SetExternalBuffer(void *pMemory, intp nSize, intp nInitialPut, + int nFlags) { + m_Memory.SetExternalBuffer((unsigned char *)pMemory, nSize); + + // Reset all indices; we just changed memory + m_Get = 0; + m_Put = nInitialPut; + m_nTab = 0; + m_Error = 0; + m_nOffset = 0; + m_Flags = static_cast(nFlags); + m_nMaxPut = -1; + AddNullTermination(m_Put); +} + +//----------------------------------------------------------------------------- +// Assumes an external buffer but manages its deletion +//----------------------------------------------------------------------------- +void CUtlBuffer::AssumeMemory(void *pMemory, intp nSize, intp nInitialPut, + int nFlags) { + m_Memory.AssumeMemory((unsigned char *)pMemory, nSize); + + // Reset all indices; we just changed memory + m_Get = 0; + m_Put = nInitialPut; + m_nTab = 0; + m_Error = 0; + m_nOffset = 0; + m_Flags = static_cast(nFlags); + m_nMaxPut = -1; + AddNullTermination(m_Put); +} + +//----------------------------------------------------------------------------- +// Allows the caller to control memory +//----------------------------------------------------------------------------- +void *CUtlBuffer::DetachMemory() { + // Reset all indices; we just changed memory + m_Get = 0; + m_Put = 0; + m_nTab = 0; + m_Error = 0; + m_nOffset = 0; + return m_Memory.DetachMemory(); +} + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +void CUtlBuffer::EnsureCapacity(intp num) { + MEM_ALLOC_CREDIT(); + // Add one extra for the null termination + num += 1; + if (m_Memory.IsExternallyAllocated()) { + if (IsGrowable() && (m_Memory.NumAllocated() < num)) { + m_Memory.ConvertToGrowableMemory(0); + } else { + num -= 1; + } + } + + m_Memory.EnsureCapacity(num); +} + +//----------------------------------------------------------------------------- +// Base get method from which all others derive +//----------------------------------------------------------------------------- +void CUtlBuffer::Get(void *pMem, intp size) { + if (size > 0 && CheckGet(size)) { + memcpy(pMem, &m_Memory[m_Get - m_nOffset], size); + m_Get += size; + } +} + +//----------------------------------------------------------------------------- +// This will get at least 1 byte and up to nSize bytes. +// It will return the number of bytes actually read. +//----------------------------------------------------------------------------- +intp CUtlBuffer::GetUpTo(void *pMem, intp nSize) { + if (CheckArbitraryPeekGet(0, nSize)) { + memcpy(pMem, &m_Memory[m_Get - m_nOffset], nSize); + m_Get += nSize; + return nSize; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Eats whitespace +//----------------------------------------------------------------------------- +void CUtlBuffer::EatWhiteSpace() { + if (IsText() && IsValid()) { + while (CheckGet(sizeof(char))) { + if (!V_isspace(*(const unsigned char *)PeekGet())) break; + m_Get += sizeof(char); + } + } +} + +//----------------------------------------------------------------------------- +// Eats C++ style comments +//----------------------------------------------------------------------------- +bool CUtlBuffer::EatCPPComment() { + if (IsText() && IsValid()) { + // If we don't have a a c++ style comment next, we're done + const char *pPeek = (const char *)PeekGet(2 * sizeof(char), 0); + if (!pPeek || (pPeek[0] != '/') || (pPeek[1] != '/')) return false; + + // Deal with c++ style comments + m_Get += 2; + + // read complete line + for (char c = GetChar(); IsValid(); c = GetChar()) { + if (c == '\n') break; + } + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Peeks how much whitespace to eat +//----------------------------------------------------------------------------- +intp CUtlBuffer::PeekWhiteSpace(intp nOffset) { + if (!IsText() || !IsValid()) return 0; + + while (CheckPeekGet(nOffset, sizeof(char))) { + if (!V_isspace(*(unsigned char *)PeekGet(nOffset))) break; + nOffset += sizeof(char); + } + + return nOffset; +} + +//----------------------------------------------------------------------------- +// Peek size of sting to come, check memory bound +//----------------------------------------------------------------------------- +intp CUtlBuffer::PeekStringLength() { + if (!IsValid()) return 0; + + // Eat preceeding whitespace + intp nOffset = 0; + if (IsText()) { + nOffset = PeekWhiteSpace(nOffset); + } + + intp nStartingOffset = nOffset; + + do { + intp nPeekAmount = 128; + + // NOTE: Add 1 for the terminating zero! + if (!CheckArbitraryPeekGet(nOffset, nPeekAmount)) { + if (nOffset == nStartingOffset) return 0; + return nOffset - nStartingOffset + 1; + } + + const char *pTest = (const char *)PeekGet(nOffset); + + if (!IsText()) { + for (intp i = 0; i < nPeekAmount; ++i) { + // The +1 here is so we eat the terminating 0 + if (pTest[i] == 0) return (i + nOffset - nStartingOffset + 1); + } + } else { + for (intp i = 0; i < nPeekAmount; ++i) { + // The +1 here is so we eat the terminating 0 + if (V_isspace((unsigned char)pTest[i]) || (pTest[i] == 0)) + return (i + nOffset - nStartingOffset + 1); + } + } + + nOffset += nPeekAmount; + + } while (true); +} + +//----------------------------------------------------------------------------- +// Peek size of line to come, check memory bound +//----------------------------------------------------------------------------- +intp CUtlBuffer::PeekLineLength() { + if (!IsValid()) return 0; + + intp nOffset = 0; + intp nStartingOffset = nOffset; + + do { + intp nPeekAmount = 128; + + // NOTE: Add 1 for the terminating zero! + if (!CheckArbitraryPeekGet(nOffset, nPeekAmount)) { + if (nOffset == nStartingOffset) return 0; + return nOffset - nStartingOffset + 1; + } + + const char *pTest = (const char *)PeekGet(nOffset); + + for (intp i = 0; i < nPeekAmount; ++i) { + // The +2 here is so we eat the terminating '\n' and 0 + if (pTest[i] == '\n' || pTest[i] == '\r') + return (i + nOffset - nStartingOffset + 2); + // The +1 here is so we eat the terminating 0 + if (pTest[i] == 0) return (i + nOffset - nStartingOffset + 1); + } + + nOffset += nPeekAmount; + + } while (true); +} + +//----------------------------------------------------------------------------- +// Does the next bytes of the buffer match a pattern? +//----------------------------------------------------------------------------- +bool CUtlBuffer::PeekStringMatch(intp nOffset, const char *pString, intp nLen) { + if (!CheckPeekGet(nOffset, nLen)) return false; + return !V_strncmp((const char *)PeekGet(nOffset), pString, nLen); +} + +//----------------------------------------------------------------------------- +// This version of PeekStringLength converts \" to \\ and " to \, etc. +// It also reads a " at the beginning and end of the string +//----------------------------------------------------------------------------- +intp CUtlBuffer::PeekDelimitedStringLength(CUtlCharConversion *pConv, + bool bActualSize) { + if (!IsText() || !pConv) return PeekStringLength(); + + // Eat preceeding whitespace + intp nOffset = 0; + if (IsText()) { + nOffset = PeekWhiteSpace(nOffset); + } + + if (!PeekStringMatch(nOffset, pConv->GetDelimiter(), + pConv->GetDelimiterLength())) + return 0; + + // Try to read ending ", but don't accept \" + intp nActualStart = nOffset; + nOffset += pConv->GetDelimiterLength(); + intp nLen = 1; // Starts at 1 for the '\0' termination + + do { + if (PeekStringMatch(nOffset, pConv->GetDelimiter(), + pConv->GetDelimiterLength())) + break; + + if (!CheckPeekGet(nOffset, 1)) break; + + char c = *(const char *)PeekGet(nOffset); + ++nLen; + ++nOffset; + if (c == pConv->GetEscapeChar()) { + intp nLength = pConv->MaxConversionLength(); + if (!CheckArbitraryPeekGet(nOffset, nLength)) break; + + pConv->FindConversion((const char *)PeekGet(nOffset), &nLength); + nOffset += nLength; + } + } while (true); + + return bActualSize ? nLen + : nOffset - nActualStart + pConv->GetDelimiterLength() + 1; +} + +//----------------------------------------------------------------------------- +// Reads a null-terminated string +//----------------------------------------------------------------------------- +void CUtlBuffer::GetString(char *pString, intp nMaxChars) { + if (!IsValid()) { + *pString = 0; + return; + } + + if (nMaxChars == 0) { + nMaxChars = INT_MAX; + } + + // Remember, this *includes* the null character + // It will be 0, however, if the buffer is empty. + intp nLen = PeekStringLength(); + + if (IsText()) { + EatWhiteSpace(); + } + + if (nLen == 0) { + *pString = 0; + m_Error |= GET_OVERFLOW; + return; + } + + // Strip off the terminating NULL + if (nLen <= nMaxChars) { + Get(pString, nLen - 1); + pString[nLen - 1] = 0; + } else { + Get(pString, nMaxChars - 1); + pString[nMaxChars - 1] = 0; + // skip the remaining characters, EXCEPT the terminating null + // thus it's ( nLen - ( nMaxChars - 1 ) - 1 ) + SeekGet(SEEK_CURRENT, nLen - nMaxChars); + } + + // Read the terminating NULL in binary formats + if (!IsText()) { + VerifyEquals(GetChar(), 0); + } +} + +//----------------------------------------------------------------------------- +// Reads up to and including the first \n +//----------------------------------------------------------------------------- +void CUtlBuffer::GetLine(char *pLine, intp nMaxChars) { + Assert(IsText() && !ContainsCRLF()); + + if (!IsValid()) { + *pLine = 0; + return; + } + + if (nMaxChars == 0) { + nMaxChars = INT_MAX; + } + + // Remember, this *includes* the null character + // It will be 0, however, if the buffer is empty. + intp nLen = PeekLineLength(); + if (nLen == 0) { + *pLine = 0; + m_Error |= GET_OVERFLOW; + return; + } + + // Strip off the terminating NULL + if (nLen <= nMaxChars) { + Get(pLine, nLen - 1); + pLine[nLen - 1] = 0; + } else { + Get(pLine, nMaxChars - 1); + pLine[nMaxChars - 1] = 0; + SeekGet(SEEK_CURRENT, nLen - 1 - nMaxChars); + } +} + +//----------------------------------------------------------------------------- +// This version of GetString converts \ to \\ and " to \", etc. +// It also places " at the beginning and end of the string +//----------------------------------------------------------------------------- +char CUtlBuffer::GetDelimitedCharInternal(CUtlCharConversion *pConv) { + char c = GetChar(); + if (c == pConv->GetEscapeChar()) { + intp nLength = pConv->MaxConversionLength(); + if (!CheckArbitraryPeekGet(0, nLength)) return '\0'; + + c = pConv->FindConversion((const char *)PeekGet(), &nLength); + SeekGet(SEEK_CURRENT, nLength); + } + + return c; +} + +char CUtlBuffer::GetDelimitedChar(CUtlCharConversion *pConv) { + if (!IsText() || !pConv) return GetChar(); + return GetDelimitedCharInternal(pConv); +} + +void CUtlBuffer::GetDelimitedString(CUtlCharConversion *pConv, char *pString, + intp nMaxChars) { + if (!IsText() || !pConv) { + GetString(pString, nMaxChars); + return; + } + + if (!IsValid()) { + *pString = 0; + return; + } + + if (nMaxChars == 0) { + nMaxChars = INT_MAX; + } + + EatWhiteSpace(); + if (!PeekStringMatch(0, pConv->GetDelimiter(), pConv->GetDelimiterLength())) + return; + + // Pull off the starting delimiter + SeekGet(SEEK_CURRENT, pConv->GetDelimiterLength()); + + intp nRead = 0; + while (IsValid()) { + if (PeekStringMatch(0, pConv->GetDelimiter(), + pConv->GetDelimiterLength())) { + SeekGet(SEEK_CURRENT, pConv->GetDelimiterLength()); + break; + } + + char c = GetDelimitedCharInternal(pConv); + + if (nRead < nMaxChars) { + pString[nRead] = c; + ++nRead; + } + } + + if (nRead >= nMaxChars) { + nRead = nMaxChars - 1; + } + pString[nRead] = '\0'; +} + +//----------------------------------------------------------------------------- +// Checks if a get is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckGet(intp nSize) { + if (m_Error & GET_OVERFLOW) return false; + + if (TellMaxPut() < m_Get + nSize) { + m_Error |= GET_OVERFLOW; + return false; + } + + if ((m_Get < m_nOffset) || + (m_Memory.NumAllocated() < m_Get - m_nOffset + nSize)) { + if (!OnGetOverflow(nSize)) { + m_Error |= GET_OVERFLOW; + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Checks if a peek get is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckPeekGet(intp nOffset, intp nSize) { + if (m_Error & GET_OVERFLOW) return false; + + // Checking for peek can't set the overflow flag + bool bOk = CheckGet(nOffset + nSize); + m_Error &= ~GET_OVERFLOW; + return bOk; +} + +//----------------------------------------------------------------------------- +// Call this to peek arbitrarily long into memory. It doesn't fail unless +// it can't read *anything* new +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckArbitraryPeekGet(intp nOffset, intp &nIncrement) { + if (TellGet() + nOffset >= TellMaxPut()) { + nIncrement = 0; + return false; + } + + if (TellGet() + nOffset + nIncrement > TellMaxPut()) { + nIncrement = TellMaxPut() - TellGet() - nOffset; + } + + // NOTE: CheckPeekGet could modify TellMaxPut for streaming files + // We have to call TellMaxPut again here + CheckPeekGet(nOffset, nIncrement); + intp nMaxGet = TellMaxPut() - TellGet(); + if (nMaxGet < nIncrement) { + nIncrement = nMaxGet; + } + return (nIncrement != 0); +} + +//----------------------------------------------------------------------------- +// Peek part of the butt +//----------------------------------------------------------------------------- +const void *CUtlBuffer::PeekGet(intp nMaxSize, intp nOffset) { + if (!CheckPeekGet(nOffset, nMaxSize)) return NULL; + return &m_Memory[m_Get + nOffset - m_nOffset]; +} + +//----------------------------------------------------------------------------- +// Change where I'm reading +//----------------------------------------------------------------------------- +void CUtlBuffer::SeekGet(SeekType_t type, intp offset) { + switch (type) { + case SEEK_HEAD: + m_Get = offset; + break; + + case SEEK_CURRENT: + m_Get += offset; + break; + + case SEEK_TAIL: + m_Get = m_nMaxPut - offset; + break; + } + + if (m_Get > m_nMaxPut) { + m_Error |= GET_OVERFLOW; + } else { + m_Error &= ~GET_OVERFLOW; + if (m_Get < m_nOffset || m_Get >= m_nOffset + Size()) { + OnGetOverflow(-1); + } + } +} + +//----------------------------------------------------------------------------- +// Parse... +//----------------------------------------------------------------------------- +intp CUtlBuffer::VaScanf(const char *pFmt, va_list list) { + Assert(pFmt); + if (m_Error || !IsText()) return 0; + + intp numScanned = 0; + while (char c = *pFmt++) { + // Stop if we hit the end of the buffer + if (m_Get >= TellMaxPut()) { + m_Error |= GET_OVERFLOW; + break; + } + + switch (c) { + case ' ': + // eat all whitespace + EatWhiteSpace(); + break; + + case '%': { + // Conversion character... try to convert baby! + char type = *pFmt++; + if (type == 0) return numScanned; + + switch (type) { + case 'c': { + char *ch = va_arg(list, char *); + if (CheckPeekGet(0, sizeof(char))) { + *ch = *(const char *)PeekGet(); + ++m_Get; + } else { + *ch = 0; + return numScanned; + } + } break; + + case 'h': { + if (*pFmt == 'd' || *pFmt == 'i') { + if (!GetTypeText(*va_arg(list, int16 *))) + return numScanned; // only support short ints, don't bother + // with hex + } else if (*pFmt == 'u') { + if (!GetTypeText(*va_arg(list, uint16 *))) return numScanned; + } else + return numScanned; + ++pFmt; + } break; + + case 'I': { + if (*pFmt++ != '6' || *pFmt++ != '4') + return numScanned; // only support "I64d" and "I64u" + + if (*pFmt == 'd') { + if (!GetTypeText(*va_arg(list, int64 *))) return numScanned; + } else if (*pFmt == 'u') { + if (!GetTypeText(*va_arg(list, uint64 *))) return numScanned; + } else { + return numScanned; + } + + ++pFmt; + } break; + + case 'i': + case 'd': { + int32 *pArg = va_arg(list, int32 *); + if (!GetTypeText(*pArg)) return numScanned; + } break; + + case 'x': { + uint32 *pArg = va_arg(list, uint32 *); + if (!GetTypeText(*pArg, 16)) return numScanned; + } break; + + case 'u': { + uint32 *pArg = va_arg(list, uint32 *); + if (!GetTypeText(*pArg)) return numScanned; + } break; + + case 'l': { + // we currently support %lf and %lld + if (*pFmt == 'f') { + if (!GetTypeText(*va_arg(list, double *))) return numScanned; + } else if (*pFmt == 'l' && *++pFmt == 'd') { + if (!GetTypeText(*va_arg(list, int64 *))) return numScanned; + } else + return numScanned; + } break; + + case 'f': { + float *pArg = va_arg(list, float *); + if (!GetTypeText(*pArg)) return numScanned; + } break; + + case 's': { + char *s = va_arg(list, char *); + GetString(s); + } break; + + default: { + // unimplemented scanf type + Assert(0); + return numScanned; + } break; + } + + ++numScanned; + } break; + + default: { + // Here we have to match the format string character + // against what's in the buffer or we're done. + if (!CheckPeekGet(0, sizeof(char))) return numScanned; + + if (c != *(const char *)PeekGet()) return numScanned; + + ++m_Get; + } + } + } + return numScanned; +} + +intp CUtlBuffer::Scanf(SCANF_FORMAT_STRING const char *pFmt, ...) { + va_list args; + + va_start(args, pFmt); + intp count = VaScanf(pFmt, args); + va_end(args); + + return count; +} + +//----------------------------------------------------------------------------- +// Advance the get index until after the particular string is found +// Do not eat whitespace before starting. Return false if it failed +//----------------------------------------------------------------------------- +bool CUtlBuffer::GetToken(const char *pToken) { + Assert(pToken); + + // Look for the token + intp nLen = V_strlen(pToken); + + // First time through on streaming, check what we already have loaded + // if we have enough loaded to do the check + intp nMaxSize = Size() - (TellGet() - m_nOffset); + if (nMaxSize <= nLen) { + nMaxSize = Size(); + } + intp nSizeRemaining = TellMaxPut() - TellGet(); + + intp nGet = TellGet(); + while (nSizeRemaining >= nLen) { + bool bOverFlow = (nSizeRemaining > nMaxSize); + intp nSizeToCheck = bOverFlow ? nMaxSize : nSizeRemaining; + if (!CheckPeekGet(0, nSizeToCheck)) break; + + const char *pBufStart = (const char *)PeekGet(); + const char *pFoundEnd = V_strnistr(pBufStart, pToken, nSizeToCheck); + + // Time to be careful: if we are in a state of overflow + // (namely, there's more of the buffer beyond the current window) + // we could be looking for 'foo' for example, and find 'foobar' + // if 'foo' happens to be the last 3 characters of the current window + size_t nOffset = (size_t)pFoundEnd - (size_t)pBufStart; + bool bPotentialMismatch = (bOverFlow && ((intp)nOffset == Size() - nLen)); + if (!pFoundEnd || bPotentialMismatch) { + nSizeRemaining -= nSizeToCheck; + if (!pFoundEnd && (nSizeRemaining < nLen)) break; + + // Second time through, stream as much in as possible + // But keep the last portion of the current buffer + // since we couldn't check it against stuff outside the window + nSizeRemaining += nLen; + nMaxSize = Size(); + SeekGet(CUtlBuffer::SEEK_CURRENT, nSizeToCheck - nLen); + continue; + } + + // Seek past the end of the found string + SeekGet(CUtlBuffer::SEEK_CURRENT, (nOffset + nLen)); + return true; + } + + // Didn't find a match, leave the get index where it was to start with + SeekGet(CUtlBuffer::SEEK_HEAD, nGet); + return false; +} + +//----------------------------------------------------------------------------- +// (For text buffers only) +// Parse a token from the buffer: +// Grab all text that lies between a starting delimiter + ending delimiter +// (skipping whitespace that leads + trails both delimiters). +// Note the delimiter checks are case-insensitive. +// If successful, the get index is advanced and the function returns true, +// otherwise the index is not advanced and the function returns false. +//----------------------------------------------------------------------------- +bool CUtlBuffer::ParseToken(const char *pStartingDelim, + const char *pEndingDelim, char *pString, + intp nMaxLen) { + intp nCharsToCopy = 0; + intp nCurrentGet = 0; + + size_t nEndingDelimLen; + + // Starting delimiter is optional + char emptyBuf = '\0'; + if (!pStartingDelim) { + pStartingDelim = &emptyBuf; + } + + // Ending delimiter is not + Assert(pEndingDelim && pEndingDelim[0]); + nEndingDelimLen = V_strlen(pEndingDelim); + + intp nStartGet = TellGet(); + char nCurrChar; + intp nTokenStart = -1; + EatWhiteSpace(); + while (*pStartingDelim) { + nCurrChar = *pStartingDelim++; + if (!V_isspace((unsigned char)nCurrChar)) { + if (tolower(GetChar()) != tolower(nCurrChar)) goto parseFailed; + } else { + EatWhiteSpace(); + } + } + + EatWhiteSpace(); + nTokenStart = TellGet(); + if (!GetToken(pEndingDelim)) goto parseFailed; + + nCurrentGet = TellGet(); + nCharsToCopy = (intp)((nCurrentGet - nEndingDelimLen) - nTokenStart); + if (nCharsToCopy >= nMaxLen) { + nCharsToCopy = nMaxLen - 1; + } + + if (nCharsToCopy > 0) { + SeekGet(CUtlBuffer::SEEK_HEAD, nTokenStart); + Get(pString, nCharsToCopy); + if (!IsValid()) goto parseFailed; + + // Eat trailing whitespace + for (; nCharsToCopy > 0; --nCharsToCopy) { + if (!V_isspace((unsigned char)pString[nCharsToCopy - 1])) break; + } + } + pString[nCharsToCopy] = '\0'; + + // Advance the Get index + SeekGet(CUtlBuffer::SEEK_HEAD, nCurrentGet); + return true; + +parseFailed: + // Revert the get index + SeekGet(SEEK_HEAD, nStartGet); + pString[0] = '\0'; + return false; +} + +//----------------------------------------------------------------------------- +// Parses the next token, given a set of character breaks to stop at +//----------------------------------------------------------------------------- +intp CUtlBuffer::ParseToken(characterset_t *pBreaks, char *pTokenBuf, + intp nMaxLen, bool bParseComments) { + Assert(nMaxLen > 0); + pTokenBuf[0] = 0; + + // skip whitespace + comments + while (true) { + if (!IsValid()) return -1; + EatWhiteSpace(); + if (bParseComments) { + if (!EatCPPComment()) break; + } else { + break; + } + } + + char c = GetChar(); + + // End of buffer + if (c == 0) return -1; + + // handle quoted strings specially + if (c == '\"') { + intp nLen = 0; + while (IsValid()) { + c = GetChar(); + if (c == '\"' || !c) { + pTokenBuf[nLen] = 0; + return nLen; + } + pTokenBuf[nLen] = c; + if (++nLen == nMaxLen) { + pTokenBuf[nLen - 1] = 0; + return nMaxLen; + } + } + + // In this case, we hit the end of the buffer before hitting the end qoute + pTokenBuf[nLen] = 0; + return nLen; + } + + // parse single characters + if (IN_CHARACTERSET(*pBreaks, c)) { + pTokenBuf[0] = c; + pTokenBuf[1] = 0; + return 1; + } + + // parse a regular word + intp nLen = 0; + while (true) { + pTokenBuf[nLen] = c; + if (++nLen == nMaxLen) { + pTokenBuf[nLen - 1] = 0; + return nMaxLen; + } + c = GetChar(); + if (!IsValid()) break; + + if (IN_CHARACTERSET(*pBreaks, c) || c == '\"' || c <= ' ') { + SeekGet(SEEK_CURRENT, -1); + break; + } + } + + pTokenBuf[nLen] = 0; + return nLen; +} + +//----------------------------------------------------------------------------- +// Serialization +//----------------------------------------------------------------------------- +void CUtlBuffer::Put(const void *pMem, intp size) { + if (size && CheckPut(size)) { + memcpy(&m_Memory[m_Put - m_nOffset], pMem, size); + m_Put += size; + + AddNullTermination(m_Put); + } +} + +//----------------------------------------------------------------------------- +// Writes a null-terminated string +//----------------------------------------------------------------------------- +void CUtlBuffer::PutString(const char *pString) { + if (!IsText()) { + if (pString) { + // Not text? append a null at the end. + intp nLen = V_strlen(pString) + 1; + Put(pString, nLen * sizeof(char)); + return; + } else { + PutTypeBin(0); + } + } else if (pString) { + intp nTabCount = (m_Flags & AUTO_TABS_DISABLED) ? 0 : m_nTab; + if (nTabCount > 0) { + if (WasLastCharacterCR()) { + PutTabs(); + } + + const char *pEndl = strchr(pString, '\n'); + while (pEndl) { + size_t nSize = (size_t)pEndl - (size_t)pString + sizeof(char); + Put(pString, (intp)nSize); + pString = pEndl + 1; + if (*pString) { + PutTabs(); + pEndl = strchr(pString, '\n'); + } else { + pEndl = NULL; + } + } + } + intp nLen = V_strlen(pString); + if (nLen) { + Put(pString, nLen * sizeof(char)); + } + } +} + +//----------------------------------------------------------------------------- +// This version of PutString converts \ to \\ and " to \", etc. +// It also places " at the beginning and end of the string +//----------------------------------------------------------------------------- +inline void CUtlBuffer::PutDelimitedCharInternal(CUtlCharConversion *pConv, + char c) { + intp l = pConv->GetConversionLength(c); + if (l == 0) { + PutChar(c); + } else { + PutChar(pConv->GetEscapeChar()); + Put(pConv->GetConversionString(c), l); + } +} + +void CUtlBuffer::PutDelimitedChar(CUtlCharConversion *pConv, char c) { + if (!IsText() || !pConv) { + PutChar(c); + return; + } + + PutDelimitedCharInternal(pConv, c); +} + +void CUtlBuffer::PutDelimitedString(CUtlCharConversion *pConv, + const char *pString) { + if (!IsText() || !pConv) { + PutString(pString); + return; + } + + if (WasLastCharacterCR()) { + PutTabs(); + } + Put(pConv->GetDelimiter(), pConv->GetDelimiterLength()); + + intp nLen = pString ? V_strlen(pString) : 0; + for (intp i = 0; i < nLen; ++i) { + PutDelimitedCharInternal(pConv, pString[i]); + } + + if (WasLastCharacterCR()) { + PutTabs(); + } + Put(pConv->GetDelimiter(), pConv->GetDelimiterLength()); +} + +void CUtlBuffer::VaPrintf(const char *pFmt, va_list list) { + char temp[8192]; + int nLen = V_vsnprintf(temp, sizeof(temp), pFmt, list); + ErrorIfNot( + nLen < sizeof(temp), + ("CUtlBuffer::VaPrintf: String overflowed buffer [%d]\n", sizeof(temp))); + PutString(temp); +} + +void CUtlBuffer::Printf(const char *pFmt, ...) { + va_list args; + + va_start(args, pFmt); + VaPrintf(pFmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// Calls the overflow functions +//----------------------------------------------------------------------------- +void CUtlBuffer::SetOverflowFuncs(UtlBufferOverflowFunc_t getFunc, + UtlBufferOverflowFunc_t putFunc) { + m_GetOverflowFunc = getFunc; + m_PutOverflowFunc = putFunc; +} + +//----------------------------------------------------------------------------- +// Calls the overflow functions +//----------------------------------------------------------------------------- +bool CUtlBuffer::OnPutOverflow(intp nSize) { + return (this->*m_PutOverflowFunc)(nSize); +} + +bool CUtlBuffer::OnGetOverflow(intp nSize) { + return (this->*m_GetOverflowFunc)(nSize); +} + +//----------------------------------------------------------------------------- +// Checks if a put is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::PutOverflow(intp nSize) { + MEM_ALLOC_CREDIT(); + + if (m_Memory.IsExternallyAllocated()) { + if (!IsGrowable()) return false; + + m_Memory.ConvertToGrowableMemory(0); + } + + while (Size() < m_Put - m_nOffset + nSize) { + m_Memory.Grow(); + } + + return true; +} + +bool CUtlBuffer::GetOverflow(intp nSize) { return false; } + +//----------------------------------------------------------------------------- +// Checks if a put is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckPut(intp nSize) { + if ((m_Error & PUT_OVERFLOW) || IsReadOnly()) return false; + + if ((m_Put < m_nOffset) || + (m_Memory.NumAllocated() < m_Put - m_nOffset + nSize)) { + if (!OnPutOverflow(nSize)) { + m_Error |= PUT_OVERFLOW; + return false; + } + } + return true; +} + +void CUtlBuffer::SeekPut(SeekType_t type, intp offset) { + intp nNextPut = m_Put; + switch (type) { + case SEEK_HEAD: + nNextPut = offset; + break; + + case SEEK_CURRENT: + nNextPut += offset; + break; + + case SEEK_TAIL: + nNextPut = m_nMaxPut - offset; + break; + } + + // Force a write of the data + // FIXME: We could make this more optimal potentially by writing out + // the entire buffer if you seek outside the current range + + // NOTE: This call will write and will also seek the file to nNextPut. + OnPutOverflow(-nNextPut - 1); + m_Put = nNextPut; + + AddNullTermination(m_Put); +} + +void CUtlBuffer::ActivateByteSwapping(bool bActivate) { + m_Byteswap.ActivateByteSwapping(bActivate); +} + +void CUtlBuffer::SetBigEndian(bool bigEndian) { + m_Byteswap.SetTargetBigEndian(bigEndian); +} + +bool CUtlBuffer::IsBigEndian(void) { return m_Byteswap.IsTargetBigEndian(); } + +//----------------------------------------------------------------------------- +// null terminate the buffer +// NOTE: Pass in nPut here even though it is just a copy of m_Put. This is +// almost always called immediately after modifying m_Put and this lets it stay +// in a register and avoid LHS on PPC. +//----------------------------------------------------------------------------- +void CUtlBuffer::AddNullTermination(intp nPut) { + if (nPut > m_nMaxPut) { + if (!IsReadOnly() && ((m_Error & PUT_OVERFLOW) == 0)) { + // Add null termination value + if (CheckPut(1)) { + m_Memory[nPut - m_nOffset] = 0; + } else { + // Restore the overflow state, it was valid before... + m_Error &= ~PUT_OVERFLOW; + } + } + m_nMaxPut = nPut; + } +} + +//----------------------------------------------------------------------------- +// Converts a buffer from a CRLF buffer to a CR buffer (and back) +// Returns false if no conversion was necessary (and outBuf is left untouched) +// If the conversion occurs, outBuf will be cleared. +//----------------------------------------------------------------------------- +bool CUtlBuffer::ConvertCRLF(CUtlBuffer &outBuf) { + if (!IsText() || !outBuf.IsText()) return false; + + if (ContainsCRLF() == outBuf.ContainsCRLF()) return false; + + intp nInCount = TellMaxPut(); + + outBuf.Purge(); + outBuf.EnsureCapacity(nInCount); + + bool bFromCRLF = ContainsCRLF(); + + // Start reading from the beginning + intp nGet = TellGet(); + intp nPut = TellPut(); + intp nGetDelta = 0; + intp nPutDelta = 0; + + const char *pBase = (const char *)Base(); + intp nCurrGet = 0; + while (nCurrGet < nInCount) { + const char *pCurr = &pBase[nCurrGet]; + if (bFromCRLF) { + const char *pNext = V_strnistr(pCurr, "\r\n", nInCount - nCurrGet); + if (!pNext) { + outBuf.Put(pCurr, nInCount - nCurrGet); + break; + } + + intp nBytes = pNext - pCurr; + outBuf.Put(pCurr, nBytes); + outBuf.PutChar('\n'); + nCurrGet += nBytes + 2; + if (nGet >= nCurrGet - 1) { + --nGetDelta; + } + if (nPut >= nCurrGet - 1) { + --nPutDelta; + } + } else { + const char *pNext = V_strnchr(pCurr, '\n', nInCount - nCurrGet); + if (!pNext) { + outBuf.Put(pCurr, nInCount - nCurrGet); + break; + } + + intp nBytes = pNext - pCurr; + outBuf.Put(pCurr, nBytes); + outBuf.PutChar('\r'); + outBuf.PutChar('\n'); + nCurrGet += nBytes + 1; + if (nGet >= nCurrGet) { + ++nGetDelta; + } + if (nPut >= nCurrGet) { + ++nPutDelta; + } + } + } + + Assert(nPut + nPutDelta <= outBuf.TellMaxPut()); + + outBuf.SeekGet(SEEK_HEAD, nGet + nGetDelta); + outBuf.SeekPut(SEEK_HEAD, nPut + nPutDelta); + + return true; +} + +//--------------------------------------------------------------------------- +// Implementation of CUtlInplaceBuffer +//--------------------------------------------------------------------------- + +CUtlInplaceBuffer::CUtlInplaceBuffer(intp growSize /* = 0 */, + intp initSize /* = 0 */, + int nFlags /* = 0 */) + : CUtlBuffer(growSize, initSize, nFlags) { +} + +bool CUtlInplaceBuffer::InplaceGetLinePtr(char **ppszInBufferPtr, + intp *pnLineLength) { + Assert(IsText() && !ContainsCRLF()); + + intp nLineLen = PeekLineLength(); + if (nLineLen <= 1) { + SeekGet(SEEK_TAIL, 0); + return false; + } + + --nLineLen; // because it accounts for putting a terminating null-character + + char *pszLine = (char *)const_cast(PeekGet()); + SeekGet(SEEK_CURRENT, nLineLen); + + // Set the out args + if (ppszInBufferPtr) *ppszInBufferPtr = pszLine; + + if (pnLineLength) *pnLineLength = nLineLen; + + return true; +} + +char *CUtlInplaceBuffer::InplaceGetLinePtr(void) { + char *pszLine = NULL; + intp nLineLen = 0; + + if (InplaceGetLinePtr(&pszLine, &nLineLen)) { + Assert(nLineLen >= 1); + + switch (pszLine[nLineLen - 1]) { + case '\n': + case '\r': + pszLine[nLineLen - 1] = 0; + if (--nLineLen) { + switch (pszLine[nLineLen - 1]) { + case '\n': + case '\r': + pszLine[nLineLen - 1] = 0; + break; + } + } + break; + + default: + Assert(pszLine[nLineLen] == 0); + break; + } + } + + return pszLine; +} diff --git a/tier1/utlstring.cpp b/tier1/utlstring.cpp new file mode 100644 index 0000000..4ef5a87 --- /dev/null +++ b/tier1/utlstring.cpp @@ -0,0 +1,454 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier1/utlstring.h" + +#include + +#include "tier1/strtools.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Base class, containing simple memory management +//----------------------------------------------------------------------------- +CUtlBinaryBlock::CUtlBinaryBlock(intp growSize, intp initSize) { + MEM_ALLOC_CREDIT(); + m_Memory.Init(growSize, initSize); + + m_nActualLength = 0; +} + +CUtlBinaryBlock::CUtlBinaryBlock(void *pMemory, intp nSizeInBytes, + intp nInitialLength) + : m_Memory((unsigned char *)pMemory, nSizeInBytes) { + m_nActualLength = nInitialLength; +} + +CUtlBinaryBlock::CUtlBinaryBlock(const void *pMemory, intp nSizeInBytes) + : m_Memory((const unsigned char *)pMemory, nSizeInBytes) { + m_nActualLength = nSizeInBytes; +} + +CUtlBinaryBlock::CUtlBinaryBlock(const CUtlBinaryBlock &src) { + Set(src.Get(), src.Length()); +} + +void CUtlBinaryBlock::Get(void *pValue, intp nLen) const { + Assert(nLen > 0); + if (m_nActualLength < nLen) { + nLen = m_nActualLength; + } + + if (nLen > 0) { + memcpy(pValue, m_Memory.Base(), nLen); + } +} + +void CUtlBinaryBlock::SetLength(intp nLength) { + MEM_ALLOC_CREDIT(); + Assert(!m_Memory.IsReadOnly()); + + m_nActualLength = nLength; + if (nLength > m_Memory.NumAllocated()) { + intp nOverFlow = nLength - m_Memory.NumAllocated(); + m_Memory.Grow(nOverFlow); + + // If the reallocation failed, clamp length + if (nLength > m_Memory.NumAllocated()) { + m_nActualLength = m_Memory.NumAllocated(); + } + } + +#ifdef _DEBUG + if (m_Memory.NumAllocated() > m_nActualLength) { + memset(((char *)m_Memory.Base()) + m_nActualLength, 0xEB, + m_Memory.NumAllocated() - m_nActualLength); + } +#endif +} + +void CUtlBinaryBlock::Set(const void *pValue, intp nLen) { + Assert(!m_Memory.IsReadOnly()); + + if (!pValue) nLen = 0; + + SetLength(nLen); + + if (m_nActualLength) { + if (((const char *)m_Memory.Base()) >= ((const char *)pValue) + nLen || + ((const char *)m_Memory.Base()) + m_nActualLength <= + ((const char *)pValue)) { + memcpy(m_Memory.Base(), pValue, m_nActualLength); + } else { + memmove(m_Memory.Base(), pValue, m_nActualLength); + } + } +} + +CUtlBinaryBlock &CUtlBinaryBlock::operator=(const CUtlBinaryBlock &src) { + Assert(!m_Memory.IsReadOnly()); + Set(src.Get(), src.Length()); + return *this; +} + +bool CUtlBinaryBlock::operator==(const CUtlBinaryBlock &src) const { + if (src.Length() != Length()) return false; + + return !memcmp(src.Get(), Get(), Length()); +} + +//----------------------------------------------------------------------------- +// Simple string class. +//----------------------------------------------------------------------------- +CUtlString::CUtlString() = default; + +CUtlString::CUtlString(const char *pString) { Set(pString); } + +CUtlString::CUtlString(const CUtlString &string) { Set(string.Get()); } + +// Attaches the string to external memory. Useful for avoiding a copy +CUtlString::CUtlString(void *pMemory, intp nSizeInBytes, intp nInitialLength) + : m_Storage(pMemory, nSizeInBytes, nInitialLength) {} + +CUtlString::CUtlString(const void *pMemory, intp nSizeInBytes) + : m_Storage(pMemory, nSizeInBytes) {} + +//----------------------------------------------------------------------------- +// Purpose: Set directly and don't look for a null terminator in pValue. +//----------------------------------------------------------------------------- +void CUtlString::SetDirect(const char *pValue, intp nChars) { + if (nChars > 0) { + m_Storage.SetLength(nChars + 1); + m_Storage.Set(pValue, nChars); + m_Storage[nChars] = 0; + } else { + m_Storage.SetLength(0); + } +} + +void CUtlString::Set(const char *pValue) { + Assert(!m_Storage.IsReadOnly()); + intp nLen = pValue ? V_strlen(pValue) + 1 : 0; + m_Storage.Set(pValue, nLen); +} + +// Returns strlen +intp CUtlString::Length() const { + return m_Storage.Length() ? m_Storage.Length() - 1 : 0; +} + +// Sets the length (used to serialize into the buffer ) +void CUtlString::SetLength(intp nLen) { + Assert(!m_Storage.IsReadOnly()); + + // Add 1 to account for the NULL + m_Storage.SetLength(nLen > 0 ? nLen + 1 : 0); +} + +const char *CUtlString::Get() const { + if (m_Storage.Length() == 0) { + return ""; + } + + return reinterpret_cast(m_Storage.Get()); +} + +// Converts to c-strings +CUtlString::operator const char *() const { return Get(); } + +char *CUtlString::Get() { + Assert(!m_Storage.IsReadOnly()); + + if (m_Storage.Length() == 0) { + // In general, we optimise away small mallocs for empty strings + // but if you ask for the non-const bytes, they must be writable + // so we can't return "" here, like we do for the const version - jd + m_Storage.SetLength(1); + m_Storage[0] = '\0'; + } + + return reinterpret_cast(m_Storage.Get()); +} + +void CUtlString::Purge() { m_Storage.Purge(); } + +void CUtlString::ToLower() { + for (intp nLength = Length() - 1; nLength >= 0; nLength--) { + m_Storage[nLength] = + static_cast(tolower(m_Storage[nLength])); + } +} + +CUtlString &CUtlString::operator=(const CUtlString &src) { + Assert(!m_Storage.IsReadOnly()); + m_Storage = src.m_Storage; + return *this; +} + +CUtlString &CUtlString::operator=(const char *src) { + Assert(!m_Storage.IsReadOnly()); + Set(src); + return *this; +} + +bool CUtlString::operator==(const CUtlString &src) const { + return m_Storage == src.m_Storage; +} + +bool CUtlString::operator==(const char *src) const { + return (strcmp(Get(), src) == 0); +} + +CUtlString &CUtlString::operator+=(const CUtlString &rhs) { + Assert(!m_Storage.IsReadOnly()); + + const intp lhsLength(Length()); + const intp rhsLength(rhs.Length()); + const intp requestedLength(lhsLength + rhsLength); + + SetLength(requestedLength); + const intp allocatedLength(Length()); + const intp copyLength(allocatedLength - lhsLength < rhsLength + ? allocatedLength - lhsLength + : rhsLength); + memcpy(Get() + lhsLength, rhs.Get(), copyLength); + m_Storage[allocatedLength] = '\0'; + + return *this; +} + +CUtlString &CUtlString::operator+=(const char *rhs) { + Assert(!m_Storage.IsReadOnly()); + + const intp lhsLength(Length()); + const intp rhsLength(V_strlen(rhs)); + const intp requestedLength(lhsLength + rhsLength); + + SetLength(requestedLength); + const intp allocatedLength(Length()); + const intp copyLength(allocatedLength - lhsLength < rhsLength + ? allocatedLength - lhsLength + : rhsLength); + memcpy(Get() + lhsLength, rhs, copyLength); + m_Storage[allocatedLength] = '\0'; + + return *this; +} + +CUtlString &CUtlString::operator+=(char c) { + Assert(!m_Storage.IsReadOnly()); + + intp nLength = Length(); + SetLength(nLength + 1); + m_Storage[nLength] = c; + m_Storage[nLength + 1] = '\0'; + return *this; +} + +CUtlString &CUtlString::operator+=(int rhs) { + Assert(!m_Storage.IsReadOnly()); + Assert(sizeof(rhs) == 4); + + char tmpBuf[12]; // Sufficient for a signed 32 bit integer [ -2147483648 to + // +2147483647 ] + V_snprintf(tmpBuf, sizeof(tmpBuf), "%d", rhs); + tmpBuf[sizeof(tmpBuf) - 1] = '\0'; + + return operator+=(tmpBuf); +} + +CUtlString &CUtlString::operator+=(double rhs) { + Assert(!m_Storage.IsReadOnly()); + + char tmpBuf[256]; // How big can doubles be??? Dunno. + V_snprintf(tmpBuf, sizeof(tmpBuf), "%lg", rhs); + tmpBuf[sizeof(tmpBuf) - 1] = '\0'; + + return operator+=(tmpBuf); +} + +bool CUtlString::MatchesPattern(const CUtlString &Pattern, int nFlags) const { + const char *pszSource = String(); + const char *pszPattern = Pattern.String(); + bool bExact = true; + + while (1) { + if ((*pszPattern) == 0) { + return ((*pszSource) == 0); + } + + if ((*pszPattern) == '*') { + pszPattern++; + + if ((*pszPattern) == 0) { + return true; + } + + bExact = false; + continue; + } + + intp nLength = 0; + + while ((*pszPattern) != '*' && (*pszPattern) != 0) { + nLength++; + pszPattern++; + } + + while (1) { + const char *pszStartPattern = pszPattern - nLength; + const char *pszSearch = pszSource; + + for (intp i = 0; i < nLength; i++, pszSearch++, pszStartPattern++) { + if ((*pszSearch) == 0) { + return false; + } + + if ((*pszSearch) != (*pszStartPattern)) { + break; + } + } + + if (pszSearch - pszSource == nLength) { + break; + } + + if (bExact == true) { + return false; + } + + if ((nFlags & PATTERN_DIRECTORY) != 0) { + if ((*pszPattern) != '/' && (*pszSource) == '/') { + return false; + } + } + + pszSource++; + } + + pszSource += nLength; + } +} + +int CUtlString::Format(PRINTF_FORMAT_STRING const char *pFormat, ...) { + Assert(!m_Storage.IsReadOnly()); + + char tmpBuf[4096]; //< Nice big 4k buffer, as much memory as my first + // computer had, a Radio Shack Color Computer + + va_list marker; + + va_start(marker, pFormat); +#ifdef _WIN32 + int len = _vsnprintf(tmpBuf, sizeof(tmpBuf) - 1, pFormat, marker); +#elif POSIX + int len = vsnprintf(tmpBuf, sizeof(tmpBuf) - 1, pFormat, marker); +#else +#error "define vsnprintf type." +#endif + va_end(marker); + + // Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows + if (len < 0 || len >= sizeof(tmpBuf) - 1) { + len = sizeof(tmpBuf) - 1; + tmpBuf[sizeof(tmpBuf) - 1] = 0; + } + + Set(tmpBuf); + + return len; +} + +//----------------------------------------------------------------------------- +// Strips the trailing slash +//----------------------------------------------------------------------------- +void CUtlString::StripTrailingSlash() { + if (IsEmpty()) return; + + intp nLastChar = Length() - 1; + char c = m_Storage[nLastChar]; + if (c == '\\' || c == '/') { + m_Storage[nLastChar] = 0; + m_Storage.SetLength(m_Storage.Length() - 1); + } +} + +CUtlString CUtlString::Slice(intp nStart, intp nEnd) const { + if (nStart < 0) + nStart = Length() - (-nStart % Length()); + else if (nStart >= Length()) + nStart = Length(); + + if (nEnd == INT32_MAX) + nEnd = Length(); + else if (nEnd < 0) + nEnd = Length() - (-nEnd % Length()); + else if (nEnd >= Length()) + nEnd = Length(); + + if (nStart >= nEnd) return CUtlString(""); + + const char *pIn = String(); + + CUtlString ret; + ret.m_Storage.SetLength(nEnd - nStart + 1); + char *pOut = (char *)ret.m_Storage.Get(); + + memcpy(ret.m_Storage.Get(), &pIn[nStart], nEnd - nStart); + pOut[nEnd - nStart] = 0; + + return ret; +} + +// Grab a substring starting from the left or the right side. +CUtlString CUtlString::Left(intp nChars) const { return Slice(0, nChars); } + +CUtlString CUtlString::Right(intp nChars) const { return Slice(-nChars); } + +CUtlString CUtlString::Replace(char cFrom, char cTo) const { + CUtlString ret = *this; + intp len = ret.Length(); + for (intp i = 0; i < len; i++) { + if (ret.m_Storage[i] == cFrom) ret.m_Storage[i] = cTo; + } + + return ret; +} + +CUtlString CUtlString::AbsPath(const char *pStartingDir) const { + char szNew[MAX_PATH]; + V_MakeAbsolutePath(szNew, sizeof(szNew), this->String(), pStartingDir); + return CUtlString(szNew); +} + +CUtlString CUtlString::UnqualifiedFilename() const { + const char *pFilename = V_UnqualifiedFileName(this->String()); + return CUtlString(pFilename); +} + +CUtlString CUtlString::DirName() const { + CUtlString ret(this->String()); + V_StripLastDir((char *)ret.m_Storage.Get(), ret.m_Storage.Length()); + V_StripTrailingSlash((char *)ret.m_Storage.Get()); + return ret; +} + +CUtlString CUtlString::PathJoin(const char *pStr1, const char *pStr2) { + char szPath[MAX_PATH]; + V_ComposeFileName(pStr1, pStr2, szPath, sizeof(szPath)); + return CUtlString(szPath); +} + +CUtlString CUtlString::operator+(const char *pOther) const { + CUtlString s = *this; + s += pOther; + return s; +} + +//----------------------------------------------------------------------------- +// Purpose: concatenate the provided string to our current content +//----------------------------------------------------------------------------- +void CUtlString::Append(const char *pchAddition) { + *this += pchAddition; +} diff --git a/tier1/utlsymbol.cpp b/tier1/utlsymbol.cpp new file mode 100644 index 0000000..b18ea46 --- /dev/null +++ b/tier1/utlsymbol.cpp @@ -0,0 +1,457 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Defines a symbol table + +#include "utlsymbol.h" + +#include + +#include "tier0/threadtools.h" +#include "stringpool.h" +#include "generichash.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define INVALID_STRING_INDEX CStringPoolIndex(0xFFFF, 0xFFFF) + +#define MIN_STRING_POOL_SIZE 2048 + +//----------------------------------------------------------------------------- +// globals +//----------------------------------------------------------------------------- + +CUtlSymbolTableMT *CUtlSymbol::s_pSymbolTable = 0; +bool CUtlSymbol::s_bAllowStaticSymbolTable = true; + +//----------------------------------------------------------------------------- +// symbol methods +//----------------------------------------------------------------------------- + +void CUtlSymbol::Initialize() { + // If this assert fails, then the module that this call is in has chosen to + // disallow use of the static symbol table. Usually, it's to prevent confusion + // because it's easy to accidentally use the global symbol table when you + // really want to use a specific one. + Assert(s_bAllowStaticSymbolTable); + + // necessary to allow us to create global symbols + static bool symbolsInitialized = false; + if (!symbolsInitialized) { + s_pSymbolTable = new CUtlSymbolTableMT; + symbolsInitialized = true; + } +} + +void CUtlSymbol::LockTableForRead() { + Initialize(); + s_pSymbolTable->LockForRead(); +} + +void CUtlSymbol::UnlockTableForRead() { s_pSymbolTable->UnlockForRead(); } + +//----------------------------------------------------------------------------- +// Purpose: Singleton to delete table on exit from module +//----------------------------------------------------------------------------- +class CCleanupUtlSymbolTable { + public: + ~CCleanupUtlSymbolTable() { + delete CUtlSymbol::s_pSymbolTable; + CUtlSymbol::s_pSymbolTable = NULL; + } +}; + +static CCleanupUtlSymbolTable g_CleanupSymbolTable; + +CUtlSymbolTableMT *CUtlSymbol::CurrTable() { + Initialize(); + return s_pSymbolTable; +} + +//----------------------------------------------------------------------------- +// string->symbol->string +//----------------------------------------------------------------------------- + +CUtlSymbol::CUtlSymbol(const char *pStr) { + m_Id = CurrTable()->AddString(pStr); +} + +const char *CUtlSymbol::String() const { return CurrTable()->String(m_Id); } + +const char *CUtlSymbol::StringNoLock() const { + return CurrTable()->StringNoLock(m_Id); +} + +void CUtlSymbol::DisableStaticSymbolTable() { + s_bAllowStaticSymbolTable = false; +} + +//----------------------------------------------------------------------------- +// checks if the symbol matches a string +//----------------------------------------------------------------------------- + +bool CUtlSymbol::operator==(const char *pStr) const { + if (m_Id == UTL_INVAL_SYMBOL) return false; + return strcmp(String(), pStr) == 0; +} + +//----------------------------------------------------------------------------- +// symbol table stuff +//----------------------------------------------------------------------------- +inline const char *CUtlSymbolTable::DecoratedStringFromIndex( + const CStringPoolIndex &index) const { + Assert(index.m_iPool < m_StringPools.Count()); + Assert(index.m_iOffset < m_StringPools[index.m_iPool]->m_TotalLen); + + // step over the hash decorating the beginning of the string + return (&m_StringPools[index.m_iPool]->m_Data[index.m_iOffset]); +} + +inline const char *CUtlSymbolTable::StringFromIndex( + const CStringPoolIndex &index) const { + // step over the hash decorating the beginning of the string + return DecoratedStringFromIndex(index) + sizeof(hashDecoration_t); +} + +// The first two bytes of each string in the pool are actually the hash for that +// string. Thus we compare hashes rather than entire strings for a significant +// perf benefit. However since there is a high rate of hash collision we must +// still compare strings if the hashes match. +bool CUtlSymbolTable::CLess::operator()(const CStringPoolIndex &i1, + const CStringPoolIndex &i2) const { + // Need to do pointer math because CUtlSymbolTable is used in CUtlVectors, and + // hence can be arbitrarily moved in memory on a realloc. Yes, this is + // portable. In reality, right now at least, because m_LessFunc is the first + // member of CUtlRBTree, and m_Lookup is the first member of CUtlSymbolTabke, + // this == pTable + CUtlSymbolTable *pTable = + (CUtlSymbolTable *)((byte *)this - + offsetof(CUtlSymbolTable::CTree, m_LessFunc)) - + offsetof(CUtlSymbolTable, m_Lookup); + +#if 1 // using the hashes + const char *str1, *str2; + hashDecoration_t hash1, hash2; + + if (i1 == INVALID_STRING_INDEX) { + str1 = pTable->m_pUserSearchString; + hash1 = pTable->m_nUserSearchStringHash; + } else { + str1 = pTable->DecoratedStringFromIndex(i1); + hashDecoration_t storedHash = + *reinterpret_cast(str1); + str1 += sizeof(hashDecoration_t); + AssertMsg2( + storedHash == (!pTable->m_bInsensitive ? HashString(str1) + : HashStringCaseless(str1)), + "The stored hash (%d) for symbol %s is not correct.", storedHash, str1); + hash1 = storedHash; + } + + if (i2 == INVALID_STRING_INDEX) { + str2 = pTable->m_pUserSearchString; + hash2 = pTable->m_nUserSearchStringHash; + } else { + str2 = pTable->DecoratedStringFromIndex(i2); + hashDecoration_t storedHash = + *reinterpret_cast(str2); + str2 += sizeof(hashDecoration_t); + AssertMsg2( + storedHash == (!pTable->m_bInsensitive ? HashString(str2) + : HashStringCaseless(str2)), + "The stored hash (%d) for symbol '%s' is not correct.", storedHash, + str2); + hash2 = storedHash; + } + + // compare the hashes + if (hash1 == hash2) { + if (!str1 && str2) return false; + if (!str2 && str1) return true; + if (!str1 && !str2) return false; + + // if the hashes match compare the strings + if (!pTable->m_bInsensitive) + return strcmp(str1, str2) < 0; + else + return V_stricmp(str1, str2) < 0; + } else { + return hash1 < hash2; + } + +#else // not using the hashes, just comparing strings + const char *str1 = (i1 == INVALID_STRING_INDEX) ? pTable->m_pUserSearchString + : pTable->StringFromIndex(i1); + const char *str2 = (i2 == INVALID_STRING_INDEX) ? pTable->m_pUserSearchString + : pTable->StringFromIndex(i2); + + if (!str1 && str2) return false; + if (!str2 && str1) return true; + if (!str1 && !str2) return false; + if (!pTable->m_bInsensitive) + return strcmp(str1, str2) < 0; + else + return strcmpi(str1, str2) < 0; +#endif +} + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +CUtlSymbolTable::CUtlSymbolTable(int growSize, int initSize, + bool caseInsensitive) + : m_Lookup(growSize, initSize), + m_bInsensitive(caseInsensitive), + m_nUserSearchStringHash(0), + m_pUserSearchString(NULL), + m_StringPools(8) {} + +CUtlSymbolTable::~CUtlSymbolTable() { + // Release the stringpool string data + RemoveAll(); +} + +CUtlSymbol CUtlSymbolTable::Find(const char *pString) const { + VPROF("CUtlSymbol::Find"); + if (!pString) return CUtlSymbol(); + + // Store a special context used to help with insertion + m_pUserSearchString = pString; + m_nUserSearchStringHash = static_cast( + m_bInsensitive ? HashStringCaseless(pString) : HashString(pString)); + + // Passing this special invalid symbol makes the comparison function + // use the string passed in the context + UtlSymId_t idx = m_Lookup.Find(INVALID_STRING_INDEX); + +#ifdef _DEBUG + m_pUserSearchString = NULL; + m_nUserSearchStringHash = 0; +#endif + + return CUtlSymbol(idx); +} + +intp CUtlSymbolTable::FindPoolWithSpace(intp len) const { + for (intp i = 0; i < m_StringPools.Count(); i++) { + StringPool_t *pPool = m_StringPools[i]; + + if ((pPool->m_TotalLen - pPool->m_SpaceUsed) >= len) { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Finds and/or creates a symbol based on the string +//----------------------------------------------------------------------------- + +CUtlSymbol CUtlSymbolTable::AddString(const char *pString) { + VPROF("CUtlSymbol::AddString"); + if (!pString) return CUtlSymbol(UTL_INVAL_SYMBOL); + + CUtlSymbol id = Find(pString); + + if (id.IsValid()) return id; + + intp lenString = V_strlen(pString) + 1; // length of just the string + intp lenDecorated = + lenString + sizeof(hashDecoration_t); // and with its hash decoration + // make sure that all strings are aligned on 2-byte boundaries so the hashes + // will read correctly + static_assert(sizeof(hashDecoration_t) == 2); + lenDecorated = + (lenDecorated + 1) & (~0x01); // round up to nearest multiple of 2 + + // Find a pool with space for this string, or allocate a new one. + intp iPool = FindPoolWithSpace(lenDecorated); + if (iPool == -1) { + // Add a new pool. + intp newPoolSize = + MAX(lenDecorated + sizeof(StringPool_t), MIN_STRING_POOL_SIZE); + StringPool_t *pPool = (StringPool_t *)malloc(newPoolSize); + pPool->m_TotalLen = newPoolSize - sizeof(StringPool_t); + pPool->m_SpaceUsed = 0; + iPool = m_StringPools.AddToTail(pPool); + } + + // Compute a hash + hashDecoration_t hash = static_cast( + m_bInsensitive ? HashStringCaseless(pString) : HashString(pString)); + + // Copy the string in. + StringPool_t *pPool = m_StringPools[iPool]; + Assert(pPool->m_SpaceUsed < + 0xFFFF); // This should never happen, because if we had a string > + // 64k, it would have been given its entire own pool. + + unsigned short iStringOffset = + static_cast(pPool->m_SpaceUsed); + const char *startingAddr = &pPool->m_Data[pPool->m_SpaceUsed]; + + // store the hash at the head of the string + *((hashDecoration_t *)(startingAddr)) = hash; + // and then the string's data + memcpy((void *)(startingAddr + sizeof(hashDecoration_t)), pString, lenString); + pPool->m_SpaceUsed += lenDecorated; + + // insert the string into the vector. + CStringPoolIndex index; + index.m_iPool = static_cast(iPool); + index.m_iOffset = iStringOffset; + + MEM_ALLOC_CREDIT(); + UtlSymId_t idx = m_Lookup.Insert(index); + return CUtlSymbol(idx); +} + +//----------------------------------------------------------------------------- +// Look up the string associated with a particular symbol +//----------------------------------------------------------------------------- + +const char *CUtlSymbolTable::String(CUtlSymbol id) const { + if (!id.IsValid()) return ""; + + Assert(m_Lookup.IsValidIndex((UtlSymId_t)id)); + return StringFromIndex(m_Lookup[id]); +} + +//----------------------------------------------------------------------------- +// Remove all symbols in the table. +//----------------------------------------------------------------------------- + +void CUtlSymbolTable::RemoveAll() { + m_Lookup.Purge(); + + for (int i = 0; i < m_StringPools.Count(); i++) free(m_StringPools[i]); + + m_StringPools.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFileName - +// Output : FileNameHandle_t +//----------------------------------------------------------------------------- +FileNameHandle_t CUtlFilenameSymbolTable::FindOrAddFileName( + const char *pFileName) { + if (!pFileName) { + return NULL; + } + + // find first + FileNameHandle_t hFileName = FindFileName(pFileName); + if (hFileName) { + return hFileName; + } + + // Fix slashes+dotslashes and make lower case first.. + char fn[MAX_PATH]; + V_strncpy(fn, pFileName, sizeof(fn)); + V_RemoveDotSlashes(fn); + + // Split the filename into constituent parts + char basepath[MAX_PATH]; + V_ExtractFilePath(fn, basepath, sizeof(basepath)); + char filename[MAX_PATH]; + V_strncpy(filename, fn + V_strlen(basepath), sizeof(filename)); + + // not found, lock and look again + alignas(FileNameHandle_t) FileNameHandleInternal_t handle; + m_lock.LockForWrite(); + handle.path = m_StringPool.FindStringHandle(basepath); + handle.file = m_StringPool.FindStringHandle(filename); + if (handle.path && handle.file) { + // found + m_lock.UnlockWrite(); + return *(FileNameHandle_t *)(&handle); + } + + // safely add it + handle.path = m_StringPool.ReferenceStringHandle(basepath); + handle.file = m_StringPool.ReferenceStringHandle(filename); + m_lock.UnlockWrite(); + + return *(FileNameHandle_t *)(&handle); +} + +FileNameHandle_t CUtlFilenameSymbolTable::FindFileName(const char *pFileName) { + if (!pFileName) { + return NULL; + } + + // Fix slashes+dotslashes and make lower case first.. + char fn[MAX_PATH]; + V_strncpy(fn, pFileName, sizeof(fn)); + V_RemoveDotSlashes(fn); + + // Split the filename into constituent parts + char basepath[MAX_PATH]; + V_ExtractFilePath(fn, basepath, sizeof(basepath)); + char filename[MAX_PATH]; + V_strncpy(filename, fn + V_strlen(basepath), sizeof(filename)); + + alignas(FileNameHandle_t) FileNameHandleInternal_t handle; + + m_lock.LockForRead(); + handle.path = m_StringPool.FindStringHandle(basepath); + handle.file = m_StringPool.FindStringHandle(filename); + m_lock.UnlockRead(); + + if ((handle.path == 0) || (handle.file == 0)) return NULL; + + return *(FileNameHandle_t *)(&handle); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// Output : const char +//----------------------------------------------------------------------------- +bool CUtlFilenameSymbolTable::String(const FileNameHandle_t &handle, char *buf, + int buflen) { + buf[0] = 0; + + FileNameHandleInternal_t *internal = (FileNameHandleInternal_t *)&handle; + + m_lock.LockForRead(); + const char *path = m_StringPool.HandleToString(internal->path); + const char *fn = m_StringPool.HandleToString(internal->file); + m_lock.UnlockRead(); + + if (!path || !fn) { + return false; + } + + V_strncpy(buf, path, buflen); + V_strncat(buf, fn, buflen, COPY_ALL_CHARACTERS); + + return true; +} + +void CUtlFilenameSymbolTable::RemoveAll() { m_StringPool.FreeAll(); } + +void CUtlFilenameSymbolTable::SpewStrings() { + m_lock.LockForRead(); + m_StringPool.SpewStrings(); + m_lock.UnlockRead(); +} + +bool CUtlFilenameSymbolTable::SaveToBuffer(CUtlBuffer &buffer) { + m_lock.LockForRead(); + bool bResult = m_StringPool.SaveToBuffer(buffer); + m_lock.UnlockRead(); + + return bResult; +} + +bool CUtlFilenameSymbolTable::RestoreFromBuffer(CUtlBuffer &buffer) { + m_lock.LockForWrite(); + bool bResult = m_StringPool.RestoreFromBuffer(buffer); + m_lock.UnlockWrite(); + + return bResult; +} diff --git a/utils/vpc/Makefile b/utils/vpc/Makefile new file mode 100644 index 0000000..d2ee222 --- /dev/null +++ b/utils/vpc/Makefile @@ -0,0 +1,299 @@ +# Make command to use for dependencies +SHELL=/bin/sh +RM:=rm +MKDIR:=mkdir +OS:=$(shell uname) +EXE_POSTFIX:="" + + +# ---------------------------------------------------------------- # +# Figure out if we're building in the Steam tree or not. +# ---------------------------------------------------------------- # + +SRCROOT:=../.. +-include $(SRCROOT)/devtools/steam_def.mak + + +# ---------------------------------------------------------------- # +# Set paths to gcc. +# ---------------------------------------------------------------- # + +CC:=gcc +CXX:=g++ + +ifeq ($(OS),Darwin) +SDKROOT:=$(shell xcodebuild -sdk macosx -version Path) +CC:=clang -m32 +CXX:=clang++ -m32 +EXE_POSTFIX:=_osx +endif + +ifeq ($(OS),Linux) +ifeq ($(wildcard /valve/bin/gcc),) +CC:=gcc +CXX:=g++ +else +CC:=/valve/bin/gcc-4.7 +CXX:=/valve/bin/g++-4.7 +endif +EXE_POSTFIX:=_linux +endif + +ifneq ($(CC_OVERRIDE),) + CC:=$(CC_OVERRIDE) + CXX:=$(CPP_OVERRIDE) +endif + + +# ---------------------------------------------------------------- # +# Lists of files. +# ---------------------------------------------------------------- # + +VPC_SRC:= \ + exprsimplifier.cpp \ + groupscript.cpp \ + conditionals.cpp \ + macros.cpp \ + projectscript.cpp \ + scriptsource.cpp \ + baseprojectdatacollector.cpp \ + configuration.cpp \ + dependencies.cpp \ + main.cpp \ + vpc.cpp \ + projectgenerator_makefile.cpp \ + solutiongenerator_makefile.cpp \ + solutiongenerator_xcode.cpp \ + sys_utils.cpp \ + ../vpccrccheck/crccheck_shared.cpp \ + projectgenerator_codelite.cpp \ + solutiongenerator_codelite.cpp \ + +TIER0_SRC:= \ + ../../tier0/assert_dialog.cpp \ + ../../tier0/cpu_posix.cpp \ + ../../tier0/cpu.cpp \ + ../../tier0/dbg.cpp \ + ../../tier0/fasttimer.cpp \ + ../../tier0/mem.cpp \ + ../../tier0/mem_helpers.cpp \ + ../../tier0/memdbg.cpp \ + ../../tier0/memstd.cpp \ + ../../tier0/memvalidate.cpp \ + ../../tier0/minidump.cpp \ + ../../tier0/pch_tier0.cpp \ + ../../tier0/threadtools.cpp \ + ../../tier0/valobject.cpp \ + ../../tier0/vprof.cpp + + +TIER1_SRC:= \ + ../../tier1/keyvalues.cpp \ + ../../tier1/checksum_crc.cpp \ + ../../tier1/checksum_md5.cpp \ + ../../tier1/convar.cpp \ + ../../tier1/generichash.cpp \ + ../../tier1/interface.cpp \ + ../../tier1/mempool.cpp \ + ../../tier1/memstack.cpp \ + ../../tier1/stringpool.cpp \ + ../../tier1/utlbuffer.cpp \ + ../../tier1/utlsymbol.cpp + +VSTDLIB_SRC:= \ + ../../vstdlib/cvar.cpp \ + ../../vstdlib/vstrtools.cpp \ + ../../vstdlib/random.cpp + + +ifeq "$(STEAM_BRANCH)" "1" + TIER0_SRC+= \ + ../../tier0/tier0.cpp \ + ../../tier0/platform_posix.cpp \ + ../../tier0/validator.cpp \ + ../../tier0/thread.cpp \ + ../../tier0/pmelib.cpp \ + ../../tier0/pme_posix.cpp \ + ../../tier0/testthread.cpp \ + ../../tier0/cpu_posix.cpp \ + ../../tier0/memblockhdr.cpp + + VSTDLIB_SRC+= \ + ../../vstdlib/keyvaluessystem.cpp \ + ../../vstdlib/qsort_s.cpp \ + ../../vstdlib/strtools.cpp \ + ../../vstdlib/stringnormalize.cpp \ + ../../vstdlib/splitstring.cpp \ + ../../vstdlib/commandline.cpp + + INTERFACES_SRC= + + BINLAUNCH_SRC = + +else + + TIER0_SRC+= \ + ../../tier0/platform_posix.cpp \ + ../../tier0/pme_posix.cpp \ + ../../tier0/commandline.cpp \ + ../../tier0/win32consoleio.cpp \ + ../../tier0/logging.cpp \ + ../../tier0/tier0_strtools.cpp + + TIER1_SRC+= \ + ../../tier1/utlstring.cpp \ + ../../tier1/tier1.cpp \ + ../../tier1/characterset.cpp \ + ../../tier1/splitstring.cpp \ + ../../tier1/strtools.cpp \ + ../../tier1/exprevaluator.cpp \ + + VSTDLIB_SRC+= \ + ../../vstdlib/keyvaluessystem.cpp + + INTERFACES_SRC= \ + ../../interfaces/interfaces.cpp + + BINLAUNCH_SRC = \ + +endif + + +SRC:=$(VPC_SRC) $(TIER0_SRC) $(TIER1_SRC) $(VSTDLIB_SRC) $(INTERFACES_SRC) $(BINLAUNCH_SRC) + + +# -----Begin user-editable area----- + +# -----End user-editable area----- + +# If no configuration is specified, "Debug" will be used +ifndef "CFG" +CFG:=Release +endif + + +# +# Configuration: Debug +# +ifeq "$(CFG)" "Debug" + +OUTDIR:=obj/$(OS)/debug +CONFIG_DEPENDENT_FLAGS:=-O0 -g3 -ggdb + +else + +OUTDIR:=obj/$(OS)/release +CONFIG_DEPENDENT_FLAGS:=-O3 -g1 -ggdb + +endif + +OBJS:=$(addprefix $(OUTDIR)/, $(subst ../../, ,$(SRC:.cpp=.o))) + + +OUTFILE:=$(OUTDIR)/vpc +CFG_INC:=-I../../public -I../../common -I../../public/tier0 \ + -I../../public/tier1 -I../../public/tier2 -I../../public/vstdlib + + +CFLAGS=-D_POSIX -DPOSIX -DGNUC -DNDEBUG $(CONFIG_DEPENDENT_FLAGS) -msse -mmmx -pipe -w -fpermissive -fPIC $(CFG_INC) +ifeq "$(STEAM_BRANCH)" "1" +CFLAGS+= -DSTEAM +endif + + +ifeq "$(OS)" "Darwin" +CFLAGS+=-I$(SDKROOT)/usr/include/malloc +CFLAGS+= -DOSX -D_OSX +CFLAGS+= -arch i386 -fasm-blocks +endif + +ifeq "$(OS)" "Linux" +CFLAGS+= -DPLATFORM_LINUX -D_LINUX -DLINUX +endif + +ifeq ($(CYGWIN),1) +CFLAGS+=-D_CYGWIN -DCYGWIN -D_CYGWIN_WINDOWS_TARGET +endif + +CFLAGS+= -DCOMPILER_GCC + +# the sed magic here adds the dependency file to the list of things that depend on the computed dependency +# set, so if any of them change, the dependencies are re-made +MAKEDEPEND=$(CXX) -M -MT $@ -MM $(CFLAGS) $< | sed -e 's@^\(.*\)\.o:@\1.d \1.o:@' > $(@:.o=.d) +COMPILE=$(CXX) -c $(CFLAGS) -o $@ $< +LINK=$(CXX) $(CONFIG_DEPENDENT_FLAGS) -o "$(OUTFILE)" $(OBJS) -ldl -lpthread + +ifeq "$(OS)" "Darwin" +LINK+=-liconv -framework Foundation +endif + +ifeq "$(OS)" "Darwin" +LINK+= -arch i386 +endif + + +# Build rules +all: $(OUTFILE) ../../../../devtools/bin/vpc$(EXE_POSTFIX) + +../../../../devtools/bin/vpc$(EXE_POSTFIX) : $(OUTFILE) + cp "$(OUTFILE)" ../../../../devtools/bin/vpc$(EXE_POSTFIX) + +$(OUTFILE): Makefile $(OBJS) + $(LINK) + + +# Rebuild this project +rebuild: cleanall all + +# Clean this project +clean: + $(RM) -f $(OUTFILE) + $(RM) -f $(OBJS) + $(RM) -f $(OBJS:.o=.d) + $(RM) -f ../../../../devtools/bin/vpc$(EXE_POSTFIX) + +# Clean this project and all dependencies +cleanall: clean + +# magic rules - tread with caution +-include $(OBJS:.o=.d) + +# Pattern rules +$(OUTDIR)/%.o : %.cpp + -$(MKDIR) -p $(@D) + @$(MAKEDEPEND); + $(COMPILE) + +$(OUTDIR)/tier0/%.o : ../../tier0/%.cpp + -$(MKDIR) -p $(@D) + @$(MAKEDEPEND); + $(COMPILE) + +$(OUTDIR)/tier1/%.o : ../../tier1/%.cpp + -$(MKDIR) -p $(@D) + @$(MAKEDEPEND); + $(COMPILE) + +$(OUTDIR)/vstdlib/%.o : ../../vstdlib/%.cpp + -$(MKDIR) -p $(@D) + @$(MAKEDEPEND); + $(COMPILE) + +$(OUTDIR)/interfaces/%.o : ../../interfaces/%.cpp + if [ ! -d $(@D) ]; then $(MKDIR) $(@D); fi + @$(MAKEDEPEND); + $(COMPILE) + +$(OUTDIR)/utils/binlaunch/%.o : ../binlaunch/%.cpp + if [ ! -d $(@D) ]; then $(MKDIR) $(@D); fi + @$(MAKEDEPEND); + $(COMPILE) + + +# the tags file) seems like more work than it's worth. feel free to fix that up +# if it bugs you. +TAGS: + @find . -name '*.cpp' -print0 | xargs -0 etags --declarations --ignore-indentation + @find . -name '*.h' -print0 | xargs -0 etags --language=c++ --declarations --ignore-indentation --append + @find . -name '*.c' -print0 | xargs -0 etags --declarations --ignore-indentation --append + diff --git a/utils/vpc/baseprojectdatacollector.cpp b/utils/vpc/baseprojectdatacollector.cpp new file mode 100644 index 0000000..8a6147d --- /dev/null +++ b/utils/vpc/baseprojectdatacollector.cpp @@ -0,0 +1,341 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "baseprojectdatacollector.h" + +#include "tier1/utlstack.h" +#include "p4lib/ip4.h" + +#include "tier0/memdbgon.h" + +static const char *const s_rgsAmbiguousPropertyNames[] = { + "$CommandLine", +}; + +// ------------------------------------------------------------------------------------------------ +// // CSpecificConfig implementation. +// ------------------------------------------------------------------------------------------------ +// // + +CSpecificConfig::CSpecificConfig(CSpecificConfig *pParentConfig) + : m_pParentConfig(pParentConfig) { + m_pKV = new KeyValues(""); + m_bFileExcluded = false; + m_bIsSchema = false; + m_bIsDynamic = false; +} + +CSpecificConfig::~CSpecificConfig() { m_pKV->deleteThis(); } + +const char *CSpecificConfig::GetConfigName() { return m_pKV->GetName(); } + +const char *CSpecificConfig::GetOption(const char *pOptionName) { + const char *pRet = m_pKV->GetString(pOptionName, NULL); + if (pRet) return pRet; + + if (m_pParentConfig) + return m_pParentConfig->m_pKV->GetString(pOptionName, NULL); + + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// // CFileConfig implementation. +// ------------------------------------------------------------------------------------------------ +// // + +CFileConfig::~CFileConfig() { Term(); } + +void CFileConfig::Term() { m_Configurations.PurgeAndDeleteElements(); } + +const char *CFileConfig::GetName() { return m_Filename.String(); } + +CSpecificConfig *CFileConfig::GetConfig(const char *pConfigName) { + int i = m_Configurations.Find(pConfigName); + if (i == m_Configurations.InvalidIndex()) + return NULL; + else + return m_Configurations[i]; +} + +CSpecificConfig *CFileConfig::GetOrCreateConfig( + const char *pConfigName, CSpecificConfig *pParentConfig) { + int i = m_Configurations.Find(pConfigName); + if (i == m_Configurations.InvalidIndex()) { + CSpecificConfig *pConfig = new CSpecificConfig(pParentConfig); + i = m_Configurations.Insert(pConfigName, pConfig); + } + + return m_Configurations[i]; +} + +bool CFileConfig::IsExcludedFrom(const char *pConfigName) { + CSpecificConfig *pSpecificConfig = GetConfig(pConfigName); + if (pSpecificConfig) + return pSpecificConfig->m_bFileExcluded; + else + return false; +} + +bool CFileConfig::IsDynamicFile(const char *pConfigName) { + CSpecificConfig *pSpecificConfig = GetConfig(pConfigName); + if (pSpecificConfig) + return pSpecificConfig->m_bIsDynamic; + else + return false; +} + +// ------------------------------------------------------------------------------------------------ +// // CBaseProjectDataCollector implementation. +// ------------------------------------------------------------------------------------------------ +// // + +CBaseProjectDataCollector::CBaseProjectDataCollector( + CRelevantPropertyNames *pNames) + : m_Files(k_eDictCompareTypeFilenames) { + m_RelevantPropertyNames.m_nNames = 0; + m_RelevantPropertyNames.m_pNames = NULL; + + if (pNames) { + m_RelevantPropertyNames = *pNames; + } +} + +CBaseProjectDataCollector::~CBaseProjectDataCollector() { Term(); } + +void CBaseProjectDataCollector::StartProject() { + for (int i = 0; i < m_RelevantPropertyNames.m_nNames; i++) { + for (auto *amb : s_rgsAmbiguousPropertyNames) { + if (V_stricmp(m_RelevantPropertyNames.m_pNames[i], amb) == 0) + g_pVPC->VPCWarning( + "Property name %s may occur in multiple contexts and should be " + "fully qualified", + m_RelevantPropertyNames.m_pNames[i]); + } + } + m_ProjectName = "UNNAMED"; + m_CurFileConfig.Push(&m_BaseConfigData); + m_CurSpecificConfig.Push(NULL); +} + +void CBaseProjectDataCollector::EndProject() {} + +void CBaseProjectDataCollector::Term() { + m_BaseConfigData.Term(); + m_Files.PurgeAndDeleteElements(); + m_CurFileConfig.Purge(); + m_CurSpecificConfig.Purge(); +} + +CUtlString CBaseProjectDataCollector::GetProjectName() { return m_ProjectName; } + +void CBaseProjectDataCollector::SetProjectName(const char *pProjectName) { + char tmpBuf[MAX_PATH]; + V_strncpy(tmpBuf, pProjectName, sizeof(tmpBuf)); + V_strlower(tmpBuf); + m_ProjectName = tmpBuf; +} + +// Get a list of all configurations. +void CBaseProjectDataCollector::GetAllConfigurationNames( + CUtlVector &configurationNames) { + configurationNames.Purge(); + for (int i = m_BaseConfigData.m_Configurations.First(); + i != m_BaseConfigData.m_Configurations.InvalidIndex(); + i = m_BaseConfigData.m_Configurations.Next(i)) { + configurationNames.AddToTail( + m_BaseConfigData.m_Configurations.GetElementName(i)); + } +} + +void CBaseProjectDataCollector::StartConfigurationBlock(const char *pConfigName, + bool bFileSpecific) { + CFileConfig *pFileConfig = m_CurFileConfig.Top(); + + // Find or add a new config block. + char sLowerCaseConfigName[MAX_PATH]; + V_strncpy(sLowerCaseConfigName, pConfigName, sizeof(sLowerCaseConfigName)); + V_strlower(sLowerCaseConfigName); + + int index = pFileConfig->m_Configurations.Find(sLowerCaseConfigName); + if (index == -1) { + CSpecificConfig *pParent = + (pFileConfig == &m_BaseConfigData + ? NULL + : m_BaseConfigData.GetOrCreateConfig(sLowerCaseConfigName, NULL)); + + CSpecificConfig *pConfig = new CSpecificConfig(pParent); + pConfig->m_bFileExcluded = false; + pConfig->m_pKV->SetName(sLowerCaseConfigName); + index = pFileConfig->m_Configurations.Insert(sLowerCaseConfigName, pConfig); + } + + // Remember what the current config is. + m_CurSpecificConfig.Push(pFileConfig->m_Configurations[index]); +} + +void CBaseProjectDataCollector::EndConfigurationBlock() { + m_CurSpecificConfig.Pop(); +} + +bool CBaseProjectDataCollector::StartPropertySection(configKeyword_e keyword, + bool *pbShouldSkip) { + m_CurPropertySection.Push(keyword); + return true; +} + +void CBaseProjectDataCollector::HandleProperty(const char *pProperty, + const char *pCustomScriptData) { + CFmtStr sQualifiedProperty( + "%s%s%s", + m_CurPropertySection.Count() + ? g_pVPC->KeywordToName(m_CurPropertySection.Top()) + : "", + m_CurPropertySection.Count() ? "/" : "", pProperty); + bool bSetQualifiedProperty = false; + int i; + for (i = 0; i < m_RelevantPropertyNames.m_nNames; i++) { + if (V_stricmp(m_RelevantPropertyNames.m_pNames[i], pProperty) == 0) break; + if (V_stricmp(m_RelevantPropertyNames.m_pNames[i], + sQualifiedProperty.Access()) == 0) { + bSetQualifiedProperty = true; + break; + } + } + if (i == m_RelevantPropertyNames.m_nNames) { + // not found + return; + } + + if (pCustomScriptData) { + g_pVPC->GetScript().PushScript("HandleProperty( custom script data )", + pCustomScriptData); + } + + const char *pNextToken = g_pVPC->GetScript().PeekNextToken(false); + if (pNextToken && pNextToken[0] != 0) { + // Pass in the previous value so the $base substitution works. + CSpecificConfig *pConfig = m_CurSpecificConfig.Top(); + const char *pBaseString = pConfig->m_pKV->GetString( + bSetQualifiedProperty ? sQualifiedProperty.Access() : pProperty); + char buff[MAX_SYSTOKENCHARS]; + if (g_pVPC->GetScript().ParsePropertyValue(pBaseString, buff, + sizeof(buff))) { + pConfig->m_pKV->SetString( + bSetQualifiedProperty ? sQualifiedProperty.Access() : pProperty, + buff); + } + } + + if (pCustomScriptData) { + // Restore prior script state + g_pVPC->GetScript().PopScript(); + } +} + +void CBaseProjectDataCollector::EndPropertySection(configKeyword_e keyword) { + configKeyword_e kw; + m_CurPropertySection.Pop(kw); + Assert(kw == keyword); +} + +void CBaseProjectDataCollector::StartFolder(const char *pFolderName) {} +void CBaseProjectDataCollector::EndFolder() {} + +bool CBaseProjectDataCollector::StartFile(const char *pFilename, + bool bWarnIfAlreadyExists) { + CFileConfig *pFileConfig = new CFileConfig; + pFileConfig->m_Filename = pFilename; + pFileConfig->m_nInsertOrder = m_Files.Count(); + m_Files.Insert(pFilename, pFileConfig); + + m_CurFileConfig.Push(pFileConfig); + m_CurSpecificConfig.Push(NULL); + + char szFullPath[MAX_PATH]; + + V_GetCurrentDirectory(szFullPath, sizeof(szFullPath)); + V_AppendSlash(szFullPath, sizeof(szFullPath)); + V_strncat(szFullPath, pFilename, sizeof(szFullPath)); + V_RemoveDotSlashes(szFullPath); + +#if 0 + // Add file to Perforce if it isn't there already + if ( Sys_Exists( szFullPath ) ) + { + if ( m_bP4AutoAdd && p4 && !p4->IsFileInPerforce( szFullPath ) ) + { + p4->OpenFileForAdd( szFullPath ); + VPCStatus( "%s automatically opened for add in default changelist.", szFullPath ); + + } + } + else + { + // g_pVPC->Warning( "%s not found on disk at location specified in project script.", szFullPath ); + } +#endif + + return true; +} + +void CBaseProjectDataCollector::EndFile() { + m_CurFileConfig.Pop(); + m_CurSpecificConfig.Pop(); +} + +// This is actually just per-file configuration data. +void CBaseProjectDataCollector::FileExcludedFromBuild(bool bExcluded) { + CSpecificConfig *pConfig = m_CurSpecificConfig.Top(); + pConfig->m_bFileExcluded = bExcluded; +} + +void CBaseProjectDataCollector::FileIsSchema(bool bIsSchema) { + CSpecificConfig *pConfig = m_CurSpecificConfig.Top(); + pConfig->m_bIsSchema = bIsSchema; +} + +void CBaseProjectDataCollector::FileIsDynamic(bool bIsDynamic) { + CSpecificConfig *pConfig = m_CurSpecificConfig.Top(); + pConfig->m_bIsDynamic = bIsDynamic; +} + +bool CBaseProjectDataCollector::RemoveFile(const char *pFilename) { + bool bRet = false; + int i = m_Files.Find(pFilename); + if (i != m_Files.InvalidIndex()) { + delete m_Files[i]; + m_Files.RemoveAt(i); + bRet = true; + } + return bRet; +} + +void CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + const char *pStartString, const char *pFullInputFilename, char *pOut, + int outLen) { + // Decompose the input filename. + char sInputDir[MAX_PATH], sFileBase[MAX_PATH]; + if (!V_ExtractFilePath(pFullInputFilename, sInputDir, sizeof(sInputDir))) + V_strcpy(sInputDir, "."); + + V_FileBase(pFullInputFilename, sFileBase, sizeof(sFileBase)); + + // Handle $(InputPath), $(InputDir), $(InputName) + char *strings[2] = {(char *)stackalloc(outLen), (char *)stackalloc(outLen)}; + + V_StrSubst(pStartString, "$(InputPath)", pFullInputFilename, strings[0], + outLen); + V_StrSubst(strings[0], "$(InputDir)", sInputDir, strings[1], outLen); + V_StrSubst(strings[1], "$(InputName)", sFileBase, strings[0], outLen); + V_StrSubst(strings[0], "$(IntDir)", "$(OBJ_DIR)", strings[1], outLen); + V_StrSubst(strings[1], "$(InputFileName)", + pFullInputFilename + V_strlen(sInputDir), strings[0], outLen); + V_StrSubst(strings[0], "$(ConfigurationName)", "${CONFIGURATION}", strings[1], + outLen); + V_StrSubst(strings[1], "$(Configuration)", "${CONFIGURATION}", strings[0], + outLen); + + V_strncpy(pOut, strings[0], outLen); + V_FixSlashes(pOut, '/'); +} diff --git a/utils/vpc/baseprojectdatacollector.h b/utils/vpc/baseprojectdatacollector.h new file mode 100644 index 0000000..016c423 --- /dev/null +++ b/utils/vpc/baseprojectdatacollector.h @@ -0,0 +1,135 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_BASEPROJECTDATACOLLECTOR_H_ +#define VPC_BASEPROJECTDATACOLLECTOR_H_ + +#include "tier1/keyvalues.h" +#include "tier1/utlstack.h" + +class CSpecificConfig { + public: + CSpecificConfig(CSpecificConfig *pParentConfig); + ~CSpecificConfig(); + + const char *GetConfigName(); + const char *GetOption(const char *pOptionName); + + public: + CSpecificConfig *m_pParentConfig; + KeyValues *m_pKV; + bool m_bFileExcluded; // Is the file that holds this config excluded from the + // build? + bool m_bIsSchema; // Is this a schema file? + bool m_bIsDynamic; // Is this a schema file? +}; + +class CFileConfig { + public: + CFileConfig() : m_nInsertOrder(0) {} + ~CFileConfig(); + + void Term(); + const char *GetName(); + CSpecificConfig *GetConfig(const char *pConfigName); + CSpecificConfig *GetOrCreateConfig(const char *pConfigName, + CSpecificConfig *pParentConfig); + bool IsExcludedFrom(const char *pConfigName); + bool IsDynamicFile(const char *pConfigName); + + public: + CUtlDict m_Configurations; + CUtlString + m_Filename; // "" if this is the config data for the whole project. + int m_nInsertOrder; +}; + +// This just holds the list of property names that we're supposed to scan for. +class CRelevantPropertyNames { + public: + const char **m_pNames; + int m_nNames; +}; + +// This class is shared by the makefile and SlickEdit project file generator. +// It just collects interesting file properties into KeyValues and then the +// project file generator is responsible for using that data to write out a +// project file. +// +class CBaseProjectDataCollector : public IBaseProjectGenerator { + // IBaseProjectGenerator implementation. + public: + CBaseProjectDataCollector(CRelevantPropertyNames *pNames); + ~CBaseProjectDataCollector(); + + // Called before doing anything in a project (in g_pVPC->GetOutputFilename()). + virtual void StartProject(); + virtual void EndProject(); + + // Access the project name. + virtual CUtlString GetProjectName(); + virtual void SetProjectName(const char *pProjectName); + + // Get a list of all configurations. + virtual void GetAllConfigurationNames( + CUtlVector &configurationNames); + + // Configuration data is specified in between these calls and inside + // BeginPropertySection/EndPropertySection. If bFileSpecific is set, then the + // configuration data only applies to the last file added. + virtual void StartConfigurationBlock(const char *pConfigName, + bool bFileSpecific); + virtual void EndConfigurationBlock(); + + // These functions are called when it enters a section like $Compiler, + // $Linker, etc. In between the BeginPropertySection/EndPropertySection, it'll + // call HandleProperty for any properties inside that section. + // + // If you pass pCustomScriptData to HandleProperty, it won't touch the global + // parsing state - it'll parse the platform filters and property value from + // pCustomScriptData instead. + virtual bool StartPropertySection(configKeyword_e keyword, + bool *pbShouldSkip = NULL); + virtual void HandleProperty(const char *pProperty, + const char *pCustomScriptData = NULL); + virtual void EndPropertySection(configKeyword_e keyword); + + // Files go in folders. The generator should maintain a stack of folders as + // they're added. + virtual void StartFolder(const char *pFolderName); + virtual void EndFolder(); + + // Add files. Any config blocks/properties between StartFile/EndFile apply to + // this file only. It will only ever have one active file. + virtual bool StartFile(const char *pFilename, bool bWarnIfAlreadyExists); + virtual void EndFile(); + + // This is actually just per-file configuration data. + virtual void FileExcludedFromBuild(bool bExcluded); + virtual void FileIsSchema(bool bIsSchema); + virtual void FileIsDynamic(bool bIsDynamic); + + // Remove the specified file. + virtual bool RemoveFile( + const char *pFilename); // returns ture if a file was removed + + public: + // This is called in EndProject if bAutoCleanupAfterProject is set. + void Term(); + static void DoStandardVisualStudioReplacements(const char *pStartString, + const char *pFullInputFilename, + char *pOut, int outLen); + + public: + CUtlString m_ProjectName; + + CUtlDict m_Files; + CFileConfig m_BaseConfigData; + + // Either m_BaseConfigData or one of the files. + CUtlStack m_CurFileConfig; + CUtlStack m_CurSpecificConfig; // Debug, release? + CUtlStack m_CurPropertySection; + CRelevantPropertyNames m_RelevantPropertyNames; +}; + +#endif // VPC_BASEPROJECTDATACOLLECTOR_H_ diff --git a/utils/vpc/conditionals.cpp b/utils/vpc/conditionals.cpp new file mode 100644 index 0000000..4711cb5 --- /dev/null +++ b/utils/vpc/conditionals.cpp @@ -0,0 +1,210 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" + +#include + +#include "tier0/memdbgon.h" + +void CVPC::SetupDefaultConditionals() { + // + // PLATFORM Conditionals + // + { + FindOrCreateConditional("WIN32", true, CONDITIONAL_PLATFORM); + FindOrCreateConditional("WIN64", true, CONDITIONAL_PLATFORM); + + // LINUX is the platform but the VPC scripts use $LINUX and $DEDICATED + // (which we automatically create later). + FindOrCreateConditional("LINUX32", true, CONDITIONAL_PLATFORM); + FindOrCreateConditional("LINUX64", true, CONDITIONAL_PLATFORM); + + FindOrCreateConditional("OSX32", true, CONDITIONAL_PLATFORM); + FindOrCreateConditional("OSX64", true, CONDITIONAL_PLATFORM); + + FindOrCreateConditional("X360", true, CONDITIONAL_PLATFORM); + FindOrCreateConditional("PS3", true, CONDITIONAL_PLATFORM); + } + + // + // CUSTOM conditionals + // + { + // setup default custom conditionals + FindOrCreateConditional("PROFILE", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("RETAIL", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("CALLCAP", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("FASTCAP", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("CERT", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("MEMTEST", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("NOFPO", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("POSIX", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("LV", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("DEMO", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("NO_STEAM", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("DVDEMU", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("QTDEBUG", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("NO_CEG", true, CONDITIONAL_CUSTOM); + FindOrCreateConditional("UPLOAD_CEG", true, CONDITIONAL_CUSTOM); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CVPC::GetTargetPlatformName() { + const auto *c = + std::find_if(std::begin(m_Conditionals), std::end(m_Conditionals), + [](const conditional_t &c) noexcept { + return c.type == CONDITIONAL_PLATFORM && c.m_bDefined; + }); + if (c != std::end(m_Conditionals)) return c->name.String(); + + // fatal - should have already been default set + Assert(0); + VPCError("Unspecified platform."); +} + +//----------------------------------------------------------------------------- +// Case Insensitive. Returns true if platform conditional has been marked +// as defined. +//----------------------------------------------------------------------------- +bool CVPC::IsPlatformDefined(const char *name) { + const auto *c = + std::find_if(std::begin(m_Conditionals), std::end(m_Conditionals), + [name](const conditional_t &c) noexcept { + return c.type == CONDITIONAL_PLATFORM && + !V_stricmp(name, c.name.String()) && c.m_bDefined; + }); + return c != std::end(m_Conditionals); +} + +//----------------------------------------------------------------------------- +// Case Insensitive +//----------------------------------------------------------------------------- +conditional_t *CVPC::FindOrCreateConditional(const char *name, + bool should_create, + conditionalType_e type) { + auto *c = std::find_if(std::begin(m_Conditionals), std::end(m_Conditionals), + [name](const conditional_t &c) noexcept { + return !V_stricmp(name, c.name.String()); + }); + if (c != std::end(m_Conditionals)) return c; + + if (!should_create) return nullptr; + + intp index = m_Conditionals.AddToTail(); + + char tmp_name[256]; + V_strncpy(tmp_name, name, sizeof(tmp_name)); + + // primary internal use as lower case, but spewed to user as upper for style + // consistency + auto &cd = m_Conditionals[index]; + cd.name = V_strlower(tmp_name); + cd.upperCaseName = V_strupr(tmp_name); + cd.type = type; + return &cd; +} + +void CVPC::SetConditional(const char *value, bool should_set) { + VPCStatus(false, "Set Conditional: $%s = %s", value, + (should_set ? "1" : "0")); + + conditional_t *c{FindOrCreateConditional(value, true, CONDITIONAL_CUSTOM)}; + if (!c) { + VPCError("Failed to find or create $%s conditional", value); + } + + c->m_bDefined = should_set; +} + +//----------------------------------------------------------------------------- +// Returns true if string has a conditional of the specified type +//----------------------------------------------------------------------------- +bool CVPC::ConditionHasDefinedType(const char *condition, + conditionalType_e type) { + char symbol[MAX_SYSTOKENCHARS]; + + for (auto &&c : m_Conditionals) { + if (c.type != type) continue; + + sprintf(symbol, "$%s", c.name.String()); + + if (V_stristr(condition, symbol)) { + // a define of expected type occurs in the conditional expression + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Callback for expression evaluator. +//----------------------------------------------------------------------------- +bool CVPC::ResolveConditionalSymbol(const char *symbol) { + if (!V_stricmp(symbol, "$0") || !V_stricmp(symbol, "0")) { + return false; + } + + if (!V_stricmp(symbol, "$1") || !V_stricmp(symbol, "1")) { + return true; + } + + const int offset{symbol[0] == '$' ? 1 : 0}; + const conditional_t *c{ + FindOrCreateConditional(symbol + offset, false, CONDITIONAL_NULL)}; + if (c) { + // game conditionals only resolve true when they are 'defined' and 'active' + // only one game conditional is expected to be active at a time + if (c->type == CONDITIONAL_GAME) { + if (!c->m_bDefined) return false; + + return c->m_bGameConditionActive; + } + + // all other type of conditions are gated by their 'defined' state + return c->m_bDefined; + } + + // unknown conditional, defaults to false + return false; +} + +//----------------------------------------------------------------------------- +// Callback for expression evaluator. +//----------------------------------------------------------------------------- +static bool ResolveSymbol(const char *symbol) { + return g_pVPC->ResolveConditionalSymbol(symbol); +} + +//----------------------------------------------------------------------------- +// Callback for expression evaluator. +//----------------------------------------------------------------------------- +static void SymbolSyntaxError(const char *reason) { + // invoke internal syntax error hndling which spews script stack as well + g_pVPC->VPCSyntaxError(reason); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVPC::EvaluateConditionalExpression(const char *expression) { + char buffer[MAX_SYSTOKENCHARS]; + ResolveMacrosInConditional(expression, buffer, sizeof(buffer)); + + if (!buffer[0]) { + // empty string, same as not having a conditional + return true; + } + + bool rc{false}; + + CExpressionEvaluator expression_eval; + const bool is_valid{expression_eval.Evaluate(rc, buffer, ::ResolveSymbol, + ::SymbolSyntaxError)}; + if (!is_valid) { + g_pVPC->VPCSyntaxError("VPC Conditional Evaluation Error"); + } + + return rc; +} diff --git a/utils/vpc/config_general.cpp b/utils/vpc/config_general.cpp new file mode 100644 index 0000000..95913a8 --- /dev/null +++ b/utils/vpc/config_general.cpp @@ -0,0 +1,178 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +bool VPC_Config_General_AdditionalOutputFiles( const char *pPropertyName ) +{ + // Ignore this. We only care about it when looking at dependencies, + // and baseprojectdatacollector will get it in that case. + char buff[MAX_SYSTOKENCHARS]; + ParsePropertyValue( &g_pScriptData, g_pScriptLine, NULL, buff, sizeof( buff ) ); + return true; +} + +bool VPC_Config_General_OutputDirectory( const char *pPropertyName ) +{ + SET_STRING_PROPERTY( pPropertyName, g_spConfig, get_OutputDirectory, put_OutputDirectory ); +} + +bool VPC_Config_General_IntermediateDirectory( const char *pPropertyName ) +{ + SET_STRING_PROPERTY( pPropertyName, g_spConfig, get_IntermediateDirectory, put_IntermediateDirectory ); +} + +bool VPC_Config_General_ExtensionsToDeleteOnClean( const char *pPropertyName ) +{ + SET_STRING_PROPERTY( pPropertyName, g_spConfig, get_DeleteExtensionsOnClean, put_DeleteExtensionsOnClean ); +} + +bool VPC_Config_General_BuildLogFile( const char *pPropertyName ) +{ + SET_STRING_PROPERTY( pPropertyName, g_spConfig, get_BuildLogFile, put_BuildLogFile ); +} + +bool VPC_Config_General_InheritedProjectPropertySheets( const char *pPropertyName ) +{ + SET_STRING_PROPERTY( pPropertyName, g_spConfig, get_InheritedPropertySheets, put_InheritedPropertySheets ); +} + +bool VPC_Config_General_ConfigurationType( const char *pPropertyName ) +{ + char buff[MAX_SYSTOKENCHARS]; + + if ( !ParsePropertyValue( &g_pScriptData, g_pScriptLine, NULL, buff, sizeof( buff ) ) ) + return true; + + ConfigurationTypes option = typeUnknown; + if ( !V_stricmp( buff, "Utility" ) ) + option = typeUnknown; + else if ( !V_stricmp( buff, "Application (.exe)" ) || !V_stricmp( buff, "Title (.xex)" ) ) + option = typeApplication; + else if ( !V_stricmp( buff, "Dynamic Library (.dll)" ) || !V_stricmp( buff, "Dynamic Library (.xex)" ) ) + option = typeDynamicLibrary; + else if ( !V_stricmp( buff, "Static Library (.lib)" ) ) + option = typeStaticLibrary; + else + VPC_SyntaxError(); + + SET_LIST_PROPERTY( pPropertyName, g_spConfig, get_ConfigurationType, put_ConfigurationType, ConfigurationTypes, option ); +} + +bool VPC_Config_General_UseOfMFC( const char *pPropertyName ) +{ + char buff[MAX_SYSTOKENCHARS]; + + if ( !ParsePropertyValue( &g_pScriptData, g_pScriptLine, NULL, buff, sizeof( buff ) ) ) + return true; + + useOfMfc option = useMfcStdWin; + if ( !V_stricmp( buff, "Use Standard Windows Libraries" ) ) + option = useMfcStdWin; + else if ( !V_stricmp( buff, "Use MFC in a Static Library" ) ) + option = useMfcStatic; + else if ( !V_stricmp( buff, "Use MFC in a Shared DLL" ) ) + option = useMfcDynamic; + else + VPC_SyntaxError(); + + SET_LIST_PROPERTY( pPropertyName, g_spConfig, get_useOfMfc, put_useOfMfc, useOfMfc, option ); +} + +bool VPC_Config_General_UseOfATL( const char *pPropertyName ) +{ + char buff[MAX_SYSTOKENCHARS]; + + if ( !ParsePropertyValue( &g_pScriptData, g_pScriptLine, NULL, buff, sizeof( buff ) ) ) + return true; + + useOfATL option = useATLNotSet; + if ( !V_stricmp( buff, "Not Using ATL" ) ) + option = useATLNotSet; + else if ( !V_stricmp( buff, "Static Link to ATL" ) ) + option = useATLStatic; + else if ( !V_stricmp( buff, "Dynamic Link to ATL" ) ) + option = useATLDynamic; + else + VPC_SyntaxError(); + + SET_LIST_PROPERTY( pPropertyName, g_spConfig, get_useOfATL, put_useOfATL, useOfATL, option ); +} + +bool VPC_Config_General_MinimizeCRTUseInATL( const char *pPropertyName ) +{ + SET_BOOL_PROPERTY( pPropertyName, g_spConfig, get_ATLMinimizesCRunTimeLibraryUsage, put_ATLMinimizesCRunTimeLibraryUsage ); +} + +bool VPC_Config_General_CharacterSet( const char *pPropertyName ) +{ + char buff[MAX_SYSTOKENCHARS]; + + if ( !ParsePropertyValue( &g_pScriptData, g_pScriptLine, NULL, buff, sizeof( buff ) ) ) + return true; + + charSet option = charSetNotSet; + if ( !V_stricmp( buff, "Not Set" ) ) + option = charSetNotSet; + else if ( !V_stricmp( buff, "Use Unicode Character Set" ) ) + option = charSetUnicode; + else if ( !V_stricmp( buff, "Use Multi-Byte Character Set" ) ) + option = charSetMBCS; + else + VPC_SyntaxError(); + + SET_LIST_PROPERTY( pPropertyName, g_spConfig, get_CharacterSet, put_CharacterSet, charSet, option ); +} + +bool VPC_Config_General_CommonLanguageRuntimeSupport( const char *pPropertyName ) +{ + VPC_Error( "Setting '%s' Not Implemented", pPropertyName ); + return false; +} + +bool VPC_Config_General_WholeProgramOptimization( const char *pPropertyName ) +{ + char buff[MAX_SYSTOKENCHARS]; + + if ( !ParsePropertyValue( &g_pScriptData, g_pScriptLine, NULL, buff, sizeof( buff ) ) ) + return true; + + WholeProgramOptimizationTypes option = WholeProgramOptimizationNone; + if ( !V_stricmp( buff, "No Whole Program Optimization" ) ) + option = WholeProgramOptimizationNone; + else if ( !V_stricmp( buff, "Use Link Time Code Generation" ) ) + option = WholeProgramOptimizationLinkTimeCodeGen; + else if ( !V_stricmp( buff, "Profile Guided Optimization - Instrument" ) ) + option = WholeProgramOptimizationPGOInstrument; + else if ( !V_stricmp( buff, "Profile Guided Optimization - Optimize" ) ) + option = WholeProgramOptimizationPGOOptimize; + else if ( !V_stricmp( buff, "Profile Guided Optimization - Update" ) ) + option = WholeProgramOptimizationPGOUpdate; + else + VPC_SyntaxError(); + + SET_LIST_PROPERTY( pPropertyName, g_spConfig, get_WholeProgramOptimization, put_WholeProgramOptimization, WholeProgramOptimizationTypes, option ); +} + +extern bool VPC_Config_IgnoreOption( const char *pPropertyName ); + +property_t g_generalProperties[] = +{ + {g_pOption_AdditionalProjectDependencies, VPC_Config_IgnoreOption }, + {g_pOption_AdditionalOutputFiles, VPC_Config_IgnoreOption }, + {"$GameOutputFile", VPC_Config_IgnoreOption }, + {"$OutputDirectory", VPC_Config_General_OutputDirectory }, + {"$IntermediateDirectory", VPC_Config_General_IntermediateDirectory}, + {"$ExtensionsToDeleteOnClean", VPC_Config_General_ExtensionsToDeleteOnClean}, + {"$BuildLogFile", VPC_Config_General_BuildLogFile}, + {"$InheritedProjectPropertySheets", VPC_Config_General_InheritedProjectPropertySheets}, + {"$ConfigurationType", VPC_Config_General_ConfigurationType}, + {"$UseOfMFC", VPC_Config_General_UseOfMFC, PLATFORM_WINDOWS}, + {"$UseOfATL", VPC_Config_General_UseOfATL, PLATFORM_WINDOWS}, + {"$MinimizeCRTUseInATL", VPC_Config_General_MinimizeCRTUseInATL, PLATFORM_WINDOWS}, + {"$CharacterSet", VPC_Config_General_CharacterSet}, + {"$CommonLanguageRuntimeSupport", VPC_Config_General_CommonLanguageRuntimeSupport, PLATFORM_WINDOWS}, + {"$WholeProgramOptimization", VPC_Config_General_WholeProgramOptimization}, + {NULL} +}; diff --git a/utils/vpc/configuration.cpp b/utils/vpc/configuration.cpp new file mode 100644 index 0000000..c6b6a3c --- /dev/null +++ b/utils/vpc/configuration.cpp @@ -0,0 +1,431 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "tier0/memdbgon.h" + +static KeywordName_t s_KeywordNameTable[] = { + {"$General", KEYWORD_GENERAL}, + {"$Debugging", KEYWORD_DEBUGGING}, + {"$Compiler", KEYWORD_COMPILER}, + {"$SNCCompiler", KEYWORD_PS3_SNCCOMPILER}, + {"$GCCCompiler", KEYWORD_PS3_GCCCOMPILER}, + {"$Librarian", KEYWORD_LIBRARIAN}, + {"$Linker", KEYWORD_LINKER}, + {"$SNCLinker", KEYWORD_PS3_SNCLINKER}, + {"$GCCLinker", KEYWORD_PS3_GCCLINKER}, + {"$ManifestTool", KEYWORD_MANIFEST}, + {"$XMLDocumentGenerator", KEYWORD_XMLDOCGEN}, + {"$BrowseInformation", KEYWORD_BROWSEINFO}, + {"$Resources", KEYWORD_RESOURCES}, + {"$PreBuildEvent", KEYWORD_PREBUILDEVENT}, + {"$PreLinkEvent", KEYWORD_PRELINKEVENT}, + {"$PostBuildEvent", KEYWORD_POSTBUILDEVENT}, + {"$CustomBuildStep", KEYWORD_CUSTOMBUILDSTEP}, + {"$Xbox360ImageConversion", KEYWORD_XBOXIMAGE}, + {"$ConsoleDeployment", KEYWORD_XBOXDEPLOYMENT}, +}; + +const char *CVPC::KeywordToName(configKeyword_e keyword) { + static_assert(V_ARRAYSIZE(s_KeywordNameTable) == KEYWORD_MAX); + + if (keyword == KEYWORD_UNKNOWN) { + return "???"; + } + + return s_KeywordNameTable[keyword].m_pName; +} + +configKeyword_e CVPC::NameToKeyword(const char *pKeywordName) { + static_assert(V_ARRAYSIZE(s_KeywordNameTable) == KEYWORD_MAX); + + for (auto &kn : s_KeywordNameTable) { + if (!V_stricmp(pKeywordName, kn.m_pName)) { + return kn.m_Keyword; + } + } + + return KEYWORD_UNKNOWN; +} + +//----------------------------------------------------------------------------- +// VPC_Config_Keyword +// +//----------------------------------------------------------------------------- +void VPC_Config_Keyword(configKeyword_e keyword, const char *pkeywordToken) { + const char *pToken; + + bool bShouldSkip = false; + if (!g_pVPC->GetProjectGenerator()->StartPropertySection(keyword, + &bShouldSkip)) { + g_pVPC->VPCSyntaxError("Unsupported Keyword: %s for target platform", + pkeywordToken); + } + + if (bShouldSkip) { + pToken = g_pVPC->GetScript().PeekNextToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) + g_pVPC->VPCSyntaxError(); + + g_pVPC->GetScript().SkipBracedSection(); + } else { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) + g_pVPC->VPCSyntaxError(); + + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "}")) { + // end of section + break; + } + + // Copy off the token name so HandleProperty() doesn't have to (or else + // the parser will overwrite it on the next token). + char tempTokenName[MAX_PATH]; + V_strncpy(tempTokenName, pToken, sizeof(tempTokenName)); + + g_pVPC->GetProjectGenerator()->HandleProperty(tempTokenName); + } + } + + g_pVPC->GetProjectGenerator()->EndPropertySection(keyword); +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_Configuration +// +//----------------------------------------------------------------------------- +void VPC_Keyword_Configuration() { + const char *pToken; + char szConfigName[MAX_PATH]; + bool bAllowNextLine = false; + int i; + CUtlVector configs; + char buff[MAX_SYSTOKENCHARS]; + + while (1) { + pToken = g_pVPC->GetScript().GetToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "\\")) { + bAllowNextLine = true; + continue; + } else { + bAllowNextLine = false; + } + + intp index = configs.AddToTail(); + configs[index] = pToken; + + // check for another optional config + pToken = g_pVPC->GetScript().PeekNextToken(bAllowNextLine); + if (!pToken || !pToken[0] || !V_stricmp(pToken, "{") || + !V_stricmp(pToken, "}") || (pToken[0] == '$')) + break; + } + + // no configuration specified, use all known + if (!configs.Count()) { + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames(configs); + if (!configs.Count()) { + g_pVPC->VPCError( + "Trying to parse a configuration block and no configs have been " + "defined yet.\n[%s line:%d]", + g_pVPC->GetScript().GetName(), g_pVPC->GetScript().GetLine()); + } + } + + // save parser state + CScriptSource scriptSource = g_pVPC->GetScript().GetCurrentScript(); + + for (i = 0; i < configs.Count(); i++) { + // restore parser state + g_pVPC->GetScript().RestoreScript(scriptSource); + + V_strncpy(szConfigName, configs[i].String(), sizeof(szConfigName)); + + // get access objects + g_pVPC->GetProjectGenerator()->StartConfigurationBlock(szConfigName, false); + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) { + g_pVPC->VPCSyntaxError(); + } + + while (1) { + g_pVPC->GetScript().SkipToValidToken(); + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff))) { + g_pVPC->GetScript().SkipBracedSection(); + continue; + } + + if (!V_stricmp(buff, "}")) { + // end of section + break; + } + + configKeyword_e keyword = g_pVPC->NameToKeyword(buff); + if (keyword == KEYWORD_UNKNOWN) { + g_pVPC->VPCSyntaxError(); + } else { + VPC_Config_Keyword(keyword, buff); + } + } + + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_FileConfiguration +// +//----------------------------------------------------------------------------- +void VPC_Keyword_FileConfiguration() { + const char *pToken; + char szConfigName[MAX_PATH]; + bool bAllowNextLine = false; + char buff[MAX_SYSTOKENCHARS]; + CUtlVector configurationNames; + + while (1) { + pToken = g_pVPC->GetScript().GetToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "\\")) { + bAllowNextLine = true; + continue; + } else { + bAllowNextLine = false; + } + + strcpy(szConfigName, pToken); + configurationNames.AddToTail(pToken); + + // check for another optional config + pToken = g_pVPC->GetScript().PeekNextToken(bAllowNextLine); + if (!pToken || !pToken[0] || !V_stricmp(pToken, "{") || + !V_stricmp(pToken, "}") || (pToken[0] == '$')) + break; + } + + // no configuration specified, use all known + if (configurationNames.Count() == 0) { + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames(configurationNames); + } + + // save parser state + CScriptSource scriptSource = g_pVPC->GetScript().GetCurrentScript(); + + for (int i = 0; i < configurationNames.Count(); i++) { + // restore parser state + g_pVPC->GetScript().RestoreScript(scriptSource); + + // Tell the generator we're about to feed it configuration data for this + // file. + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[i].String(), true); + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) { + g_pVPC->VPCSyntaxError(); + } + + while (1) { + g_pVPC->GetScript().SkipToValidToken(); + + pToken = g_pVPC->GetScript().PeekNextToken(true); + if (pToken && pToken[0] && !V_stricmp(pToken, "$ExcludedFromBuild")) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) g_pVPC->VPCSyntaxError(); + + char buf[MAX_SYSTOKENCHARS]; + if (g_pVPC->GetScript().ParsePropertyValue(NULL, buf, sizeof(buf))) { + g_pVPC->GetProjectGenerator()->FileExcludedFromBuild( + Sys_StringToBool(buf)); + } + + continue; + } + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff))) { + g_pVPC->GetScript().SkipBracedSection(); + continue; + } + + if (!V_stricmp(buff, "}")) { + // end of section + break; + } + + configKeyword_e keyword = g_pVPC->NameToKeyword(buff); + switch (keyword) { + case KEYWORD_COMPILER: + case KEYWORD_PS3_SNCCOMPILER: + case KEYWORD_PS3_GCCCOMPILER: + case KEYWORD_RESOURCES: + case KEYWORD_CUSTOMBUILDSTEP: + VPC_Config_Keyword(keyword, buff); + break; + default: + g_pVPC->VPCSyntaxError(); + } + } + + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } +} + +//----------------------------------------------------------------------------- +// Just advances past config keywords without acting on them +// +//----------------------------------------------------------------------------- +void VPC_Read_Config_Keywords(const char *pkeywordToken) { + const char *pToken; + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) g_pVPC->VPCSyntaxError(); + + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "}")) { + // end of section + break; + } + } +} + +//----------------------------------------------------------------------------- +// Read a configuration block that applies to an entire folder +// +//----------------------------------------------------------------------------- +void VPC_Keyword_FolderConfiguration(folderConfig_t *pFolderConfig) { + pFolderConfig->Clear(); + + const char *pToken; + char szConfigName[MAX_PATH]; + bool bAllowNextLine = false; + char buff[MAX_SYSTOKENCHARS]; + + while (1) { + pToken = g_pVPC->GetScript().GetToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "\\")) { + bAllowNextLine = true; + continue; + } else { + bAllowNextLine = false; + } + + strcpy(szConfigName, pToken); + pFolderConfig->vecConfigurationNames.AddToTail(pToken); + + // check for another optional config + pToken = g_pVPC->GetScript().PeekNextToken(bAllowNextLine); + if (!pToken || !pToken[0] || !V_stricmp(pToken, "{") || + !V_stricmp(pToken, "}") || (pToken[0] == '$')) + break; + } + + // no configuration specified, use all known + if (pFolderConfig->vecConfigurationNames.Count() == 0) { + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames( + pFolderConfig->vecConfigurationNames); + } + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) { + g_pVPC->VPCSyntaxError(); + } + + // save parser state so we remember where the block is. We'll refer back to + // it when handlign individual files. + pFolderConfig->scriptSource = g_pVPC->GetScript().GetCurrentScript(); + + // just read past all the tokens. We'll reparse this later, per file. + // it would be cool to parse just once, but leaf code in the project + // generator expects the parser to be in the right position and parses + // directly. + while (1) { + g_pVPC->GetScript().SkipToValidToken(); + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff))) { + g_pVPC->GetScript().SkipBracedSection(); + continue; + } + + if (!V_stricmp(buff, "}")) { + // end of section + break; + } + + configKeyword_e keyword = g_pVPC->NameToKeyword(buff); + switch (keyword) { + case KEYWORD_COMPILER: + case KEYWORD_PS3_SNCCOMPILER: + case KEYWORD_PS3_GCCCOMPILER: + case KEYWORD_RESOURCES: + case KEYWORD_CUSTOMBUILDSTEP: { + VPC_Read_Config_Keywords(buff); + } break; + default: + g_pVPC->VPCSyntaxError(); + } + } +} + +//----------------------------------------------------------------------------- +// Apply a folder-wide configuration to an individual file +// +//----------------------------------------------------------------------------- +void VPC_ApplyFolderConfigurationToFile(const folderConfig_t &folderConfig) { + char buff[MAX_SYSTOKENCHARS]; + g_pVPC->GetScript().PushCurrentScript(); + + for (int i = 0; i < folderConfig.vecConfigurationNames.Count(); i++) { + g_pVPC->GetScript().RestoreScript(folderConfig.scriptSource); + + // Tell the generator we're about to feed it configuration data for this + // file. + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + folderConfig.vecConfigurationNames[i].String(), true); + + while (1) { + g_pVPC->GetScript().SkipToValidToken(); + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff))) { + g_pVPC->GetScript().SkipBracedSection(); + continue; + } + + if (!V_stricmp(buff, "}")) { + // end of section + break; + } + + configKeyword_e keyword = g_pVPC->NameToKeyword(buff); + switch (keyword) { + case KEYWORD_COMPILER: + case KEYWORD_PS3_SNCCOMPILER: + case KEYWORD_PS3_GCCCOMPILER: + case KEYWORD_RESOURCES: + case KEYWORD_CUSTOMBUILDSTEP: + VPC_Config_Keyword(keyword, buff); + break; + default: + g_pVPC->VPCSyntaxError(); + } + } + + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } + + g_pVPC->GetScript().PopScript(); +} \ No newline at end of file diff --git a/utils/vpc/dependencies.cpp b/utils/vpc/dependencies.cpp new file mode 100644 index 0000000..c44e0d2 --- /dev/null +++ b/utils/vpc/dependencies.cpp @@ -0,0 +1,1140 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "dependencies.h" +#include "baseprojectdatacollector.h" +#include "tier0/fasttimer.h" + +#include "tier0/memdbgon.h" + + +#define VPC_CRC_CACHE_VERSION 3 + +extern const char *g_IncludeSeparators[2]; + +static const char *g_pDependencyRelevantProperties[] = +{ + g_pOption_AdditionalProjectDependencies, + g_pOption_AdditionalOutputFiles, + g_pOption_AdditionalIncludeDirectories, + g_pOption_GameOutputFile, + g_pOption_ImportLibrary, + g_pOption_OutputFile, +}; + +static CRelevantPropertyNames g_DependencyRelevantPropertyNames = +{ + g_pDependencyRelevantProperties, + V_ARRAYSIZE( g_pDependencyRelevantProperties ) +}; + + +bool IsSharedLibraryFile( const char *pFilename ) +{ + const char *pExt = V_GetFileExtension( pFilename ); + if ( pExt && ( V_stricmp( pExt, "so" ) == 0 || V_stricmp( pExt, "dylib" ) == 0 || V_stricmp( pExt, "dll" ) == 0 ) ) + { + return true; + } + else + { + return false; + } +} + +bool IsLibraryFile( const char *pFilename ) +{ + const char *pExt = V_GetFileExtension( pFilename ); + if ( IsSharedLibraryFile( pFilename ) || ( pExt && ( V_stricmp( pExt, "lib" ) == 0 || V_stricmp( pExt, "a" ) == 0 ) ) ) + { + return true; + } + else + { + return false; + } +} + +static inline bool IsSourceFile( const char *pFilename ) +{ + const char *pExt = V_GetFileExtension( pFilename ); + + if ( pExt && ( IsCFileExtension( pExt ) || IsHFileExtension( pExt ) || + !V_stricmp( pExt, "rc" ) || !V_stricmp( pExt, "inc" ) ) ) + { + return true; + } + else + { + return false; + } +} + + + +// ------------------------------------------------------------------------------------------------------- // +// CDependency functions. +// ------------------------------------------------------------------------------------------------------- // + +CDependency::CDependency( CProjectDependencyGraph *pDependencyGraph ) : + m_pDependencyGraph( pDependencyGraph ) +{ + m_Type = k_eDependencyType_Unknown; + m_iDependencyMark = m_pDependencyGraph->m_iDependencyMark - 1; + m_bCheckedIncludes = false; + m_nCacheModificationTime = m_nCacheFileSize = 0; + m_bCacheDirty = false; +} + +CDependency::~CDependency() +{ +} + +const char* CDependency::GetName() const +{ + return m_Filename.String(); +} + +bool CDependency::CompareAbsoluteFilename( const char *pAbsPath ) const +{ + return ( V_stricmp( m_Filename.String(), pAbsPath ) == 0 ); +} + +bool CDependency::DependsOn( CDependency *pTest, int flags ) +{ + m_pDependencyGraph->ClearAllDependencyMarks(); + CUtlVector callTreeOutputStack; + if ( FindDependency_Internal( callTreeOutputStack, pTest, flags, 1 ) ) + { + if ( g_pVPC->IsShowDependencies() ) + { + printf( "-------------------------------------------------------------------------------\n" ); + printf( "%s\n", GetName() ); + intp i; + for( i = callTreeOutputStack.Count() - 1; i >= 0; i-- ) + { + printf("%s", (const char *)callTreeOutputStack[i].Base()); + } + printf( "-------------------------------------------------------------------------------\n" ); + } + return true; + } + else + { + return false; + } +} + +bool CDependency::FindDependency_Internal( CUtlVector &callTreeOutputStack, CDependency *pTest, int flags, int depth ) +{ + if ( pTest == this ) + return true; + + // Don't revisit us. + if ( HasBeenMarked() ) + return false; + + Mark(); + + // Don't recurse further? + if ( depth > 1 && !(flags & k_EDependsOnFlagRecurse) ) + return false; + + // Don't go into the children of libs if they don't want. + if ( !(flags & k_EDependsOnFlagTraversePastLibs) && m_Type == k_eDependencyType_Library ) + return false; + + // Go through everything I depend on. If any of those things + for ( int iDepList=0; iDepList < 2; iDepList++ ) + { + if ( iDepList == 1 && !(flags & k_EDependsOnFlagCheckAdditionalDependencies) ) + continue; + + CUtlVector &depList = (iDepList == 0 ? m_Dependencies : m_AdditionalDependencies); + + for ( int i=0; i < depList.Count(); i++ ) + { + CDependency *pChild = depList[i]; + if ( pChild->FindDependency_Internal( callTreeOutputStack, pTest, flags, depth+1 ) ) + { + if ( g_pVPC->IsShowDependencies() ) + { + char buf[2048]; + V_strncpy( buf, "depends on ", sizeof( buf ) ); + V_strcat( buf, pChild->GetName(), sizeof( buf ) ); + V_strcat( buf, "\n", sizeof( buf ) ); + intp n = callTreeOutputStack.AddToTail(); + CUtlBuffer &b = callTreeOutputStack[n]; + b.EnsureCapacity( V_strlen( buf ) + 2 ); + b.PutString( buf ); + b.PutChar( 0 ); + } + return true; + } + } + } + + return false; +} + +void CDependency::Mark() +{ + m_iDependencyMark = m_pDependencyGraph->m_iDependencyMark; +} + +bool CDependency::HasBeenMarked() +{ + return m_iDependencyMark == m_pDependencyGraph->m_iDependencyMark; +} + + +CDependency_Project::CDependency_Project( CProjectDependencyGraph *pDependencyGraph ) + : CDependency( pDependencyGraph ) +{ + m_iProjectIndex = -1; + m_szStoredScriptName[0] = '\0'; + m_szStoredCurrentDirectory[0] = '\0'; +} + + +void CDependency_Project::StoreProjectParameters( const char *szScriptName ) +{ + m_StoredOutputFilename = g_pVPC->GetOutputFilename(); + V_GetCurrentDirectory( m_szStoredCurrentDirectory, sizeof( m_szStoredCurrentDirectory ) ); + V_strncpy( m_szStoredScriptName, szScriptName, sizeof( m_szStoredScriptName ) ); + m_StoredConditionalsActive.SetSize( g_pVPC->m_Conditionals.Count() ); + + for ( int iConditional=0; iConditional < g_pVPC->m_Conditionals.Count(); iConditional++ ) + { + m_StoredConditionalsActive[iConditional] = g_pVPC->m_Conditionals[iConditional].m_bGameConditionActive; + } +} + + +void CDependency_Project::ExportProjectParameters() +{ + g_pVPC->SetOutputFilename( m_StoredOutputFilename.Get() ); + V_SetCurrentDirectory( m_szStoredCurrentDirectory ); + + if ( m_StoredConditionalsActive.Count() > g_pVPC->m_Conditionals.Count() ) + { + g_pVPC->VPCError( "ExportProjectParameters( %s ) - too many defines stored.", m_szStoredScriptName ); + } + + for ( int iConditional=0; iConditional < g_pVPC->m_Conditionals.Count(); iConditional++ ) + { + g_pVPC->m_Conditionals[iConditional].m_bGameConditionActive = m_StoredConditionalsActive[iConditional]; + } +} + +int CDependency_Project::FindByProjectName( CUtlVector &projects, const char *pTestName ) +{ + for ( int i=0; i < projects.Count(); i++ ) + { + CDependency_Project *pProject = projects[i]; + + if ( V_stricmp( pProject->m_ProjectName.String(), pTestName ) == 0 ) + return i; + } + + return -1; +} + + +// This is responsible for scanning a project file and pulling out: +// - a list of libraries it uses +// - the $AdditionalIncludeDirectories paths +// - a list of source files it uses +// - the name of the file it generates +class CSingleProjectScanner : public CBaseProjectDataCollector +{ +public: + typedef CBaseProjectDataCollector BaseClass; + + CSingleProjectScanner() : CBaseProjectDataCollector( &g_DependencyRelevantPropertyNames ) + { + m_bInLinker = false; + } + + virtual void EndProject() + { + } + + void ScanProjectFile( CProjectDependencyGraph *pGraph, const char *szScriptName, CDependency_Project *pProject ) + { + // Someday we'll pass this interface down into VPC_ParseProjectScript instead of using the global. + IBaseProjectGenerator *pOldGenerator = g_pVPC->GetProjectGenerator(); + g_pVPC->SetProjectGenerator( this ); + + // This has VPC parse the script and CBaseProjectDataCollector collects all the data into lists of the + // stuff we care about like source files and include paths. + m_ScriptName = szScriptName; + g_pVPC->ParseProjectScript( szScriptName, 0, true, false ); + + int iConfig = m_BaseConfigData.m_Configurations.First(); + if ( iConfig != m_BaseConfigData.m_Configurations.InvalidIndex() ) + { + CSpecificConfig *pConfig = m_BaseConfigData.m_Configurations[iConfig]; + + SetupIncludeDirectories( pConfig, szScriptName ); + SetupFilesList( pGraph, pProject ); + SetupImportLibrary( pGraph, pConfig, szScriptName ); + SetupAdditionalProjectDependencies( pProject, pConfig ); + SetupAdditionalOutputFiles( pProject, pConfig ); + + } + + g_pVPC->SetProjectGenerator( pOldGenerator ); + Term(); + } + + void SetupFilesList( CProjectDependencyGraph *pGraph, CDependency_Project *pProject ) + { + for ( int i=m_Files.First(); i != m_Files.InvalidIndex(); i=m_Files.Next( i ) ) + { + CFileConfig *pFile = m_Files[i]; + + // If this file is excluded from all configs, then exclude it. + if ( pFile->m_Configurations.Count() > 0 ) + { + int nExcluded = 0; + for ( int iSpecific=pFile->m_Configurations.First(); iSpecific != pFile->m_Configurations.InvalidIndex(); iSpecific=pFile->m_Configurations.Next( iSpecific ) ) + { + CSpecificConfig *pTest = pFile->m_Configurations[iSpecific]; + if ( pTest->m_bFileExcluded && !pTest->m_bIsSchema ) + ++nExcluded; + } + if ( nExcluded == (int)m_BaseConfigData.m_Configurations.Count() ) + continue; + } + + + // Make this an absolute path. + const char *pFilename = pFile->GetName(); + char sAbsolutePath[MAX_PATH]; + V_MakeAbsolutePath( sAbsolutePath, sizeof( sAbsolutePath ), pFilename ); + + // Don't bother with source files if we're not building the full dependency set. + if ( !pGraph->m_bFullDependencySet ) + if ( IsSourceFile( sAbsolutePath ) ) + continue; + + // For source files, don't bother with files that don't exist. If we do create entries + // for files that don't exist, then they'll have a "cache file size" + if ( !Sys_Exists( sAbsolutePath ) && IsSourceFile( sAbsolutePath ) ) + continue; + + // Add an entry for this file. + CDependency *pDep = pGraph->FindOrCreateDependency( sAbsolutePath ); + pProject->m_Dependencies.AddToTail( pDep ); + + // Add includes. + if ( pDep->m_Type == k_eDependencyType_SourceFile ) + AddIncludesForFile( pGraph, pDep ); + } + } + + void AddIncludesForFile( CProjectDependencyGraph *pGraph, CDependency *pFile ) + { + // Have we already parsed this file for its includes? + if ( pFile->m_bCheckedIncludes ) + return; + + pFile->m_bCheckedIncludes = true; + + // Setup all the include paths we want to search. + CUtlVector includeDirs; + char szDir[MAX_PATH]; + if ( !V_ExtractFilePath( pFile->GetName(), szDir, sizeof( szDir ) ) ) + g_pVPC->VPCError( "AddIncludesForFile: V_ExtractFilePath( %s ) failed.", pFile->GetName() ); + + includeDirs.AddToTail( szDir ); + includeDirs.AddMultipleToTail( m_IncludeDirectories.Count(), m_IncludeDirectories.Base() ); + + // Get all the #include directives. + CUtlVector includes; + GetIncludeFiles( pFile->GetName(), includes ); + ++pGraph->m_nFilesParsedForIncludes; + + // Now see which of them we can open. + for ( int iIncludeFile=0; iIncludeFile < includes.Count(); iIncludeFile++ ) + { + for ( int iIncludeDir=0; iIncludeDir < includeDirs.Count(); iIncludeDir++ ) + { + char szFullName[MAX_PATH]; + V_ComposeFileName( includeDirs[iIncludeDir].String(), includes[iIncludeFile].String(), szFullName, sizeof( szFullName ) ); + + CDependency *pIncludeFile = pGraph->FindDependency( szFullName ); + if ( !pIncludeFile ) + { + if ( !Sys_Exists( szFullName ) ) + continue; + + // Find or add the dependency. + pIncludeFile = pGraph->FindOrCreateDependency( szFullName ); + } + pFile->m_Dependencies.AddToTail( pIncludeFile ); + + // Recurse. + AddIncludesForFile( pGraph, pIncludeFile ); + } + } + } + + bool SeekToIncludeStart( const char* &pSearchPos ) + { + while ( 1 ) + { + ++pSearchPos; + if ( *pSearchPos == 0 || *pSearchPos == '\r' || *pSearchPos == '\n' ) + return false; + + if ( *pSearchPos == '\"' || *pSearchPos == '<' ) + { + ++pSearchPos; + return true; + } + } + } + + bool SeekToIncludeEnd( const char* &pSearchPos ) + { + while ( 1 ) + { + ++pSearchPos; + if ( *pSearchPos == 0 || *pSearchPos == '\r' || *pSearchPos == '\n' ) + return false; + + if ( *pSearchPos == '\"' || *pSearchPos == '>' ) + return true; + } + } + + void GetIncludeFiles( const char *pFilename, CUtlVector &includes ) + { + char *pFileData; + int ret = Sys_LoadFile( pFilename, (void**)&pFileData, false ); + if ( ret == -1 ) + { + if ( g_pVPC->IsVerbose() ) + { + g_pVPC->VPCWarning( "GetIncludeFiles( %s ) - can't open file (included by project %s).", pFilename, m_ScriptName.String() ); + } + return; + } + + const char *pSearchPos = pFileData; + while ( 1 ) + { + const char *pLookFor = "#include"; + const char *pIncludeStatement = V_strstr( pSearchPos, pLookFor ); + if ( !pIncludeStatement ) + break; + + pSearchPos = pIncludeStatement + V_strlen( pLookFor ); + + if ( !SeekToIncludeStart( pSearchPos ) ) + continue; + const char *pFilenameStart = pSearchPos; + + if ( !SeekToIncludeEnd( pSearchPos ) ) + continue; + const char *pFilenameEnd = pSearchPos; + + if ( (pFilenameEnd - pFilenameStart) > MAX_PATH-10 ) + g_pVPC->VPCError( "Include statement too long in %s.", pFilename ); + + char szIncludeFilename[MAX_PATH], szFixed[MAX_PATH]; + V_strncpy( szIncludeFilename, pFilenameStart, pFilenameEnd - pFilenameStart + 1 ); + + // Fixup double slashes. + V_StrSubst( szIncludeFilename, "\\\\", "\\", szFixed, sizeof( szFixed ) ); + V_FixSlashes( szFixed ); + + includes.AddToTail( szFixed ); + } + + free( pFileData ); + } + + void SetupIncludeDirectories( CSpecificConfig *pConfig, const char *szScriptName ) + { + if ( m_BaseConfigData.m_Configurations.Count() == 0 ) + g_pVPC->VPCError( "No configurations for %s in project %s.", szScriptName, m_ScriptName.String() ); + + const char *pIncludes = pConfig->m_pKV->GetString( g_pOption_AdditionalIncludeDirectories, "" ); + CSplitString relativeIncludeDirs( pIncludes, (const char**)g_IncludeSeparators, V_ARRAYSIZE( g_IncludeSeparators ) ); + + for ( int i=0; i < relativeIncludeDirs.Count(); i++ ) + { + char sAbsolute[MAX_PATH]; + V_MakeAbsolutePath( sAbsolute, sizeof( sAbsolute ), relativeIncludeDirs[i] ); + m_IncludeDirectories.AddToTail( sAbsolute ); + } + } + + void SetupImportLibrary( CProjectDependencyGraph *pGraph, CSpecificConfig *pConfig, const char *szScriptName ) + { + m_ImportLibrary = pConfig->m_pKV->GetString( g_pOption_ImportLibrary, NULL ); + // XXX(JohnS): For projects that define a separate "GameOutputFile" step, that is the final product. This was + // kind of hackily added originally -- the $OutputFile directive was relative to the base directory, + // but some generators (XCode) put all their outputs into a object directory, then use + // $GameOutputFile to *actually* output. + m_LinkerOutputFile = pConfig->m_pKV->GetString( g_pOption_GameOutputFile, NULL ); + if ( !m_LinkerOutputFile.Length() ) + { + m_LinkerOutputFile = pConfig->m_pKV->GetString( g_pOption_OutputFile, NULL ); + } + } + + void SetupAdditionalProjectDependencies( CDependency_Project *pProject, CSpecificConfig *pConfig ) + { + const char *pVal = pConfig->m_pKV->GetString( g_pOption_AdditionalProjectDependencies ); + if ( pVal ) + { + pProject->m_AdditionalProjectDependencies.Purge(); + + CSplitString outStrings ( pVal, ";" ); + for ( int i=0; i < outStrings.Count(); i++ ) + { + char szProjectName[MAX_PATH]; + sprintf( szProjectName, "%s", outStrings[i] ); + + if ( g_pVPC->IsDecorateProject() ) + { + g_pVPC->DecorateProjectName( szProjectName ); + } + pProject->m_AdditionalProjectDependencies.AddToTail( szProjectName ); + } + } + } + + void SetupAdditionalOutputFiles( CDependency_Project *pProject, CSpecificConfig *pConfig ) + { + const char *pVal = pConfig->m_pKV->GetString( g_pOption_AdditionalOutputFiles ); + if ( pVal ) + { + pProject->m_AdditionalOutputFiles.Purge(); + + CSplitString outStrings( pVal, ";" ); + for ( int i=0; i < outStrings.Count(); i++ ) + { + pProject->m_AdditionalOutputFiles.AddToTail( outStrings[i] ); + } + } + } + + virtual const char* GetProjectFileExtension() + { + return "UNUSED"; + } + +protected: + + virtual bool StartPropertySection( configKeyword_e keyword, bool *pbShouldSkip ) + { + m_bInLinker = ( keyword == KEYWORD_LINKER || keyword == KEYWORD_LIBRARIAN ); + return true; + } + + virtual void HandleProperty( const char *pProperty, const char *pCustomScriptData ) + { + // We don't want the $OutputFile property from the $BrowseInformation section. + if ( V_stricmp( pProperty, g_pOption_OutputFile ) == 0 && !m_bInLinker ) + return; + + BaseClass::HandleProperty( pProperty, pCustomScriptData ); + } + + virtual void EndPropertySection( configKeyword_e keyword ) + { + m_bInLinker = false; + } + +public: + // Project include directories. These strings are deleted when the object goes away. + CUtlVector m_IncludeDirectories; + CUtlString m_ImportLibrary; + CUtlString m_LinkerOutputFile; + CUtlString m_ScriptName; + bool m_bInLinker; +}; + + +CProjectDependencyGraph::CProjectDependencyGraph() +{ + m_nFilesParsedForIncludes = 0; + m_iDependencyMark = 0; + m_bFullDependencySet = false; + m_bHasGeneratedDependencies = false; +} + + +void CProjectDependencyGraph::BuildProjectDependencies( int nBuildProjectDepsFlags, CUtlVector< CDependency_Project *> *pPhase1Projects ) +{ + m_bFullDependencySet = ( ( nBuildProjectDepsFlags & BUILDPROJDEPS_FULL_DEPENDENCY_SET ) != 0 ); + m_nFilesParsedForIncludes = 0; + + if ( m_bFullDependencySet ) + { + Log_Msg( LOG_VPC, "\nBuilding full dependency set (all sources and headers)..." ); + } + else + { + Log_Msg( LOG_VPC, "\nBuilding partial dependency set (libs only)..." ); + } + + // Have it iterate ALL projects in the list, with whatever platform conditionals are around. + // When it visits a + CUtlVector projectList; + CUtlVector oldState; + + if ( nBuildProjectDepsFlags & BUILDPROJDEPS_CHECK_ALL_PROJECTS ) + { + // So iterate all projects. + projectList.SetSize( g_pVPC->m_Projects.Count() ); + for ( int i=0; i < g_pVPC->m_Projects.Count(); i++ ) + projectList[i] = i; + + // Simulate /allgames but remember the old state too. + for ( int j=0; jm_Conditionals.Count(); j++ ) + { + if ( g_pVPC->m_Conditionals[j].type == CONDITIONAL_GAME ) + { + oldState.AddToTail( (j << 16) + (int)g_pVPC->m_Conditionals[j].m_bDefined ); + g_pVPC->m_Conditionals[j].m_bDefined = true; + } + } + } + else + { + projectList.AddMultipleToTail( g_pVPC->m_TargetProjects.Count(), g_pVPC->m_TargetProjects.Base() ); + } + + // Load any prior results so we don't have to regenerate the whole cache (which can take a couple minutes). + char sCacheFile[MAX_PATH] = {0}; + V_ComposeFileName( g_pVPC->GetSourcePath(), "vpc.cache", sCacheFile, sizeof( sCacheFile ) ); + if ( m_bFullDependencySet ) + { + if ( !LoadCache( sCacheFile ) ) + { + Log_Msg( LOG_VPC, "\n\nNo vpc.cache file found.\nThis will take a minute to generate dependency info from all the sources.\nPut the kleenex down.\nNext time it will have a cache file and be fast.\n\n" ); + } + } + + CFastTimer timer; + timer.Start(); + g_pVPC->IterateTargetProjects( projectList, this ); + timer.End(); + + ResolveAdditionalProjectDependencies( pPhase1Projects ); + + // Restore the old game defines state? + if ( nBuildProjectDepsFlags & BUILDPROJDEPS_CHECK_ALL_PROJECTS ) + { + for ( int i=0; i < oldState.Count(); i++ ) + { + int iDefine = oldState[i] >> 16; + g_pVPC->m_Conditionals[iDefine].m_bDefined = ( (oldState[i] & 1) != 0 ); + } + } + + // Save the expensive work we did into a cache file so it can be used next time. + if ( m_bFullDependencySet ) + { + SaveCache( sCacheFile ); + } + + Log_Msg( LOG_VPC, "\n\n" ); + if ( m_nFilesParsedForIncludes > 0 ) + { + Log_Msg( LOG_VPC, "%d files parsed in %.2f seconds for #includes.\n", m_nFilesParsedForIncludes, timer.GetDuration().GetSeconds() ); + } + + m_bHasGeneratedDependencies = true; +} + +void CProjectDependencyGraph::ResolveAdditionalProjectDependencies( CUtlVector< CDependency_Project *> *pPhase1Projects ) +{ + for ( int iMainProject=0; iMainProject < m_Projects.Count(); iMainProject++ ) + { + CDependency_Project *pMainProject = m_Projects[iMainProject]; + + for ( int i=0; i < pMainProject->m_AdditionalProjectDependencies.Count(); i++ ) + { + const char *pLookingFor = pMainProject->m_AdditionalProjectDependencies[i].String(); + + // Look for this project name among all the projects. + int j; + for ( j=0; j < m_Projects.Count(); j++ ) + { + if ( V_stricmp( m_Projects[j]->m_ProjectName.String(), pLookingFor ) == 0 ) + break; + } + + if ( j == m_Projects.Count() ) + { + //VPCError( "Project %s lists '%s' in its $AdditionalProjectDependencies, but there is no project by that name.", pMainProject->GetName(), pLookingFor ); + continue; + } + + if ( pMainProject->m_AdditionalDependencies.Find( m_Projects[j] ) == pMainProject->m_AdditionalDependencies.InvalidIndex() ) + pMainProject->m_AdditionalDependencies.AddToTail( m_Projects[j] ); + } + + + if ( pPhase1Projects != NULL ) + { + // + // See if there's a project in phase 1 built from the same VPC, and, if so, add it as a dependency. + // This prevents a bunch of race conditions when projects are built in a distributed fashion + // (ala Incredibuild) and the projects step on each other. + // + const char *pFileName = pMainProject->m_Filename.String(); + + int j; + for ( j = 0; j < pPhase1Projects->Count(); j++ ) + { + if ( V_stricmp( (*pPhase1Projects)[j]->m_Filename.String(), pFileName ) == 0) + { + break; + } + } + if ( j == pPhase1Projects->Count() ) + { + continue; + } + + if ( pMainProject->m_AdditionalDependencies.Find( (*pPhase1Projects)[j] ) == pMainProject->m_AdditionalDependencies.InvalidIndex() ) + { + pMainProject->m_AdditionalDependencies.AddToTail( (*pPhase1Projects)[j] ); + } + } + } +} + +bool CProjectDependencyGraph::HasGeneratedDependencies() const +{ + return m_bHasGeneratedDependencies; +} + +bool CProjectDependencyGraph::VisitProject( projectIndex_t iProject, const char *szProjectName ) +{ + // Read in the project. + if ( !Sys_Exists( szProjectName ) ) + { + return false; + } + + // Add another dot for the pacifier. + Log_Msg( LOG_VPC, "." ); + + // Add this project. + CDependency_Project *pProject = new CDependency_Project( this ); + + char szAbsolute[MAX_PATH]; + V_MakeAbsolutePath( szAbsolute, sizeof( szAbsolute ), szProjectName ); + pProject->m_Filename = szAbsolute; + + pProject->m_Type = k_eDependencyType_Project; + pProject->m_iProjectIndex = iProject; + m_Projects.AddToTail( pProject ); + m_AllFiles.Insert( szAbsolute, pProject ); + + // Remember various parameters passed to us so we can regenerate this project without having + // to call VPC_IterateTargetProjects. + pProject->StoreProjectParameters( szProjectName ); + + char sAbsProjectFilename[MAX_PATH]; + V_MakeAbsolutePath( sAbsProjectFilename, sizeof( sAbsProjectFilename ), g_pVPC->GetOutputFilename() ); + pProject->m_ProjectFilename = sAbsProjectFilename; + + // Scan the project file and get all its libs, cpp, and h files. + CSingleProjectScanner scanner; + scanner.ScanProjectFile( this, szAbsolute, pProject ); + pProject->m_IncludeDirectories = scanner.m_IncludeDirectories; + pProject->m_ProjectName = scanner.m_ProjectName; + + // Get a list of all files that depend on this project, starting with the .lib if it generates one. + CUtlVector outputFiles; + outputFiles = pProject->m_AdditionalOutputFiles; + + // Now note that the import library depends on this project. + // $(ImportLibrary) will be a lib in the case of DLLs that create libs (like tier0). + // $(OutputFile) will be a lib in the case of static libs (like tier1). + const char *pLinkerOutputFile = scanner.m_LinkerOutputFile.String(); + const char *pImportLibrary = scanner.m_ImportLibrary.String(); + if ( !IsLibraryFile( pImportLibrary ) ) + { + pImportLibrary = pLinkerOutputFile; + } + + if ( IsLibraryFile( pImportLibrary ) ) + { + outputFiles.AddToTail( pImportLibrary ); + } + + // The string that we replace $(TargetName) with is the output project filename without the path or extension. + // That'll be something like "tier0_360". + char sTargetNameReplacement[MAX_PATH]; + V_FileBase( pLinkerOutputFile, sTargetNameReplacement, sizeof( sTargetNameReplacement ) ); + + // Now add a CDependency for each file. + for ( int i=0; i < outputFiles.Count(); i++ ) + { + const char *pFilename = outputFiles[i].String(); + + // Replace $(TargetName) and fixup the path. + char sReplaced[MAX_PATH], sAbsImportLibrary[MAX_PATH]; + V_StrSubst( pFilename, "$(TargetName)", sTargetNameReplacement, sReplaced, sizeof( sReplaced ) ); + V_MakeAbsolutePath( sAbsImportLibrary, sizeof( sAbsImportLibrary ), sReplaced ); + + CDependency *il = FindOrCreateDependency( sAbsImportLibrary ); + il->m_Dependencies.AddToTail( pProject ); + } + + return true; +} + + +void CProjectDependencyGraph::GetProjectDependencyTree( projectIndex_t iProject, CUtlVector &dependentProjects, bool bDownwards ) +{ + // First add the project itself. + if ( dependentProjects.Find( iProject ) == dependentProjects.InvalidIndex() ) + dependentProjects.AddToTail( iProject ); + + // Now add anything that depends on it. + for ( int i=0; i < m_Projects.Count(); i++) + { + CDependency_Project *pProject = m_Projects[i]; + + if ( pProject->m_iProjectIndex != iProject ) + continue; + + // Ok, this project/game/platform combo comes from iProject. Now find anything that depends on it. + for ( int iOther=0; iOther < m_Projects.Count(); iOther++ ) + { + CDependency_Project *pOther = m_Projects[iOther]; + + if ( pOther->m_iProjectIndex == iProject ) + continue; + + bool bThereIsADependency; + if ( bDownwards ) + bThereIsADependency = pProject->DependsOn( pOther, k_EDependsOnFlagCheckNormalDependencies | k_EDependsOnFlagCheckAdditionalDependencies | k_EDependsOnFlagRecurse | k_EDependsOnFlagTraversePastLibs ); + else + bThereIsADependency = pOther->DependsOn( pProject, k_EDependsOnFlagCheckNormalDependencies | k_EDependsOnFlagCheckAdditionalDependencies | k_EDependsOnFlagRecurse | k_EDependsOnFlagTraversePastLibs ); + + if ( bThereIsADependency ) + { + if ( dependentProjects.Find( pOther->m_iProjectIndex ) == dependentProjects.InvalidIndex() ) + dependentProjects.AddToTail( pOther->m_iProjectIndex ); + } + } + } +} + + +CDependency* CProjectDependencyGraph::FindDependency( const char *pFilename ) +{ + int i = m_AllFiles.Find( pFilename ); + if ( i == m_AllFiles.InvalidIndex() ) + return NULL; + else + return m_AllFiles[i]; +} + + +CDependency* CProjectDependencyGraph::FindOrCreateDependency( const char *pFilename ) +{ + // Fix up stuff like blah/../blah + char sFixed[MAX_PATH]; + + V_FixupPathName( sFixed, sizeof( sFixed ), pFilename ); + pFilename = sFixed; + + CDependency *pDependency = FindDependency( pFilename ); + if ( pDependency ) + return pDependency; + + // Couldn't find it. Create one. + pDependency = new CDependency( this ); + pDependency->m_Filename = pFilename; + m_AllFiles.Insert( pFilename, pDependency ); + + Sys_FileInfo( pFilename, pDependency->m_nCacheFileSize, pDependency->m_nCacheModificationTime ); + + if ( IsSourceFile( pFilename ) ) + pDependency->m_Type = k_eDependencyType_SourceFile; + else if ( IsLibraryFile( pFilename ) ) + pDependency->m_Type = k_eDependencyType_Library; + else + pDependency->m_Type = k_eDependencyType_Unknown; + + return pDependency; +} + + +void CProjectDependencyGraph::ClearAllDependencyMarks() +{ + if ( m_iDependencyMark == 0xFFFFFFFF ) + { + m_iDependencyMark = 1; + for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next(i) ) + { + m_AllFiles[i]->m_iDependencyMark = 0; + } + } + else + { + // The 99.9999999% chance case. + ++m_iDependencyMark; + } +} + +bool CProjectDependencyGraph::LoadCache( const char *pFilename ) +{ + FILE *fp = fopen( pFilename, "rb" ); + if ( !fp ) + return false; + + int version; + fread( &version, sizeof( version ), 1, fp ); + if ( version != VPC_CRC_CACHE_VERSION ) + { + fclose( fp ); + g_pVPC->VPCWarning( "Invalid dependency cache file version in %s.", pFilename ); + return false; + } + + while ( 1 ) + { + byte bMore; + if ( fread( &bMore, 1, 1, fp ) != 1 || bMore == 0 ) + break; + + CUtlString filename = ReadString( fp ); + CDependency *pDep = FindOrCreateDependency( filename.String() ); + if ( pDep->m_Dependencies.Count() != 0 ) + g_pVPC->VPCError( "Cache loading dependency %s but it already exists!", filename.String() ); + + fread( &pDep->m_nCacheFileSize, sizeof( pDep->m_nCacheFileSize ), 1, fp ); + fread( &pDep->m_nCacheModificationTime, sizeof( pDep->m_nCacheModificationTime ), 1, fp ); + + int nDependencies; + fread( &nDependencies, sizeof( nDependencies ), 1, fp ); + pDep->m_Dependencies.SetSize( nDependencies ); + + for ( int iDependency=0; iDependency < nDependencies; iDependency++ ) + { + CUtlString childDepName = ReadString( fp ); + CDependency *pChildDep = FindOrCreateDependency( childDepName.String() ); + pDep->m_Dependencies[iDependency] = pChildDep; + } + } + + fclose( fp ); + + int nOriginalEntries = m_AllFiles.Count(); + + CheckCacheEntries(); + RemoveDirtyCacheEntries(); + MarkAllCacheEntriesValid(); + + Log_Msg( LOG_VPC, "\n\nLoaded %d valid dependency cache entries (%d were out of date).\n\n", m_AllFiles.Count(), nOriginalEntries-m_AllFiles.Count() ); + return true; +} + +bool CProjectDependencyGraph::SaveCache( const char *pFilename ) +{ + FILE *fp = fopen( pFilename, "wb" ); + if ( !fp ) + return false; + + // Write the version. + int version = VPC_CRC_CACHE_VERSION; + fwrite( &version, sizeof( version ), 1, fp ); + + // Write each file. + for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) + { + CDependency *pDep = m_AllFiles[i]; + + // We only care about source files. + if ( pDep->m_Type != k_eDependencyType_SourceFile ) + continue; + + // Write that there's a file here. + byte bYesThereIsAFileHere = 1; + fwrite( &bYesThereIsAFileHere, 1, 1, fp ); + + WriteString( fp, pDep->m_Filename ); + fwrite( &pDep->m_nCacheFileSize, sizeof( pDep->m_nCacheFileSize ), 1, fp ); + fwrite( &pDep->m_nCacheModificationTime, sizeof( pDep->m_nCacheModificationTime ), 1, fp ); + + intp nDependencies = pDep->m_Dependencies.Count(); + fwrite( &nDependencies, sizeof( nDependencies ), 1, fp ); + + for ( intp iDependency=0; iDependency < pDep->m_Dependencies.Count(); iDependency++ ) + { + WriteString( fp, pDep->m_Dependencies[iDependency]->m_Filename ); + } + } + + // Write a terminator. + byte bNoMore = 0; + fwrite( &bNoMore, 1, 1, fp ); + + fclose( fp ); + + Sys_CopyToMirror( pFilename ); + + return true; +} + +void CProjectDependencyGraph::WriteString( FILE *fp, CUtlString &utlString ) +{ + const char *pStr = utlString.String(); + intp len = V_strlen( pStr ); + fwrite( &len, sizeof( len ), 1, fp ); + fwrite( pStr, len, 1, fp ); +} + +CUtlString CProjectDependencyGraph::ReadString( FILE *fp ) +{ + int len; + fread( &len, sizeof( len ), 1, fp ); + + char *pTemp = new char[len+1]; + fread( pTemp, len, 1, fp ); + pTemp[len] = 0; + + CUtlString ret = pTemp; + delete [] pTemp; + + return ret; +} + + +void CProjectDependencyGraph::CheckCacheEntries() +{ + for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) + { + CDependency *pDep = m_AllFiles[i]; + pDep->m_bCacheDirty = false; + + if ( pDep->m_Type != k_eDependencyType_SourceFile ) + continue; + + int64 fileSize, modTime; + if ( !Sys_FileInfo( pDep->m_Filename.String(), fileSize, modTime ) || + pDep->m_nCacheFileSize != fileSize || + pDep->m_nCacheModificationTime != modTime ) + { + pDep->m_bCacheDirty = true; + } + } +} + +void CProjectDependencyGraph::RemoveDirtyCacheEntries() +{ + // NOTE: This could be waaaay more efficient by pointing files at their parents and removing all the way + // up the chain rather than iterating over and over but this keeps the data structures simple. + bool bAnyDirty = true; + while ( bAnyDirty ) + { + bAnyDirty = false; + + for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) + { + CDependency *pDep = m_AllFiles[i]; + if ( pDep->m_bCacheDirty ) + continue; + + // If any of its children are dirty, then mark this guy as dirty and make sure to remove the child. + for ( int iChild=0; iChild < pDep->m_Dependencies.Count(); iChild++ ) + { + CDependency *pChild = pDep->m_Dependencies[iChild]; + if ( pChild->m_bCacheDirty ) + { + pDep->m_bCacheDirty = true; + bAnyDirty = true; + } + } + } + } + + // Now that any dirty children have flagged their parents as dirty, we can remove them. + int iNext; + for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=iNext ) + { + iNext = m_AllFiles.Next( i ); + + if ( m_AllFiles[i]->m_bCacheDirty ) + { + delete m_AllFiles[ i ]; + m_AllFiles.RemoveAt( i ); + } + } +} + + +void CProjectDependencyGraph::MarkAllCacheEntriesValid() +{ + for ( int i=m_AllFiles.First(); i != m_AllFiles.InvalidIndex(); i=m_AllFiles.Next( i ) ) + { + CDependency *pDep = m_AllFiles[i]; + pDep->m_bCheckedIncludes = true; + } +} + + + +// This is called by VPC_IterateTargetProjects and all it does is look forf a +class CGameFilterProjectIterator : public IProjectIterator +{ +public: + CGameFilterProjectIterator() : m_pAllProjectsList(NULL), m_pOutProjectsList(NULL) + { + } + + virtual bool VisitProject( projectIndex_t iProject, const char *szProjectName ) + { + char szAbsolute[MAX_PATH]; + V_MakeAbsolutePath( szAbsolute, sizeof( szAbsolute ), szProjectName ); + + // Ok, we've got an (absolute) project filename. Search all the projects for one with that name. + bool bAdded = false; + for ( int i=0; i < m_pAllProjectsList->Count(); i++ ) + { + CDependency_Project *pProject = m_pAllProjectsList->Element( i ); + + if ( pProject->CompareAbsoluteFilename( szAbsolute ) ) + { + m_pOutProjectsList->AddToTail( pProject ); + bAdded = true; + break; + } + } + + if ( !bAdded ) + { + g_pVPC->VPCError( "CGameFilterProjectIterator::VisitProject( %s ) - no project found by that name.", szProjectName ); + } + + return true; + } + +public: + const CUtlVector *m_pAllProjectsList; + CUtlVector *m_pOutProjectsList; +}; + +void CProjectDependencyGraph::TranslateProjectIndicesToDependencyProjects( CUtlVector &projectList, CUtlVector &out ) +{ + CGameFilterProjectIterator iterator; + iterator.m_pAllProjectsList = &m_Projects; + iterator.m_pOutProjectsList = &out; + + g_pVPC->IterateTargetProjects( projectList, &iterator ); +} diff --git a/utils/vpc/dependencies.h b/utils/vpc/dependencies.h new file mode 100644 index 0000000..6dda9c9 --- /dev/null +++ b/utils/vpc/dependencies.h @@ -0,0 +1,220 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_DEPENDENCIES_H_ +#define VPC_DEPENDENCIES_H_ + +enum EDependencyType { + k_eDependencyType_SourceFile, // .cpp, .cxx, .h, .hxx + k_eDependencyType_Project, // this is a project file WITHOUT the + // target-specific extension (.mak, .vpj, + // .vcproj). + k_eDependencyType_Library, // this is a library file + k_eDependencyType_Unknown // Unrecognized file extension (probably .ico or + // .rc2 or somesuch). +}; + +class CProjectDependencyGraph; +enum k_EDependsOnFlags { + k_EDependsOnFlagCheckNormalDependencies = 0x01, + k_EDependsOnFlagCheckAdditionalDependencies = 0x02, + k_EDependsOnFlagRecurse = 0x04, + k_EDependsOnFlagTraversePastLibs = 0x08 +}; + +// Flags to CProjectDependencyGraph::BuildProjectDependencies. +#define BUILDPROJDEPS_FULL_DEPENDENCY_SET \ + 0x01 // This tells it to build a graph of all projects in the source tree + // _including_ all games. +#define BUILDPROJDEPS_CHECK_ALL_PROJECTS \ + 0x02 // If this is set, then it reads all .vpc files. + // If this is not set, then it only includes the files from the command + // line with the "vpc +tier0 *bitmap +client /tf" syntax + +class CDependency { + friend class CProjectDependencyGraph; + friend class CSingleProjectScanner; + + public: + CDependency(CProjectDependencyGraph *pDependencyGraph); + virtual ~CDependency(); + + // Flags are a combination of k_EDependsOnFlags. + bool DependsOn(CDependency *pTest, + int flags = k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse); + const char *GetName() const; + + // Returns true if the absolute filename of this thing + // (CDependency::m_Filename) matches the absolute path specified. + bool CompareAbsoluteFilename(const char *pAbsPath) const; + + private: + bool FindDependency_Internal(CUtlVector &callTreeOutputStack, + CDependency *pTest, int flags, int depth); + void Mark(); + bool HasBeenMarked(); + + public: + CUtlString m_Filename; // Full paths (slashes are platform dependent). + // This is the VPC filename for a project (use + // CDependency_Project::m_ProjectFilename for the + // VCPROJ/VPJ filename). + EDependencyType m_Type; + + // Files that this guy depends on. + CUtlVector m_Dependencies; + + // Files added by $AdditionalProjectDependencies. This is in a separate list + // because we don't always want DependsOn() to check this. + CUtlVector m_AdditionalDependencies; + + private: + CProjectDependencyGraph *m_pDependencyGraph; + unsigned int m_iDependencyMark; + bool m_bCheckedIncludes; // Set to true when we have checked all the includes + // for this. + + // Cache info. + int64 m_nCacheFileSize; + int64 m_nCacheModificationTime; + + // Used by the cache. + bool m_bCacheDirty; // File size or modification time don't match. +}; + +// This represents a project (.vcproj) file, NOT a project like a +// projectIndex_t. There can be separate .vcproj files (and thus separate +// CDependency_Project) for each game and platform of a projectIndex_t. If +// m_Type == k_eDependencyType_Project, then you can cast to this. +class CDependency_Project : public CDependency { + public: + typedef CDependency BaseClass; + + CDependency_Project(CProjectDependencyGraph *pDependencyGraph); + + // These functions read/write g_pVPC->GetOutputFilename() and such (all the + // m_xxStoredXXXX vars below). + void StoreProjectParameters(const char *szScriptName); + void ExportProjectParameters(); + + // Does a case-insensitive string compare against m_ProjectName. + // Returns -1 if not found or the index into projects. + static int FindByProjectName(CUtlVector &projects, + const char *pTestName); + + public: + // Include directories for the project. + CUtlVector m_IncludeDirectories; + + // Straight out of the $AdditionalProjectDependencies key (split on + // semicolons). + CUtlVector m_AdditionalProjectDependencies; + + // Straight out of the $AdditionalOutputFiles key (split on semicolons). + CUtlVector m_AdditionalOutputFiles; + + CUtlString + m_ProjectName; // This comes from the $Project key in the .vpc file. + CUtlString + m_ProjectFilename; // Absolute path to the VCPROJ file + // (g_pVPC->GetOutputFilename() - see + // CDependency::m_Filename for the VPC filename). + CUtlString m_ImportLibrary; + + // Note that there can be multiple CDependency_Projects with the same + // m_iProjectIndex. + projectIndex_t m_iProjectIndex; + + // This is used by /p4sln. It uses this to call into VPC_ParseProjectScript. + // These are the values of g_pVPC->GetOutputFilename(), szScriptName, and the + // defines at the time of building this project. + CUtlString m_StoredOutputFilename; + char m_szStoredScriptName[MAX_PATH]; + char m_szStoredCurrentDirectory[MAX_PATH]; + CUtlVector m_StoredConditionalsActive; +}; + +// This class builds a graph of all dependencies, starting at the projects. +class CProjectDependencyGraph : public IProjectIterator { + friend class CDependency; + + public: + CProjectDependencyGraph(); + + // This is the main function to generate dependencies. + // nBuildProjectDepsFlags is a combination of BUILDPROJDEPS_ flags. + void BuildProjectDependencies( + int nBuildProjectDepsFlags, + CUtlVector *pPhase1Projects = NULL); + + bool HasGeneratedDependencies() const; + + CDependency *FindDependency(const char *pFilename); + CDependency *FindOrCreateDependency(const char *pFilename); + + // Look for all projects (that we've scanned during BuildProjectDependencies) + // that depend on the specified project. If bDownwards is true, then it adds + // iProject and all projects that _it depends on_. If bDownwards is false, + // then it adds iProject and all projects that _depend on it_. + void GetProjectDependencyTree(projectIndex_t iProject, + CUtlVector &dependentProjects, + bool bDownwards); + + // This solves the central mismatch between the way VPC references projects + // and the way the CDependency stuff does. + // + // - VPC uses projectIndex_t, but a single projectIndex_t can turn into + // multiple games (server_tf, server_episodic, etc) in + // VPC_IterateTargetProjects. + // - The dependency code has a separate CDependency_Project for each game. + // + // This takes a bunch of project indices (usually m_targetProjects, which + // comes from the command line's "+this -that *theother" syntax), which are + // game-agnostic, and based on what games were specified on the command line, + // it builds the list of CDependency_Project*s. + void TranslateProjectIndicesToDependencyProjects( + CUtlVector &projectList, + CUtlVector &out); + + // IProjectIterator overrides. + protected: + virtual bool VisitProject(projectIndex_t iProject, const char *szProjectName); + + private: + void ClearAllDependencyMarks(); + + // Functions for the vpc.cache file management. + bool LoadCache(const char *pFilename); + bool SaveCache(const char *pFilename); + void WriteString(FILE *fp, CUtlString &utlString); + CUtlString ReadString(FILE *fp); + + void CheckCacheEntries(); + void RemoveDirtyCacheEntries(); + void MarkAllCacheEntriesValid(); + + void ResolveAdditionalProjectDependencies( + CUtlVector *pPhase1Projects = NULL); + + public: + // Projects and everything they depend on. + CUtlVector m_Projects; + CUtlDict + m_AllFiles; // All files go in here. They should never be duplicated. + // These are indexed by the full filename (except .lib files, + // which have that stripped off). + bool m_bFullDependencySet; // See bFullDepedencySet passed into + // BuildProjectDependencies. + int m_nFilesParsedForIncludes; + + private: + // Used when sweeping the dependency graph to prevent looping around forever. + unsigned int m_iDependencyMark; + bool m_bHasGeneratedDependencies; // Set to true after finishing + // BuildProjectDependencies. +}; + +bool IsLibraryFile(const char *pFilename); +bool IsSharedLibraryFile(const char *pFilename); + +#endif // VPC_DEPENDENCIES_H_ diff --git a/utils/vpc/exprsimplifier.cpp b/utils/vpc/exprsimplifier.cpp new file mode 100644 index 0000000..27e39c0 --- /dev/null +++ b/utils/vpc/exprsimplifier.cpp @@ -0,0 +1,266 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: ExprSimplifier builds a binary tree from an infix expression (in the +// form of a character array). + +#include "vpc.h" + +#include "tier0/memdbgon.h" + +static ExprTree mExprTree; // Tree representation of the expression +static char mCurToken; // Current token read from the input expression +static const char* mExpression; // Array of the expression characters +static int mCurPosition; // Current position in the input expression +static char mIdentifier[MAX_IDENTIFIER_LEN]; // Stores the identifier string +static GetSymbolProc_t g_pGetSymbolProc; + +//----------------------------------------------------------------------------- +// Sets mCurToken to the next token in the input string. Skips all +//whitespace. +//----------------------------------------------------------------------------- +static char GetNextToken(void) { + // while whitespace, Increment CurrentPosition + while (mExpression[mCurPosition] == ' ') ++mCurPosition; + + // CurrentToken = Expression[CurrentPosition] + mCurToken = mExpression[mCurPosition++]; + + return mCurToken; +} + +//----------------------------------------------------------------------------- +// Utility funcs +//----------------------------------------------------------------------------- +static void FreeNode(ExprNode* node) { delete node; } + +static ExprNode* AllocateNode(void) { return new ExprNode; } + +static void FreeTree(ExprTree& node) { + if (!node) return; + + FreeTree(node->left); + FreeTree(node->right); + FreeNode(node); + node = 0; +} + +static bool IsConditional(const char token) { + char nextchar = ' '; + if (token == OR_OP || token == AND_OP) { + nextchar = mExpression[mCurPosition++]; + if ((token & nextchar) == token) { + return true; + } else + g_pVPC->VPCSyntaxError("Bad expression token: %c %c", token, nextchar); + } + + return false; +} + +static bool IsNotOp(const char token) { + if (token == NOT_OP) + return true; + else + return false; +} + +static bool IsIdentifierOrConstant(const char token) { + bool success = false; + if (token == '$') { + // store the entire identifier + int i = 0; + mIdentifier[i++] = token; + while ((isalnum(mExpression[mCurPosition]) || + mExpression[mCurPosition] == '_') && + i < MAX_IDENTIFIER_LEN) { + mIdentifier[i] = mExpression[mCurPosition]; + ++mCurPosition; + ++i; + } + + if (i < MAX_IDENTIFIER_LEN - 1) { + mIdentifier[i] = '\0'; + success = true; + } + } else { + if (isdigit(token)) { + int i = 0; + mIdentifier[i++] = token; + while (isdigit(mExpression[mCurPosition]) && (i < MAX_IDENTIFIER_LEN)) { + mIdentifier[i] = mExpression[mCurPosition]; + ++mCurPosition; + ++i; + } + if (i < MAX_IDENTIFIER_LEN - 1) { + mIdentifier[i] = '\0'; + success = true; + } + } + } + + return success; +} + +static void MakeExprNode(ExprTree& tree, char token, Kind kind, ExprTree left, + ExprTree right) { + tree = AllocateNode(); + tree->left = left; + tree->right = right; + tree->kind = kind; + + switch (kind) { + case CONDITIONAL: + tree->data.cond = token; + break; + case LITERAL: + if (isdigit(mIdentifier[0])) { + tree->data.value = atoi(mIdentifier) != 0; + } else { + tree->data.value = g_pGetSymbolProc(mIdentifier); + } + break; + case NOT: + break; + default: + g_pVPC->VPCError("Error in ExpTree"); + } +} + +static void MakeExpression(ExprTree& tree); +//----------------------------------------------------------------------------- +// Makes a factor :: { } | . +//----------------------------------------------------------------------------- +static void MakeFactor(ExprTree& tree) { + if (mCurToken == '(') { + // Get the next token + GetNextToken(); + + // Make an expression, setting Tree to point to it + MakeExpression(tree); + } else if (IsIdentifierOrConstant(mCurToken)) { + // Make a literal node, set Tree to point to it, set left/right children to + // NULL. + MakeExprNode(tree, mCurToken, LITERAL, NULL, NULL); + } else if (IsNotOp(mCurToken)) { + // do nothing + return; + } else { + // This must be a bad token + g_pVPC->VPCSyntaxError("Bad expression token: %c", mCurToken); + } + + // Get the next token + GetNextToken(); +} + +//----------------------------------------------------------------------------- +// Makes a term :: { }. +//----------------------------------------------------------------------------- +static void MakeTerm(ExprTree& tree) { + // Make a factor, setting Tree to point to it + MakeFactor(tree); + + // while the next token is ! + while (IsNotOp(mCurToken)) { + // Make an operator node, setting left child to Tree and right to NULL. + // (Tree points to new node) + MakeExprNode(tree, mCurToken, NOT, tree, NULL); + + // Get the next token. + GetNextToken(); + + // Make a factor, setting the right child of Tree to point to it. + MakeFactor(tree->right); + } +} + +//----------------------------------------------------------------------------- +// Makes a complete expression :: { }. +//----------------------------------------------------------------------------- +static void MakeExpression(ExprTree& tree) { + // Make a term, setting Tree to point to it + MakeTerm(tree); + + // while the next token is a conditional + while (IsConditional(mCurToken)) { + // Make a conditional node, setting left child to Tree and right to NULL. + // (Tree points to new node) + MakeExprNode(tree, mCurToken, CONDITIONAL, tree, NULL); + + // Get the next token. + GetNextToken(); + + // Make a term, setting the right child of Tree to point to it. + MakeTerm(tree->right); + } +} + +//----------------------------------------------------------------------------- +// returns true for success, false for failure +//----------------------------------------------------------------------------- +static bool BuildExpression(void) { + // Get the first token, and build the tree. + GetNextToken(); + + MakeExpression(mExprTree); + return true; +} + +//----------------------------------------------------------------------------- +// returns the value of the node after resolving all children +//----------------------------------------------------------------------------- +static bool SimplifyNode(ExprTree& node) { + if (!node) return false; + + // Simplify the left and right children of this node + bool leftVal = SimplifyNode(node->left); + bool rightVal = SimplifyNode(node->right); + + // Simplify this node + switch (node->kind) { + case NOT: + // the child of '!' is always to the right + node->data.value = !rightVal; + break; + + case CONDITIONAL: + if (node->data.cond == AND_OP) { + node->data.value = leftVal && rightVal; + } else // OR_OP + { + node->data.value = leftVal || rightVal; + } + break; + + default: // LITERAL + break; + } + + // This node has beed resolved + node->kind = LITERAL; + return node->data.value; +} + +//----------------------------------------------------------------------------- +// Interface to solve a conditional expression. Returns false on failure. +//----------------------------------------------------------------------------- +bool EvaluateExpression(bool& result, const char* InfixExpression, + GetSymbolProc_t pGetSymbolProc) { + if (!InfixExpression) return false; + + g_pGetSymbolProc = pGetSymbolProc; + + bool success = false; + mExpression = InfixExpression; + mExprTree = 0; + mCurPosition = 0; + + // Building the expression tree will fail on bad syntax + if (BuildExpression()) { + success = true; + result = SimplifyNode(mExprTree); + } + + FreeTree(mExprTree); + return success; +} diff --git a/utils/vpc/generatordefinition.cpp b/utils/vpc/generatordefinition.cpp new file mode 100644 index 0000000..1a346eb --- /dev/null +++ b/utils/vpc/generatordefinition.cpp @@ -0,0 +1,352 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" + +#include "tier0/memdbgon.h" + +CGeneratorDefinition::CGeneratorDefinition() +{ + Clear(); +} + +void CGeneratorDefinition::Clear() +{ + m_pPropertyNames = NULL; + m_ScriptName.Clear(); + m_NameString.Clear(); + m_VersionString.Clear(); + m_Tools.Purge(); + m_ScriptCRC = 0; +} + +void CGeneratorDefinition::IterateAttributesKey( ToolProperty_t *pProperty, KeyValues *pAttributesKV ) +{ + const char *pAttributeName = pAttributesKV->GetName(); + const char *pValue = pAttributesKV->GetString( "" ); + + //Msg( "Attribute name: %s\n", pAttributeName ); + + if ( !V_stricmp( pAttributeName, "type" ) ) + { + if ( !V_stricmp( pValue, "bool" ) || !V_stricmp( pValue, "boolean" ) ) + { + pProperty->m_nType = PT_BOOLEAN; + } + else if ( !V_stricmp( pValue, "string" ) ) + { + pProperty->m_nType = PT_STRING; + } + else if ( !V_stricmp( pValue, "list" ) ) + { + pProperty->m_nType = PT_LIST; + } + else if ( !V_stricmp( pValue, "int" ) || !V_stricmp( pValue, "integer" ) ) + { + pProperty->m_nType = PT_INTEGER; + } + else if ( !V_stricmp( pValue, "ignore" ) || !V_stricmp( pValue, "none" ) ) + { + pProperty->m_nType = PT_IGNORE; + } + else if ( !V_stricmp( pValue, "deprecated" ) || !V_stricmp( pValue, "donotuse" ) ) + { + pProperty->m_nType = PT_DEPRECATED; + } + else + { + // unknown + g_pVPC->VPCError( "Unknown type '%s' in '%s'", pValue, pProperty->m_ParseString.Get() ); + } + } + else if ( !V_stricmp( pAttributeName, "alias" ) ) + { + pProperty->m_AliasString = pValue; + } + else if ( !V_stricmp( pAttributeName, "legacy" ) ) + { + pProperty->m_LegacyString = pValue; + } + else if ( !V_stricmp( pAttributeName, "InvertOutput" ) ) + { + pProperty->m_bInvertOutput = pAttributesKV->GetBool(); + } + else if ( !V_stricmp( pAttributeName, "output" ) ) + { + pProperty->m_OutputString = pValue; + } + else if ( !V_stricmp( pAttributeName, "fixslashes" ) ) + { + pProperty->m_bFixSlashes = pAttributesKV->GetBool(); + } + else if ( !V_stricmp( pAttributeName, "PreferSemicolonNoComma" ) ) + { + pProperty->m_bPreferSemicolonNoComma = pAttributesKV->GetBool(); + } + else if ( !V_stricmp( pAttributeName, "PreferSemicolonNoSpace" ) ) + { + pProperty->m_bPreferSemicolonNoSpace = pAttributesKV->GetBool(); + } + else if ( !V_stricmp( pAttributeName, "AppendSlash" ) ) + { + pProperty->m_bAppendSlash = pAttributesKV->GetBool(); + } + else if ( !V_stricmp( pAttributeName, "GlobalProperty" ) ) + { + pProperty->m_bEmitAsGlobalProperty = pAttributesKV->GetBool(); + } + else if ( !V_stricmp( pAttributeName, "ordinals" ) ) + { + if ( pProperty->m_nType == PT_UNKNOWN ) + { + pProperty->m_nType = PT_LIST; + } + + for ( KeyValues *pKV = pAttributesKV->GetFirstSubKey(); pKV; pKV = pKV->GetNextKey() ) + { + const char *pOrdinalName = pKV->GetName(); + const char *pOrdinalValue = pKV->GetString(); + if ( !pOrdinalValue[0] ) + { + g_pVPC->VPCError( "Unknown ordinal value for name '%s' in '%s'", pOrdinalName, pProperty->m_ParseString.Get() ); + } + + intp iIndex = pProperty->m_Ordinals.AddToTail(); + pProperty->m_Ordinals[iIndex].m_ParseString = pOrdinalName; + pProperty->m_Ordinals[iIndex].m_ValueString = pOrdinalValue; + } + } + else + { + g_pVPC->VPCError( "Unknown attribute '%s' in '%s'", pAttributeName, pProperty->m_ParseString.Get() ); + } +} + +void CGeneratorDefinition::IteratePropertyKey( GeneratorTool_t *pTool, KeyValues *pPropertyKV ) +{ + //Msg( "Property Key name: %s\n", pPropertyKV->GetName() ); + + intp iIndex = pTool->m_Properties.AddToTail(); + ToolProperty_t *pProperty = &pTool->m_Properties[iIndex]; + + pProperty->m_ParseString = pPropertyKV->GetName(); + + KeyValues *pKV = pPropertyKV->GetFirstSubKey(); + if ( !pKV ) + return; + + for ( ;pKV; pKV = pKV->GetNextKey() ) + { + IterateAttributesKey( pProperty, pKV ); + } +} + +void CGeneratorDefinition::IterateToolKey( KeyValues *pToolKV ) +{ + //Msg( "Tool Key name: %s\n", pToolKV->GetName() ); + + // find or create + GeneratorTool_t *pTool = NULL; + for ( intp i = 0; i < m_Tools.Count(); i++ ) + { + if ( !V_stricmp( pToolKV->GetName(), m_Tools[i].m_ParseString ) ) + { + pTool = &m_Tools[i]; + break; + } + } + if ( !pTool ) + { + intp iIndex = m_Tools.AddToTail(); + pTool = &m_Tools[iIndex]; + } + + pTool->m_ParseString = pToolKV->GetName(); + + KeyValues *pKV = pToolKV->GetFirstSubKey(); + if ( !pKV ) + return; + + for ( ;pKV; pKV = pKV->GetNextKey() ) + { + IteratePropertyKey( pTool, pKV ); + } +} + +void CGeneratorDefinition::AssignIdentifiers() +{ + CUtlVector< bool > usedPropertyNames; + intp nTotalPropertyNames = 0; + while ( m_pPropertyNames[nTotalPropertyNames].m_nPropertyId >= 0 ) + { + nTotalPropertyNames++; + } + usedPropertyNames.SetCount( nTotalPropertyNames ); + + // assign property identifiers + for ( intp i = 0; i < m_Tools.Count(); i++ ) + { + GeneratorTool_t *pTool = &m_Tools[i]; + + // assign the tool keyword + configKeyword_e keyword = g_pVPC->NameToKeyword( pTool->m_ParseString.Get() ); + if ( keyword == KEYWORD_UNKNOWN ) + { + g_pVPC->VPCError( "Unknown Tool Keyword '%s' in '%s'", pTool->m_ParseString.Get(), m_ScriptName.Get() ); + } + pTool->m_nKeyword = keyword; + + const char *pPrefix = m_NameString.Get(); + const char *pToolName = pTool->m_ParseString.Get(); + if ( pToolName[0] == '$' ) + { + pToolName++; + } + + for ( intp j = 0; j < pTool->m_Properties.Count(); j++ ) + { + ToolProperty_t *pProperty = &pTool->m_Properties[j]; + + if ( pProperty->m_nType == PT_IGNORE || pProperty->m_nType == PT_DEPRECATED ) + { + continue; + } + + const char *pPropertyName = pProperty->m_AliasString.Get(); + if ( !pPropertyName[0] ) + { + pPropertyName = pProperty->m_ParseString.Get(); + } + if ( pPropertyName[0] == '$' ) + { + pPropertyName++; + } + + CUtlString prefixString = CUtlString( CFmtStr( "%s_%s", pPrefix, pToolName ) ); + + bool bFound = false; + for ( intp k = 0; k < nTotalPropertyNames && !bFound; k++ ) + { + if ( !V_stricmp( prefixString.Get(), m_pPropertyNames[k].m_pPrefixName ) ) + { + if ( !V_stricmp( pPropertyName, m_pPropertyNames[k].m_pPropertyName ) ) + { + pProperty->m_nPropertyId = m_pPropertyNames[k].m_nPropertyId; + bFound = true; + usedPropertyNames[k] = true; + } + } + } + if ( !bFound ) + { + g_pVPC->VPCError( "Could not find PROPERTYNAME( %s, %s ) for %s", prefixString.Get(), pPropertyName, m_ScriptName.Get() ); + } + } + } + + if ( g_pVPC->IsVerbose() ) + { + for ( intp i = 0; i < usedPropertyNames.Count(); i++ ) + { + if ( !usedPropertyNames[i] ) + { + g_pVPC->VPCWarning( "Unused PROPERTYNAME( %s, %s ) in %s", m_pPropertyNames[i].m_pPrefixName, m_pPropertyNames[i].m_pPropertyName, m_ScriptName.Get() ); + } + } + } +} + +void CGeneratorDefinition::LoadDefinition( const char *pDefnitionName, PropertyName_t *pPropertyNames ) +{ + Clear(); + + m_pPropertyNames = pPropertyNames; + g_pVPC->GetScript().PushScript( CFmtStr( "vpc_scripts\\definitions\\%s", pDefnitionName ) ); + + // project definitions are KV format + KeyValues *pScriptKV = new KeyValues( g_pVPC->GetScript().GetName() ); + + pScriptKV->LoadFromBuffer( g_pVPC->GetScript().GetName(), g_pVPC->GetScript().GetData() ); + + m_ScriptName = g_pVPC->GetScript().GetName(); + m_ScriptCRC = CRC32_ProcessSingleBuffer( g_pVPC->GetScript().GetData(), V_strlen( g_pVPC->GetScript().GetData() ) ); + + m_NameString = pScriptKV->GetName(); + + KeyValues *pKV = pScriptKV->GetFirstSubKey(); + for ( ;pKV; pKV = pKV->GetNextKey() ) + { + const char *pKeyName = pKV->GetName(); + if ( !V_stricmp( pKeyName, "version" ) ) + { + m_VersionString = pKV->GetString(); + } + else + { + IterateToolKey( pKV ); + } + } + + g_pVPC->GetScript().PopScript(); + pScriptKV->deleteThis(); + + g_pVPC->VPCStatus( false, "Definition: '%s' Version: %s", m_NameString.Get(), m_VersionString.Get() ); + + AssignIdentifiers(); +} + +const char *CGeneratorDefinition::GetScriptName( CRC32_t *pCRC ) +{ + if ( pCRC ) + { + *pCRC = m_ScriptCRC; + } + + return m_ScriptName.Get(); +} + +ToolProperty_t *CGeneratorDefinition::GetProperty( configKeyword_e keyword, const char *pPropertyName ) +{ + for ( intp i = 0; i < m_Tools.Count(); i++ ) + { + GeneratorTool_t *pTool = &m_Tools[i]; + if ( pTool->m_nKeyword != keyword ) + continue; + + for ( intp j = 0; j < pTool->m_Properties.Count(); j++ ) + { + ToolProperty_t *pToolProperty = &pTool->m_Properties[j]; + if ( !V_stricmp( pToolProperty->m_ParseString.Get(), pPropertyName ) ) + { + // found + return pToolProperty; + } + if ( !pToolProperty->m_LegacyString.IsEmpty() && !V_stricmp( pToolProperty->m_LegacyString.Get(), pPropertyName ) ) + { + // found + return pToolProperty; + } + } + } + + // not found + return NULL; +} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utils/vpc/generatordefinition.h b/utils/vpc/generatordefinition.h new file mode 100644 index 0000000..ea5a8cc --- /dev/null +++ b/utils/vpc/generatordefinition.h @@ -0,0 +1,114 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_GENERATORDEFINITION_H_ +#define VPC_GENERATORDEFINITION_H_ + +struct PropertyName_t { + int m_nPropertyId; + const char *m_pPrefixName; + const char *m_pPropertyName; +}; + +enum configKeyword_e { + KEYWORD_UNKNOWN = -1, + KEYWORD_GENERAL, + KEYWORD_DEBUGGING, + KEYWORD_COMPILER, + KEYWORD_PS3_SNCCOMPILER, + KEYWORD_PS3_GCCCOMPILER, + KEYWORD_LIBRARIAN, + KEYWORD_LINKER, + KEYWORD_PS3_SNCLINKER, + KEYWORD_PS3_GCCLINKER, + KEYWORD_MANIFEST, + KEYWORD_XMLDOCGEN, + KEYWORD_BROWSEINFO, + KEYWORD_RESOURCES, + KEYWORD_PREBUILDEVENT, + KEYWORD_PRELINKEVENT, + KEYWORD_POSTBUILDEVENT, + KEYWORD_CUSTOMBUILDSTEP, + KEYWORD_XBOXIMAGE, + KEYWORD_XBOXDEPLOYMENT, + KEYWORD_MAX, +}; + +enum PropertyType_e { + PT_UNKNOWN = 0, + PT_BOOLEAN, + PT_STRING, + PT_INTEGER, + PT_LIST, + PT_IGNORE, + PT_DEPRECATED, +}; + +struct PropertyOrdinal_t { + CUtlString m_ParseString; + CUtlString m_ValueString; +}; + +struct ToolProperty_t { + ToolProperty_t() { + m_nPropertyId = -1; + m_nType = PT_UNKNOWN; + m_bFixSlashes = false; + m_bEmitAsGlobalProperty = false; + m_bInvertOutput = false; + m_bAppendSlash = false; + m_bPreferSemicolonNoComma = false; + m_bPreferSemicolonNoSpace = false; + } + + CUtlString m_ParseString; + CUtlString m_AliasString; + CUtlString m_LegacyString; + CUtlString m_OutputString; + CUtlVector m_Ordinals; + + int m_nPropertyId; + PropertyType_e m_nType; + bool m_bFixSlashes; + bool m_bEmitAsGlobalProperty; + bool m_bInvertOutput; + bool m_bAppendSlash; + bool m_bPreferSemicolonNoComma; + bool m_bPreferSemicolonNoSpace; +}; + +struct GeneratorTool_t { + GeneratorTool_t() { m_nKeyword = KEYWORD_UNKNOWN; } + + CUtlString m_ParseString; + CUtlVector m_Properties; + configKeyword_e m_nKeyword; +}; + +class CGeneratorDefinition { + public: + CGeneratorDefinition(); + + void LoadDefinition(const char *pDefinitionName, + PropertyName_t *pPropertyNames); + ToolProperty_t *GetProperty(configKeyword_e keyword, + const char *pPropertyName); + + const char *GetScriptName(CRC32_t *pCRC); + + private: + void AssignIdentifiers(); + void IterateToolKey(KeyValues *pToolKV); + void IteratePropertyKey(GeneratorTool_t *pTool, KeyValues *pPropertyKV); + void IterateAttributesKey(ToolProperty_t *pProperty, + KeyValues *pAttributesKV); + void Clear(); + + PropertyName_t *m_pPropertyNames; + CUtlString m_ScriptName; + CUtlString m_NameString; + CUtlString m_VersionString; + CUtlVector m_Tools; + CRC32_t m_ScriptCRC; +}; + +#endif // VPC_GENERATORDEFINITION_H_ diff --git a/utils/vpc/groupscript.cpp b/utils/vpc/groupscript.cpp new file mode 100644 index 0000000..a6d9f1e --- /dev/null +++ b/utils/vpc/groupscript.cpp @@ -0,0 +1,369 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "tier0/memdbgon.h" + +// This keyword works in both group and project scripts +extern void VPC_SharedKeyword_Conditional(); + + +//----------------------------------------------------------------------------- +// VPC_Group_FindOrCreateProject +// +//----------------------------------------------------------------------------- +projectIndex_t VPC_Group_FindOrCreateProject( const char *pName, bool bCreate ) +{ + for ( intp i = 0; i < g_pVPC->m_Projects.Count(); i++ ) + { + if ( !V_stricmp( pName, g_pVPC->m_Projects[i].name.String() ) ) + { + return i; + } + } + + if ( !bCreate ) + return INVALID_INDEX; + + intp index = g_pVPC->m_Projects.AddToTail(); + g_pVPC->m_Projects[index].name = pName; + + return index; +} + +//----------------------------------------------------------------------------- +// VPC_Group_CreateGroup +// +//----------------------------------------------------------------------------- +groupIndex_t VPC_Group_CreateGroup() +{ + groupIndex_t index = g_pVPC->m_Groups.AddToTail(); + return index; +} + +//----------------------------------------------------------------------------- +// VPC_Group_FindOrCreateGroupTag +// +//----------------------------------------------------------------------------- +groupTagIndex_t VPC_Group_FindOrCreateGroupTag( const char *pName, bool bCreate ) +{ + for (intp i=0; im_GroupTags.Count(); i++) + { + if ( !V_stricmp( pName, g_pVPC->m_GroupTags[i].name.String() ) ) + return i; + } + + if ( !bCreate ) + return INVALID_INDEX; + + groupTagIndex_t index = g_pVPC->m_GroupTags.AddToTail(); + g_pVPC->m_GroupTags[index].name = pName; + + return index; +} + +//----------------------------------------------------------------------------- +// VPC_GroupKeyword_Games +// +//----------------------------------------------------------------------------- +void VPC_GroupKeyword_Games() +{ + const char *pToken; + + pToken = g_pVPC->GetScript().GetToken( true ); + if ( !pToken || !pToken[0] || V_stricmp( pToken, "{" ) ) + g_pVPC->VPCSyntaxError(); + + while ( 1 ) + { + pToken = g_pVPC->GetScript().GetToken( true ); + if ( !pToken || !pToken[0] ) + g_pVPC->VPCSyntaxError(); + + if ( !V_stricmp( pToken, "}" ) ) + { + // end of section + break; + } + else + { + g_pVPC->FindOrCreateConditional( pToken, true, CONDITIONAL_GAME ); + } + } +} + +//----------------------------------------------------------------------------- +// VPC_GroupKeyword_Group +// +//----------------------------------------------------------------------------- +void VPC_GroupKeyword_Group() +{ + const char *pToken; + bool bFirstToken = true; + groupIndex_t groupIndex; + projectIndex_t projectIndex; + + groupIndex = VPC_Group_CreateGroup(); + + while ( 1 ) + { + if ( !bFirstToken ) + { + pToken = g_pVPC->GetScript().PeekNextToken( false ); + if ( !pToken || !pToken[0] ) + break; + } + else + { + bFirstToken = false; + } + + pToken = g_pVPC->GetScript().GetToken( false ); + if ( !pToken || !pToken[0] ) + g_pVPC->VPCSyntaxError(); + + // specified tag now builds this group + groupTagIndex_t groupTagIndex = VPC_Group_FindOrCreateGroupTag( pToken, true ); + g_pVPC->m_GroupTags[groupTagIndex].groups.AddToTail( groupIndex ); + } + + pToken = g_pVPC->GetScript().GetToken( true ); + if ( !pToken || !pToken[0] || V_stricmp( pToken, "{" ) ) + g_pVPC->VPCSyntaxError(); + + while ( 1 ) + { + pToken = g_pVPC->GetScript().GetToken( true ); + if ( !pToken || !pToken[0] ) + g_pVPC->VPCSyntaxError(); + + if ( !V_stricmp( pToken, "}" ) ) + { + // end of section + break; + } + else + { + projectIndex = VPC_Group_FindOrCreateProject( pToken, false ); + if ( projectIndex != INVALID_INDEX ) + { + intp index = g_pVPC->m_Groups[groupIndex].projects.AddToTail(); + g_pVPC->m_Groups[groupIndex].projects[index] = projectIndex; + } + else + { + g_pVPC->VPCWarning( "No Project %s defined, ignoring.", pToken ); + continue; + } + } + } +} + +//----------------------------------------------------------------------------- +// VPC_GroupKeyword_Project +// +//----------------------------------------------------------------------------- +void VPC_GroupKeyword_Project() +{ + const char *pToken; + + pToken = g_pVPC->GetScript().GetToken( false ); + if ( !pToken || !pToken[0] ) + g_pVPC->VPCSyntaxError(); + + if ( VPC_Group_FindOrCreateProject( pToken, false ) != INVALID_INDEX ) + { + // already defined + g_pVPC->VPCWarning( "project %s already defined", pToken ); + g_pVPC->VPCSyntaxError(); + } + + projectIndex_t projectIndex = VPC_Group_FindOrCreateProject( pToken, true ); + + // create a default group that contains just this project + groupIndex_t groupIndex = VPC_Group_CreateGroup(); + g_pVPC->m_Groups[groupIndex].projects.AddToTail( projectIndex ); + + // create a default tag that matches the project name + groupTagIndex_t groupTagIndex = VPC_Group_FindOrCreateGroupTag( pToken, true ); + g_pVPC->m_GroupTags[groupTagIndex].groups.AddToTail( groupIndex ); + g_pVPC->m_GroupTags[groupTagIndex].bSameAsProject = true; + + pToken = g_pVPC->GetScript().GetToken( true ); + if ( !pToken || !pToken[0] || V_stricmp( pToken, "{" ) ) + g_pVPC->VPCSyntaxError(); + + while ( 1 ) + { + pToken = g_pVPC->GetScript().GetToken( true ); + if ( !pToken || !pToken[0] ) + g_pVPC->VPCSyntaxError(); + + if ( !V_stricmp( pToken, "}" ) ) + { + // end of section + break; + } + else + { + scriptIndex_t scriptIndex = g_pVPC->m_Projects[projectIndex].scripts.AddToTail(); + g_pVPC->m_Projects[projectIndex].scripts[scriptIndex].name = pToken; + + pToken = g_pVPC->GetScript().PeekNextToken( false ); + if ( pToken && pToken[0] ) + { + pToken = g_pVPC->GetScript().GetToken( false ); + g_pVPC->m_Projects[projectIndex].scripts[scriptIndex].m_condition = pToken; + } + } + } +} + + +//----------------------------------------------------------------------------- +// VPC_ParseGroupScript +// +//----------------------------------------------------------------------------- +void VPC_ParseGroupScript( const char *script_name ) +{ + char szScriptName[MAX_PATH]; + const char *pToken; + + // caller's pointer is aliased + strcpy( szScriptName, script_name ); + V_FixSlashes( szScriptName ); + + g_pVPC->VPCStatus( false, "Parsing: %s", szScriptName ); + g_pVPC->GetScript().PushScript( szScriptName ); + + while ( 1 ) + { + pToken = g_pVPC->GetScript().GetToken( true ); + if ( !pToken || !pToken[0] ) + { + // end of file + break; + } + + if ( !V_stricmp( pToken, "$include" ) ) + { + pToken = g_pVPC->GetScript().GetToken( false ); + if ( !pToken || !pToken[0] ) + { + // end of file + g_pVPC->VPCSyntaxError(); + } + + // recurse into and run + VPC_ParseGroupScript( pToken ); + } + else if ( !V_stricmp( pToken, "$games" ) ) + { + VPC_GroupKeyword_Games(); + } + else if ( !V_stricmp( pToken, "$group" ) ) + { + VPC_GroupKeyword_Group(); + } + else if ( !V_stricmp( pToken, "$project" ) ) + { + VPC_GroupKeyword_Project(); + } + else if ( !V_stricmp( pToken, "$Conditional" ) ) + { + VPC_SharedKeyword_Conditional(); + } + else + { + g_pVPC->VPCSyntaxError(); + } + } + + g_pVPC->GetScript().PopScript(); +} + +//----------------------------------------------------------------------------- +// Collect all the +XXX, remove all the -XXX +// This allows removal to be the expected trumping operation. +//----------------------------------------------------------------------------- +void CVPC::GenerateBuildSet( CProjectDependencyGraph &dependencyGraph ) +{ + // process +XXX commands + for ( intp i = 0; i < m_BuildCommands.Count(); i++ ) + { + const char *pCommand = m_BuildCommands[i].Get(); + if ( pCommand[0] == '-' ) + continue; + + groupTagIndex_t groupTagIndex = VPC_Group_FindOrCreateGroupTag( pCommand+1, false ); + if ( groupTagIndex == INVALID_INDEX ) + continue; + groupTag_t *pGroupTag = &g_pVPC->m_GroupTags[groupTagIndex]; + + CUtlVector projectsToAdd; + + for ( intp j=0; jgroups.Count(); j++ ) + { + group_t *pGroup = &g_pVPC->m_Groups[pGroupTag->groups[j]]; + for ( intp k=0; kprojects.Count(); k++ ) + { + projectIndex_t targetProject = pGroup->projects[k]; + if ( pCommand[0] == '*' ) + { + // Add this project and any projects that depend on it. + if ( !dependencyGraph.HasGeneratedDependencies() ) + dependencyGraph.BuildProjectDependencies( BUILDPROJDEPS_CHECK_ALL_PROJECTS, m_pPhase1Projects ); + + dependencyGraph.GetProjectDependencyTree( targetProject, projectsToAdd, false ); + } + else if ( pCommand[0] == '@' ) + { + // Add this project and any projects that it depends on. + if ( !dependencyGraph.HasGeneratedDependencies() ) + dependencyGraph.BuildProjectDependencies( BUILDPROJDEPS_CHECK_ALL_PROJECTS, m_pPhase1Projects ); + + dependencyGraph.GetProjectDependencyTree( targetProject, projectsToAdd, true ); + } + else + { + projectsToAdd.AddToTail( targetProject ); + } + } + } + + // Add all the projects in the list. + for ( intp j=0; j < projectsToAdd.Count(); j++ ) + { + projectIndex_t targetProject = projectsToAdd[j]; + + if ( g_pVPC->m_TargetProjects.Find( targetProject ) == -1 ) + { + g_pVPC->m_TargetProjects.AddToTail( targetProject ); + } + } + } + + // process -XXX commands, explicitly remove tagge projects + for ( intp i=0; im_GroupTags[groupTagIndex]; + + for ( intp j=0; jgroups.Count(); j++ ) + { + group_t *pGroup = &g_pVPC->m_Groups[pGroupTag->groups[j]]; + for ( intp k=0; kprojects.Count(); k++ ) + { + g_pVPC->m_TargetProjects.FindAndRemove( pGroup->projects[k] ); + } + } + } +} diff --git a/utils/vpc/ibaseprojectgenerator.h b/utils/vpc/ibaseprojectgenerator.h new file mode 100644 index 0000000..aaf9eed --- /dev/null +++ b/utils/vpc/ibaseprojectgenerator.h @@ -0,0 +1,75 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_IBASEPROJECTGENERATOR_H_ +#define VPC_IBASEPROJECTGENERATOR_H_ + +// Usage: +// +// StartProject +// StartConfigurationBlock +// StartPropertySection +// HandleProperty... +// EndPropertySection +// EndConfigurationBlock +// +// AddFile... +// [inside each file it can do another configuration block +// as above] +// [also, files can be put in folders with StartFolder/AddFolder] +// EndProject +// +class IBaseProjectGenerator { + public: + // What file extension does this use? (vcproj, mak, vpj). + virtual const char *GetProjectFileExtension() = 0; + + // Called before doing anything in a project (in g_pVPC->GetOutputFilename()). + virtual void StartProject() = 0; + virtual void EndProject() = 0; + + // Access the project name. + virtual CUtlString GetProjectName() = 0; + virtual void SetProjectName(const char *pProjectName) = 0; + + // Get a list of all configurations. + virtual void GetAllConfigurationNames( + CUtlVector &configurationNames) = 0; + + // Configuration data is specified in between these calls and inside + // BeginPropertySection/EndPropertySection. If bFileSpecific is set, then the + // configuration data only applies to the last file added. + virtual void StartConfigurationBlock(const char *pConfigName, + bool bFileSpecific) = 0; + virtual void EndConfigurationBlock() = 0; + + // These functions are called when it enters a section like $Compiler, + // $Linker, etc. In between the BeginPropertySection/EndPropertySection, it'll + // call HandleProperty for any properties inside that section. + virtual bool StartPropertySection(configKeyword_e keyword, + bool *pbShouldSkip = NULL) = 0; + virtual void HandleProperty(const char *pProperty, + const char *pCustomScriptData = NULL) = 0; + virtual void EndPropertySection(configKeyword_e keyword) = 0; + + // Files go in folders. The generator should maintain a stack of folders as + // they're added. + virtual void StartFolder(const char *pFolderName) = 0; + virtual void EndFolder() = 0; + + // Add files. Any config blocks/properties between StartFile/EndFile apply to + // this file only. It will only ever have one active file. + virtual bool StartFile(const char *pFilename, bool bWarnIfAlreadyExists) = 0; + virtual void EndFile() = 0; + + // This is actually just per-file configuration data. + virtual void FileExcludedFromBuild(bool bExcluded) = 0; + virtual void FileIsSchema( + bool bIsSchema) = 0; // Mark the current file as schema. + virtual void FileIsDynamic( + bool bIsDynamic) = 0; // Mark the current file as dynamic. + + // Remove the specified file. return true if success + virtual bool RemoveFile(const char *pFilename) = 0; +}; + +#endif // VPC_IBASEPROJECTGENERATOR_H_ diff --git a/utils/vpc/ibasesolutiongenerator.h b/utils/vpc/ibasesolutiongenerator.h new file mode 100644 index 0000000..2dcb536 --- /dev/null +++ b/utils/vpc/ibasesolutiongenerator.h @@ -0,0 +1,15 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_IBASESOLUTIONGENERATOR_H_ +#define VPC_IBASESOLUTIONGENERATOR_H_ + +#include "dependencies.h" + +class IBaseSolutionGenerator { + public: + virtual void GenerateSolutionFile( + const char *pSolutionFilename, + CUtlVector &projects) = 0; +}; + +#endif // VPC_IBASESOLUTIONGENERATOR_H_ diff --git a/utils/vpc/macros.cpp b/utils/vpc/macros.cpp new file mode 100644 index 0000000..eed274f --- /dev/null +++ b/utils/vpc/macros.cpp @@ -0,0 +1,163 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "tier0/memdbgon.h" + +void CVPC::SetMacro(const char *pName, const char *pValue, + bool bSetupDefineInProjectFile) { + // Setup the macro. + VPCStatus(false, "Set Macro: $%s = %s", pName, pValue); + + macro_t *pMacro = FindOrCreateMacro(pName, true, pValue); + pMacro->m_bSetupDefineInProjectFile = bSetupDefineInProjectFile; + pMacro->m_bInternalCreatedMacro = true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +macro_t *CVPC::FindOrCreateMacro(const char *pName, bool bCreate, + const char *pValue) { + for (int i = 0; i < m_Macros.Count(); i++) { + if (!V_stricmp(pName, m_Macros[i].name.String())) { + if (pValue && V_stricmp(pValue, m_Macros[i].value.String())) { + // update + m_Macros[i].value = pValue; + } + + return &m_Macros[i]; + } + } + + if (!bCreate) { + return NULL; + } + + intp index = m_Macros.AddToTail(); + m_Macros[index].name = pName; + m_Macros[index].value = pValue; + + return &m_Macros[index]; +} + +intp CVPC::GetMacrosMarkedForCompilerDefines( + CUtlVector ¯oDefines) { + macroDefines.Purge(); + + for (int i = 0; i < m_Macros.Count(); i++) { + if (m_Macros[i].m_bSetupDefineInProjectFile) { + macroDefines.AddToTail(&m_Macros[i]); + } + } + + return macroDefines.Count(); +} + +static int __cdecl SortMacrosByNameLength(const macro_t *lhs, + const macro_t *rhs) { + if (lhs->name.Length() < rhs->name.Length()) return 1; + if (lhs->name.Length() > rhs->name.Length()) return -1; + return 0; +} + +void CVPC::ResolveMacrosInStringInternal(char const *pString, char *pOutBuff, + int outBuffSize, + bool bStringIsConditional) { + char macroName[MAX_SYSTOKENCHARS]; + char buffer1[MAX_SYSTOKENCHARS]; + char buffer2[MAX_SYSTOKENCHARS]; + buffer2[0] = '\0'; + int i; + + // ensure a "greedy" match by sorting longest to shortest + m_Macros.Sort(SortMacrosByNameLength); + + // iterate and resolve user macros until all macros resolved + strcpy(buffer1, pString); + bool bDone; + do { + bDone = true; + bool bDoReplace = true; + for (i = 0; i < m_Macros.Count(); i++) { + sprintf(macroName, "$%s", m_Macros[i].name.String()); + const char *pFound = V_stristr(buffer1, macroName); + if (pFound && bStringIsConditional) { + // if expanding a conditional, give conditionals priority over macros + // i.e. if the string we've found begins both a macro and conditional, + // don't expand the macro + for (int j = 0; j < m_Conditionals.Count(); j++) { + if (V_stristr(pFound + 1, m_Conditionals[j].name.String()) == + pFound + 1) { + bDoReplace = false; + // the warning is super chatty about $LINUX and $POSIX + if (V_stricmp(macroName, "$LINUX") && + V_stricmp(macroName, "$POSIX")) + g_pVPC->VPCWarning( + "Not replacing macro %s with its value (%s) in conditional " + "%s\n", + macroName, + m_Macros[i].value.Length() ? m_Macros[i].value.String() + : "null", + pFound); + break; + } + } + } + + // can't use ispunct as '|' and '&' are punctuation, but we dont want to + // warn on them + if (pFound && bStringIsConditional && bDoReplace && + (isalnum(pFound[strlen(macroName)]) || + pFound[strlen(macroName)] == '_')) + g_pVPC->VPCWarning( + "Replacing macro %s with its value (%s) in conditional %s\n", + macroName, + m_Macros[i].value.Length() ? m_Macros[i].value.String() : "null", + pFound); + + if (bDoReplace && + Sys_ReplaceString(buffer1, macroName, m_Macros[i].value.String(), + buffer2, sizeof(buffer2))) { + bDone = false; + } + strcpy(buffer1, buffer2); + } + } while (!bDone); + + intp len = V_strlen(buffer1); + if (outBuffSize < len) len = outBuffSize; + memcpy(pOutBuff, buffer1, len); + pOutBuff[len] = '\0'; +} + +void CVPC::ResolveMacrosInString(char const *pString, char *pOutBuff, + int outBuffSize) { + ResolveMacrosInStringInternal(pString, pOutBuff, outBuffSize, false); +} + +void CVPC::ResolveMacrosInConditional(char const *pString, char *pOutBuff, + int outBuffSize) { + ResolveMacrosInStringInternal(pString, pOutBuff, outBuffSize, true); +} + +void CVPC::RemoveScriptCreatedMacros() { + for (int i = 0; i < m_Macros.Count(); i++) { + if (!m_Macros[i].m_bInternalCreatedMacro) { + m_Macros.Remove(i); + --i; + } + } +} + +const char *CVPC::GetMacroValue(const char *pName) { + for (int i = 0; i < m_Macros.Count(); i++) { + if (!V_stricmp(pName, m_Macros[i].name.String())) { + return m_Macros[i].value.String(); + } + } + + // not found + return ""; +} diff --git a/utils/vpc/main.cpp b/utils/vpc/main.cpp new file mode 100644 index 0000000..9edc0ab --- /dev/null +++ b/utils/vpc/main.cpp @@ -0,0 +1,209 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include + +#include "vpc.h" +#include "dependencies.h" + +#include "tier0/logging.h" + +#ifdef _WIN64 +#include "memory_reservation_x64.h" +#endif + +#include "tier0/memdbgon.h" + +DEFINE_LOGGING_CHANNEL_NO_TAGS(LOG_VPC, "VPC"); + +namespace { + +/** + * @brief Is two phase VPC required. + * @param argc Argc. + * @param argv Argv. + * @return true if two phase, false if not. + */ +bool IsTwoPhaseVpc(int argc, char **argv) noexcept { + for (int i{0}; i < argc; i++) { + if (V_stricmp("/windows", argv[i]) == 0) { + return true; + } + } + + return false; +} + +} // namespace + +// main +// VPC is a DLL in Source. +#if defined(STANDALONE_VPC) || defined(OSX) || defined(LINUX) +/** + * @brief Main entry point. + */ +int main(int argc, char **argv) +#else +/** + * @brief Main entry point for DLL. + * @param argc Argument count. + * @param argv Arguments. + * @return return code. + */ +int vpcmain(int argc, char **argv) +#endif +{ +#ifdef _WIN64 + // Catch x64 errors early. + vpc::win::ReserveBottomMemoryFor64Bit(); +#endif + + const bool is_windows_two_phase{IsTwoPhaseVpc(argc, argv)}; + + g_pVPC = new CVPC(); + + if (is_windows_two_phase) { + // We're going to do some grotesque hackery by manipulating the command line + // arguments. + // + // First we're going to generate a VPC object to generate all the 32-bit + // .vcxproj files but not write a solution file. Then we'll run it for the + // 64-bit .vcxproj files, then write a solution file containing both (if a + // solution file was requested). + // + // To accomplish this, subtract the /windows parameter (and /MKSLN if + // present), run, extract the list of generated project files, add a /WIN64 + // (and put /MKSLN back if necessary) and pass in the generated project file + // list. + bool should_build_solution{false}; + char solution_path[_MAX_PATH]; + + const char *new_argv[128] = {nullptr}; + unsigned new_argc{0}; + + // Do the 32-bit VPC, capturing the list of project files produced. + Assert(static_cast(argc) <= std::size(new_argv)); + for (int i{0}; i < argc; i++) { + const char *arg{argv[i]}; + + if (!V_stricmp(arg, "/windows") || !V_stricmp(arg, "/dp")) { + // Skip + } else if (!V_stricmp(arg, "/mksln") && i + 1 < argc) { + // If the next parameter is a standard + or - or / or * parameter, then + // we take that to be the name of the solution file. So vpc /mksln + // +engine would generate engine.sln. + const char *next_arg{argv[i + 1]}, sign{next_arg[0]}; + + if (sign == '+' || sign == '-' || sign == '/' || sign == '*' || + sign == '@') { + V_strncpy(solution_path, &next_arg[1], static_cast(std::size(solution_path))); + } else { + V_strncpy(solution_path, next_arg, static_cast(std::size(solution_path))); + i++; + } + + should_build_solution = true; + } else { + new_argv[new_argc++] = arg; + } + } + + if (new_argc >= std::size(new_argv)) { + Error("Overflow argc. Max %zu, got %u.", std::size(new_argv), + new_argc + 1); + } + + // Add a /dp parameter to decorate the 32-bit project names with (win32). + // This makes it easier to differentiate between projects in the solution + // explorer. + new_argv[new_argc++] = "/dp"; + + if (new_argc >= std::size(new_argv)) { + Error("Overflow argc. Max %zu, got %u.", std::size(new_argv), + new_argc + 1); + } + + // Add a define for PHASE1 for any VPC files that need to be aware of that + // sort of thing. + new_argv[new_argc++] = "/define:PHASE1"; + + if (!g_pVPC->Init(new_argc, new_argv)) return 1; + + const int rc{g_pVPC->ProcessCommandLine()}; + if (rc != 0) return rc; + + // Grab a list of all the project files that got generated by this pass, we + // will stuff these into the solution file later. If we aren't building a + // solution, then don't bother. + CUtlVector dependencies; + if (should_build_solution) { + g_pVPC->GetProjectDependencies(dependencies); + } + + // Now reconstruct the command line with the 64-bit flag and the solution + // file added back in. Decrement cParms to eat the /dp and /define we added + // before. + new_argc -= 2; + new_argv[new_argc++] = "/win64"; + + // Add a define for PHASE2 for any VPC files that need to be aware of that + // sort of thing. Example: Projects that generate headers-only and are not + // bitness specific. + new_argv[new_argc++] = "/define:PHASE2"; + + if (should_build_solution) { + if (new_argc >= std::size(new_argv)) { + Error("Overflow argc. Max %zu, got %u.", std::size(new_argv), + new_argc + 1); + } + + new_argv[new_argc++] = "/mksln"; + + if (new_argc >= std::size(new_argv)) { + Error("Overflow argc. Max %zu, got %u.", std::size(new_argv), + new_argc + 1); + } + + new_argv[new_argc++] = solution_path; + } + + g_pVPC->Shutdown(); + delete g_pVPC; + g_pVPC = new CVPC(); + + if (!g_pVPC->Init(new_argc, new_argv)) return 1; + + if (should_build_solution) { + g_pVPC->SetPhase1Projects(&dependencies); + } + } else { + if (!g_pVPC->Init(argc, const_cast(argv))) return 0; + } + + const int rc{g_pVPC->ProcessCommandLine()}; + + g_pVPC->Shutdown(); + delete g_pVPC; + g_pVPC = nullptr; + + return rc; +} + +// VPC is a DLL in Source. +#if !(defined(STANDALONE_VPC) || defined(OSX) || defined(LINUX)) +#include "ilaunchabledll.h" +#include "tier1/interface.h" + +// VPC is launched by vpc.exe, which is a copy of binlaunch.exe. All binlaunch +// does is setup the path to game\bin and load an ILaunchableDLL interface out +// of a DLL with the same name as the exe. +class VpcLaunchableDLL : public ILaunchableDLL { + public: + // All vpc.exe does is load the vpc DLL and run this. + virtual int main(int argc, char **argv) { return vpcmain(argc, argv); } +}; + +EXPOSE_SINGLE_INTERFACE(VpcLaunchableDLL, ILaunchableDLL, + LAUNCHABLE_DLL_INTERFACE_VERSION); +#endif diff --git a/utils/vpc/memory_reservation_x64.cpp b/utils/vpc/memory_reservation_x64.cpp new file mode 100644 index 0000000..e95d27e --- /dev/null +++ b/utils/vpc/memory_reservation_x64.cpp @@ -0,0 +1,124 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "memory_reservation_x64.h" + +#include +#include "winlite.h" + +#include "tier0/memdbgon.h" + +namespace vpc::win { + +#if defined(_DEBUG) && defined(_WIN64) +// Copyright 2012 Bruce Dawson. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +bool ReserveBottomMemoryFor64Bit() noexcept { + static bool is_initialized{false}; + if (is_initialized) return true; + + // Start by reserving large blocks of address space, and then gradually reduce + // the size in order to capture all of the fragments. Technically we should + // continue down to 64 KiB but stopping at 1 MiB is sufficient to keep most + // allocators out. + + constexpr size_t kLowMemoryLine{0x100000000ULL}; + constexpr size_t kOneMiB{static_cast(1024U) * 1024U}; + + size_t total_reserved_bytes{0}, virtual_allocs_num{0}, heap_allocs_num{0}; + + // Virtually allocate memory. + for (size_t size{256U * kOneMiB}; size >= kOneMiB; size /= 2U) { + for (;;) { + void *p{::VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS)}; + if (!p) break; + + if (reinterpret_cast(p) >= kLowMemoryLine) { + // We don't need this memory, so release it completely. + ::VirtualFree(p, 0, MEM_RELEASE); + break; + } + + total_reserved_bytes += size; + ++virtual_allocs_num; + } + } + + // Now repeat the same process but making heap allocations, to use up the + // already reserved heap blocks that are below the 4 GB line. + HANDLE heap{::GetProcessHeap()}; + for (size_t block_size{static_cast(64U) * 1024}; block_size >= 16U; + block_size /= 2U) { + for (;;) { + void *p{::HeapAlloc(heap, 0, block_size)}; + if (!p) break; + + if (reinterpret_cast(p) >= kLowMemoryLine) { + // We don't need this memory, so release it completely. + ::HeapFree(heap, 0, p); + break; + } + + total_reserved_bytes += block_size; + ++heap_allocs_num; + } + } + + // Perversely enough the CRT doesn't use the process heap. Suck up the memory + // the CRT heap has already reserved. + for (size_t block_size{static_cast(64U) * 1024U}; block_size >= 16U; + block_size /= 2U) { + for (;;) { + void *p{malloc(block_size)}; + if (!p) break; + + if (reinterpret_cast(p) >= kLowMemoryLine) { + // We don't need this memory, so release it completely. + free(p); + break; + } + + total_reserved_bytes += block_size; + ++heap_allocs_num; + } + } + + // Print diagnostics showing how many allocations we had to make in + // order to reserve all of low memory, typically less than 200. + char buffer[1000]; + sprintf_s(buffer, + "Reserved %1.3f MiB (%zu virtual allocs, %zu heap allocs) of " + "low-memory.\n", + total_reserved_bytes / (1024.0 * 1024.0), virtual_allocs_num, + heap_allocs_num); + ::OutputDebugStringA(buffer); + + if (total_reserved_bytes > 0) { + is_initialized = true; + return true; + } + + return false; +} +#else +bool ReserveBottomMemoryFor64Bit() noexcept { + OutputDebugStringA("No memory reserved as not x64 or Release mode.\n"); + + return true; +} +#endif + +} // namespace vpc::win \ No newline at end of file diff --git a/utils/vpc/memory_reservation_x64.h b/utils/vpc/memory_reservation_x64.h new file mode 100644 index 0000000..d04a648 --- /dev/null +++ b/utils/vpc/memory_reservation_x64.h @@ -0,0 +1,16 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_MEMORY_RESERVATION_X64_H_ +#define VPC_MEMORY_RESERVATION_X64_H_ + +namespace vpc::win { + +/** + * @brief Reserves bottom memory for x64 to catch x64 problems eariler. + * @return true if success, false otherwise. + */ +bool ReserveBottomMemoryFor64Bit() noexcept; + +} // namespace vpc::win + +#endif // VPC_MEMORY_RESERVATION_X64_H_ diff --git a/utils/vpc/p4sln.cpp b/utils/vpc/p4sln.cpp new file mode 100644 index 0000000..dd3eae9 --- /dev/null +++ b/utils/vpc/p4sln.cpp @@ -0,0 +1,240 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "p4lib/ip4.h" + +#include "tier0/memdbgon.h" + +// fix filenames that have double backslashes at the start +// (Perforce will return this if the root of a clientspec is e.g. "D:\") +static const char* FixPerforceFilename( const char *filename ) +{ + if ( filename && V_strlen( filename ) > 2 && !V_strnicmp( filename + 1, ":\\\\", 3 ) ) + { + // strip out the first backslash + static char newFilename[MAX_PATH]; + V_snprintf( newFilename, sizeof(newFilename), "%c:%s", + filename[0], + &filename[3] + ); + + return newFilename; + } + return filename; +} + +static void GetChangelistFilenames( CUtlVector &changelists, CUtlVector &changelistFilenames ) +{ + // P4 interface didn't initalize in main - abort + if ( !p4 ) + { + g_pVPC->VPCWarning( "P4SLN: Perforce interface not available. Unable to generate solution from the given Perforce changelist." ); + return; + } + + CUtlVector fileList; + int changeListIndex = 0; + if ( changelists.Count() ) + { + changeListIndex = changelists[0]; + } + if ( changeListIndex == -1 ) + { + p4->GetOpenedFileList( fileList, false ); + } + else if ( changeListIndex == 0 ) + { + p4->GetOpenedFileList( fileList, true ); + } + else + { + CUtlVector partialFileList; + + FOR_EACH_VEC( changelists, i ) + { + p4->GetFileListInChangelist( changelists[i], partialFileList ); + + FOR_EACH_VEC( partialFileList, j ) + { + fileList.AddToTail( partialFileList[j] ); + } + } + } + + // If -1 is in the changelist index, then include all. + bool bIncludeAllChangelists = ( changelists.Find( -1 ) != changelists.InvalidIndex() ); + + for ( int i=0; i < fileList.Count(); i++ ) + { + if ( bIncludeAllChangelists || changelists.Find( fileList[i].m_iChangelist ) != changelists.InvalidIndex() ) + { + const char *pFilename = p4->String( fileList[i].m_sLocalFile ); + + const char *pNewFilename = FixPerforceFilename( pFilename ); + + changelistFilenames.AddToTail( pNewFilename ); + } + } +} + +static void GetProjectsDependingOnFiles( CProjectDependencyGraph &dependencyGraph, CUtlVector &filenames, CUtlVector &projects ) +{ + // Now figure out the projects that depend on each of these files. + for ( int iFile=0; iFile < filenames.Count(); iFile++ ) + { + CDependency *pFile = dependencyGraph.FindDependency( filenames[iFile].String() ); + if ( !pFile ) + { + char szRelative[MAX_PATH]; + if ( !V_MakeRelativePath( filenames[iFile].String(), g_pVPC->GetSourcePath(), szRelative, sizeof( szRelative ) ) ) + { + V_strncpy( szRelative, filenames[iFile].String(), sizeof( szRelative ) ); + } + + // This probably means their build commands on the command line didn't include + // any projects that included this file. + g_pVPC->VPCWarning( "%s is not found in the projects searched.", szRelative ); + continue; + } + + // Now see which projects depend on this file. + for ( int iProject=0; iProject < dependencyGraph.m_Projects.Count(); iProject++ ) + { + CDependency_Project *pProject = dependencyGraph.m_Projects[iProject]; + + if ( pProject->DependsOn( pFile, k_EDependsOnFlagCheckNormalDependencies | k_EDependsOnFlagRecurse | k_EDependsOnFlagTraversePastLibs | k_EDependsOnFlagCheckAdditionalDependencies ) ) + { + if ( projects.Find( pProject ) == -1 ) + projects.AddToTail( pProject ); + } + } + } + + // generate the group restrictions + // the user can explicitly provide a set of groups to narrow the wide dependency target + CUtlVector< CUtlString > groupRestrictions; + groupRestrictions = g_pVPC->m_P4GroupRestrictions; + if ( !groupRestrictions.Count() ) + { + // the default restriction is to the "everything" group + groupRestrictions.AddToTail( "everything" ); + } + + CUtlVector< projectIndex_t > allowedProjectIndices; + if ( groupRestrictions.Count() ) + { + // get all of the allowed projects by iterating the restrict-to-groups + for ( int i = 0; i < groupRestrictions.Count(); i++ ) + { + CUtlVector< projectIndex_t > projectIndices; + if ( !g_pVPC->GetProjectsInGroup( projectIndices, groupRestrictions[i].Get() ) ) + { + g_pVPC->VPCError( "No projects found in group '%s'.", groupRestrictions[i].Get() ); + } + + // aggregate into wider list + for ( int j = 0; j < projectIndices.Count(); j++ ) + { + allowedProjectIndices.AddToTail( projectIndices[j] ); + } + } + } + + // Make sure that each of the dependent projects are members of the restricted groups, otherwise prevent their inclusion. + CUtlVector< int > doomedProjectIndices; + if ( allowedProjectIndices.Count() ) + { + for ( int j = 0; j < projects.Count(); j++ ) + { + // find the target project in the allowed set + if ( allowedProjectIndices.Find( projects[j]->m_iProjectIndex ) == allowedProjectIndices.InvalidIndex() ) + { + // the target project is not in the allowed set + // add in descending order so indices can be properly removed below from largest index to smallest + doomedProjectIndices.AddToHead( j ); + } + } + + // Remove the projects that are not part of the restrict-to-groups + // Indexes were added in descending order, so removal is actually from the end, truncating the set + for ( int j = 0; j < doomedProjectIndices.Count(); j++ ) + { + projects.Remove( doomedProjectIndices[j] ); + } + } +} + + +static void UpdateProjects( CUtlVector &projects ) +{ + for ( int iProject=0; iProject < projects.Count(); iProject++ ) + { + Log_Msg( LOG_VPC, "\n" ); + + CDependency_Project *pDependency = projects[iProject]; + pDependency->ExportProjectParameters(); + + if ( g_pVPC->IsForceGenerate() || !g_pVPC->IsProjectCurrent( g_pVPC->GetOutputFilename(), true ) ) + { + project_t *pProject = &g_pVPC->m_Projects[ pDependency->m_iProjectIndex ]; + g_pVPC->SetProjectName( pProject->name.String() ); + g_pVPC->SetLoadAddressName( pProject->name.String() ); + + g_pVPC->ParseProjectScript( pDependency->m_szStoredScriptName, 0, false, true ); + } + } +} + +class CStringCaseLess +{ +public: + bool Less( const char *lhs, const char *rhs, void *pCtx ) + { + return ( V_stricmp( lhs, rhs ) < 0 ? true : false ); + } +}; + +void GenerateSolutionForPerforceChangelist( CProjectDependencyGraph &dependencyGraph, CUtlVector &changelists, IBaseSolutionGenerator *pGenerator, const char *pSolutionFilename ) +{ + // We want to check against ALL projects in projects.vgc. + int nDepFlags = BUILDPROJDEPS_FULL_DEPENDENCY_SET | BUILDPROJDEPS_CHECK_ALL_PROJECTS; + dependencyGraph.BuildProjectDependencies( nDepFlags ); + + // Get the list of files from Perforce. + CUtlVector filenames; + GetChangelistFilenames( changelists, filenames ); + + // Get the list of projects that depend on these files. + CUtlVector projects; + GetProjectsDependingOnFiles( dependencyGraph, filenames, projects ); + + // Add g_targetProjects, which will include any other projects that they added on the command line with +tier0 *engine syntax. + CUtlVector commandLineProjects; + dependencyGraph.TranslateProjectIndicesToDependencyProjects( g_pVPC->m_TargetProjects, commandLineProjects ); + for ( int i=0; i < commandLineProjects.Count(); i++ ) + { + if ( projects.Find( commandLineProjects[i] ) == projects.InvalidIndex() ) + projects.AddToTail( commandLineProjects[i] ); + } + + // Make sure the latest .vcproj files are generated. + UpdateProjects( projects ); + + // List the projects. + CUtlSortVector< CUtlString, CStringCaseLess > sortedProjectNames; + for ( int i=0; i < projects.Count(); i++ ) + { + sortedProjectNames.InsertNoSort( projects[i]->GetName() ); + } + sortedProjectNames.RedoSort(); + + Msg( "Dependent projects: \n\n" ); + for ( int i=0; i < sortedProjectNames.Count(); i++ ) + { + Msg( "%s\n", sortedProjectNames[i].Get() ); + } + + // Write the solution file. + pGenerator->GenerateSolutionFile( pSolutionFilename, projects ); +} + diff --git a/utils/vpc/p4sln.h b/utils/vpc/p4sln.h new file mode 100644 index 0000000..a30bb7b --- /dev/null +++ b/utils/vpc/p4sln.h @@ -0,0 +1,10 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_P4SLN_H_ +#define VPC_P4SLN_H_ + +void GenerateSolutionForPerforceChangelist( + CProjectDependencyGraph &dependencyGraph, CUtlVector &changelists, + IBaseSolutionGenerator *pGenerator, const char *pSolutionFilename); + +#endif // VPC_P4SLN_H_ diff --git a/utils/vpc/projectgenerator_codelite.cpp b/utils/vpc/projectgenerator_codelite.cpp new file mode 100644 index 0000000..686cffd --- /dev/null +++ b/utils/vpc/projectgenerator_codelite.cpp @@ -0,0 +1,188 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "projectgenerator_codelite.h" + +#ifdef _WIN32 +#include +#define mkdir(dir, mode) _mkdir(dir) +#define getcwd _getcwd +#endif + +#include "tier0/memdbgon.h" + +static const char *k_pchSource = "Source Files"; +static const char *k_pchHeaders = "Header Files"; +static const char *k_pchResources = "Resources"; +static const char *k_pchVPCFiles = "VPC Files"; + +void CProjectGenerator_CodeLite::GenerateCodeLiteProject( + CBaseProjectDataCollector *pCollector, const char *pOutFilename, + const char *pMakefileFilename) { + char szProjectFile[MAX_PATH]; + sprintf(szProjectFile, "%s.project", pOutFilename); + + g_pVPC->VPCStatus(true, "Saving CodeLite project for: '%s' File: '%s'", + pCollector->GetProjectName().String(), szProjectFile); + + m_fp = fopen(szProjectFile, "wt"); + + m_nIndent = 0; + m_pCollector = pCollector; + m_pMakefileFilename = pMakefileFilename; + + Write("\n"); + Write("\n", + pCollector->GetProjectName().String()); + { + ++m_nIndent; + Write("\n"); + Write("\n"); + Write("\n"); + { + ++m_nIndent; + Write("\n"); + ++m_nIndent; + Write("\n"); + ++m_nIndent; + Write("\n"); + --m_nIndent; + Write("\n"); + Write("\n"); + ++m_nIndent; + Write("\n"); + --m_nIndent; + Write("\n"); + Write("\n"); + --m_nIndent; + Write("\n"); + Write( + "\n"); + { + ++m_nIndent; + Write("\n"); + { + ++m_nIndent; + Write( + "make CFG=debug -f %s clean " + "all\n", + pMakefileFilename); + Write("make CFG=debug -f %s clean\n", + pMakefileFilename); + Write( + "make CFG=debug -f %s -j `getconf " + "_NPROCESSORS_ONLN`\n", + pMakefileFilename); + Write("$(ProjectPath)\n"); + --m_nIndent; + } + Write("\n"); + --m_nIndent; + } + Write("\n"); + + Write( + "\n"); + { + ++m_nIndent; + Write("\n"); + { + ++m_nIndent; + Write("make -f %s clean all\n", + pMakefileFilename); + Write("make -f %s clean\n", + pMakefileFilename); + Write( + "make -f %s -j `getconf " + "_NPROCESSORS_ONLN`\n", + pMakefileFilename); + Write("$(ProjectPath)\n"); + --m_nIndent; + } + Write("\n"); + --m_nIndent; + } + Write("\n"); + --m_nIndent; + } + Write("\n"); + + { + ++m_nIndent; + WriteFilesFolder(k_pchSource, + "*.c;*.C;*.cc;*.cpp;*.cp;*.cxx;*.c++;*.prg;*.pas;*.dpr;*" + ".asm;*.s;*.bas;*.java;*.cs;*.sc;*.e;*.cob;*.html;*.rc;*" + ".tcl;*.py;*.pl;*.m;*.mm"); + WriteFilesFolder(k_pchHeaders, + "*.h;*.H;*.hh;*.hpp;*.hxx;*.inc;*.sh;*.cpy;*.if"); + WriteFilesFolder(k_pchResources, "*.plist;*.strings;*.xib"); + WriteFilesFolder(k_pchVPCFiles, "*.vpc"); + --m_nIndent; + } + --m_nIndent; + } + Write("\n"); + fclose(m_fp); +} + +void CProjectGenerator_CodeLite::WriteFilesFolder(const char *pFolderName, + const char *pExtensions) { + CUtlVector extensions; + V_SplitString(pExtensions, ";", extensions); + + Write("\n", pFolderName); + { + ++m_nIndent; + for (int i = m_pCollector->m_Files.First(); + i != m_pCollector->m_Files.InvalidIndex(); + i = m_pCollector->m_Files.Next(i)) { + const char *pFilename = m_pCollector->m_Files[i]->GetName(); + + // Make sure this file's extension is one of the extensions they're asking + // for. + bool bValidExt = false; + const char *pFileExtension = V_GetFileExtension(pFilename); + if (pFileExtension) { + for (int iExt = 0; iExt < extensions.Count(); iExt++) { + const char *pTestExt = extensions[iExt]; + + if (pTestExt[0] == '*' && pTestExt[1] == '.' && + V_stricmp(pTestExt + 2, pFileExtension) == 0) { + bValidExt = true; + break; + } + } + } + + if (bValidExt) { + char sFixedSlashes[MAX_PATH]; + V_strncpy(sFixedSlashes, pFilename, sizeof(sFixedSlashes)); + Write("\n", sFixedSlashes); + } + } + --m_nIndent; + } + Write("\n"); +} + +void CProjectGenerator_CodeLite::Write(PRINTF_FORMAT_STRING const char *pMsg, + ...) { + char sOut[8192]; + + va_list marker; + va_start(marker, pMsg); + V_vsnprintf(sOut, sizeof(sOut), pMsg, marker); + va_end(marker); + + for (int i = 0; i < m_nIndent; i++) fprintf(m_fp, " "); + + fprintf(m_fp, "%s", sOut); +} diff --git a/utils/vpc/projectgenerator_codelite.h b/utils/vpc/projectgenerator_codelite.h new file mode 100644 index 0000000..fff34fe --- /dev/null +++ b/utils/vpc/projectgenerator_codelite.h @@ -0,0 +1,40 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_PROJECTGENERATOR_CODELITE_H_ +#define VPC_PROJECTGENERATOR_CODELITE_H_ + +#include "baseprojectdatacollector.h" + +class CProjectGenerator_CodeLite { + public: + void GenerateCodeLiteProject(CBaseProjectDataCollector *pCollector, + const char *pOutFilename, + const char *pMakefileFilename); + + private: + void Write(PRINTF_FORMAT_STRING const char *pMsg, ...); + void WriteHeader(); + void WriteFileReferences(); + void WriteProject(const char *pchMakefileName); + void WriteBuildFiles(); + void WriteBuildConfigurations(); + void WriteLegacyTargets(const char *pchMakefileName); + void WriteTrailer(); + void WriteConfig(CSpecificConfig *pConfig); + void WriteTarget_Build(CSpecificConfig *pConfig); + void WriteTarget_Compile(CSpecificConfig *pConfig); + void WriteTarget_Rebuild(CSpecificConfig *pConfig); + void WriteTarget_Link(CSpecificConfig *pConfig); + void WriteTarget_Debug(CSpecificConfig *pConfig); + void WriteIncludes(CSpecificConfig *pConfig); + void WriteFilesFolder(const char *pFolderName, const char *pExtensions); + void WriteFiles(); + + private: + CBaseProjectDataCollector *m_pCollector; + FILE *m_fp; + const char *m_pMakefileFilename; + int m_nIndent; +}; + +#endif // VPC_PROJECTGENERATOR_CODELITE_H_ diff --git a/utils/vpc/projectgenerator_makefile.cpp b/utils/vpc/projectgenerator_makefile.cpp new file mode 100644 index 0000000..abb008e --- /dev/null +++ b/utils/vpc/projectgenerator_makefile.cpp @@ -0,0 +1,1152 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "baseprojectdatacollector.h" +#include "tier1/utlstack.h" +#include "projectgenerator_codelite.h" + +#include "tier0/memdbgon.h" + +static const char *k_pszBase_Makefile = + "$(SRCROOT)/devtools/makefile_base_posix.mak"; + +static const char *g_pOption_BufferSecurityCheck = "$BufferSecurityCheck"; +static const char *g_pOption_CustomBuildStepCommandLine = + "$CustomBuildStep/$CommandLine"; +static const char *g_pOption_PostBuildEventCommandLine = + "$PostBuildEvent/$CommandLine"; +static const char *g_pOption_CompileAs = "$CompileAs"; +static const char *g_pOption_ConfigurationType = "$ConfigurationType"; +static const char *g_pOption_Description = "$Description"; +static const char *g_pOption_EntryPoint = "$EntryPoint"; +static const char *g_pOption_ExtraCompilerFlags = "$GCC_ExtraCompilerFlags"; +static const char *g_pOption_ExtraLinkerFlags = "$GCC_ExtraLinkerFlags"; +static const char *g_pOption_CustomVersionScript = "$GCC_CustomVersionScript"; +static const char *g_pOption_ForceInclude = "$ForceIncludes"; +static const char *g_pOption_IgnoreAllDefaultLibraries = + "$IgnoreAllDefaultLibraries"; +static const char *g_pOption_LocalFrameworks = "$LocalFrameworks"; +static const char *g_pOption_LowerCaseFileNames = "$LowerCaseFileNames"; +static const char *g_pOption_OptimizerLevel = "$OptimizerLevel"; +static const char *g_pOption_AdditionalDependencies = "$AdditionalDependencies"; +static const char *g_pOption_Outputs = "$Outputs"; +static const char *g_pOption_PrecompiledHeader = "$Create/UsePrecompiledHeader"; +static const char *g_pOption_PrecompiledHeaderFile = "$PrecompiledHeaderFile"; +static const char *g_pOption_SymbolVisibility = "$SymbolVisibility"; +static const char *g_pOption_SystemFrameworks = "$SystemFrameworks"; +static const char *g_pOption_SystemLibraries = "$SystemLibraries"; +static const char *g_pOption_UsePCHThroughFile = "$Create/UsePCHThroughFile"; +static const char *g_pOption_TargetCopies = "$TargetCopies"; +static const char *g_pOption_TreatWarningsAsErrors = "$TreatWarningsAsErrors"; + +// These are the only properties we care about for makefiles. +static const char *g_pRelevantProperties[] = { + g_pOption_AdditionalIncludeDirectories, + g_pOption_CompileAs, + g_pOption_OptimizerLevel, + g_pOption_OutputFile, + g_pOption_GameOutputFile, + g_pOption_SymbolVisibility, + g_pOption_PreprocessorDefinitions, + g_pOption_ConfigurationType, + g_pOption_ImportLibrary, + g_pOption_PrecompiledHeader, + g_pOption_UsePCHThroughFile, + g_pOption_PrecompiledHeaderFile, + g_pOption_CustomBuildStepCommandLine, + g_pOption_PostBuildEventCommandLine, + g_pOption_AdditionalDependencies, + g_pOption_Outputs, + g_pOption_Description, + g_pOption_SystemLibraries, + g_pOption_SystemFrameworks, + g_pOption_LocalFrameworks, + g_pOption_ExtraCompilerFlags, + g_pOption_ExtraLinkerFlags, + g_pOption_CustomVersionScript, + g_pOption_EntryPoint, + g_pOption_IgnoreAllDefaultLibraries, + g_pOption_BufferSecurityCheck, + g_pOption_ForceInclude, + g_pOption_TargetCopies, + g_pOption_TreatWarningsAsErrors, + g_pOption_LowerCaseFileNames, +}; + +static CRelevantPropertyNames g_RelevantPropertyNames = { + g_pRelevantProperties, V_ARRAYSIZE(g_pRelevantProperties)}; + +void MakeFriendlyProjectName(char *pchProject); + +void V_MakeAbsoluteCygwinPath(char *pOut, int outLen, + const char *pRelativePath) { + // While generating makefiles under Win32, we must translate drive letters like c:\ + // to Cygwin-style paths like /cygdrive/c/ +#ifdef _WIN32 + char tmp[MAX_PATH]; + V_MakeAbsolutePath(tmp, sizeof(tmp), pRelativePath); + V_FixSlashes(tmp, '/'); + + if (tmp[0] != 0 && tmp[1] == ':' && tmp[2] == '/') { + // Ok, this is an absolute path + V_snprintf(pOut, outLen, "/cygdrive/%c/", tmp[0]); + V_strncat(pOut, &tmp[3], outLen); + } else +#endif // _WIN32 + { + V_MakeAbsolutePath(pOut, outLen, pRelativePath); + V_RemoveDotSlashes(pOut); + } +} + +// caller is responsible for free'ing (or leaking) the allocated buffer +static const char *UsePOSIXSlashes(const char *pStr) { + intp len = V_strlen(pStr) + 2; + // Esnure str has '\0' in the end. + char *str = (char *)calloc(len, sizeof(char)); + V_strncpy(str, pStr, len); + for (intp i = 0; i < len; i++) { + if (str[i] == '\\') { + // allow escaping of bash special characters + if (i + 1 < len && (str[i + 1] != '"' && str[i + 1] != '$' && + str[i + 1] != '\'' && str[i + 1] != '\\')) + str[i] = '/'; + } + if (str[i] == '\0') break; + } + return str; +} + +// pExt should be the bare extension without the . in front. i.e. "h", "cpp", +// "lib". +static inline bool CheckExtension(const char *pFilename, const char *pExt) { + Assert(pExt[0] != '.'); + + intp nFilenameLen = V_strlen(pFilename); + intp nExtensionLen = V_strlen(pExt); + + return (nFilenameLen > nExtensionLen && + pFilename[nFilenameLen - nExtensionLen - 1] == '.' && + V_stricmp(&pFilename[nFilenameLen - nExtensionLen], pExt) == 0); +} + +static bool CheckExtensions(const char *pFilename, const char **ppExtensions) { + for (int i = 0; ppExtensions[i] != NULL; i++) { + if (CheckExtension(pFilename, ppExtensions[i])) return true; + } + return false; +} + +static void GetObjFilenameForFile(const char *pConfigName, + const char *pFilename, char *pOut, + int maxLen) { + char sBaseFilename[MAX_PATH]; + V_FileBase(pFilename, sBaseFilename, sizeof(sBaseFilename)); + // V_strlower( sBaseFilename ); + + // We had .cxx files -> .oxx, but this macro: + // GENDEP_CXXFLAGS = -MD -MP -MF $(@:.o=.P) + // was turning into -MF blah.oxx, which led to blah.oxx containing the file + // dependencies. This obviously failed to link. Quick fix is to just have .cxx + // -> .o like everything else. + //$ const char *pObjExtension = (CheckExtension( pFilename, "cxx" ) ? "oxx" : + //"o"); + const char *pObjExtension = "o"; + + V_snprintf(pOut, maxLen, "$(OBJ_DIR)/%s.%s", sBaseFilename, pObjExtension); +} + +// This class drastically accelerates looking up which file creates which +// precompiled header. +class CPrecompiledHeaderAccel { + public: + void Setup(CUtlDict &files, CFileConfig *pBaseConfig) { + for (int i = files.First(); i != files.InvalidIndex(); i = files.Next(i)) { + CFileConfig *pFile = files[i]; + + for (int iSpecific = pFile->m_Configurations.First(); + iSpecific != pFile->m_Configurations.InvalidIndex(); + iSpecific = pFile->m_Configurations.Next(iSpecific)) { + CSpecificConfig *pSpecific = pFile->m_Configurations[iSpecific]; + if (pSpecific->m_bFileExcluded) continue; + + // Does this file create a precompiled header? + const char *pPrecompiledHeaderOption = + pSpecific->GetOption(g_pOption_PrecompiledHeader); + if (pPrecompiledHeaderOption && + V_stristr(pPrecompiledHeaderOption, "Create")) { + // Ok, which header do we scan through? + const char *pUsePCHThroughFile = + pSpecific->GetOption(g_pOption_UsePCHThroughFile); + if (!pUsePCHThroughFile) { + g_pVPC->VPCError( + "File %s creates a precompiled header in config %s but no " + "UsePCHThroughFile option specified.", + pFile->m_Filename.String(), pSpecific->GetConfigName()); + } + + char sLookup[1024]; + V_snprintf(sLookup, sizeof(sLookup), "%s__%s", + pSpecific->GetConfigName(), pUsePCHThroughFile); + + if (m_Lookup.Find(sLookup) != m_Lookup.InvalidIndex()) { + g_pVPC->VPCError( + "File %s has UsePCHThroughFile of %s but another file already " + "does.", + pFile->m_Filename.String(), pUsePCHThroughFile); + } + + m_Lookup.Insert(sLookup, pFile); + } + } + } + } + + CFileConfig *FindFileThatCreatesPrecompiledHeader( + const char *pConfigName, const char *pUsePCHThroughFile) { + char sLookup[1024]; + V_snprintf(sLookup, sizeof(sLookup), "%s__%s", pConfigName, + pUsePCHThroughFile); + + int i = m_Lookup.Find(sLookup); + if (i == m_Lookup.InvalidIndex()) + return NULL; + else + return m_Lookup[i]; + } + + private: + // This indexes whatever file creates a certain precompiled header for a + // certain config. These are indexed as _. So an + // entry might look like release_cbase.h + CUtlDict m_Lookup; +}; + +class CProjectGenerator_Makefile : public CBaseProjectDataCollector { + public: + typedef CBaseProjectDataCollector BaseClass; + + CProjectGenerator_Makefile() : BaseClass(&g_RelevantPropertyNames) { + m_bForceLowerCaseFileName = false; + } + + virtual void Setup() {} + + virtual const char *GetProjectFileExtension() { return "mak"; } + + virtual void EndProject() { + const char *pMakefileFilename = g_pVPC->GetOutputFilename(); + + CUtlString strProjectName = GetProjectName(); + bool bProjectIsCurrent = g_pVPC->IsProjectCurrent(pMakefileFilename, false); + if (g_pVPC->IsForceGenerate() || !bProjectIsCurrent) { + g_pVPC->VPCStatus(true, "Saving makefile project for: '%s' File: '%s'", + strProjectName.String(), g_pVPC->GetOutputFilename()); + WriteMakefile(pMakefileFilename); + } + + const char *pTargetPlatformName = g_pVPC->GetTargetPlatformName(); + if (!pTargetPlatformName) g_pVPC->VPCError("GetTargetPlatformName failed."); + + if (!V_stricmp(pTargetPlatformName, "LINUX32") || + !V_stricmp(pTargetPlatformName, "LINUX64")) { + if (g_pVPC->IsForceGenerate() || !bProjectIsCurrent) { + // Write a CodeLite project as well. + char sFilename[MAX_PATH]; + V_StripExtension(g_pVPC->GetOutputFilename(), sFilename, + sizeof(sFilename)); + CProjectGenerator_CodeLite codeLiteGenerator; + codeLiteGenerator.GenerateCodeLiteProject(this, sFilename, + pMakefileFilename); + } + Term(); + } else { + Term(); + } + } + + void WriteSourceFilesList(FILE *fp, const char *pListName, + const char **pExtensions, const char *pConfigName) { + fprintf(fp, "%s= \\\n", pListName); + for (int i = m_Files.First(); i != m_Files.InvalidIndex(); + i = m_Files.Next(i)) { + CFileConfig *pFileConfig = m_Files[i]; + if (pFileConfig->IsExcludedFrom(pConfigName)) continue; + + const char *pFilename = m_Files[i]->m_Filename.String(); + if (CheckExtensions(pFilename, pExtensions)) { + if (m_bForceLowerCaseFileName) + fprintf(fp, " %s \\\n", + UsePOSIXSlashes(V_strlower((char *)pFilename))); + else + fprintf(fp, " %s \\\n", UsePOSIXSlashes(pFilename)); + } + } + fprintf(fp, "\n\n"); + } + + void WriteNonConfigSpecificStuff(FILE *fp) { + fprintf(fp, "ifneq \"$(LINUX_TOOLS_PATH)\" \"\"\n"); + fprintf(fp, "TOOL_PATH = $(LINUX_TOOLS_PATH)/\n"); + fprintf(fp, "endif\n\n"); + + // NAME + char szName[256]; + V_strncpy(szName, m_ProjectName.String(), sizeof(szName)); + MakeFriendlyProjectName(szName); + fprintf(fp, "NAME=%s\n", szName); + + // SRCDIR + char sSrcRootRelative[MAX_PATH]; + g_pVPC->ResolveMacrosInString("$SRCDIR", sSrcRootRelative, + sizeof(sSrcRootRelative)); + + fprintf(fp, "SRCROOT=%s\n", UsePOSIXSlashes(sSrcRootRelative)); + + // TargetPlatformName + const char *pTargetPlatformName; + // forestw: if PLATFORM macro exists we should use its value, this + // accommodates overrides of PLATFORM in .vpc files + macro_t *pMacro = g_pVPC->FindOrCreateMacro("PLATFORM", false, NULL); + if (pMacro) + pTargetPlatformName = pMacro->value.String(); + else + pTargetPlatformName = g_pVPC->GetTargetPlatformName(); + if (!pTargetPlatformName) g_pVPC->VPCError("GetTargetPlatformName failed."); + fprintf(fp, "TARGET_PLATFORM=%s\n", pTargetPlatformName); + fprintf(fp, "TARGET_PLATFORM_EXT=%s\n", + g_pVPC->IsDedicatedBuild() ? "_srv" : ""); + fprintf(fp, "USE_VALVE_BINDIR=%s\n", + (g_pVPC->UseValveBinDir() ? "1" : "0")); + + fprintf(fp, "PWD:=$(shell $(TOOL_PATH)pwd)\n"); + + // Select debug config if no config is specified. + fprintf(fp, + "# If no configuration is specified, \"release\" will be used.\n"); + fprintf(fp, "ifeq \"$(CFG)\" \"\"\n"); + fprintf(fp, "\tCFG = release\n"); + fprintf(fp, "endif\n\n"); + } + + struct Filename_t { + char szFilename[MAX_PATH]; + intp iBasename; + }; + + class FileSortSortFunc { + public: + bool Less(const CFileConfig *const &src1, const CFileConfig *const &src2, + void *pCtx) { + return src1->m_nInsertOrder < src2->m_nInsertOrder; + } + }; + + void WriteVpcMacroDefines(CSpecificConfig *pConfig, FILE *fp) { + // Add VPC macros marked to become defines. + CUtlVector macroDefines; + g_pVPC->GetMacrosMarkedForCompilerDefines(macroDefines); + for (intp i = 0; i < macroDefines.Count(); i++) { + macro_t *pMacro = macroDefines[i]; + fprintf(fp, "-D%s=%s ", pMacro->name.String(), pMacro->value.String()); + } + } + + void WriteConfigSpecificStuff(CSpecificConfig *pConfig, FILE *fp, + CPrecompiledHeaderAccel *pAccel, + CSpecificConfig *pConfig1) { + KeyValues *pKV = pConfig->m_pKV; + + // If we've got a pConfig1, then that means pConfig0 == pConfig1, except for + // $PreprocessorDefinitions. So don't special case anything other than that + // one section. + if (!pConfig1) { + fprintf(fp, "#\n#\n# CFG=%s\n#\n#\n\n", pConfig->GetConfigName()); + fprintf(fp, "ifeq \"$(CFG)\" \"%s\"\n\n", pConfig->GetConfigName()); + } + + // GCC_ExtraCompilerFlags + // Hopefully, they don't ever need to use backslashes because we're turning + // them into forward slashes here. If that does become a problem, we can put + // some token around the pathnames we need to be fixed up and leave the rest + // alone. + fprintf(fp, "GCC_ExtraCompilerFlags=%s\n", + UsePOSIXSlashes(pKV->GetString(g_pOption_ExtraCompilerFlags, ""))); + + // GCC_ExtraLinkerFlags + fprintf(fp, "GCC_ExtraLinkerFlags=%s\n", + pKV->GetString(g_pOption_ExtraLinkerFlags, "")); + + // GCC_CustomVersionScript + fprintf(fp, "GCC_CustomVersionScript=%s\n", + pKV->GetString(g_pOption_CustomVersionScript, "")); + + // EntryPoint + fprintf(fp, "EntryPoint=%s\n", pKV->GetString(g_pOption_EntryPoint, "")); + + // IgnoreAllDefaultLibraries + fprintf(fp, "IgnoreAllDefaultLibraries=%s\n", + pKV->GetString(g_pOption_IgnoreAllDefaultLibraries, "no")); + + // BufferSecurityCheck + fprintf(fp, "BufferSecurityCheck=%s\n", + pKV->GetString(g_pOption_BufferSecurityCheck, "Yes")); + + // SymbolVisibility + fprintf(fp, "SymbolVisibility=%s\n", + pKV->GetString(g_pOption_SymbolVisibility, "hidden")); + + // TreatWarningsAsErrors + fprintf(fp, "TreatWarningsAsErrors=%s\n", + pKV->GetString(g_pOption_TreatWarningsAsErrors, "false")); + + // OptimizerLevel + fprintf( + fp, "OptimizerLevel=%s\n", + pKV->GetString(g_pOption_OptimizerLevel, "$(SAFE_OPTFLAGS_GCC_422)")); + + // system libraries + { + fprintf(fp, "SystemLibraries="); + { + CSplitString libs(pKV->GetString(g_pOption_SystemLibraries), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < libs.Count(); i++) { + fprintf(fp, "-l%s ", libs[i]); + } + } + if (!V_stricmp(g_pVPC->GetTargetPlatformName(), "OSX32") || + !V_stricmp(g_pVPC->GetTargetPlatformName(), "OSX64")) { + char rgchFrameworkCompilerFlags[1024]; + rgchFrameworkCompilerFlags[0] = '\0'; + CSplitString systemFrameworks( + pKV->GetString(g_pOption_SystemFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < systemFrameworks.Count(); i++) { + fprintf(fp, "-framework %s ", systemFrameworks[i]); + } + CSplitString localFrameworks(pKV->GetString(g_pOption_LocalFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < localFrameworks.Count(); i++) { + char rgchFrameworkName[MAX_PATH]; + V_StripExtension(V_UnqualifiedFileName(localFrameworks[i]), + rgchFrameworkName, sizeof(rgchFrameworkName)); + V_StripFilename(localFrameworks[i]); + fprintf(fp, "-F%s ", localFrameworks[i]); + fprintf(fp, "-framework %s ", rgchFrameworkName); + strcat(rgchFrameworkCompilerFlags, "-F"); + strcat(rgchFrameworkCompilerFlags, localFrameworks[i]); + } + fprintf(fp, "\n"); + if (rgchFrameworkCompilerFlags[0]) + // the colon here is important - and should probably get percolated to + // more places in our generated makefiles - it means to perform the + // assignment once, rather than at evaluation time + fprintf(fp, "GCC_ExtraCompilerFlags:=$(GCC_ExtraCompilerFlags) %s\n", + rgchFrameworkCompilerFlags); + } else + fprintf(fp, "\n"); + } + + macro_t *pMacro = g_pVPC->FindOrCreateMacro("_DLL_EXT", false, NULL); + if (pMacro) fprintf(fp, "DLL_EXT=%s\n", pMacro->value.String()); + + pMacro = g_pVPC->FindOrCreateMacro("_SYM_EXT", false, NULL); + if (pMacro) fprintf(fp, "SYM_EXT=%s\n", pMacro->value.String()); + + // ForceIncludes + { + CSplitString outStrings(pKV->GetString(g_pOption_ForceInclude), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + fprintf(fp, "FORCEINCLUDES= "); + for (int i = 0; i < outStrings.Count(); i++) { + if (V_strlen(outStrings[i]) > 2) + fprintf(fp, "-include %s ", UsePOSIXSlashes(outStrings[i])); + } + } + fprintf(fp, "\n"); + + // DEFINES + if (!pConfig1) { + CSplitString outStrings(pKV->GetString(g_pOption_PreprocessorDefinitions), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + fprintf(fp, "DEFINES= "); + for (int i = 0; i < outStrings.Count(); i++) { + fprintf(fp, "-D%s ", outStrings[i]); + } + + WriteVpcMacroDefines(pConfig, fp); + fprintf(fp, "\n"); + } else { + // pConfig0 and pConfig1 are the same other than this section. So write + // just this one out something like: + // ifeq "$(CFG)" "debug" + // DEFINES += -DDEBUG -D_DEBUG -DPOSIX ... + // else + // DEFINES += -DNDEBUG -DPOSIX ... + // endif + fprintf(fp, "ifeq \"$(CFG)\" \"%s\"\n", pConfig->GetConfigName()); + + CSplitString outStrings0( + pKV->GetString(g_pOption_PreprocessorDefinitions), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + fprintf(fp, "DEFINES += "); + for (int i = 0; i < outStrings0.Count(); i++) { + fprintf(fp, "-D%s ", outStrings0[i]); + } + WriteVpcMacroDefines(pConfig, fp); + + fprintf(fp, "\nelse\n"); + + CSplitString outStrings1( + pConfig1->m_pKV->GetString(g_pOption_PreprocessorDefinitions), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + fprintf(fp, "DEFINES += "); + for (int i = 0; i < outStrings1.Count(); i++) { + fprintf(fp, "-D%s ", outStrings1[i]); + } + WriteVpcMacroDefines(pConfig1, fp); + + fprintf(fp, "\nendif\n"); + } + + // INCLUDEDIRS + { + CSplitString outStrings( + pKV->GetString(g_pOption_AdditionalIncludeDirectories), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + fprintf(fp, "INCLUDEDIRS += "); + for (int i = 0; i < outStrings.Count(); i++) { + char sDir[MAX_PATH]; + V_strncpy(sDir, outStrings[i], sizeof(sDir)); + if (!V_stricmp(sDir, "$(IntDir)")) + V_strncpy(sDir, "$(OBJ_DIR)", sizeof(sDir)); + + V_FixSlashes(sDir, '/'); + fprintf(fp, "%s ", sDir); + } + fprintf(fp, "\n"); + } + // CONFTYPE + if (V_stristr(pKV->GetString(g_pOption_ConfigurationType), "dll")) { + fprintf(fp, "CONFTYPE=dll\n"); + + // Write ImportLibrary for dll (so) builds. + const char *pRelative = pKV->GetString(g_pOption_ImportLibrary, ""); + fprintf(fp, "IMPORTLIBRARY=%s\n", UsePOSIXSlashes(pRelative)); + } else if (V_stristr(pKV->GetString(g_pOption_ConfigurationType), "lib")) { + fprintf(fp, "CONFTYPE=lib\n"); + } else if (V_stristr(pKV->GetString(g_pOption_ConfigurationType), "exe")) { + fprintf(fp, "CONFTYPE=exe\n"); + } else { + fprintf(fp, "CONFTYPE=***UNKNOWN***\n"); + } + + // GameOutputFile is where it copies OutputFile to. + fprintf(fp, "GAMEOUTPUTFILE=%s\n", + UsePOSIXSlashes(pKV->GetString(g_pOption_GameOutputFile, ""))); + + // TargetCopies are where OutputFile copies are placed. + fprintf(fp, "TARGETCOPIES=%s\n", + UsePOSIXSlashes(pKV->GetString(g_pOption_TargetCopies, ""))); + + // OutputFile is where it builds to. + char szFixedOutputFile[MAX_PATH]; + V_strncpy(szFixedOutputFile, pKV->GetString(g_pOption_OutputFile), + sizeof(szFixedOutputFile)); + V_FixSlashes(szFixedOutputFile, '/'); + // This file uses a custom build step. + char sFormattedOutputFile[MAX_PATH]; + char szAbsPath[MAX_PATH]; + V_MakeAbsolutePath(szAbsPath, sizeof(szAbsPath), szFixedOutputFile); + DoStandardVisualStudioReplacements(szFixedOutputFile, szAbsPath, + sFormattedOutputFile, + sizeof(sFormattedOutputFile)); + + fprintf(fp, "OUTPUTFILE=%s\n", sFormattedOutputFile); + + fprintf(fp, "\n\n"); + + // post build event + char rgchPostBuildCommand[2048]; + rgchPostBuildCommand[0] = '\0'; + if (pKV->GetString(g_pOption_PostBuildEventCommandLine, NULL)) { + V_strncpy(rgchPostBuildCommand, + pKV->GetString(g_pOption_PostBuildEventCommandLine, NULL), + sizeof(rgchPostBuildCommand)); + // V_StripPrecedingAndTrailingWhitespace( rgchPostBuildCommand ); + } + if (V_strlen(rgchPostBuildCommand)) + fprintf(fp, "POSTBUILDCOMMAND=%s\n", + UsePOSIXSlashes(rgchPostBuildCommand)); + else + fprintf(fp, "POSTBUILDCOMMAND=/bin/true\n"); + + fprintf(fp, "\n\n"); + + // Write all the filenames. + const char *sSourceFileExtensions[] = {"cpp", "cxx", "cc", "c", "mm", NULL}; + fprintf(fp, "\n"); + WriteSourceFilesList(fp, "CPPFILES", (const char **)sSourceFileExtensions, + pConfig->GetConfigName()); + + // LIBFILES + char sImportLibraryFile[MAX_PATH]; + const char *pRelative = pKV->GetString(g_pOption_ImportLibrary, ""); + V_strncpy(sImportLibraryFile, UsePOSIXSlashes(pRelative), + sizeof(sImportLibraryFile)); + V_RemoveDotSlashes(sImportLibraryFile); + + char sOutputFile[MAX_PATH]; + const char *pOutputFile = pKV->GetString(g_pOption_OutputFile, ""); + V_strncpy(sOutputFile, UsePOSIXSlashes(pOutputFile), sizeof(sOutputFile)); + V_RemoveDotSlashes(sOutputFile); + + fprintf(fp, "LIBFILES = \\\n"); + + // Get original order the link files were specified in the .vpc files. See: + // https://stackoverflow.com/questions/45135/why-does-the-order-in-which-libraries-are-linked-sometimes-cause-errors-in-gcc + // TL;DR. Gcc does a single pass through the list of libraries to resolve + // references. + // If library A depends on symbols in library B, library A should appear + // first so we need to restore the original order to allow users to control + // link order via their .vpc files. + CUtlSortVector OriginalSort; + + for (int i = m_Files.First(); i != m_Files.InvalidIndex(); + i = m_Files.Next(i)) { + CFileConfig *pFileConfig = m_Files[i]; + OriginalSort.InsertNoSort(pFileConfig); + } + OriginalSort.RedoSort(); + + CUtlVector ImportLib; + CUtlVector StaticLib; + + for (int i = 0; i < OriginalSort.Count(); i++) { + CFileConfig *pFileConfig = OriginalSort[i]; + if (pFileConfig->IsExcludedFrom(pConfig->GetConfigName())) continue; + + Filename_t Filename; + + Filename.iBasename = 0; + + V_strncpy(Filename.szFilename, + UsePOSIXSlashes(pFileConfig->m_Filename.String()), + sizeof(Filename.szFilename)); + const char *pFilename = Filename.szFilename; + if (IsLibraryFile(pFilename)) { + char *pchFileName = (char *)V_strrchr(pFilename, '/') + 1; + if ((!sImportLibraryFile[0] || + V_stricmp(sImportLibraryFile, + pFilename)) && // only link this as a library if it + // isn't our own output! + (!sOutputFile[0] || V_stricmp(sOutputFile, pFilename))) { + char szExt[32]; + V_ExtractFileExtension(pFilename, szExt, sizeof(szExt)); + if (!(!V_stricmp(g_pVPC->GetTargetPlatformName(), "OSX32") || + !V_stricmp(g_pVPC->GetTargetPlatformName(), "OSX64")) && + IsLibraryFile(pFilename) && (pchFileName - 1) && + pchFileName[0] == 'l' && pchFileName[1] == 'i' && + pchFileName[2] == 'b' && + szExt[0] != 'a') // its a lib ext but not an archive file, link + // like a library + { + *(pchFileName - 1) = 0; + + // Cygwin import libraries use ".dll.a", so get rid of any file + // extensions here. + char *pExt; + while (1) { + pExt = (char *)V_strrchr(pchFileName, '.'); + if (!pExt || V_strrchr(pchFileName, '/') > pExt || + V_strrchr(pchFileName, '\\') > pExt) + break; + + *pExt = 0; + } + + // +3 to dodge the lib ext + Filename.iBasename = (pchFileName - pFilename) + 3; + ImportLib.AddToTail(Filename); + } else { + StaticLib.AddToTail(Filename); + } + } + } + } + + // Spew static libs out first, then import libraries. Otherwise things like + // bsppack + // will fail to link because libvstdlib.so came before tier1.a. + for (int i = 0; i < StaticLib.Count(); i++) { + fprintf(fp, " %s \\\n", StaticLib[i].szFilename); + } + for (int i = 0; i < ImportLib.Count(); i++) { + fprintf( + fp, " -L%s -l%s \\\n", ImportLib[i].szFilename, + &ImportLib[i] + .szFilename[ImportLib[i].iBasename]); // +3 to dodge the lib ext + } + + fprintf(fp, "\n\n"); + + fprintf(fp, "LIBFILENAMES = \\\n"); + for (int i = m_Files.First(); i != m_Files.InvalidIndex(); + i = m_Files.Next(i)) { + CFileConfig *pFileConfig = m_Files[i]; + if (pFileConfig->IsExcludedFrom(pConfig->GetConfigName())) continue; + + const char *pFilename = pFileConfig->m_Filename.String(); + if (IsLibraryFile(pFilename)) { + if ((!sImportLibraryFile[0] || + V_stricmp(sImportLibraryFile, + pFilename)) && // only link this as a library if it + // isn't our own output! + (!sOutputFile[0] || V_stricmp(sOutputFile, pFilename))) { + fprintf(fp, " %s \\\n", UsePOSIXSlashes(pFilename)); + } + } + } + + fprintf(fp, "\n\n"); + + CUtlVector otherDependencies; + static const char *sDependenciesSeparators[] = {";", "\r", "\n"}; + + // Scan the list of files for any generated dependencies so we can pull them + // up front + for (int i = m_Files.First(); i != m_Files.InvalidIndex(); + i = m_Files.Next(i)) { + CFileConfig *pFileConfig = m_Files[i]; + CSpecificConfig *pFileSpecificData = + pFileConfig->GetOrCreateConfig(pConfig->GetConfigName(), pConfig); + + if (pFileConfig->IsExcludedFrom(pConfig->GetConfigName())) { + continue; + } + + const char *pCustomBuildCommandLine = + pFileSpecificData->GetOption(g_pOption_CustomBuildStepCommandLine); + const char *of = pFileSpecificData->GetOption(g_pOption_Outputs); + if (of && pCustomBuildCommandLine && + V_strlen(pCustomBuildCommandLine) > 0) { + char szTempFilename[MAX_PATH]; + V_strncpy(szTempFilename, + UsePOSIXSlashes(pFileConfig->m_Filename.String()), + sizeof(szTempFilename)); + const char *pFilename = szTempFilename; + + // This file uses a custom build step. + char fof[8192]; + char absPath[MAX_PATH]; + V_MakeAbsolutePath(absPath, sizeof(absPath), pFilename); + DoStandardVisualStudioReplacements(of, absPath, fof, sizeof(fof)); + + CSplitString outFiles(fof, sDependenciesSeparators, + sizeof(sDependenciesSeparators) / + sizeof(sDependenciesSeparators[0])); + + for (int j = 0; j < outFiles.Count(); j++) { + const char *pchOneFile = outFiles[j]; + if (*pchOneFile == '\0') { + continue; + } + // Remember this as a dependency so the executable will depend on it. + if (otherDependencies.Find(pchOneFile) == + otherDependencies.InvalidIndex()) + otherDependencies.AddToTail(pchOneFile); + } + } + } + + WriteOtherDependencies(fp, otherDependencies); + fprintf(fp, "\n\n"); + + // Include the base makefile before the rules to build the .o files. + // Do this after we output otherDependencies definition since + // $(OTHER_DEPENDENCIES) is a dependency of all: target + const char *sMakeFileDependency = ""; + bool bCondPOSIX = g_pVPC->FindOrCreateConditional("POSIX", false, + CONDITIONAL_NULL) != NULL; + fprintf(fp, "# Include the base makefile now.\n"); + if (bCondPOSIX) { + sMakeFileDependency = k_pszBase_Makefile; + fprintf(fp, "include %s\n\n\n", sMakeFileDependency); + } + + // Now write the rules to build the .o files. + // .o files go in [project dir]/obj/[config]/[base filename] + for (int i = m_Files.First(); i != m_Files.InvalidIndex(); + i = m_Files.Next(i)) { + CFileConfig *pFileConfig = m_Files[i]; + CSpecificConfig *pFileSpecificData = + pFileConfig->GetOrCreateConfig(pConfig->GetConfigName(), pConfig); + + if (pFileConfig->IsExcludedFrom(pConfig->GetConfigName())) { + continue; + } + + char szTempFilename[MAX_PATH]; + V_strncpy(szTempFilename, + UsePOSIXSlashes(pFileConfig->m_Filename.String()), + sizeof(szTempFilename)); + const char *pFilename = szTempFilename; + + // Custom build steps?? + const char *pCustomBuildCommandLine = + pFileSpecificData->GetOption(g_pOption_CustomBuildStepCommandLine); + const char *of = pFileSpecificData->GetOption(g_pOption_Outputs); + if (of && pCustomBuildCommandLine && + V_strlen(pCustomBuildCommandLine) > 0) { + // This file uses a custom build step. + char fof[8192]; + char sFormattedCommandLine[8192]; + char sFormattedDependencies[8192]; + DoStandardVisualStudioReplacements( + pCustomBuildCommandLine, UsePOSIXSlashes(pFilename), + sFormattedCommandLine, sizeof(sFormattedCommandLine)); + DoStandardVisualStudioReplacements(of, UsePOSIXSlashes(pFilename), fof, + sizeof(fof)); + + // AdditionalDependencies only applies to custom build steps, not normal + // compilation steps + const char *pAdditionalDeps = + pFileSpecificData->GetOption(g_pOption_AdditionalDependencies); + if (pAdditionalDeps) + DoStandardVisualStudioReplacements(pAdditionalDeps, szAbsPath, + sFormattedDependencies, + sizeof(sFormattedDependencies)); + else + sFormattedDependencies[0] = 0; + + CSplitString additionalDeps(sFormattedDependencies, + sDependenciesSeparators, + sizeof(sDependenciesSeparators) / + sizeof(sDependenciesSeparators[0])); + FOR_EACH_VEC_BACK(additionalDeps, j) { + const char *pchOneFile = additionalDeps[j]; + if (*pchOneFile == '\0') additionalDeps.Remove(i); + } + + CSplitString outFiles(fof, sDependenciesSeparators, + sizeof(sDependenciesSeparators) / + sizeof(sDependenciesSeparators[0])); + FOR_EACH_VEC_BACK(outFiles, j) { + const char *pchOneFile = outFiles[j]; + if (*pchOneFile == '\0') outFiles.Remove(i); + } + + char rgchIntermediateFile[MAX_PATH]; + rgchIntermediateFile[0] = 0; + + // ABSPATH NOTE + // + // Make will not do what we expect with ../../this/dir/foo.cpp, and + // these are often the result of nested macros in VPC files, so ensure + // we are giving make absolute paths for our targets via $(abspath ...) + + if (outFiles.Count() == 1) { + // one output file: create a standard rule -- output : input \n \t + // command + fprintf(fp, "\n$(abspath %s) ", outFiles[0]); + } else { + // multiple output files: DO NOT DO THIS -- output output output : + // input \n \t command as this will cause up to three parallel + // invocations of command! best-practice workaround is to create a + // temp intermediate file + static int s_uniqueId = 0; + V_snprintf(rgchIntermediateFile, sizeof(rgchIntermediateFile), + "$(OBJ_DIR)/_custombuildstep_%d.touchfile", s_uniqueId); + fprintf(fp, "\n%s ", rgchIntermediateFile); + ++s_uniqueId; + + V_strcat(sFormattedCommandLine, + CFmtStrMax("\n@touch %s", rgchIntermediateFile), + sizeof(sFormattedCommandLine)); + } + // Outputs dependent on input file and .mak file + fprintf(fp, ": $(abspath %s) %s", UsePOSIXSlashes(pFilename), + g_pVPC->GetOutputFilename()); + FOR_EACH_VEC(additionalDeps, j) { + fprintf(fp, " %s", additionalDeps[j]); + } + /// XXX(JohnS): Was this double-added as an accident, or is there some + /// arcane make reason to have it be the + /// first and last dep? + fprintf(fp, " $(abspath %s)\n", UsePOSIXSlashes(pFilename)); + const char *pDescription = + pFileSpecificData->GetOption(g_pOption_Description); + DoStandardVisualStudioReplacements( + pDescription, UsePOSIXSlashes(pFilename), fof, sizeof(fof)); + + fprintf(fp, "\t @echo \"%s\";mkdir -p $(OBJ_DIR) 2> /dev/null;\n", fof); + + static const char *sSeparators[] = {"\r", "\n"}; + CSplitString outLines(sFormattedCommandLine, sSeparators, + sizeof(sSeparators) / sizeof(sSeparators[0])); + for (int j = 0; j < outLines.Count(); ++j) { + const char *pchOneLine = outLines[j]; + if (*pchOneLine == '\0') continue; + + fprintf(fp, "\t %s\n", pchOneLine); + } + fprintf(fp, "\n"); + + // for multiple output files, create a dependency between output files + // and intermediate file + makefile + if (outFiles.Count() > 1) { + FOR_EACH_VEC(outFiles, j) { + // See ABSPATH NOTE above + fprintf(fp, "$(abspath %s) : %s %s\n\t @touch %s\n\n", outFiles[j], + rgchIntermediateFile, g_pVPC->GetOutputFilename(), + outFiles[j]); + } + } + } else if (CheckExtensions(pFilename, + (const char **)sSourceFileExtensions)) { + char sObjFilename[MAX_PATH]; + GetObjFilenameForFile(pConfig->GetConfigName(), pFilename, sObjFilename, + sizeof(sObjFilename)); + + // Get the base obj filename for the .P file. + char sPFileBase[MAX_PATH]; + V_StripExtension(sObjFilename, sPFileBase, sizeof(sPFileBase)); + + fprintf(fp, "\nifneq (clean, $(findstring clean, $(MAKECMDGOALS)))\n"); + fprintf(fp, "-include %s.P\n", sPFileBase); + fprintf(fp, "endif\n"); + + bool bUsedPrecompiledHeader = false; + + // Handle precompiled header options. + const char *pPrecompiledHeaderOption = + pFileSpecificData->GetOption(g_pOption_PrecompiledHeader); + const char *pUsePCHThroughFile = + pFileSpecificData->GetOption(g_pOption_UsePCHThroughFile); + + if (!g_pVPC->IsPosixPCHDisabled() && pPrecompiledHeaderOption && + pUsePCHThroughFile) { + char sIncludeFilename[MAX_PATH]; + const char *pHeaderFileName = V_GetFileName(pUsePCHThroughFile); + V_snprintf(sIncludeFilename, sizeof(sIncludeFilename), + "$(OBJ_DIR)/%s", pHeaderFileName); + + // Note that we use $(abspath ...) for most things, but the provided + // PCH file (pUsePCHThroughFile) is actually a include filename -- + // that is, it is meant to be found the same way #include "pch" would + // be. We use make's vpath directive to tell it to resolve this file + // relative to the INCLUDEDIRS list we conveniently have, (starting + // with PWD to mimic c/c++ include searching), but that means we have + // to be careful to always reference it as a dependency the same way. + // + // However, since we explicitly opt-in to files using said PCH, we can + // just specifically set the PCH as a dependency of every file that + // has opted-in to use it, so we don't run into issues with things + // like the compiler's -MD dependencies referring to it differently. + if (V_stristr(pPrecompiledHeaderOption, "Not Using")) { + // Don't do anything special if this file doesn't want to use a + // precompiled header. + } else if (V_stristr(pPrecompiledHeaderOption, "Create")) { + // Compile pUsePCHThroughFile and output it to + // obj//filename.h.gch + fprintf(fp, "\n%s.gch : %s $(PWD)/%s %s $(OTHER_DEPENDENCIES)\n", + sIncludeFilename, pUsePCHThroughFile, + g_pVPC->GetOutputFilename(), sMakeFileDependency); + fprintf(fp, "\t$(PRE_COMPILE_FILE)\n"); + fprintf(fp, "\t$(COMPILE_PCH) $(POST_COMPILE_FILE)\n"); + + // include the .P it spits out, ensuring it is marked as depending + // on the gch build finishing so we don't include a stale one. + fprintf(fp, "\n%s.P : %s.gch\n", sIncludeFilename, + sIncludeFilename); + fprintf(fp, "\nvpath %s . $(INCLUDEDIRS)\n", pUsePCHThroughFile); + fprintf(fp, + "\nifneq (clean, $(findstring clean, $(MAKECMDGOALS)))\n"); + fprintf(fp, "include %s.P\n", sIncludeFilename); + fprintf(fp, "endif\n"); + + // Create obj//filename.h as well, depending on the GCH so + // it is re-copied when we recompile it. + // + // Because we pass this as -include to the compiler, this + // allows conditions where the PCH cannot be used to fall back to + // the compiler simply using the .h, rather than failing entirely. + fprintf(fp, "\n%s : %s %s.gch $(PWD)/%s %s\n", sIncludeFilename, + pUsePCHThroughFile, sIncludeFilename, + g_pVPC->GetOutputFilename(), sMakeFileDependency); + fprintf(fp, "\tcp -f $< %s\n", sIncludeFilename); + } else if (V_stristr(pPrecompiledHeaderOption, "Use")) { + CFileConfig *pCreator = + pAccel->FindFileThatCreatesPrecompiledHeader( + pConfig->GetConfigName(), pUsePCHThroughFile); + if (pCreator && + !pCreator->IsExcludedFrom(pConfig->GetConfigName())) { + const char *pCompileAsOption = + pFileSpecificData->GetOption(g_pOption_CompileAs); + fprintf(fp, "\n%s : TARGET_PCH_FILE = %s\n", sObjFilename, + sIncludeFilename); + fprintf(fp, "%s : $(abspath %s) %s.gch %s $(PWD)/%s %s\n", + sObjFilename, UsePOSIXSlashes(pFilename), + sIncludeFilename, sIncludeFilename, + g_pVPC->GetOutputFilename(), sMakeFileDependency); + fprintf(fp, "\t$(PRE_COMPILE_FILE)\n"); + if (pCompileAsOption && + strstr(pCompileAsOption, "(/TC)")) // Compile as C code (/TC) + { + fprintf(fp, + "\t$(COMPILE_FILE_WITH_PCH_C) $(POST_COMPILE_FILE)\n"); + } else { + fprintf(fp, + "\t$(COMPILE_FILE_WITH_PCH) $(POST_COMPILE_FILE)\n"); + } + bUsedPrecompiledHeader = true; + } + } + } + + if (!bUsedPrecompiledHeader) { + const char *pCompileAsOption = + pFileSpecificData->GetOption(g_pOption_CompileAs); + fprintf(fp, + "\n%s : $(abspath %s) $(PWD)/%s %s $(OTHER_DEPENDENCIES)\n", + sObjFilename, UsePOSIXSlashes(pFilename), + g_pVPC->GetOutputFilename(), sMakeFileDependency); + fprintf(fp, "\t$(PRE_COMPILE_FILE)\n"); + if (pCompileAsOption && + strstr(pCompileAsOption, "(/TC)")) // Compile as C code (/TC) + { + fprintf(fp, "\t$(COMPILE_FILE_C) $(POST_COMPILE_FILE)\n"); + } else { + fprintf(fp, "\t$(COMPILE_FILE) $(POST_COMPILE_FILE)\n"); + } + } + } + } + + if (!pConfig1) { + fprintf(fp, "\n\nendif # (CFG=%s)\n\n", pConfig->GetConfigName()); + fprintf(fp, "\n\n"); + } + } + + void WriteOtherDependencies(FILE *fp, + CUtlVector &otherDependencies) { + fprintf(fp, "\nOTHER_DEPENDENCIES = \\\n"); + for (int i = 0; i < otherDependencies.Count(); i++) { + fprintf(fp, "\t$(abspath %s)%s\n", + UsePOSIXSlashes(otherDependencies[i].String()), + (i == otherDependencies.Count() - 1) ? "" : " \\"); + } + fprintf(fp, "\n\n"); + fprintf(fp, "-include $(OBJ_DIR)/_other_deps.P\n"); + } + + bool CheckReleaseDebugConfigsAreSame() { + if (g_pVPC->IsVerboseMakefile()) return false; + + // Go through two configs and check to see that all the strings except the + // defines are the same. If so, we can write the entire makefile and only + // special case the $PreprocessorDefinitions portion. Makes for a much + // simpler makefile to read and modify when testing various flags, etc. + if (m_BaseConfigData.m_Configurations.Count() == 2) { + CSpecificConfig *pConfig0 = m_BaseConfigData.m_Configurations[0]; + CSpecificConfig *pConfig1 = m_BaseConfigData.m_Configurations[1]; + KeyValues *pKV0 = pConfig0->m_pKV; + KeyValues *pKV1 = pConfig1->m_pKV; + + KeyValues *val0 = pKV0->GetFirstValue(); + KeyValues *val1 = pKV1->GetFirstValue(); + for (;;) { + // If one has run out and the other hasn't, bail. + if (!val0 != !val1) break; + + if (!val0) { + // We've hit the end of both keyvalues, and everything was the same. + Assert(!val1); + return true; + } + + // If the datatypes aren't strings or aren't the same, bail. + if ((val0->GetDataType() != KeyValues::TYPE_STRING) || + (val0->GetDataType() != val1->GetDataType())) + break; + + // If the keynames differ, bail. + if (V_strcmp(val0->GetName(), val1->GetName())) break; + + // If this isn't the $PreprocessorDefinitions key, check the values. + if (V_strcmp(val0->GetName(), g_pOption_PreprocessorDefinitions)) { + if (V_strcmp(val0->GetString(), val1->GetString())) break; + + // look for visual studio macros and assume those evaluate to config + // specific values + if (V_strstr(val0->GetString(), "$(")) break; + } + + // Next. + val0 = val0->GetNextValue(); + val1 = val1->GetNextValue(); + } + } + + return false; + } + + void WriteMakefile(const char *pFilename) { + FILE *fp = fopen(pFilename, "wt"); + + CPrecompiledHeaderAccel accel; + accel.Setup(m_Files, &m_BaseConfigData); + + m_bForceLowerCaseFileName = false; + + // Write all the non-config-specific stuff. + WriteNonConfigSpecificStuff(fp); + + bool bReleaseDebugAreSame = CheckReleaseDebugConfigsAreSame(); + if (bReleaseDebugAreSame) { + // If the two configs are the same except for $PreprocessorDefinitions, + // then call WriteConfigSpecificStuff() once with both configs. + Assert(m_BaseConfigData.m_Configurations.Count() == 2); + CSpecificConfig *pConfig0 = m_BaseConfigData.m_Configurations[0]; + CSpecificConfig *pConfig1 = m_BaseConfigData.m_Configurations[1]; + + // Make sure we always have "release" second (ie, the default case). + if (!V_stricmp(pConfig0->GetConfigName(), "release")) { + CSpecificConfig *pConfigTemp = pConfig0; + pConfig0 = pConfig1; + pConfig1 = pConfigTemp; + } + + m_bForceLowerCaseFileName = + pConfig0->m_pKV->GetBool(g_pOption_LowerCaseFileNames, false); + WriteConfigSpecificStuff(pConfig0, fp, &accel, pConfig1); + } else { + // Write each config out. + for (int i = m_BaseConfigData.m_Configurations.First(); + i != m_BaseConfigData.m_Configurations.InvalidIndex(); + i = m_BaseConfigData.m_Configurations.Next(i)) { + CSpecificConfig *pConfig = m_BaseConfigData.m_Configurations[i]; + m_bForceLowerCaseFileName = + pConfig->m_pKV->GetBool(g_pOption_LowerCaseFileNames, false); + WriteConfigSpecificStuff(pConfig, fp, &accel, NULL); + } + } + + fclose(fp); + Sys_CopyToMirror(pFilename); + } + + bool m_bForceLowerCaseFileName; +}; + +static CProjectGenerator_Makefile g_ProjectGenerator_Makefile; +IBaseProjectGenerator *GetMakefileProjectGenerator() { + return &g_ProjectGenerator_Makefile; +} diff --git a/utils/vpc/projectgenerator_ps3.cpp b/utils/vpc/projectgenerator_ps3.cpp new file mode 100644 index 0000000..3d05a7e --- /dev/null +++ b/utils/vpc/projectgenerator_ps3.cpp @@ -0,0 +1,1242 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" + +#include "projectgenerator_ps3.h" + +#include "projectgenerator_vcproj.h" + +#include "tier0/memdbgon.h" + +#undef PROPERTYNAME +#define PROPERTYNAME(X, Y) {X##_##Y, #X, #Y}, + +static PropertyName_t s_PS3PropertyNames[] = { +#include "projectgenerator_ps3.inc" + {-1, NULL, NULL}}; + +IBaseProjectGenerator *GetPS3ProjectGenerator() { + static CProjectGenerator_PS3 *s_pProjectGenerator = NULL; + if (!s_pProjectGenerator) { + s_pProjectGenerator = new CProjectGenerator_PS3(); + } + + return s_pProjectGenerator->GetProjectGenerator(); +} + +CProjectGenerator_PS3::CProjectGenerator_PS3() { + m_pVCProjGenerator = new CVCProjGenerator(); + m_pVCProjGenerator->SetupGeneratorDefinition(this, "ps3.def", + s_PS3PropertyNames); +} + +bool CProjectGenerator_PS3::WriteFile(CProjectFile *pFile) { + m_XMLWriter.PushNode("File"); + m_XMLWriter.Write(CFmtStrMax("RelativePath=\"%s\"", pFile->m_Name.Get())); + m_XMLWriter.Write(">"); + + for (int i = 0; i < pFile->m_Configs.Count(); i++) { + if (!WriteConfiguration(pFile->m_Configs[i])) return false; + } + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_PS3::WriteFolder(CProjectFolder *pFolder) { + m_XMLWriter.PushNode("Filter"); + // String() returns temporary object, so save in var to prevent stale memory + // usage. + CUtlString name = m_XMLWriter.FixupXMLString(pFolder->m_Name.Get()); + m_XMLWriter.Write(CFmtStrMax("Name=\"%s\"", name.String())); + m_XMLWriter.Write(">"); + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pFolder->m_Folders[iIndex])) return false; + } + + for (auto iIndex = pFolder->m_Files.Head(); + iIndex != pFolder->m_Files.InvalidIndex(); + iIndex = pFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pFolder->m_Files[iIndex])) return false; + } + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_PS3::WritePreBuildEventTool( + CPreBuildEventTool *pPreBuildEventTool) { + if (!pPreBuildEventTool) { + // not an error, some tools n/a for aconfig + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCPreBuildEventTool\""); + + for (int i = 0; i < pPreBuildEventTool->m_PropertyStates.m_Properties.Count(); + i++) { + switch (pPreBuildEventTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_PREBUILDEVENT_CommandLine: { + // String() returns temporary object, so save in var to prevent stale + // memory usage. + CUtlString cl = m_XMLWriter.FixupXMLString( + pPreBuildEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()); + m_XMLWriter.Write(CFmtStrMax("CommandLine=\"%s\"", cl.String())); + } break; + + case PS3_PREBUILDEVENT_Description: + m_XMLWriter.Write( + CFmtStrMax("Description=\"%s\"", + pPreBuildEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_PREBUILDEVENT_ExcludedFromBuild: + m_XMLWriter.Write( + CFmtStrMax("ExcludedFromBuild=\"%s\"", + BoolStringToTrueFalseString( + pPreBuildEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()))); + break; + } + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WriteCustomBuildTool( + CCustomBuildTool *pCustomBuildTool) { + if (!pCustomBuildTool) { + // not an error, some tools n/a for aconfig + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCCustomBuildTool\""); + + for (int i = 0; i < pCustomBuildTool->m_PropertyStates.m_Properties.Count(); + i++) { + switch (pCustomBuildTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_CUSTOMBUILDSTEP_CommandLine: { + // String() returns temporary object, so save in var to prevent stale + // memory usage. + CUtlString cl = m_XMLWriter.FixupXMLString( + pCustomBuildTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()); + m_XMLWriter.Write(CFmtStrMax("CommandLine=\"%s\"", cl.String())); + } break; + + case PS3_CUSTOMBUILDSTEP_Description: + m_XMLWriter.Write( + CFmtStrMax("Description=\"%s\"", + pCustomBuildTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_CUSTOMBUILDSTEP_Outputs: + m_XMLWriter.Write(CFmtStrMax( + "Outputs=\"%s\"", pCustomBuildTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_CUSTOMBUILDSTEP_AdditionalDependencies: + m_XMLWriter.Write( + CFmtStrMax("AdditionalDependencies=\"%s\"", + pCustomBuildTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + } + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WriteSNCCompilerTool(CCompilerTool *pCompilerTool) { + if (!pCompilerTool) { + // not an error, some tools n/a for a config + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCCLCompilerTool\""); + + // aggregates or purges state as needed + CUtlString additionalOptions = ""; + + for (int i = 0; i < pCompilerTool->m_PropertyStates.m_Properties.Count(); + i++) { + int nOrdinalValue = atoi( + pCompilerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get()); + + switch (pCompilerTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_SNCCOMPILER_AdditionalIncludeDirectories: + m_XMLWriter.Write( + CFmtStrMax("AdditionalIncludeDirectories=\"%s\"", + pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_SNCCOMPILER_PreprocessorDefinitions: + m_XMLWriter.Write( + CFmtStrMax("PreprocessorDefinitions=\"%s\"", + pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_SNCCOMPILER_ForceIncludes: + m_XMLWriter.Write( + CFmtStrMax("ForcedIncludeFiles=\"%s\"", + pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_SNCCOMPILER_GenerateDebugInformation: + if (nOrdinalValue) { + additionalOptions += "-g "; + } + break; + + case PS3_SNCCOMPILER_Warnings: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-Xdiag=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_TreatMessagesAsErrors: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-Xquit=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_DisableSpecificWarnings: + m_XMLWriter.Write( + CFmtStrMax("DisableSpecificWarnings=\"%s\"", + pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_SNCCOMPILER_ObjectFileName: + m_XMLWriter.Write(CFmtStrMax( + "ObjectFile=\"%s\"", pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_SNCCOMPILER_CallprofHierarchicalProfiling: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-Xcallprof=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_OptimizationLevel: + if (nOrdinalValue == 0) { + // lack of any -0 means -O0 + additionalOptions += " "; + } else if (nOrdinalValue == 1) { + additionalOptions += "-O1 "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-O2 "; + } else if (nOrdinalValue == 3) { + additionalOptions += "-Os "; + } else if (nOrdinalValue == 4) { + additionalOptions += "-Od "; + } + break; + + case PS3_SNCCOMPILER_FastMath: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-Xfastmath=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_RelaxAliasChecking: + if (nOrdinalValue >= 0) { + additionalOptions += CFmtStrMax("-Xrelaxalias=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_BranchlessCompares: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-Xbranchless=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_UnrollLoops: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-Xunrollssa=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_AssumeAlignedPointers: + if (nOrdinalValue) { + additionalOptions += "-Xassumecorrectalignment=1 "; + } + break; + + case PS3_SNCCOMPILER_AssumeCorrectSign: + if (nOrdinalValue) { + additionalOptions += "-Xassumecorrectsign=1 "; + } + break; + + case PS3_SNCCOMPILER_TOCPointerPreservation: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-Xnotocrestore=%d ", nOrdinalValue); + } + break; + + case PS3_SNCCOMPILER_InitializedDataPlacement: + additionalOptions += CFmtStrMax("-Xbss=%d ", nOrdinalValue); + break; + + case PS3_SNCCOMPILER_PromoteFPConstantsToDoubles: + if (nOrdinalValue) { + additionalOptions += "-Xfltconst=8 "; + } + break; + + case PS3_SNCCOMPILER_CCPPDialect: + if (nOrdinalValue) { + if (nOrdinalValue == 1) { + additionalOptions += "-Xc=ansi "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-Xc=arm "; + } else if (nOrdinalValue == 3) { + additionalOptions += "-Xc=cp "; + } else if (nOrdinalValue == 4) { + additionalOptions += "-Xc=cfront "; + } else if (nOrdinalValue == 5) { + additionalOptions += "-Xc=knr "; + } + } + break; + + case PS3_SNCCOMPILER_CPPExceptionsAndRTTIUsage: + if (nOrdinalValue == 0) { + additionalOptions += "-Xc-=rtti -Xc-=exceptions "; + } else if (nOrdinalValue == 1) { + additionalOptions += "-Xc+=rtti -Xc-=exceptions "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-Xc+=rtti -Xc+=exceptions "; + } + break; + + case PS3_SNCCOMPILER_DefaultCharUnsigned: + if (nOrdinalValue) { + additionalOptions += "-Xchar=unsigned "; + } + break; + + case PS3_SNCCOMPILER_DefaultFPConstantsAsTypeFloat: + if (nOrdinalValue) { + additionalOptions += "-Xsingleconst=1 "; + } + break; + + case PS3_SNCCOMPILER_BuiltInDefinitionForWCHAR_TType: + if (nOrdinalValue == 0) { + additionalOptions += "-Xwchart=uint "; + } else if (nOrdinalValue == 1) { + additionalOptions += "-Xwchart=ulong "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-Xwchart=ushort "; + } else if (nOrdinalValue == 3) { + additionalOptions += "-Xwchart=uchar "; + } else if (nOrdinalValue == 4) { + additionalOptions += "-Xwchart=int "; + } else if (nOrdinalValue == 5) { + additionalOptions += "-Xwchart=long "; + } else if (nOrdinalValue == 6) { + additionalOptions += "-Xwchart=short "; + } else if (nOrdinalValue == 7) { + additionalOptions += "-Xwchart=char "; + } else if (nOrdinalValue == 8) { + additionalOptions += "-Xwchart=schar "; + } + break; + + case PS3_SNCCOMPILER_CreateUsePrecompiledHeader: + if (nOrdinalValue == 1) { + additionalOptions += + CFmtStrMax("--create_pch="%s" ", + pCompilerTool->m_PropertyStates + .GetProperty(PS3_SNCCOMPILER_PrecompiledHeaderFile) + ->m_StringValue.String()); + } else if (nOrdinalValue == 2) { + additionalOptions += "--pch --pch_dir="$(IntDir)" "; + } else if (nOrdinalValue == 3) { + additionalOptions += + CFmtStrMax("--use_pch="%s" ", + pCompilerTool->m_PropertyStates + .GetProperty(PS3_SNCCOMPILER_PrecompiledHeaderFile) + ->m_StringValue.String()); + } + break; + + case PS3_SNCCOMPILER_PrecompiledHeaderFile: + // already accounted for + break; + + case PS3_SNCCOMPILER_AdditionalOptions: + if (!pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.IsEmpty()) { + additionalOptions += + pCompilerTool->m_PropertyStates.m_Properties[i].m_StringValue; + additionalOptions += " "; + } + break; + } + } + + if (!additionalOptions.IsEmpty()) { + m_XMLWriter.Write( + CFmtStrMax("AdditionalOptions=\"%s\"", additionalOptions.Get())); + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WriteGCCCompilerTool(CCompilerTool *pCompilerTool) { + if (!pCompilerTool) { + // not an error, some tools n/a for a config + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCCLCompilerTool\""); + + // aggregates or purges state as needed + CUtlString additionalOptions = ""; + + for (int i = 0; i < pCompilerTool->m_PropertyStates.m_Properties.Count(); + i++) { + int nOrdinalValue = atoi( + pCompilerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get()); + + switch (pCompilerTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_GCCCOMPILER_AdditionalIncludeDirectories: { + // String() returns temporary object, so save in var to prevent stale + // memory usage. + CUtlString aid = m_XMLWriter.FixupXMLString( + pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()); + m_XMLWriter.Write( + CFmtStrMax("AdditionalIncludeDirectories=\"%s\"", aid.String())); + } break; + + case PS3_GCCCOMPILER_PreprocessorDefinitions: + m_XMLWriter.Write( + CFmtStrMax("PreprocessorDefinitions=\"%s\"", + pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_GCCCOMPILER_ForceIncludes: + m_XMLWriter.Write( + CFmtStrMax("ForcedIncludeFiles=\"%s\"", + pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_GCCCOMPILER_GenerateDebugInformation: + if (nOrdinalValue) { + additionalOptions += "-g "; + } + break; + + case PS3_GCCCOMPILER_Warnings: + if (nOrdinalValue == 0) { + additionalOptions += "-w "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-Wall "; + } + break; + + case PS3_GCCCOMPILER_ExtraWarnings: + if (nOrdinalValue) { + additionalOptions += "-Wextra "; + } + break; + + case PS3_GCCCOMPILER_WarnLoadHitStores: + break; + + case PS3_GCCCOMPILER_WarnMicrocodedInstruction: + break; + + case PS3_GCCCOMPILER_TreatWarningsAsErrors: + if (nOrdinalValue) { + additionalOptions += "-Werror "; + } + break; + + case PS3_GCCCOMPILER_ObjectFileName: + m_XMLWriter.Write(CFmtStrMax( + "ObjectFile=\"%s\"", pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_GCCCOMPILER_CallprofHierarchicalProfiling: + break; + + case PS3_GCCCOMPILER_SPURSUsage: + if (nOrdinalValue == 1) { + additionalOptions += "-mspurs-job-initialize "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-mspurs-job "; + } else if (nOrdinalValue == 3) { + additionalOptions += "-mspurs-task "; + } + break; + + case PS3_GCCCOMPILER_OptimizationLevel: + if (nOrdinalValue == 0) { + additionalOptions += "-O0 "; + } else if (nOrdinalValue == 1) { + additionalOptions += "-O1 "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-O2 "; + } else if (nOrdinalValue == 3) { + additionalOptions += "-O3 "; + } else if (nOrdinalValue == 4) { + additionalOptions += "-Os "; + } + break; + + case PS3_GCCCOMPILER_FastMath: + if (nOrdinalValue) { + additionalOptions += "-ffast-math "; + } + break; + + case PS3_GCCCOMPILER_NoStrictAliasing: + if (nOrdinalValue) { + additionalOptions += "-fno-strict-aliasing "; + } + break; + + case PS3_GCCCOMPILER_UnrollLoops: + if (nOrdinalValue) { + additionalOptions += "-funroll-loops "; + } + break; + + case PS3_GCCCOMPILER_InlineFunctionSizeLimit: + if (nOrdinalValue) { + additionalOptions += CFmtStrMax("-finline-limit=%d ", nOrdinalValue); + } + break; + + case PS3_GCCCOMPILER_TOCUsage: + break; + + case PS3_GCCCOMPILER_SaveRestoreFunctions: + break; + + case PS3_GCCCOMPILER_GenerateMicrocodedInstructions: + break; + + case PS3_GCCCOMPILER_PositionIndependentCode: + if (nOrdinalValue) { + additionalOptions += "-fpic "; + } + break; + + case PS3_GCCCOMPILER_FunctionSections: + if (nOrdinalValue) { + additionalOptions += "-ffunction-sections "; + } + break; + + case PS3_GCCCOMPILER_DataSections: + if (nOrdinalValue) { + additionalOptions += "-fdata-sections "; + } + break; + + case PS3_GCCCOMPILER_StackCheck: + if (nOrdinalValue) { + additionalOptions += "-fstack-check "; + } + break; + + case PS3_GCCCOMPILER_CPPExceptionsAndRTTIUsage: + if (nOrdinalValue == 0) { + additionalOptions += "-fno-exceptions -fno-rtti "; + } + break; + + case PS3_GCCCOMPILER_CheckANSICompliance: + if (nOrdinalValue) { + additionalOptions += "-ansi "; + } + break; + + case PS3_GCCCOMPILER_DefaultCharSigned: + if (nOrdinalValue) { + additionalOptions += "-fsigned-char "; + } + break; + + case PS3_GCCCOMPILER_Permissive: + if (nOrdinalValue) { + additionalOptions += "-fpermissive "; + } + break; + + case PS3_GCCCOMPILER_EnableMSExtensions: + break; + + case PS3_GCCCOMPILER_RelaxCPPCompliance: + if (nOrdinalValue) { + additionalOptions += "-fsource-402 "; + } + break; + + case PS3_GCCCOMPILER_AdditionalOptions: + if (!pCompilerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.IsEmpty()) { + additionalOptions += + pCompilerTool->m_PropertyStates.m_Properties[i].m_StringValue; + additionalOptions += " "; + } + break; + } + } + + if (!additionalOptions.IsEmpty()) { + m_XMLWriter.Write( + CFmtStrMax("AdditionalOptions=\"%s\"", additionalOptions.Get())); + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WritePreLinkEventTool( + CPreLinkEventTool *pPreLinkEventTool) { + if (!pPreLinkEventTool) { + // not an error, some tools n/a for aconfig + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCPreLinkEventTool\""); + + for (int i = 0; i < pPreLinkEventTool->m_PropertyStates.m_Properties.Count(); + i++) { + switch (pPreLinkEventTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_PRELINKEVENT_CommandLine: { + // String() returns temporary object, so save in var to prevent stale + // memory usage. + CUtlString cl = m_XMLWriter.FixupXMLString( + pPreLinkEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()); + m_XMLWriter.Write(CFmtStrMax("CommandLine=\"%s\"", cl.String())); + } break; + + case PS3_PRELINKEVENT_Description: + m_XMLWriter.Write( + CFmtStrMax("Description=\"%s\"", + pPreLinkEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_PRELINKEVENT_ExcludedFromBuild: + m_XMLWriter.Write( + CFmtStrMax("ExcludedFromBuild=\"%s\"", + BoolStringToTrueFalseString( + pPreLinkEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()))); + break; + } + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WriteSNCLinkerTool(CLinkerTool *pLinkerTool) { + if (!pLinkerTool) { + // not an error, some tools n/a for a config + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCLinkerTool\""); + + // aggregates or purges state as needed + CUtlString additionalOptions = ""; + + for (int i = 0; i < pLinkerTool->m_PropertyStates.m_Properties.Count(); i++) { + int nOrdinalValue = + atoi(pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get()); + + switch (pLinkerTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_SNCLINKER_OutputFile: + m_XMLWriter.Write(CFmtStrMax( + "OutputFile=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_SNCLINKER_OutputFormat: + if (nOrdinalValue == 1) { + additionalOptions += "-oformat=fself "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-oformat=fself_npdrm "; + } else if (nOrdinalValue == 3) { + additionalOptions += "-oformat=prx -prx-with-runtime "; + } else if (nOrdinalValue == 4) { + additionalOptions += "-oformat=fsprx -prx-with-runtime "; + } + break; + + case PS3_SNCLINKER_AdditionalDependencies: + m_XMLWriter.Write(CFmtStrMax( + "AdditionalDependencies=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_SNCLINKER_AdditionalLibraryDirectories: + m_XMLWriter.Write(CFmtStrMax( + "AdditionalLibraryDirectories=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_SNCLINKER_IgnoreAllDefaultLibraries: + if (nOrdinalValue) { + m_XMLWriter.Write("IgnoreAllDefaultLibraries=\"true\""); + } + break; + + case PS3_SNCLINKER_UsingExceptionHandling: + if (nOrdinalValue) { + additionalOptions += "--exceptions "; + } + break; + + case PS3_SNCLINKER_TOCPointerElimination: + if (nOrdinalValue) { + additionalOptions += "--notocrestore "; + } + break; + + case PS3_SNCLINKER_ForceSymbolReferences: + m_XMLWriter.Write(CFmtStrMax( + "ForceSymbolReferences=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_SNCLINKER_CallprofHierarchicalProfiling: + if (nOrdinalValue) { + additionalOptions += "--callprof "; + } + break; + + case PS3_SNCLINKER_DebugInfoAndSymbolStripping: + if (nOrdinalValue == 1) { + additionalOptions += "-S "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-s "; + } + break; + + case PS3_SNCLINKER_UnusedFunctionAndDataStripping: + if (nOrdinalValue == 1) { + additionalOptions += "-strip-unused "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-strip-unused-data "; + } + break; + + case PS3_SNCLINKER_ImportLibrary: + m_XMLWriter.Write(CFmtStrMax( + "ImportLibrary=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_SNCLINKER_GenerateMapFile: + if (nOrdinalValue == 1) { + additionalOptions += + CFmtStrMax("-Map="%s" ", + pLinkerTool->m_PropertyStates + .GetProperty(PS3_SNCLINKER_MapFileName) + ->m_StringValue.String()); + } else if (nOrdinalValue == 2) { + additionalOptions += + CFmtStrMax("-Map="%s" -sn-full-map ", + pLinkerTool->m_PropertyStates + .GetProperty(PS3_SNCLINKER_MapFileName) + ->m_StringValue.String()); + } + break; + + case PS3_SNCLINKER_MapFileName: + m_XMLWriter.Write(CFmtStrMax( + "MapFileName=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_SNCLINKER_LinkLibraryDependencies: + m_XMLWriter.Write( + CFmtStrMax("LinkLibraryDependencies=\"%s\"", + BoolStringToTrueFalseString( + pLinkerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()))); + break; + + case PS3_SNCLINKER_AdditionalOptions: + if (!pLinkerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.IsEmpty()) { + additionalOptions += + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue; + additionalOptions += " "; + } + break; + } + } + + if (!additionalOptions.IsEmpty()) { + m_XMLWriter.Write( + CFmtStrMax("AdditionalOptions=\"%s\"", additionalOptions.Get())); + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WriteGCCLinkerTool(CLinkerTool *pLinkerTool) { + if (!pLinkerTool) { + // not an error, some tools n/a for a config + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCLinkerTool\""); + + // aggregates or purges state as needed + CUtlString additionalOptions = ""; + + for (int i = 0; i < pLinkerTool->m_PropertyStates.m_Properties.Count(); i++) { + int nOrdinalValue = + atoi(pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get()); + + switch (pLinkerTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_GCCLINKER_OutputFile: + m_XMLWriter.Write(CFmtStrMax( + "OutputFile=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GCCLINKER_AdditionalDependencies: + m_XMLWriter.Write(CFmtStrMax( + "AdditionalDependencies=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GCCLINKER_AdditionalLibraryDirectories: + m_XMLWriter.Write(CFmtStrMax( + "AdditionalLibraryDirectories=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GCCLINKER_ImportLibrary: + m_XMLWriter.Write(CFmtStrMax( + "ImportLibrary=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GCCLINKER_SPURSUsage: + if (nOrdinalValue == 1) { + additionalOptions += "-mspurs-job-initialize "; + } else if (nOrdinalValue == 2) { + additionalOptions += "-mspurs-job "; + } else if (nOrdinalValue == 3) { + additionalOptions += "-mspurs-task "; + } + break; + + case PS3_GCCLINKER_PositionIndependentCode: + if (nOrdinalValue) { + additionalOptions += "-fpic "; + } + break; + + case PS3_GCCLINKER_EmitRelocations: + if (nOrdinalValue) { + additionalOptions += "-Wl,-q "; + } + break; + + case PS3_GCCLINKER_GarbageCollection: + if (nOrdinalValue) { + additionalOptions += "-Wl,--gc-sections "; + } + break; + + case PS3_GCCLINKER_GenerateMapFile: + if (nOrdinalValue == 1) { + additionalOptions += + CFmtStrMax("-Map="%s" ", + pLinkerTool->m_PropertyStates + .GetProperty(PS3_GCCLINKER_MapFileName) + ->m_StringValue.String()); + } + break; + + case PS3_GCCLINKER_MapFileName: + m_XMLWriter.Write(CFmtStrMax( + "MapFileName=\"%s\"", + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GCCLINKER_LinkLibraryDependencies: + m_XMLWriter.Write( + CFmtStrMax("LinkLibraryDependencies=\"%s\"", + BoolStringToTrueFalseString( + pLinkerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()))); + break; + + case PS3_SNCLINKER_AdditionalOptions: + if (!pLinkerTool->m_PropertyStates.m_Properties[i] + .m_StringValue.IsEmpty()) { + additionalOptions += + pLinkerTool->m_PropertyStates.m_Properties[i].m_StringValue; + additionalOptions += " "; + } + break; + } + } + + if (!additionalOptions.IsEmpty()) { + m_XMLWriter.Write( + CFmtStrMax("AdditionalOptions=\"%s\"", additionalOptions.Get())); + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WriteLibrarianTool(CLibrarianTool *pLibrarianTool) { + if (!pLibrarianTool) { + // not an error, some tools n/a for aconfig + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCLibrarianTool\""); + + for (int i = 0; i < pLibrarianTool->m_PropertyStates.m_Properties.Count(); + i++) { + switch (pLibrarianTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_LIBRARIAN_OutputFile: + m_XMLWriter.Write( + CFmtStrMax("OutputFile=\"%s\"", + pLibrarianTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + case PS3_LIBRARIAN_AdditionalDependencies: + m_XMLWriter.Write( + CFmtStrMax("AdditionalDependencies=\"%s\"", + pLibrarianTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + case PS3_LIBRARIAN_WholeArchive: + // can't decode, seems broken + break; + case PS3_LIBRARIAN_LinkLibraryDependencies: + m_XMLWriter.Write( + CFmtStrMax("LinkLibraryDependencies=\"%s\"", + BoolStringToTrueFalseString( + pLibrarianTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()))); + break; + } + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_PS3::WritePostBuildEventTool( + CPostBuildEventTool *pPostBuildEventTool) { + if (!pPostBuildEventTool) { + // not an error, some tools n/a for aconfig + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write("Name=\"VCPostBuildEventTool\""); + + for (int i = 0; + i < pPostBuildEventTool->m_PropertyStates.m_Properties.Count(); i++) { + switch (pPostBuildEventTool->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_POSTBUILDEVENT_CommandLine: { + // String() returns temporary object, so save in var to prevent stale + // memory usage. + CUtlString cl = m_XMLWriter.FixupXMLString( + pPostBuildEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()); + m_XMLWriter.Write(CFmtStrMax("CommandLine=\"%s\"", cl.String())); + } break; + + case PS3_POSTBUILDEVENT_Description: + m_XMLWriter.Write( + CFmtStrMax("Description=\"%s\"", + pPostBuildEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get())); + break; + + case PS3_POSTBUILDEVENT_ExcludedFromBuild: + m_XMLWriter.Write( + CFmtStrMax("ExcludedFromBuild=\"%s\"", + BoolStringToTrueFalseString( + pPostBuildEventTool->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()))); + break; + } + } + + m_XMLWriter.PopNode(false); + + return true; +} + +const char *CProjectGenerator_PS3::BoolStringToTrueFalseString( + const char *pValue) { + return Sys_StringToBool(pValue) ? "true" : "false"; +} + +bool CProjectGenerator_PS3::WriteConfiguration(CProjectConfiguration *pConfig) { + if (pConfig->m_bIsFileConfig) { + m_XMLWriter.PushNode("FileConfiguration"); + } else { + m_XMLWriter.PushNode("Configuration"); + } + + const char *pOutputName = "???"; + if (!V_stricmp(pConfig->m_Name.Get(), "debug")) { + if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_SNC) { + pOutputName = "PS3SNCDebug|Win32"; + } else if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_GCC) { + pOutputName = "PS3GCCDebug|Win32"; + } + } else if (!V_stricmp(pConfig->m_Name.Get(), "release")) { + if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_SNC) { + pOutputName = "PS3SNCRelease|Win32"; + } else if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_GCC) { + pOutputName = "PS3GCCRelease|Win32"; + } + } else { + return false; + } + + m_XMLWriter.Write(CFmtStrMax("Name=\"%s\"", pOutputName)); + + // write configuration properties + for (int i = 0; i < pConfig->m_PropertyStates.m_Properties.Count(); i++) { + switch (pConfig->m_PropertyStates.m_Properties[i] + .m_pToolProperty->m_nPropertyId) { + case PS3_GENERAL_ConfigurationType: + m_XMLWriter.Write(CFmtStrMax( + "ConfigurationType=\"%s\"", + pConfig->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GENERAL_ExcludedFromBuild: + m_XMLWriter.Write( + CFmtStrMax("ExcludedFromBuild=\"%s\"", + BoolStringToTrueFalseString( + pConfig->m_PropertyStates.m_Properties[i] + .m_StringValue.Get()))); + break; + + case PS3_GENERAL_OutputDirectory: + m_XMLWriter.Write(CFmtStrMax( + "OutputDirectory=\"%s\"", + pConfig->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GENERAL_IntermediateDirectory: + m_XMLWriter.Write(CFmtStrMax( + "IntermediateDirectory=\"%s\"", + pConfig->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GENERAL_ExtensionsToDeleteOnClean: + m_XMLWriter.Write(CFmtStrMax( + "DeleteExtensionsOnClean=\"%s\"", + pConfig->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GENERAL_BuildLogFile: + m_XMLWriter.Write(CFmtStrMax( + "BuildLogFile=\"%s\"", + pConfig->m_PropertyStates.m_Properties[i].m_StringValue.Get())); + break; + + case PS3_GENERAL_SystemIncludeDependencies: + // ignoring + break; + + case PS3_GENERAL_SaveDebuggerPropertiesInProject: + // ignoring + break; + } + } + + m_XMLWriter.Write(">"); + + if (!WritePreBuildEventTool(pConfig->GetPreBuildEventTool())) return false; + + if (!WriteCustomBuildTool(pConfig->GetCustomBuildTool())) return false; + + if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_SNC) { + if (!WriteSNCCompilerTool(pConfig->GetCompilerTool())) return false; + } else if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_GCC) { + if (!WriteGCCCompilerTool(pConfig->GetCompilerTool())) return false; + } + + if (!WritePreLinkEventTool(pConfig->GetPreLinkEventTool())) return false; + + if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_SNC) { + if (!WriteSNCLinkerTool(pConfig->GetLinkerTool())) return false; + } else if (m_pVCProjGenerator->GetVSIType() == PS3_VSI_TYPE_GCC) { + if (!WriteGCCLinkerTool(pConfig->GetLinkerTool())) return false; + } + + if (!WriteLibrarianTool(pConfig->GetLibrarianTool())) return false; + + if (!WritePostBuildEventTool(pConfig->GetPostBuildEventTool())) return false; + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_PS3::WriteToXML() { + m_XMLWriter.PushNode("VisualStudioProject"); + m_XMLWriter.Write("ProjectType=\"Visual C++\""); + m_XMLWriter.Write("Version=\"8.00\""); + m_XMLWriter.Write( + CFmtStrMax("Name=\"%s\"", m_pVCProjGenerator->GetProjectName().Get())); + m_XMLWriter.Write(CFmtStrMax("ProjectGUID=\"%s\"", + m_pVCProjGenerator->GetGUIDString().Get())); + m_XMLWriter.Write(CFmtStrMax("RootNamespace=\"%s\"", + m_pVCProjGenerator->GetProjectName().Get())); + if (g_pVPC->BUseP4SCC()) + m_XMLWriter.Write( + "SccProjectName=\"Perforce " + "Project\"\nSccLocalPath=\"..\"\nSccProvider=\"MSSCCI:Perforce " + "SCM\"\n"); + m_XMLWriter.Write(">"); + + m_XMLWriter.PushNode("Platforms"); + m_XMLWriter.PushNode("Platform"); + m_XMLWriter.Write("Name=\"win32\""); + m_XMLWriter.PopNode(false); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("ToolFiles"); + m_XMLWriter.PopNode(true); + + CUtlVector configurationNames; + m_pVCProjGenerator->GetAllConfigurationNames(configurationNames); + + // write the root configurations + m_XMLWriter.PushNode("Configurations"); + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + if (!WriteConfiguration(pConfiguration)) return false; + } + } + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("References"); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("Files"); + + CProjectFolder *pRootFolder = m_pVCProjGenerator->GetRootFolder(); + for (auto iIndex = pRootFolder->m_Folders.Head(); + iIndex != pRootFolder->m_Folders.InvalidIndex(); + iIndex = pRootFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pRootFolder->m_Folders[iIndex])) return false; + } + + for (auto iIndex = pRootFolder->m_Files.Head(); + iIndex != pRootFolder->m_Files.InvalidIndex(); + iIndex = pRootFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pRootFolder->m_Files[iIndex])) return false; + } + + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("Globals"); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_PS3::Save(const char *pOutputFilename) { + if (!m_XMLWriter.Open(pOutputFilename)) return false; + + bool bValid = WriteToXML(); + + m_XMLWriter.Close(); + + if (bValid) { + // Not sure what this file does or why, but we emit it and + // its part of a default SN project. The custom build steps + // were copied are hosted in all the vpc ps3 base scripts. + FILE *fp = fopen("vsi.nul", "wt"); + if (fp) { + fprintf(fp, "SN Visual Studio Integration\n"); + fprintf(fp, + "IMPORTANT: Do not remove the custom build step for this file\n"); + fclose(fp); + } + } + + return bValid; +} diff --git a/utils/vpc/projectgenerator_ps3.h b/utils/vpc/projectgenerator_ps3.h new file mode 100644 index 0000000..95f6bff --- /dev/null +++ b/utils/vpc/projectgenerator_ps3.h @@ -0,0 +1,41 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_PROJECTGENERATOR_PS3_H_ +#define VPC_PROJECTGENERATOR_PS3_H_ + +#include "projectgenerator_vcproj.h" + +#define PROPERTYNAME(X, Y) X##_##Y , + +enum PS3Properties_e { +#include "projectgenerator_ps3.inc" +}; + +class CProjectGenerator_PS3 : public IVCProjWriter { + public: + CProjectGenerator_PS3(); + IBaseProjectGenerator *GetProjectGenerator() { return m_pVCProjGenerator; } + + virtual bool Save(const char *pOutputFilename); + + private: + bool WriteToXML(); + bool WriteFolder(CProjectFolder *pFolder); + bool WriteFile(CProjectFile *pFile); + bool WriteConfiguration(CProjectConfiguration *pConfig); + bool WritePreBuildEventTool(CPreBuildEventTool *pPreBuildEventTool); + bool WriteCustomBuildTool(CCustomBuildTool *pCustomBuildTool); + bool WriteSNCCompilerTool(CCompilerTool *pCompilerTool); + bool WriteGCCCompilerTool(CCompilerTool *pCompilerTool); + bool WriteSNCLinkerTool(CLinkerTool *pLinkerTool); + bool WriteGCCLinkerTool(CLinkerTool *pLinkerTool); + bool WritePreLinkEventTool(CPreLinkEventTool *pPreLinkEventTool); + bool WriteLibrarianTool(CLibrarianTool *pLibrarianTool); + bool WritePostBuildEventTool(CPostBuildEventTool *pPostBuildEventTool); + const char *BoolStringToTrueFalseString(const char *pValue); + + CXMLWriter m_XMLWriter; + CVCProjGenerator *m_pVCProjGenerator; +}; + +#endif // VPC_PROJECTGENERATOR_PS3_H_ diff --git a/utils/vpc/projectgenerator_ps3.inc b/utils/vpc/projectgenerator_ps3.inc new file mode 100644 index 0000000..ceb272d --- /dev/null +++ b/utils/vpc/projectgenerator_ps3.inc @@ -0,0 +1,137 @@ + +//========= Copyright ?1996-2006, Valve Corporation, All rights reserved. ============// +// +// Property Enumerations +// +//=====================================================================================// + +// Config +PROPERTYNAME( PS3_GENERAL, ConfigurationType ) +PROPERTYNAME( PS3_GENERAL, ExcludedFromBuild ) +PROPERTYNAME( PS3_GENERAL, OutputDirectory ) +PROPERTYNAME( PS3_GENERAL, IntermediateDirectory ) +PROPERTYNAME( PS3_GENERAL, ExtensionsToDeleteOnClean ) +PROPERTYNAME( PS3_GENERAL, BuildLogFile ) +PROPERTYNAME( PS3_GENERAL, SystemIncludeDependencies ) +PROPERTYNAME( PS3_GENERAL, SaveDebuggerPropertiesInProject ) + +// GCC Compiler +PROPERTYNAME( PS3_GCCCOMPILER, AdditionalIncludeDirectories ) +PROPERTYNAME( PS3_GCCCOMPILER, PreprocessorDefinitions ) +PROPERTYNAME( PS3_GCCCOMPILER, ForceIncludes ) +PROPERTYNAME( PS3_GCCCOMPILER, GenerateDebugInformation ) +PROPERTYNAME( PS3_GCCCOMPILER, Warnings ) +PROPERTYNAME( PS3_GCCCOMPILER, ExtraWarnings ) +PROPERTYNAME( PS3_GCCCOMPILER, WarnLoadHitStores ) +PROPERTYNAME( PS3_GCCCOMPILER, WarnMicrocodedInstruction ) +PROPERTYNAME( PS3_GCCCOMPILER, TreatWarningsAsErrors ) +PROPERTYNAME( PS3_GCCCOMPILER, ObjectFileName ) +PROPERTYNAME( PS3_GCCCOMPILER, CallprofHierarchicalProfiling ) +PROPERTYNAME( PS3_GCCCOMPILER, SPURSUsage ) +PROPERTYNAME( PS3_GCCCOMPILER, OptimizationLevel ) +PROPERTYNAME( PS3_GCCCOMPILER, FastMath ) +PROPERTYNAME( PS3_GCCCOMPILER, NoStrictAliasing ) +PROPERTYNAME( PS3_GCCCOMPILER, UnrollLoops ) +PROPERTYNAME( PS3_GCCCOMPILER, InlineFunctionSizeLimit ) +PROPERTYNAME( PS3_GCCCOMPILER, TOCUsage ) +PROPERTYNAME( PS3_GCCCOMPILER, SaveRestoreFunctions ) +PROPERTYNAME( PS3_GCCCOMPILER, GenerateMicrocodedInstructions ) +PROPERTYNAME( PS3_GCCCOMPILER, PositionIndependentCode ) +PROPERTYNAME( PS3_GCCCOMPILER, FunctionSections ) +PROPERTYNAME( PS3_GCCCOMPILER, DataSections ) +PROPERTYNAME( PS3_GCCCOMPILER, StackCheck ) +PROPERTYNAME( PS3_GCCCOMPILER, CPPExceptionsAndRTTIUsage ) +PROPERTYNAME( PS3_GCCCOMPILER, CheckANSICompliance ) +PROPERTYNAME( PS3_GCCCOMPILER, DefaultCharSigned ) +PROPERTYNAME( PS3_GCCCOMPILER, Permissive ) +PROPERTYNAME( PS3_GCCCOMPILER, EnableMSExtensions ) +PROPERTYNAME( PS3_GCCCOMPILER, RelaxCPPCompliance ) +PROPERTYNAME( PS3_GCCCOMPILER, AdditionalOptions ) + +// Librarian +PROPERTYNAME( PS3_LIBRARIAN, OutputFile ) +PROPERTYNAME( PS3_LIBRARIAN, AdditionalDependencies ) +PROPERTYNAME( PS3_LIBRARIAN, WholeArchive ) +PROPERTYNAME( PS3_LIBRARIAN, LinkLibraryDependencies ) + +// GCC Linker +PROPERTYNAME( PS3_GCCLINKER, OutputFile ) +PROPERTYNAME( PS3_GCCLINKER, AdditionalDependencies ) +PROPERTYNAME( PS3_GCCLINKER, AdditionalLibraryDirectories ) +PROPERTYNAME( PS3_GCCLINKER, ImportLibrary ) +PROPERTYNAME( PS3_GCCLINKER, SPURSUsage ) +PROPERTYNAME( PS3_GCCLINKER, PositionIndependentCode ) +PROPERTYNAME( PS3_GCCLINKER, EmitRelocations ) +PROPERTYNAME( PS3_GCCLINKER, GarbageCollection ) +PROPERTYNAME( PS3_GCCLINKER, GenerateMapFile ) +PROPERTYNAME( PS3_GCCLINKER, MapFileName ) +PROPERTYNAME( PS3_GCCLINKER, LinkLibraryDependencies ) +PROPERTYNAME( PS3_GCCLINKER, AdditionalOptions ) + +// SNC Compiler +PROPERTYNAME( PS3_SNCCOMPILER, AdditionalIncludeDirectories ) +PROPERTYNAME( PS3_SNCCOMPILER, PreprocessorDefinitions ) +PROPERTYNAME( PS3_SNCCOMPILER, ForceIncludes ) +PROPERTYNAME( PS3_SNCCOMPILER, GenerateDebugInformation ) +PROPERTYNAME( PS3_SNCCOMPILER, Warnings ) +PROPERTYNAME( PS3_SNCCOMPILER, TreatMessagesAsErrors ) +PROPERTYNAME( PS3_SNCCOMPILER, DisableSpecificWarnings ) +PROPERTYNAME( PS3_SNCCOMPILER, ObjectFileName ) +PROPERTYNAME( PS3_SNCCOMPILER, CallprofHierarchicalProfiling ) +PROPERTYNAME( PS3_SNCCOMPILER, OptimizationLevel ) +PROPERTYNAME( PS3_SNCCOMPILER, FastMath ) +PROPERTYNAME( PS3_SNCCOMPILER, RelaxAliasChecking ) +PROPERTYNAME( PS3_SNCCOMPILER, BranchlessCompares ) +PROPERTYNAME( PS3_SNCCOMPILER, UnrollLoops ) +PROPERTYNAME( PS3_SNCCOMPILER, AssumeAlignedPointers ) +PROPERTYNAME( PS3_SNCCOMPILER, AssumeCorrectSign ) +PROPERTYNAME( PS3_SNCCOMPILER, TOCPointerPreservation ) +PROPERTYNAME( PS3_SNCCOMPILER, InitializedDataPlacement ) +PROPERTYNAME( PS3_SNCCOMPILER, PromoteFPConstantsToDoubles ) +PROPERTYNAME( PS3_SNCCOMPILER, CCPPDialect ) +PROPERTYNAME( PS3_SNCCOMPILER, CPPExceptionsAndRTTIUsage ) +PROPERTYNAME( PS3_SNCCOMPILER, DefaultCharUnsigned ) +PROPERTYNAME( PS3_SNCCOMPILER, DefaultFPConstantsAsTypeFloat ) +PROPERTYNAME( PS3_SNCCOMPILER, BuiltInDefinitionForWCHAR_TType ) +PROPERTYNAME( PS3_SNCCOMPILER, CreateUsePrecompiledHeader ) +PROPERTYNAME( PS3_SNCCOMPILER, PrecompiledHeaderFile ) +PROPERTYNAME( PS3_SNCCOMPILER, AdditionalOptions ) + +// SNC Linker +PROPERTYNAME( PS3_SNCLINKER, OutputFile ) +PROPERTYNAME( PS3_SNCLINKER, OutputFormat ) +PROPERTYNAME( PS3_SNCLINKER, AdditionalDependencies ) +PROPERTYNAME( PS3_SNCLINKER, AdditionalLibraryDirectories ) +PROPERTYNAME( PS3_SNCLINKER, IgnoreAllDefaultLibraries ) +PROPERTYNAME( PS3_SNCLINKER, UsingExceptionHandling ) +PROPERTYNAME( PS3_SNCLINKER, TOCPointerElimination ) +PROPERTYNAME( PS3_SNCLINKER, ForceSymbolReferences ) +PROPERTYNAME( PS3_SNCLINKER, CallprofHierarchicalProfiling ) +PROPERTYNAME( PS3_SNCLINKER, DebugInfoAndSymbolStripping ) +PROPERTYNAME( PS3_SNCLINKER, UnusedFunctionAndDataStripping ) +PROPERTYNAME( PS3_SNCLINKER, ImportLibrary ) +PROPERTYNAME( PS3_SNCLINKER, GenerateMapFile ) +PROPERTYNAME( PS3_SNCLINKER, MapFileName ) +PROPERTYNAME( PS3_SNCLINKER, LinkLibraryDependencies ) +PROPERTYNAME( PS3_SNCLINKER, AdditionalOptions ) + +// Pre Build +PROPERTYNAME( PS3_PREBUILDEVENT, CommandLine ) +PROPERTYNAME( PS3_PREBUILDEVENT, Description ) +PROPERTYNAME( PS3_PREBUILDEVENT, ExcludedFromBuild ) + +// Pre Link +PROPERTYNAME( PS3_PRELINKEVENT, CommandLine ) +PROPERTYNAME( PS3_PRELINKEVENT, Description ) +PROPERTYNAME( PS3_PRELINKEVENT, ExcludedFromBuild ) + +// Post Build +PROPERTYNAME( PS3_POSTBUILDEVENT, CommandLine ) +PROPERTYNAME( PS3_POSTBUILDEVENT, Description ) +PROPERTYNAME( PS3_POSTBUILDEVENT, ExcludedFromBuild ) + +// Custom Build +PROPERTYNAME( PS3_CUSTOMBUILDSTEP, CommandLine ) +PROPERTYNAME( PS3_CUSTOMBUILDSTEP, Description ) +PROPERTYNAME( PS3_CUSTOMBUILDSTEP, Outputs ) +PROPERTYNAME( PS3_CUSTOMBUILDSTEP, AdditionalDependencies ) diff --git a/utils/vpc/projectgenerator_vcproj.cpp b/utils/vpc/projectgenerator_vcproj.cpp new file mode 100644 index 0000000..b5d32f4 --- /dev/null +++ b/utils/vpc/projectgenerator_vcproj.cpp @@ -0,0 +1,1773 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "projectgenerator_vcproj.h" + +#include "tier0/memdbgon.h" + +CProjectFile::CProjectFile(CVCProjGenerator *pGenerator, const char *pFilename) + : m_Name(pFilename), m_pGenerator(pGenerator) {} + +CProjectFile::~CProjectFile() { m_Configs.PurgeAndDeleteElements(); } + +bool CProjectFile::GetConfiguration(const char *pConfigName, + CProjectConfiguration **ppConfig) { + if (!pConfigName || !pConfigName[0]) { + g_pVPC->VPCError("Empty or bad configuration name."); + } + + if (ppConfig) { + // assume not found + *ppConfig = NULL; + } + + for (int i = 0; i < m_Configs.Count(); i++) { + if (!V_stricmp(m_Configs[i]->m_Name.Get(), pConfigName)) { + // found + if (ppConfig) { + *ppConfig = m_Configs[i]; + } + return true; + } + } + + // not found + return false; +} + +bool CProjectFile::AddConfiguration(const char *pConfigName, + CProjectConfiguration **ppConfig) { + if (ppConfig) { + // assume not found + *ppConfig = NULL; + } + + if (GetConfiguration(pConfigName, NULL)) { + // found, cannot add duplicate + return false; + } + + // add in alphabetic order + CProjectConfiguration *pNewConfig = + new CProjectConfiguration(m_pGenerator, pConfigName, m_Name.Get()); + + int iIndex = 0; + for (iIndex = 0; iIndex < m_Configs.Count(); iIndex++) { + if (V_stricmp(pConfigName, m_Configs[iIndex]->m_Name.Get()) < 0) { + m_Configs.InsertBefore(iIndex, pNewConfig); + break; + } + } + if (iIndex == m_Configs.Count()) { + m_Configs.AddToTail(pNewConfig); + } + + if (ppConfig) { + *ppConfig = pNewConfig; + } + return true; +} + +bool CProjectFile::RemoveConfiguration(CProjectConfiguration *pConfiguration) { + for (int i = 0; i < m_Configs.Count(); i++) { + if (m_Configs[i] == pConfiguration) { + m_Configs.Remove(i); + delete pConfiguration; + return true; + } + } + + return false; +} + +CProjectFolder::CProjectFolder(CVCProjGenerator *pGenerator, + const char *pFolderName) + : m_Name(pFolderName), m_pGenerator(pGenerator) {} + +CProjectFolder::~CProjectFolder() { + m_Folders.PurgeAndDeleteElements(); + m_Files.PurgeAndDeleteElements(); +} + +bool CProjectFolder::GetFolder(const char *pFolderName, + CProjectFolder **pFolder) { + if (pFolder) { + // assume not found + *pFolder = NULL; + } + + if (!pFolderName || !pFolderName[0]) { + g_pVPC->VPCError("Empty or bad folder name."); + } + + for (auto iIndex = m_Folders.Head(); iIndex != m_Folders.InvalidIndex(); + iIndex = m_Folders.Next(iIndex)) { + if (!V_stricmp(m_Folders[iIndex]->m_Name.Get(), pFolderName)) { + // found + if (pFolder) { + *pFolder = m_Folders[iIndex]; + } + return true; + } + } + + // not found + return false; +} + +bool CProjectFolder::AddFolder(const char *pFolderName, + CProjectFolder **pFolder) { + if (pFolder) { + // assume not added + *pFolder = NULL; + } + + if (GetFolder(pFolderName, NULL)) { + // found, cannot add duplicate + return false; + } + + CProjectFolder *pNewFolder = new CProjectFolder(m_pGenerator, pFolderName); + + // maintain sorted ascending alphabetic order + unsigned short iIndex; + for (iIndex = m_Folders.Head(); iIndex != m_Folders.InvalidIndex(); + iIndex = m_Folders.Next(iIndex)) { + if (V_stricmp(pFolderName, m_Folders[iIndex]->m_Name.Get()) < 0) { + m_Folders.InsertBefore(iIndex, pNewFolder); + break; + } + } + if (iIndex == m_Folders.InvalidIndex()) { + m_Folders.AddToTail(pNewFolder); + } + + if (pFolder) { + *pFolder = pNewFolder; + } + return true; +} + +void CProjectFolder::AddFile(const char *pFilename, CProjectFile **ppFile) { + if (!pFilename || !pFilename[0]) { + g_pVPC->VPCError("Empty or bad filename."); + } + + CProjectFile *pNewFile = new CProjectFile(m_pGenerator, pFilename); + + // maintain sorted ascending alphabetic order + unsigned short iIndex; + for (iIndex = m_Files.Head(); iIndex != m_Files.InvalidIndex(); + iIndex = m_Files.Next(iIndex)) { + if (g_pVPC->IsPlatformDefined("PS3")) { + // temporary legacy behavior for diff ease until I can be sure project + // generation is equivalent + iIndex = m_Files.InvalidIndex(); + break; + } + + // the COM layer for WIN32 sorted by filename only, and NOT the entire path + if (V_stricmp(V_GetFileName(pFilename), + V_GetFileName(m_Files[iIndex]->m_Name.Get())) < 0) { + m_Files.InsertBefore(iIndex, pNewFile); + break; + } + } + if (iIndex == m_Files.InvalidIndex()) { + m_Files.AddToTail(pNewFile); + } + + if (ppFile) { + *ppFile = pNewFile; + } +} + +bool CProjectFolder::FindFile(const char *pFilename) { + if (!pFilename || !pFilename[0]) { + g_pVPC->VPCError("Empty or bad filename."); + } + + for (auto iIndex = m_Files.Head(); iIndex != m_Files.InvalidIndex(); + iIndex = m_Files.Next(iIndex)) { + if (!V_stricmp(m_Files[iIndex]->m_Name.Get(), pFilename)) { + // found + return true; + } + } + + return false; +} + +bool CProjectFolder::RemoveFile(const char *pFilename) { + if (!pFilename || !pFilename[0]) { + g_pVPC->VPCError("Empty or bad filename."); + } + + for (auto iIndex = m_Files.Head(); iIndex != m_Files.InvalidIndex(); + iIndex = m_Files.Next(iIndex)) { + if (!V_stricmp(m_Files[iIndex]->m_Name.Get(), pFilename)) { + // found, remove + delete m_Files[iIndex]; + m_Files.Unlink(iIndex); + return true; + } + } + + return false; +} + +bool CPropertyStateLessFunc::Less(const intp &lhs, const intp &rhs, + void *pContext) { + int lhsPropertyId = ((CPropertyStates *)pContext) + ->m_Properties[lhs] + .m_pToolProperty->m_nPropertyId; + int rhsPropertyId = ((CPropertyStates *)pContext) + ->m_Properties[rhs] + .m_pToolProperty->m_nPropertyId; + + return lhsPropertyId < rhsPropertyId; +} + +CPropertyStates::CPropertyStates() { + m_PropertiesInOutputOrder.SetLessContext(this); +} + +PropertyState_t *CPropertyStates::GetProperty(int nPropertyId) { + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty->m_nPropertyId == nPropertyId) { + return &m_Properties[i]; + } + } + + return NULL; +} + +PropertyState_t *CPropertyStates::GetProperty(const char *pPropertyName) { + if (pPropertyName[0] == '$') { + pPropertyName++; + } + + for (int i = 0; i < m_Properties.Count(); i++) { + const char *pParseString = + m_Properties[i].m_pToolProperty->m_ParseString.Get(); + if (pParseString[0] == '$') { + pParseString++; + } + if (!V_stricmp(pPropertyName, pParseString)) { + return &m_Properties[i]; + } + + const char *pLegacyString = + m_Properties[i].m_pToolProperty->m_LegacyString.Get(); + if (pLegacyString[0]) { + if (pLegacyString[0] == '$') { + pLegacyString++; + } + if (!V_stricmp(pPropertyName, pLegacyString)) { + return &m_Properties[i]; + } + } + } + + return NULL; +} + +bool CPropertyStates::SetStringProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + // find possible current value + const char *pCurrentValue = NULL; + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + pCurrentValue = m_Properties[i].m_StringValue.Get(); + break; + } + } + + if (!pCurrentValue && pRootTool) { + // fallback to root tool's config to find current value + for (int i = 0; i < pRootTool->m_PropertyStates.m_Properties.Count(); i++) { + if (pRootTool->m_PropertyStates.m_Properties[i].m_pToolProperty == + pToolProperty) { + pCurrentValue = + pRootTool->m_PropertyStates.m_Properties[i].m_StringValue.Get(); + break; + } + } + } + + // feed in current value to resolve $BASE + // possibly culled or tokenized new value + char buff[MAX_SYSTOKENCHARS]; + if (!g_pVPC->GetScript().ParsePropertyValue(pCurrentValue, buff, + sizeof(buff))) + return true; + + if (pToolProperty->m_bFixSlashes) { + V_FixSlashes(buff); + } + + if (pToolProperty->m_bPreferSemicolonNoComma) { + CUtlString buffCopy = buff; + V_StrSubst(buffCopy.Get(), ",", ";", buff, sizeof(buff), false); + } + + if (pToolProperty->m_bPreferSemicolonNoSpace) { + CUtlString buffCopy = buff; + V_StrSubst(buffCopy.Get(), " ", ";", buff, sizeof(buff), false); + } + + if (pToolProperty->m_bAppendSlash) { + intp len = V_strlen(buff); + if (len >= 1 && buff[len - 1] != '\\') { + V_strncat(buff, "\\", sizeof(buff)); + } + } + + if (!V_stricmp(pToolProperty->m_ParseString.Get(), "$CommandLine") && + !V_strnicmp(buff, "echo ", 5)) { + // the COM layer auto appended a CR-LF for a command line with an echo + intp len = V_strlen(buff); + if ((len >= 1 && buff[len - 1] != '\n') && + (len >= 12 && V_stricmp(buff + len - 12, " "))) { + V_strncat(buff, "\n", sizeof(buff)); + } + } + + if (pCurrentValue && !V_stricmp(pCurrentValue, buff)) { + g_pVPC->VPCWarning("%s matches default setting, [%s line:%d]", + pToolProperty->m_ParseString.Get(), + g_pVPC->GetScript().GetName(), + g_pVPC->GetScript().GetLine()); + } + + if (pCurrentValue) { + // update existing state + // always replace or add strings due to case changes + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + m_Properties[i].m_StringValue = buff; + return true; + } + } + } + + // add + intp iIndex = m_Properties.AddToTail(); + m_Properties[iIndex].m_pToolProperty = pToolProperty; + m_Properties[iIndex].m_StringValue = buff; + + m_PropertiesInOutputOrder.Insert(iIndex); + + return true; +} + +bool CPropertyStates::SetListProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + char buff[MAX_SYSTOKENCHARS]; + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff))) + return true; + + // resolve the parsed token to an expected ordinal + const char *pNewOrdinalValue = NULL; + for (int i = 0; i < pToolProperty->m_Ordinals.Count(); i++) { + if (!V_stricmp(pToolProperty->m_Ordinals[i].m_ParseString.Get(), buff)) { + pNewOrdinalValue = pToolProperty->m_Ordinals[i].m_ValueString.Get(); + break; + } + } + + if (!pNewOrdinalValue && !V_stricmp(buff, "default")) { + // allow "default" if not explicitly provided + // same as empty, state stays unaffected + return true; + } + + if (!pNewOrdinalValue) { + g_pVPC->VPCSyntaxError("Unknown Ordinal for %s", + pToolProperty->m_ParseString.Get()); + } + + // find possible current value + const char *pCurrentOrdinalValue = NULL; + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + pCurrentOrdinalValue = m_Properties[i].m_StringValue.Get(); + break; + } + } + + if (!pCurrentOrdinalValue && pRootTool) { + // fallback to root tool's config to find current value + for (int i = 0; i < pRootTool->m_PropertyStates.m_Properties.Count(); i++) { + if (pRootTool->m_PropertyStates.m_Properties[i].m_pToolProperty == + pToolProperty) { + pCurrentOrdinalValue = + pRootTool->m_PropertyStates.m_Properties[i].m_StringValue.Get(); + break; + } + } + } + + if (pCurrentOrdinalValue && + !V_stricmp(pCurrentOrdinalValue, pNewOrdinalValue)) { + g_pVPC->VPCWarning("%s matches default setting, [%s line:%d]", + pToolProperty->m_ParseString.Get(), + g_pVPC->GetScript().GetName(), + g_pVPC->GetScript().GetLine()); + } + + if (pCurrentOrdinalValue) { + // update existing state + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + m_Properties[i].m_OrdinalString = buff; + m_Properties[i].m_StringValue = pNewOrdinalValue; + return true; + } + } + } + + // add + intp iIndex = m_Properties.AddToTail(); + m_Properties[iIndex].m_pToolProperty = pToolProperty; + m_Properties[iIndex].m_OrdinalString = buff; + m_Properties[iIndex].m_StringValue = pNewOrdinalValue; + + m_PropertiesInOutputOrder.Insert(iIndex); + + return true; +} + +bool CPropertyStates::SetBoolProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool, bool bEnabled) { + const char *pNewOrdinalValue = bEnabled ? "1" : "0"; + + // find possible current value + const char *pCurrentOrdinalValue = NULL; + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + pCurrentOrdinalValue = m_Properties[i].m_StringValue.Get(); + break; + } + } + + if (!pCurrentOrdinalValue && pRootTool) { + // fallback to root tool's config to find current value + for (int i = 0; i < pRootTool->m_PropertyStates.m_Properties.Count(); i++) { + if (pRootTool->m_PropertyStates.m_Properties[i].m_pToolProperty == + pToolProperty) { + pCurrentOrdinalValue = + pRootTool->m_PropertyStates.m_Properties[i].m_StringValue.Get(); + break; + } + } + } + + if (pCurrentOrdinalValue && + !V_stricmp(pCurrentOrdinalValue, pNewOrdinalValue)) { + g_pVPC->VPCWarning("%s matches default setting, [%s line:%d]", + pToolProperty->m_ParseString.Get(), + g_pVPC->GetScript().GetName(), + g_pVPC->GetScript().GetLine()); + } + + if (pCurrentOrdinalValue) { + // update existing state + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + m_Properties[i].m_StringValue = pNewOrdinalValue; + return true; + } + } + } + + // add + intp iIndex = m_Properties.AddToTail(); + m_Properties[iIndex].m_pToolProperty = pToolProperty; + m_Properties[iIndex].m_StringValue = pNewOrdinalValue; + + m_PropertiesInOutputOrder.Insert(iIndex); + + return true; +} + +bool CPropertyStates::SetBoolProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + char buff[MAX_SYSTOKENCHARS]; + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff))) + return true; + + return SetBoolProperty(pToolProperty, pRootTool, Sys_StringToBool(buff)); +} + +bool CPropertyStates::SetBoolProperty(ToolProperty_t *pToolProperty, + bool bEnabled) { + return SetBoolProperty(pToolProperty, NULL, bEnabled); +} + +bool CPropertyStates::SetIntegerProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + char buff[MAX_SYSTOKENCHARS]; + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff))) + return true; + + // ensure the parsed token is a real integer and not just quietly mapped to 0 + int nParsedValue = atoi(buff); + if (V_stricmp(CFmtStr("%d", nParsedValue), buff)) { + g_pVPC->VPCSyntaxError("Unrecognized integer value: %s", buff); + } + + // find possible current value + const char *pCurrentOrdinalValue = NULL; + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + pCurrentOrdinalValue = m_Properties[i].m_StringValue.Get(); + break; + } + } + + if (!pCurrentOrdinalValue && pRootTool) { + // fallback to root tool's config to find current value + for (int i = 0; i < pRootTool->m_PropertyStates.m_Properties.Count(); i++) { + if (pRootTool->m_PropertyStates.m_Properties[i].m_pToolProperty == + pToolProperty) { + pCurrentOrdinalValue = + pRootTool->m_PropertyStates.m_Properties[i].m_StringValue.Get(); + break; + } + } + } + + if (pCurrentOrdinalValue && (atoi(pCurrentOrdinalValue) == atoi(buff))) { + g_pVPC->VPCWarning("%s matches default setting, [%s line:%d]", + pToolProperty->m_ParseString.Get(), + g_pVPC->GetScript().GetName(), + g_pVPC->GetScript().GetLine()); + } + + if (pCurrentOrdinalValue) { + // update existing state + for (int i = 0; i < m_Properties.Count(); i++) { + if (m_Properties[i].m_pToolProperty == pToolProperty) { + m_Properties[i].m_StringValue = buff; + return true; + } + } + } + + // add + intp iIndex = m_Properties.AddToTail(); + m_Properties[iIndex].m_pToolProperty = pToolProperty; + m_Properties[iIndex].m_StringValue = buff; + + m_PropertiesInOutputOrder.Insert(iIndex); + + return true; +} + +bool CPropertyStates::SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + bool bHandled = false; + switch (pToolProperty->m_nType) { + case PT_BOOLEAN: + bHandled = SetBoolProperty(pToolProperty, pRootTool); + break; + + case PT_STRING: + bHandled = SetStringProperty(pToolProperty, pRootTool); + break; + + case PT_INTEGER: + bHandled = SetIntegerProperty(pToolProperty, pRootTool); + break; + + case PT_LIST: + bHandled = SetListProperty(pToolProperty, pRootTool); + break; + + case PT_IGNORE: + bHandled = true; + g_pVPC->GetScript().SkipRestOfLine(); + break; + + case PT_DEPRECATED: + g_pVPC->VPCError( + "SetProperty: Property %s has been deprecated and is no longer " + "supported!", + pToolProperty->m_ParseString.Get()); + break; + + default: + g_pVPC->VPCError( + "SetProperty: Unknown type for %s - requires implementation", + pToolProperty->m_ParseString.Get()); + } + + return bHandled; +} + +static bool FilesSortLessFunc(CProjectFile *const &pLHS, + CProjectFile *const &pRHS) { + return CaselessStringLessThan(pLHS->m_Name.Get(), pRHS->m_Name.Get()); +} + +CProjectConfiguration::CProjectConfiguration(CVCProjGenerator *pGenerator, + const char *pConfigName, + const char *pFilename) + : m_pGenerator(pGenerator), + m_bIsFileConfig(pFilename != NULL), + m_Name(pConfigName) { + m_pDebuggingTool = NULL; + m_pCompilerTool = NULL; + m_pLibrarianTool = NULL; + m_pLinkerTool = NULL; + m_pManifestTool = NULL; + m_pXMLDocGenTool = NULL; + m_pBrowseInfoTool = NULL; + m_pResourcesTool = NULL; + m_pPreBuildEventTool = NULL; + m_pPreLinkEventTool = NULL; + m_pPostBuildEventTool = NULL; + m_pCustomBuildTool = NULL; + m_pXboxImageTool = NULL; + m_pXboxDeploymentTool = NULL; + + if (!m_bIsFileConfig) { + m_pDebuggingTool = new CDebuggingTool(pGenerator); + m_pCompilerTool = new CCompilerTool(pGenerator, pConfigName, false); + m_pLibrarianTool = new CLibrarianTool(pGenerator); + m_pLinkerTool = new CLinkerTool(pGenerator); + m_pManifestTool = new CManifestTool(pGenerator); + m_pXMLDocGenTool = new CXMLDocGenTool(pGenerator); + m_pBrowseInfoTool = new CBrowseInfoTool(pGenerator); + m_pResourcesTool = new CResourcesTool(pGenerator); + m_pPreBuildEventTool = new CPreBuildEventTool(pGenerator); + m_pPreLinkEventTool = new CPreLinkEventTool(pGenerator); + m_pPostBuildEventTool = new CPostBuildEventTool(pGenerator); + m_pCustomBuildTool = new CCustomBuildTool(pGenerator, pConfigName, false); + m_pXboxImageTool = new CXboxImageTool(pGenerator); + m_pXboxDeploymentTool = new CXboxDeploymentTool(pGenerator); + } else { + // a file's config can only be the compiler or the custom build tool + const char *pExtension = V_GetFileExtension(pFilename); + bool bIsCPP = IsCFileExtension(pExtension); + bool bIsLib = pExtension && !V_stricmp(pExtension, "lib"); + if (bIsCPP) { + m_pCompilerTool = new CCompilerTool(pGenerator, pConfigName, true); + } else if (bIsLib) { + m_pLibrarianTool = new CLibrarianTool(pGenerator); + } else { + m_pCustomBuildTool = new CCustomBuildTool(pGenerator, pConfigName, true); + } + } +} + +CProjectConfiguration::~CProjectConfiguration() { + delete m_pDebuggingTool; + delete m_pCompilerTool; + delete m_pLibrarianTool; + delete m_pLinkerTool; + delete m_pManifestTool; + delete m_pXMLDocGenTool; + delete m_pBrowseInfoTool; + delete m_pResourcesTool; + delete m_pPreBuildEventTool; + delete m_pPreLinkEventTool; + delete m_pPostBuildEventTool; + delete m_pCustomBuildTool; + delete m_pXboxImageTool; + delete m_pXboxDeploymentTool; +} + +bool CProjectConfiguration::IsEmpty() { + if (m_PropertyStates.m_Properties.Count()) return false; + + if (m_pDebuggingTool && + m_pDebuggingTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pCompilerTool && m_pCompilerTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pLibrarianTool && + m_pLibrarianTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pLinkerTool && m_pLinkerTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pManifestTool && m_pManifestTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pXMLDocGenTool && + m_pXMLDocGenTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pBrowseInfoTool && + m_pBrowseInfoTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pResourcesTool && + m_pResourcesTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pPreBuildEventTool && + m_pPreBuildEventTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pPreLinkEventTool && + m_pPreLinkEventTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pPostBuildEventTool && + m_pPostBuildEventTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pCustomBuildTool && + m_pCustomBuildTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pXboxImageTool && + m_pXboxImageTool->m_PropertyStates.m_Properties.Count()) + return false; + + if (m_pXboxDeploymentTool && + m_pXboxDeploymentTool->m_PropertyStates.m_Properties.Count()) + return false; + + return true; +} + +bool CProjectConfiguration::SetProperty(ToolProperty_t *pToolProperty) { + bool bHandled = m_PropertyStates.SetProperty(pToolProperty); + + // have to mimic what the COM layer used to do which is to configure itself + // based on the type of application its building VPC enforces a strict order, + // configuration blocks must come before any tool block to allow this to be + // rational + if (bHandled && + !V_stricmp(pToolProperty->m_ParseString, "$ConfigurationType")) { + PropertyState_t *pPropertyState = + m_PropertyStates.GetProperty(pToolProperty->m_nPropertyId); + if (pPropertyState && + ((V_stristr(pPropertyState->m_OrdinalString.Get(), "static library") || + !V_stricmp(pPropertyState->m_OrdinalString.Get(), "LIB")))) { + // static library does not get these tools + delete m_pResourcesTool; + m_pResourcesTool = NULL; + + delete m_pManifestTool; + m_pManifestTool = NULL; + + delete m_pLinkerTool; + m_pLinkerTool = NULL; + + delete m_pXboxImageTool; + m_pXboxImageTool = NULL; + + delete m_pXboxDeploymentTool; + m_pXboxDeploymentTool = NULL; + } else { + // exe/dlls do not get the librarian + delete m_pLibrarianTool; + m_pLibrarianTool = NULL; + } + } + + return bHandled; +} + +bool CProjectTool::SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + return m_PropertyStates.SetProperty(pToolProperty, pRootTool); +} + +//----------------------------------------------------------------------------- + +bool CCompilerTool::SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + if (m_bIsFileConfig) { + CProjectConfiguration *pConfig; + if (!GetGenerator()->GetRootConfiguration(m_ConfigName.Get(), &pConfig)) + return false; + + return CProjectTool::SetProperty(pToolProperty, pConfig->GetCompilerTool()); + } + return CProjectTool::SetProperty(pToolProperty); +} + +//----------------------------------------------------------------------------- + +bool CCustomBuildTool::SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool) { + if (m_bIsFileConfig) { + CProjectConfiguration *pConfig; + if (!GetGenerator()->GetRootConfiguration(m_ConfigName.Get(), &pConfig)) + return false; + + return CProjectTool::SetProperty(pToolProperty, + pConfig->GetCustomBuildTool()); + } + return CProjectTool::SetProperty(pToolProperty); +} + +// These are the only properties we care about for makefiles. +static const char *g_pRelevantSchemaProperties[] = { + g_pOption_AdditionalIncludeDirectories, + g_pOption_PreprocessorDefinitions, + g_pOption_AdditionalProjectDependencies, +}; + +CRelevantPropertyNames g_RelevantSchemaPropertyNames = { + g_pRelevantSchemaProperties, V_ARRAYSIZE(g_pRelevantSchemaProperties)}; + +CVCProjGenerator::CVCProjGenerator() + : BaseClass(&g_RelevantSchemaPropertyNames) { + m_pGeneratorDefinition = NULL; + m_pRootFolder = NULL; + m_FileDictionary.SetLessFunc(FilesSortLessFunc); + + Clear(); +} + +void CVCProjGenerator::Clear() { + m_nActivePropertySection = KEYWORD_UNKNOWN; + + m_pProjectFile = NULL; + m_pConfig = NULL; + m_pFileConfig = NULL; + + m_pDebuggingTool = NULL; + m_pCompilerTool = NULL; + m_pLibrarianTool = NULL; + m_pLinkerTool = NULL; + m_pManifestTool = NULL; + m_pXMLDocGenTool = NULL; + m_pBrowseInfoTool = NULL; + m_pResourcesTool = NULL; + m_pPreBuildEventTool = NULL; + m_pPreLinkEventTool = NULL; + m_pPostBuildEventTool = NULL; + m_pCustomBuildTool = NULL; + m_pXboxImageTool = NULL; + m_pXboxDeploymentTool = NULL; + + m_spFolderStack.Purge(); + m_spCompilerStack.Purge(); + m_spCustomBuildToolStack.Purge(); + + // undefined until set + m_VSIType = PS3_VSI_TYPE_UNDEFINED; + + m_FileDictionary.Purge(); + + // setup expected root folder + delete m_pRootFolder; + m_pRootFolder = new CProjectFolder(this, "???"); + + // setup the root configurations + m_RootConfigurations.PurgeAndDeleteElements(); + + CProjectConfiguration *pDebugConfig = + new CProjectConfiguration(this, "Debug", NULL); + m_RootConfigurations.AddToTail(pDebugConfig); + + CProjectConfiguration *pReleaseConfig = + new CProjectConfiguration(this, "Release", NULL); + m_RootConfigurations.AddToTail(pReleaseConfig); +} + +void CVCProjGenerator::SetupGeneratorDefinition( + IVCProjWriter *pVCProjWriter, const char *pDefinitionName, + PropertyName_t *pPropertyNames) { + m_pVCProjWriter = pVCProjWriter; + + delete m_pGeneratorDefinition; + m_pGeneratorDefinition = new CGeneratorDefinition(); + m_pGeneratorDefinition->LoadDefinition(pDefinitionName, pPropertyNames); +} + +const char *CVCProjGenerator::GetProjectFileExtension() { + if (g_pVPC->Is2010() || g_pVPC->Is2012() || g_pVPC->Is2013() || + g_pVPC->Is2015() || g_pVPC->Is2022()) { + return "vcxproj"; + } + return "vcproj"; +} + +void CVCProjGenerator::StartProject() { + if (!m_pGeneratorDefinition) { + g_pVPC->VPCError("Missing a properly configured generator definition"); + } + + BaseClass::StartProject(); + + // create the default project + // must have a root project for most operations + m_ProjectName = "UNNAMED"; + m_OutputFilename = g_pVPC->GetOutputFilename(); + + SetGUID(m_OutputFilename.Get()); +} + +void CVCProjGenerator::EndProject() { + BaseClass::EndProject(); + + // push generator definition scripts into CRC check + CRC32_t scriptCRC = 0; + const char *pScriptName = m_pGeneratorDefinition->GetScriptName(&scriptCRC); + char scriptPath[MAX_PATH]; + g_pVPC->ResolveMacrosInString(CFmtStr("$SRCDIR\\%s", pScriptName), scriptPath, + sizeof(scriptPath)); + g_pVPC->AddScriptToCRCCheck(scriptPath, scriptCRC); + + // done once, right before save + ApplyInternalPreprocessorDefinitions(); + + VPC_FakeKeyword_SchemaFolder(this); + +#ifdef STEAM +#error( "NEEDS TO BE FIXED" ) + // add the perforce integration magic + bstr = "Perforce Project"; + g_spProject->put_SccProjectName(bstr); + bstr = ".."; + g_spProject->put_SccLocalPath(bstr); + bstr = "MSSCCI:Perforce SCM"; + g_spProject->put_SccProvider(bstr); +#endif + + g_pVPC->VPCStatus(true, "Saving... Project: '%s' File: '%s'", + GetProjectName().String(), g_pVPC->GetOutputFilename()); + + if (m_ProjectName.IsEmpty()) { + g_pVPC->VPCError("Invalid Empty Project Name"); + } + + if (m_OutputFilename.IsEmpty()) { + g_pVPC->VPCError("Invalid Empty Output Filename"); + } + + if (m_GUIDString.IsEmpty()) { + g_pVPC->VPCError("Invalid Empty GUID String"); + } + + // Save the .vcproj file. + bool bValid = m_pVCProjWriter->Save(m_OutputFilename.Get()); + if (!bValid) { + g_pVPC->VPCError("Cannot save the specified project '%s' to '%s'", + GetProjectName().Get(), m_OutputFilename.Get()); + } + + // Expected to not be inside a property section. + Assert(m_nActivePropertySection == KEYWORD_UNKNOWN); + + Clear(); +} + +void CVCProjGenerator::SetGUID(const char *pOutputFilename) { + char szBasename[MAX_PATH]; + V_FileBase(pOutputFilename, szBasename, sizeof(szBasename)); + + // set the GUID + MD5Context_t ctx; + unsigned char digest[MD5_DIGEST_LENGTH]; + V_memset(&ctx, 0, sizeof(ctx)); + V_memset(digest, 0, sizeof(digest)); + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char *)szBasename, V_strlen(szBasename)); + MD5Final(digest, &ctx); + + char szMD5[64]; + V_binarytohex(digest, MD5_DIGEST_LENGTH, szMD5, sizeof(szMD5)); + V_strupr(szMD5); + + char szGUID[MAX_PATH]; + V_snprintf(szGUID, sizeof(szGUID), "{%8.8s-%4.4s-%4.4s-%4.4s-%12.12s}", szMD5, + &szMD5[8], &szMD5[12], &szMD5[16], &szMD5[20]); + m_GUIDString = szGUID; +} + +CUtlString CVCProjGenerator::GetProjectName() { return m_ProjectName; } + +void CVCProjGenerator::SetProjectName(const char *pProjectName) { + m_ProjectName = pProjectName; +} + +void CVCProjGenerator::StartFolder(const char *pFolderName) { + BaseClass::StartFolder(pFolderName); + + bool bValid; + CProjectFolder *pFolder = NULL; + + if (m_spFolderStack.Count() == 0) { + // add to root + bValid = AddFolder(pFolderName, NULL, &pFolder); + } else { + // add as subfolder + bValid = AddFolder(pFolderName, m_spFolderStack.Top(), &pFolder); + } + + if (!bValid) { + // resolve failure + // folder already exists, not an error + // find the matching object + pFolder = NULL; + if (m_spFolderStack.Count() == 0) { + // at root + GetFolder(pFolderName, NULL, &pFolder); + } else { + // at subfolder + GetFolder(pFolderName, m_spFolderStack.Top(), &pFolder); + } + if (!pFolder) { + g_pVPC->VPCError("Cannot find expected folder %s", pFolderName); + } + } + + m_spFolderStack.Push(pFolder); +} + +void CVCProjGenerator::EndFolder() { + BaseClass::EndFolder(); + + if (m_spFolderStack.Count() == 0) { + g_pVPC->VPCError("EndFolder called and no folder has been started."); + } + + m_spFolderStack.Pop(); +} + +bool CVCProjGenerator::StartFile(const char *pFilename, + bool bWarnIfAlreadyExists) { + // normalize filename, filenames need to compare correctly + char cleanFilename[MAX_PATH]; + V_strncpy(cleanFilename, pFilename, sizeof(cleanFilename)); + V_RemoveDotSlashes(cleanFilename, CORRECT_PATH_SEPARATOR); + + // some vpc scripts decided to unecessarily double quote their filenames + // remove any incoming surrounding quotes, this only causes string handling + // problems (i.e. extension comparison, etc) all files get serialized to xml + // output with mandatory surrounding quotes + if (cleanFilename[0] == '\"') { + intp len = V_strlen(cleanFilename); + if (len > 1 && cleanFilename[len - 1] == '\"') { + memcpy(cleanFilename, cleanFilename + 1, len - 2); + cleanFilename[len - 2] = '\0'; + } + } + + pFilename = cleanFilename; + + BaseClass::StartFile(pFilename, bWarnIfAlreadyExists); + + CProjectFile *pFile = NULL; + + if (m_spFolderStack.Count() == 0) { + // add at root + AddFileToFolder(pFilename, NULL, bWarnIfAlreadyExists, &pFile); + } else { + // add at subfolder + AddFileToFolder(pFilename, m_spFolderStack.Top(), bWarnIfAlreadyExists, + &pFile); + } + + m_pProjectFile = pFile; + return (pFile != NULL); +} + +void CVCProjGenerator::EndFile() { BaseClass::EndFile(); } + +bool CVCProjGenerator::RemoveFile(const char *pFilename) { + // normalize filename, filenames need to compare correctly + char cleanFilename[MAX_PATH]; + V_strncpy(cleanFilename, pFilename, sizeof(cleanFilename)); + V_RemoveDotSlashes(cleanFilename, CORRECT_PATH_SEPARATOR); + pFilename = cleanFilename; + + BaseClass::RemoveFile(pFilename); + + bool bValid; + if (m_spFolderStack.Count() == 0) { + // remove from root + bValid = RemoveFileFromFolder(pFilename, NULL); + } else { + // remove at subfolder + bValid = RemoveFileFromFolder(pFilename, m_spFolderStack.Top()); + } + + return bValid; +} + +bool CVCProjGenerator::Config_GetConfigurations(const char *pszConfigName) { + CProjectConfiguration *pConfig = NULL; + bool bValid = GetRootConfiguration(pszConfigName, &pConfig); + if (!bValid) { + g_pVPC->VPCError("Could not get configuration '%s'", pszConfigName); + } + m_pConfig = pConfig; + + return true; +} + +void CVCProjGenerator::StartConfigurationBlock(const char *pConfigName, + bool bFileSpecific) { + BaseClass::StartConfigurationBlock(pConfigName, bFileSpecific); + + if (bFileSpecific) { + CProjectConfiguration *pFileConfig = NULL; + bool bValid = m_pProjectFile->GetConfiguration(pConfigName, &pFileConfig); + if (!bValid) { + // not found, must be valid config + // must match predefined configurations, prevents misspellings + if (!IsConfigurationNameValid(pConfigName)) { + g_pVPC->VPCError("File %s, Unknown configuration '%s'", + m_pProjectFile->m_Name.Get(), pConfigName); + } + + bValid = m_pProjectFile->AddConfiguration(pConfigName, &pFileConfig); + if (!bValid) { + g_pVPC->VPCError("File %s, Could not get file configuration '%s'", + m_pProjectFile->m_Name.Get(), pConfigName); + } + } + m_pFileConfig = pFileConfig; + } else { + Config_GetConfigurations(pConfigName); + } +} + +void CVCProjGenerator::EndConfigurationBlock() { + BaseClass::EndConfigurationBlock(); + + if (m_pFileConfig && m_pFileConfig->IsEmpty()) { + // any file configuration (after parsing) that has no property state gets + // purged + m_pProjectFile->RemoveConfiguration(m_pFileConfig); + } + + m_pFileConfig = NULL; +} + +void CVCProjGenerator::FileExcludedFromBuild(bool bExcluded) { + if (!m_pFileConfig) { + g_pVPC->VPCSyntaxError( + "Cannot set $ExcludedFromBuild unless in a $File configuration " + "context"); + } + + BaseClass::FileExcludedFromBuild(bExcluded); + + ToolProperty_t *pToolProperty = m_pGeneratorDefinition->GetProperty( + KEYWORD_GENERAL, "$ExcludedFromBuild"); + if (!pToolProperty) { + g_pVPC->VPCError("Missing proper declaration for $ExcludedFromBuild"); + } + + m_pFileConfig->m_PropertyStates.SetBoolProperty(pToolProperty, bExcluded); +} + +bool CVCProjGenerator::StartPropertySection(configKeyword_e eKeyword, + bool *pbShouldSkip) { + BaseClass::StartPropertySection(eKeyword); + + *pbShouldSkip = false; + m_nActivePropertySection = KEYWORD_UNKNOWN; + bool bHandled = false; + + switch (eKeyword) { + case KEYWORD_GENERAL: + bHandled = true; + break; + + case KEYWORD_DEBUGGING: + m_pDebuggingTool = m_pConfig->GetDebuggingTool(); + if (!m_pDebuggingTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_COMPILER: + case KEYWORD_PS3_SNCCOMPILER: + case KEYWORD_PS3_GCCCOMPILER: + eKeyword = SetPS3VisualStudioIntegrationType(eKeyword); + if (eKeyword == KEYWORD_UNKNOWN) { + // skip this section + break; + } + + m_spCompilerStack.Push(m_pCompilerTool); + if (m_pFileConfig) { + m_pCompilerTool = m_pFileConfig->GetCompilerTool(); + } else { + m_pCompilerTool = m_pConfig->GetCompilerTool(); + } + if (!m_pCompilerTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_LIBRARIAN: + m_pLibrarianTool = m_pConfig->GetLibrarianTool(); + if (!m_pLibrarianTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_LINKER: + case KEYWORD_PS3_SNCLINKER: + case KEYWORD_PS3_GCCLINKER: + eKeyword = SetPS3VisualStudioIntegrationType(eKeyword); + if (eKeyword == KEYWORD_UNKNOWN) { + // skip this section + break; + } + + m_pLinkerTool = m_pConfig->GetLinkerTool(); + if (!m_pLinkerTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_MANIFEST: + if (!(g_pVPC->IsPlatformDefined("WIN32") || + g_pVPC->IsPlatformDefined("WIN64"))) { + // windows specific + break; + } + + m_pManifestTool = m_pConfig->GetManifestTool(); + if (!m_pManifestTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_XMLDOCGEN: + if (!(g_pVPC->IsPlatformDefined("WIN32") || + g_pVPC->IsPlatformDefined("WIN64"))) { + // windows specific + break; + } + + m_pXMLDocGenTool = m_pConfig->GetXMLDocGenTool(); + if (!m_pXMLDocGenTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_BROWSEINFO: + if (g_pVPC->IsPlatformDefined("PS3")) { + // not for ps3 + break; + } + + m_pBrowseInfoTool = m_pConfig->GetBrowseInfoTool(); + if (!m_pBrowseInfoTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_RESOURCES: + if (!(g_pVPC->IsPlatformDefined("WIN32") || + g_pVPC->IsPlatformDefined("WIN64"))) { + // windows specific + break; + } + + m_pResourcesTool = m_pConfig->GetResourcesTool(); + if (!m_pResourcesTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_PREBUILDEVENT: + m_pPreBuildEventTool = m_pConfig->GetPreBuildEventTool(); + if (!m_pPreBuildEventTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_PRELINKEVENT: + m_pPreLinkEventTool = m_pConfig->GetPreLinkEventTool(); + if (!m_pPreLinkEventTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_POSTBUILDEVENT: + m_pPostBuildEventTool = m_pConfig->GetPostBuildEventTool(); + if (!m_pPostBuildEventTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_CUSTOMBUILDSTEP: + m_spCustomBuildToolStack.Push(m_pCustomBuildTool); + if (m_pFileConfig) { + m_pCustomBuildTool = m_pFileConfig->GetCustomBuildTool(); + } else { + m_pCustomBuildTool = m_pConfig->GetCustomBuildTool(); + } + if (!m_pCustomBuildTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_XBOXIMAGE: + if (!g_pVPC->IsPlatformDefined("X360")) { + // xbox generator specific + break; + } + + m_pXboxImageTool = m_pConfig->GetXboxImageTool(); + if (!m_pXboxImageTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + case KEYWORD_XBOXDEPLOYMENT: + if (!g_pVPC->IsPlatformDefined("X360")) { + // xbox generator specific + break; + } + + m_pXboxDeploymentTool = m_pConfig->GetXboxDeploymentTool(); + if (!m_pXboxDeploymentTool) { + g_pVPC->VPCError("Could not get %s tool interface from configuration", + g_pVPC->KeywordToName(eKeyword)); + } + bHandled = true; + break; + + default: + // unknown + return false; + } + + if (bHandled) { + // handled + m_nActivePropertySection = eKeyword; + } else { + // allow other platform specifc sections to just be quietly ignored + *pbShouldSkip = true; + } + + return true; +} + +void CVCProjGenerator::EndPropertySection(configKeyword_e eKeyword) { + BaseClass::EndPropertySection(eKeyword); + + switch (eKeyword) { + case KEYWORD_CUSTOMBUILDSTEP: + m_spCustomBuildToolStack.Pop(m_pCustomBuildTool); + break; + + case KEYWORD_COMPILER: + case KEYWORD_PS3_SNCCOMPILER: + case KEYWORD_PS3_GCCCOMPILER: + eKeyword = SetPS3VisualStudioIntegrationType(eKeyword); + m_spCompilerStack.Pop(m_pCompilerTool); + break; + } + + m_nActivePropertySection = KEYWORD_UNKNOWN; +} + +void CVCProjGenerator::HandleProperty(const char *pPropertyName, + const char *pCustomScriptData) { + // don't allow the baseclass to alter the script state + g_pVPC->GetScript().PushCurrentScript(); + BaseClass::HandleProperty(pPropertyName, pCustomScriptData); + g_pVPC->GetScript().PopScript(); + + if (pCustomScriptData) { + g_pVPC->GetScript().PushScript("HandleProperty custom data", + pCustomScriptData); + } + + ToolProperty_t *pToolProperty = m_pGeneratorDefinition->GetProperty( + m_nActivePropertySection, pPropertyName); + if (!pToolProperty) { + // unknown property + g_pVPC->VPCSyntaxError("Unknown property %s", pPropertyName); + } + + const char *pToken = g_pVPC->GetScript().PeekNextToken(false); + if (!pToken || !pToken[0]) { + // quietly ignoring any property without a value + // not an error + if (pCustomScriptData) { + g_pVPC->GetScript().PopScript(); + } + return; + } + + CProjectConfiguration *pConfig = NULL; + CProjectTool *pTool = NULL; + switch (m_nActivePropertySection) { + case KEYWORD_GENERAL: + pConfig = m_pConfig; + break; + + case KEYWORD_DEBUGGING: + pTool = m_pDebuggingTool; + break; + + case KEYWORD_COMPILER: + case KEYWORD_PS3_SNCCOMPILER: + case KEYWORD_PS3_GCCCOMPILER: + pTool = m_pCompilerTool; + break; + + case KEYWORD_LIBRARIAN: + pTool = m_pLibrarianTool; + break; + + case KEYWORD_LINKER: + case KEYWORD_PS3_SNCLINKER: + case KEYWORD_PS3_GCCLINKER: + pTool = m_pLinkerTool; + break; + + case KEYWORD_MANIFEST: + pTool = m_pManifestTool; + break; + + case KEYWORD_XMLDOCGEN: + pTool = m_pXMLDocGenTool; + break; + + case KEYWORD_BROWSEINFO: + pTool = m_pBrowseInfoTool; + break; + + case KEYWORD_RESOURCES: + pTool = m_pResourcesTool; + break; + + case KEYWORD_PREBUILDEVENT: + pTool = m_pPreBuildEventTool; + break; + + case KEYWORD_PRELINKEVENT: + pTool = m_pPreLinkEventTool; + break; + + case KEYWORD_POSTBUILDEVENT: + pTool = m_pPostBuildEventTool; + break; + + case KEYWORD_CUSTOMBUILDSTEP: + pTool = m_pCustomBuildTool; + break; + + case KEYWORD_XBOXIMAGE: + pTool = m_pXboxImageTool; + break; + + case KEYWORD_XBOXDEPLOYMENT: + pTool = m_pXboxDeploymentTool; + break; + + default: + g_pVPC->VPCError( + "HandleProperty: No support for Tool:%s Property:%s - requires " + "implementation", + g_pVPC->KeywordToName(m_nActivePropertySection), pPropertyName); + } + + bool bHandled = false; + if (pTool) { + bHandled = pTool->SetProperty(pToolProperty); + } else if (pConfig) { + bHandled = pConfig->SetProperty(pToolProperty); + } + + if (!bHandled) { + g_pVPC->VPCError("HandleProperty: Failed to set %s", pPropertyName); + } + + if (pCustomScriptData) { + g_pVPC->GetScript().PopScript(); + } +} + +bool CVCProjGenerator::GetFolder(const char *pFolderName, + CProjectFolder *pParentFolder, + CProjectFolder **ppOutFolder) { + bool bValid; + if (!pParentFolder) { + bValid = m_pRootFolder->GetFolder(pFolderName, ppOutFolder); + } else { + bValid = pParentFolder->GetFolder(pFolderName, ppOutFolder); + } + return bValid; +} + +bool CVCProjGenerator::AddFolder(const char *pFolderName, + CProjectFolder *pParentFolder, + CProjectFolder **ppOutFolder) { + bool bValid; + if (!pParentFolder) { + bValid = m_pRootFolder->AddFolder(pFolderName, ppOutFolder); + } else { + bValid = pParentFolder->AddFolder(pFolderName, ppOutFolder); + } + return bValid; +} + +bool CVCProjGenerator::FindFile(const char *pFilename, CProjectFile **ppFile) { + CProjectFile findProjectFile(this, pFilename); + + int iIndex = m_FileDictionary.Find(&findProjectFile); + if (iIndex != m_FileDictionary.InvalidIndex()) { + // found + if (ppFile) { + *ppFile = m_FileDictionary[iIndex]; + } + return true; + } + + // not found + if (ppFile) { + *ppFile = NULL; + } + + return false; +} + +void CVCProjGenerator::AddFileToFolder(const char *pFilename, + CProjectFolder *pFolder, + bool bWarnIfAlreadyExists, + CProjectFile **ppFile) { + if (FindFile(pFilename, ppFile)) { + // already present + if (bWarnIfAlreadyExists) { + g_pVPC->VPCWarning("File %s already exists in project", pFilename); + } + return; + } + + CProjectFile *pFile; + if (!pFolder) { + // add at root + m_pRootFolder->AddFile(pFilename, &pFile); + } else { + // add at folder + pFolder->AddFile(pFilename, &pFile); + } + + // add to dictionary + m_FileDictionary.Insert(pFile); + if (ppFile) { + *ppFile = pFile; + } +} + +bool CVCProjGenerator::RemoveFileFromFolder(const char *pFilename, + CProjectFolder *pFolder) { + bool bFound = false; + CProjectFile findProjectFile(this, pFilename); + + int iIndex = m_FileDictionary.Find(&findProjectFile); + if (iIndex != m_FileDictionary.InvalidIndex()) { + bFound = true; + m_FileDictionary.RemoveAt(iIndex); + } + + if (!bFound) return false; + + if (!pFolder) { + m_pRootFolder->RemoveFile(pFilename); + } else { + pFolder->RemoveFile(pFilename); + } + + return bFound; +} + +void CVCProjGenerator::GetAllConfigurationNames( + CUtlVector &configurationNames) { + configurationNames.Purge(); + for (int i = 0; i < m_RootConfigurations.Count(); i++) { + configurationNames.AddToTail(m_RootConfigurations[i]->m_Name.Get()); + } +} + +bool CVCProjGenerator::GetRootConfiguration(const char *pConfigName, + CProjectConfiguration **ppConfig) { + if (!pConfigName || !pConfigName[0]) { + g_pVPC->VPCError("Empty or bad configuration name."); + } + + if (ppConfig) { + // assume not found + *ppConfig = NULL; + } + + for (int i = 0; i < m_RootConfigurations.Count(); i++) { + if (!V_stricmp(m_RootConfigurations[i]->m_Name.Get(), pConfigName)) { + // found + if (ppConfig) { + *ppConfig = m_RootConfigurations[i]; + } + return true; + } + } + + return false; +} + +bool CVCProjGenerator::IsConfigurationNameValid(const char *pConfigName) { + return GetRootConfiguration(pConfigName, NULL); +} + +configKeyword_e CVCProjGenerator::SetPS3VisualStudioIntegrationType( + configKeyword_e eKeyword) { + PS3VSIType_e vsiType = PS3_VSI_TYPE_UNDEFINED; + + switch (eKeyword) { + case KEYWORD_COMPILER: + case KEYWORD_LINKER: + if (!g_pVPC->IsPlatformDefined("PS3")) { + return eKeyword; + } + + if (m_VSIType == PS3_VSI_TYPE_UNDEFINED) { + // PS3 defaults to SNC, unless explictly specified + vsiType = PS3_VSI_TYPE_SNC; + } else { + // already set + vsiType = m_VSIType; + } + break; + + case KEYWORD_PS3_SNCCOMPILER: + case KEYWORD_PS3_SNCLINKER: + if (!g_pVPC->IsPlatformDefined("PS3")) { + // ps3 generator specific + // not available for other platforms + return KEYWORD_UNKNOWN; + } + vsiType = PS3_VSI_TYPE_SNC; + break; + + case KEYWORD_PS3_GCCCOMPILER: + case KEYWORD_PS3_GCCLINKER: + if (!g_pVPC->IsPlatformDefined("PS3")) { + // ps3 generator specific + // not available for other platforms + return KEYWORD_UNKNOWN; + } + vsiType = PS3_VSI_TYPE_GCC; + break; + + default: + g_pVPC->VPCError("Unknown PS3 compiler/linker type"); + break; + } + + if (m_VSIType == PS3_VSI_TYPE_UNDEFINED) { + // once set, compiler/linker choice (snc or gcc) cannot be changed + m_VSIType = vsiType; + } else if (m_VSIType != vsiType) { + // cannot intermix tool properties, they must be exclusive + g_pVPC->VPCSyntaxError( + "PS3 compiler/linker (GCC or SNC) already set, cannot be changed"); + } + + // remap ambiguous compiler/linker tool to explicit SNC/GCC tool flavor + if (eKeyword == KEYWORD_COMPILER) { + eKeyword = (m_VSIType == PS3_VSI_TYPE_SNC) ? KEYWORD_PS3_SNCCOMPILER + : KEYWORD_PS3_GCCCOMPILER; + } else if (eKeyword == KEYWORD_LINKER) { + eKeyword = (m_VSIType == PS3_VSI_TYPE_SNC) ? KEYWORD_PS3_SNCLINKER + : KEYWORD_PS3_GCCLINKER; + } + + return eKeyword; +} + +void CVCProjGenerator::ApplyInternalPreprocessorDefinitions() { + // prep to add in vpc generated compiler defines + CUtlVector macroDefines; + g_pVPC->GetMacrosMarkedForCompilerDefines(macroDefines); + + if (!macroDefines.Count()) { + // nothing to fixup + return; + } + + // get all the vpc macros that have been marked for auto adding as compiler + // define + CUtlString extraDefineString; + for (intp i = 0; i < macroDefines.Count(); i++) { + macro_t *pMacro = macroDefines[i]; + + CUtlString tempString; + tempString.Format(";%s=%s", pMacro->name.String(), pMacro->value.String()); + extraDefineString += tempString; + } + + // fixup root configurations + for (int i = 0; i < m_RootConfigurations.Count(); i++) { + CCompilerTool *pCompilerTool = m_RootConfigurations[i]->GetCompilerTool(); + if (pCompilerTool) { + PropertyState_t *pPropertyState = + pCompilerTool->m_PropertyStates.GetProperty( + "$PreprocessorDefinitions"); + if (pPropertyState) { + pPropertyState->m_StringValue += extraDefineString; + } + } + } + + // fixup any file confiuration overrides + for (int iIndex = m_FileDictionary.FirstInorder(); + iIndex != m_FileDictionary.InvalidIndex(); + iIndex = m_FileDictionary.NextInorder(iIndex)) { + CProjectFile *pProjectFile = m_FileDictionary[iIndex]; + for (int i = 0; i < pProjectFile->m_Configs.Count(); i++) { + CCompilerTool *pCompilerTool = + pProjectFile->m_Configs[i]->GetCompilerTool(); + if (pCompilerTool) { + PropertyState_t *pPropertyState = + pCompilerTool->m_PropertyStates.GetProperty( + "$PreprocessorDefinitions"); + if (pPropertyState) { + pPropertyState->m_StringValue += extraDefineString; + } + } + } + } +} diff --git a/utils/vpc/projectgenerator_vcproj.h b/utils/vpc/projectgenerator_vcproj.h new file mode 100644 index 0000000..807c036 --- /dev/null +++ b/utils/vpc/projectgenerator_vcproj.h @@ -0,0 +1,375 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_VCPROJGENERATOR_H_ +#define VPC_VCPROJGENERATOR_H_ + +#include "baseprojectdatacollector.h" + +class CProjectConfiguration; +class CVCProjGenerator; +class CProjectTool; + +struct PropertyState_t { + ToolProperty_t *m_pToolProperty; + CUtlString m_OrdinalString; + CUtlString m_StringValue; +}; + +// ps3 visual studio integration +enum PS3VSIType_e { + PS3_VSI_TYPE_UNDEFINED = -1, + PS3_VSI_TYPE_SNC = 0, + PS3_VSI_TYPE_GCC = 1, +}; + +class CProjectFile { + public: + CProjectFile(CVCProjGenerator *pGenerator, const char *pFilename); + ~CProjectFile(); + + bool GetConfiguration(const char *pConfigName, + CProjectConfiguration **ppConfig); + bool AddConfiguration(const char *pConfigName, + CProjectConfiguration **ppConfig); + bool RemoveConfiguration(CProjectConfiguration *pConfig); + + CUtlString m_Name; + CVCProjGenerator *m_pGenerator; + CUtlVector m_Configs; +}; + +class CProjectFolder { + public: + CProjectFolder(CVCProjGenerator *pGenerator, const char *pFolderName); + ~CProjectFolder(); + + bool GetFolder(const char *pFolderName, CProjectFolder **pFolder); + bool AddFolder(const char *pFolderName, CProjectFolder **pFolder); + void AddFile(const char *pFilename, CProjectFile **ppFile); + bool FindFile(const char *pFilename); + bool RemoveFile(const char *pFilename); + + CUtlString m_Name; + CVCProjGenerator *m_pGenerator; + CUtlLinkedList m_Folders; + CUtlLinkedList m_Files; +}; + +class CPropertyStateLessFunc { + public: + bool Less(const intp &lhs, const intp &rhs, void *pContext); +}; + +class CPropertyStates { + public: + CPropertyStates(); + + bool SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); + bool SetBoolProperty(ToolProperty_t *pToolProperty, bool bEnabled); + + PropertyState_t *GetProperty(int nPropertyId); + PropertyState_t *GetProperty(const char *pPropertyName); + + CUtlVector m_Properties; + CUtlSortVector m_PropertiesInOutputOrder; + + private: + bool SetStringProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); + bool SetListProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); + bool SetBoolProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); + bool SetBoolProperty(ToolProperty_t *pToolProperty, CProjectTool *pRootTool, + bool bEnabled); + bool SetIntegerProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); +}; + +class CProjectTool { + public: + CProjectTool(CVCProjGenerator *pGenerator) { m_pGenerator = pGenerator; } + + CVCProjGenerator *GetGenerator() { return m_pGenerator; } + + // when the property belongs to the root tool (i.e. linker), no root tool is + // passed in when the property is for the file's specific configuration tool, + // (i.e. compiler/debug), the root tool must be supplied + virtual bool SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); + + CPropertyStates m_PropertyStates; + + private: + CVCProjGenerator *m_pGenerator; +}; + +class CDebuggingTool : public CProjectTool { + public: + CDebuggingTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CCompilerTool : public CProjectTool { + public: + CCompilerTool(CVCProjGenerator *pGenerator, const char *pConfigName, + bool bIsFileConfig) + : CProjectTool(pGenerator), + m_ConfigName(pConfigName), + m_bIsFileConfig(bIsFileConfig) { + } + + bool SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); + + private: + CUtlString m_ConfigName; + bool m_bIsFileConfig; +}; + +class CLibrarianTool : public CProjectTool { + public: + CLibrarianTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CLinkerTool : public CProjectTool { + public: + CLinkerTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CManifestTool : public CProjectTool { + public: + CManifestTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CXMLDocGenTool : public CProjectTool { + public: + CXMLDocGenTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CBrowseInfoTool : public CProjectTool { + public: + CBrowseInfoTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CResourcesTool : public CProjectTool { + public: + CResourcesTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CPreBuildEventTool : public CProjectTool { + public: + CPreBuildEventTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CPreLinkEventTool : public CProjectTool { + public: + CPreLinkEventTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CPostBuildEventTool : public CProjectTool { + public: + CPostBuildEventTool(CVCProjGenerator *pGenerator) + : CProjectTool(pGenerator) {} +}; + +class CCustomBuildTool : public CProjectTool { + public: + CCustomBuildTool(CVCProjGenerator *pGenerator, const char *pConfigName, + bool bIsFileConfig) + : CProjectTool(pGenerator), + m_ConfigName(pConfigName), + m_bIsFileConfig(bIsFileConfig) { + } + + bool SetProperty(ToolProperty_t *pToolProperty, + CProjectTool *pRootTool = NULL); + + private: + CUtlString m_ConfigName; + bool m_bIsFileConfig; +}; + +class CXboxImageTool : public CProjectTool { + public: + CXboxImageTool(CVCProjGenerator *pGenerator) : CProjectTool(pGenerator) {} +}; + +class CXboxDeploymentTool : public CProjectTool { + public: + CXboxDeploymentTool(CVCProjGenerator *pGenerator) + : CProjectTool(pGenerator) {} +}; + +class CProjectConfiguration { + public: + CProjectConfiguration(CVCProjGenerator *pGenerator, const char *pConfigName, + const char *pFilename); + ~CProjectConfiguration(); + + CDebuggingTool *GetDebuggingTool() { return m_pDebuggingTool; } + CCompilerTool *GetCompilerTool() { return m_pCompilerTool; } + CLibrarianTool *GetLibrarianTool() { return m_pLibrarianTool; } + CLinkerTool *GetLinkerTool() { return m_pLinkerTool; } + CManifestTool *GetManifestTool() { return m_pManifestTool; } + CXMLDocGenTool *GetXMLDocGenTool() { return m_pXMLDocGenTool; } + CBrowseInfoTool *GetBrowseInfoTool() { return m_pBrowseInfoTool; } + CResourcesTool *GetResourcesTool() { return m_pResourcesTool; } + CPreBuildEventTool *GetPreBuildEventTool() { return m_pPreBuildEventTool; } + CPreLinkEventTool *GetPreLinkEventTool() { return m_pPreLinkEventTool; } + CPostBuildEventTool *GetPostBuildEventTool() { return m_pPostBuildEventTool; } + CCustomBuildTool *GetCustomBuildTool() { return m_pCustomBuildTool; } + CXboxImageTool *GetXboxImageTool() { return m_pXboxImageTool; } + CXboxDeploymentTool *GetXboxDeploymentTool() { return m_pXboxDeploymentTool; } + + bool IsEmpty(); + + bool SetProperty(ToolProperty_t *pToolProperty); + + CVCProjGenerator *m_pGenerator; + + // type of config, and config's properties + bool m_bIsFileConfig; + CUtlString m_Name; + + CPropertyStates m_PropertyStates; + + private: + // the config's tools + CDebuggingTool *m_pDebuggingTool; + CCompilerTool *m_pCompilerTool; + CLibrarianTool *m_pLibrarianTool; + CLinkerTool *m_pLinkerTool; + CManifestTool *m_pManifestTool; + CXMLDocGenTool *m_pXMLDocGenTool; + CBrowseInfoTool *m_pBrowseInfoTool; + CResourcesTool *m_pResourcesTool; + CPreBuildEventTool *m_pPreBuildEventTool; + CPreLinkEventTool *m_pPreLinkEventTool; + CPostBuildEventTool *m_pPostBuildEventTool; + CCustomBuildTool *m_pCustomBuildTool; + CXboxImageTool *m_pXboxImageTool; + CXboxDeploymentTool *m_pXboxDeploymentTool; +}; + +class IVCProjWriter { + public: + virtual bool Save(const char *pOutputFilename) = 0; +}; + +class CVCProjGenerator : public CBaseProjectDataCollector { + public: + typedef CBaseProjectDataCollector BaseClass; + CVCProjGenerator(); + + virtual const char *GetProjectFileExtension(); + virtual void StartProject(); + virtual void EndProject(); + virtual CUtlString GetProjectName(); + virtual void SetProjectName(const char *pProjectName); + virtual void GetAllConfigurationNames( + CUtlVector &configurationNames); + virtual void StartConfigurationBlock(const char *pConfigName, + bool bFileSpecific); + virtual void EndConfigurationBlock(); + virtual bool StartPropertySection(configKeyword_e keyword, + bool *pbShouldSkip); + virtual void HandleProperty(const char *pProperty, + const char *pCustomScriptData); + virtual void EndPropertySection(configKeyword_e keyword); + virtual void StartFolder(const char *pFolderName); + virtual void EndFolder(); + virtual bool StartFile(const char *pFilename, bool bWarnIfAlreadyExists); + virtual void EndFile(); + virtual void FileExcludedFromBuild(bool bExcluded); + virtual bool RemoveFile(const char *pFilename); + + CGeneratorDefinition *GetGeneratorDefinition() { + return m_pGeneratorDefinition; + } + void SetupGeneratorDefinition(IVCProjWriter *pVCProjWriter, + const char *pDefinitionName, + PropertyName_t *pPropertyNames); + + PS3VSIType_e GetVSIType() { return m_VSIType; } + + CUtlString GetGUIDString() { return m_GUIDString; } + + bool GetRootConfiguration(const char *pConfigName, + CProjectConfiguration **pConfig); + + CProjectFolder *GetRootFolder() { return m_pRootFolder; } + + private: + void Clear(); + bool Config_GetConfigurations(const char *pszConfigName); + + // returns true if found, false otherwise + bool GetFolder(const char *pFolderName, CProjectFolder *pParentFolder, + CProjectFolder **pOutFolder); + // returns true if added, false otherwise (duplicate) + bool AddFolder(const char *pFolderName, CProjectFolder *pParentFolder, + CProjectFolder **pOutFolder); + + // returns true if found, false otherwise + bool FindFile(const char *pFilename, CProjectFile **pFile); + void AddFileToFolder(const char *pFilename, CProjectFolder *pFolder, + bool bWarnIfExists, CProjectFile **pFile); + + // returns true if removed, false otherwise (not found) + bool RemoveFileFromFolder(const char *pFilename, CProjectFolder *pFolder); + + bool IsConfigurationNameValid(const char *pConfigName); + + void SetGUID(const char *pOutputFilename); + + configKeyword_e SetPS3VisualStudioIntegrationType(configKeyword_e eKeyword); + + void ApplyInternalPreprocessorDefinitions(); + + private: + configKeyword_e m_nActivePropertySection; + CGeneratorDefinition *m_pGeneratorDefinition; + + CDebuggingTool *m_pDebuggingTool; + CCompilerTool *m_pCompilerTool; + CLibrarianTool *m_pLibrarianTool; + CLinkerTool *m_pLinkerTool; + CManifestTool *m_pManifestTool; + CXMLDocGenTool *m_pXMLDocGenTool; + CBrowseInfoTool *m_pBrowseInfoTool; + CResourcesTool *m_pResourcesTool; + CPreBuildEventTool *m_pPreBuildEventTool; + CPreLinkEventTool *m_pPreLinkEventTool; + CPostBuildEventTool *m_pPostBuildEventTool; + CCustomBuildTool *m_pCustomBuildTool; + CXboxImageTool *m_pXboxImageTool; + CXboxDeploymentTool *m_pXboxDeploymentTool; + + CProjectConfiguration *m_pConfig; + CProjectConfiguration *m_pFileConfig; + CProjectFile *m_pProjectFile; + + CSimplePointerStack m_spFolderStack; + CSimplePointerStack m_spCompilerStack; + CSimplePointerStack + m_spCustomBuildToolStack; + + CUtlString m_OutputFilename; + + CProjectFolder *m_pRootFolder; + + CUtlVector m_RootConfigurations; + + // primary file dictionary + CUtlRBTree m_FileDictionary; + + CUtlString m_GUIDString; + + IVCProjWriter *m_pVCProjWriter; + + // ps3 visual studio integration + PS3VSIType_e m_VSIType; +}; + +#endif // VPC_VCPROJGENERATOR_H_ diff --git a/utils/vpc/projectgenerator_win32.cpp b/utils/vpc/projectgenerator_win32.cpp new file mode 100644 index 0000000..41437fb --- /dev/null +++ b/utils/vpc/projectgenerator_win32.cpp @@ -0,0 +1,326 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "projectgenerator_win32.h" +#include "projectgenerator_vcproj.h" + +#include "tier0/memdbgon.h" + +#undef PROPERTYNAME +#define PROPERTYNAME(X, Y) {X##_##Y, #X, #Y}, + +static PropertyName_t s_Win32PropertyNames[] = { +#include "projectgenerator_win32.inc" + {-1, NULL, NULL}}; + +IBaseProjectGenerator *GetWin32ProjectGenerator() { + static CProjectGenerator_Win32 *s_pProjectGenerator = NULL; + if (!s_pProjectGenerator) { + s_pProjectGenerator = new CProjectGenerator_Win32(); + } + + return s_pProjectGenerator->GetProjectGenerator(); +} + +CProjectGenerator_Win32::CProjectGenerator_Win32() { + m_pVCProjGenerator = new CVCProjGenerator(); + m_pVCProjGenerator->SetupGeneratorDefinition(this, "win32_2005.def", + s_Win32PropertyNames); +} + +bool CProjectGenerator_Win32::WriteFile(CProjectFile *pFile) { + m_XMLWriter.PushNode("File"); + m_XMLWriter.Write(CFmtStrMax("RelativePath=\"%s\"", pFile->m_Name.Get())); + m_XMLWriter.Write(">"); + + for (intp i = 0; i < pFile->m_Configs.Count(); i++) { + if (!WriteConfiguration(pFile->m_Configs[i])) return false; + } + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Win32::WriteFolder(CProjectFolder *pFolder) { + m_XMLWriter.PushNode("Filter"); + // String() returns temporary object, so save name in var to prevent stale + // memory usage. + CUtlString name = m_XMLWriter.FixupXMLString(pFolder->m_Name.Get()); + m_XMLWriter.Write(CFmtStrMax("Name=\"%s\"", name.String())); + m_XMLWriter.Write(">"); + + for (auto iIndex = pFolder->m_Files.Head(); + iIndex != pFolder->m_Files.InvalidIndex(); + iIndex = pFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pFolder->m_Files[iIndex])) return false; + } + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pFolder->m_Folders[iIndex])) return false; + } + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Win32::WriteConfiguration( + CProjectConfiguration *pConfig) { + const char *pTargetPlatformName = + g_pVPC->IsPlatformDefined("win64") ? "x64" : "Win32"; + + if (pConfig->m_bIsFileConfig) { + m_XMLWriter.PushNode("FileConfiguration"); + } else { + m_XMLWriter.PushNode("Configuration"); + } + + const char *pOutputName = "???"; + if (!V_stricmp(pConfig->m_Name.Get(), "debug")) { + pOutputName = "Debug"; + } else if (!V_stricmp(pConfig->m_Name.Get(), "release")) { + pOutputName = "Release"; + } else { + return false; + } + + m_XMLWriter.Write( + CFmtStrMax("Name=\"%s|%s\"", pOutputName, pTargetPlatformName)); + + // write configuration properties + for (intp i = 0; + i < pConfig->m_PropertyStates.m_PropertiesInOutputOrder.Count(); i++) { + intp sortedIndex = pConfig->m_PropertyStates.m_PropertiesInOutputOrder[i]; + WriteProperty(&pConfig->m_PropertyStates.m_Properties[sortedIndex]); + } + + m_XMLWriter.Write(">"); + + if (!WriteTool("VCPreBuildEventTool", pConfig->GetPreBuildEventTool())) + return false; + + if (!WriteTool("VCCustomBuildTool", pConfig->GetCustomBuildTool())) + return false; + + if (!WriteNULLTool("VCXMLDataGeneratorTool", pConfig)) return false; + + if (!WriteNULLTool("VCWebServiceProxyGeneratorTool", pConfig)) return false; + + if (!WriteNULLTool("VCMIDLTool", pConfig)) return false; + + if (!WriteTool("VCCLCompilerTool", pConfig->GetCompilerTool())) return false; + + if (!WriteNULLTool("VCManagedResourceCompilerTool", pConfig)) return false; + + if (!WriteTool("VCResourceCompilerTool", pConfig->GetResourcesTool())) + return false; + + if (!WriteTool("VCPreLinkEventTool", pConfig->GetPreLinkEventTool())) + return false; + + if (!WriteTool("VCLinkerTool", pConfig->GetLinkerTool())) return false; + + if (!WriteTool("VCLibrarianTool", pConfig->GetLibrarianTool())) return false; + + if (!WriteNULLTool("VCALinkTool", pConfig)) return false; + + if (!WriteTool("VCManifestTool", pConfig->GetManifestTool())) return false; + + if (!WriteTool("VCXDCMakeTool", pConfig->GetXMLDocGenTool())) return false; + + if (!WriteTool("VCBscMakeTool", pConfig->GetBrowseInfoTool())) return false; + + if (!WriteNULLTool("VCFxCopTool", pConfig)) return false; + + if (!pConfig->GetLibrarianTool()) { + if (!WriteNULLTool("VCAppVerifierTool", pConfig)) return false; + + if (!WriteNULLTool("VCWebDeploymentTool", pConfig)) return false; + } + + if (!WriteTool("VCPostBuildEventTool", pConfig->GetPostBuildEventTool())) + return false; + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Win32::WriteToXML() { + const char *pTargetPlatformName = + g_pVPC->IsPlatformDefined("win64") ? "x64" : "Win32"; + + m_XMLWriter.PushNode("VisualStudioProject"); + m_XMLWriter.Write("ProjectType=\"Visual C++\""); + + if (g_pVPC->Is2008()) + m_XMLWriter.Write("Version=\"9.00\""); + else + m_XMLWriter.Write("Version=\"8.00\""); + + m_XMLWriter.Write( + CFmtStrMax("Name=\"%s\"", m_pVCProjGenerator->GetProjectName().Get())); + m_XMLWriter.Write(CFmtStrMax("ProjectGUID=\"%s\"", + m_pVCProjGenerator->GetGUIDString().Get())); + if (g_pVPC->BUseP4SCC()) + m_XMLWriter.Write( + "SccProjectName=\"Perforce " + "Project\"\nSccLocalPath=\"..\"\nSccProvider=\"MSSCCI:Perforce " + "SCM\"\n"); + m_XMLWriter.Write(">"); + + m_XMLWriter.PushNode("Platforms"); + m_XMLWriter.PushNode("Platform"); + m_XMLWriter.Write(CFmtStrMax("Name=\"%s\"", pTargetPlatformName)); + m_XMLWriter.PopNode(false); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("ToolFiles"); + m_XMLWriter.PopNode(true); + + CUtlVector configurationNames; + m_pVCProjGenerator->GetAllConfigurationNames(configurationNames); + + // write the root configurations + m_XMLWriter.PushNode("Configurations"); + for (intp i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + if (!WriteConfiguration(pConfiguration)) return false; + } + } + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("References"); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("Files"); + + CProjectFolder *pRootFolder = m_pVCProjGenerator->GetRootFolder(); + + for (auto iIndex = pRootFolder->m_Folders.Head(); + iIndex != pRootFolder->m_Folders.InvalidIndex(); + iIndex = pRootFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pRootFolder->m_Folders[iIndex])) return false; + } + + for (auto iIndex = pRootFolder->m_Files.Head(); + iIndex != pRootFolder->m_Files.InvalidIndex(); + iIndex = pRootFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pRootFolder->m_Files[iIndex])) return false; + } + + m_XMLWriter.PopNode(true); + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Win32::Save(const char *pOutputFilename) { + if (!m_XMLWriter.Open(pOutputFilename)) return false; + + bool bValid = WriteToXML(); + + m_XMLWriter.Close(); + + return bValid; +} + +bool CProjectGenerator_Win32::WriteNULLTool( + const char *pToolName, const CProjectConfiguration *pConfig) { + if (pConfig->m_bIsFileConfig) return true; + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write(CFmtStr("Name=\"%s\"", pToolName)); + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_Win32::WriteTool(const char *pToolName, + const CProjectTool *pProjectTool) { + if (!pProjectTool) { + // not an error, some tools n/a for a config + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write(CFmtStr("Name=\"%s\"", pToolName)); + + for (intp i = 0; + i < pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + i++) { + intp sortedIndex = + pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder[i]; + WriteProperty(&pProjectTool->m_PropertyStates.m_Properties[sortedIndex]); + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_Win32::WriteProperty( + const PropertyState_t *pPropertyState, const char *pOutputName, + const char *pOutputValue) { + if (!pPropertyState) { + m_XMLWriter.Write(CFmtStrMax("%s=\"%s\"", pOutputName, pOutputValue)); + return true; + } + + if (!pOutputName) { + pOutputName = pPropertyState->m_pToolProperty->m_OutputString.Get(); + if (!pOutputName[0]) { + pOutputName = pPropertyState->m_pToolProperty->m_ParseString.Get(); + if (pOutputName[0] == '$') { + pOutputName++; + } + } + } + + switch (pPropertyState->m_pToolProperty->m_nType) { + case PT_BOOLEAN: { + bool bEnabled = Sys_StringToBool(pPropertyState->m_StringValue.Get()); + if (pPropertyState->m_pToolProperty->m_bInvertOutput) { + bEnabled ^= 1; + } + m_XMLWriter.Write( + CFmtStrMax("%s=\"%s\"", pOutputName, bEnabled ? "true" : "false")); + } break; + + case PT_STRING: { + // String() returns temporary object, so save in var to prevent stale + // memory usage. + CUtlString s = + m_XMLWriter.FixupXMLString(pPropertyState->m_StringValue.Get()); + m_XMLWriter.Write(CFmtStrMax("%s=\"%s\"", pOutputName, s.String())); + } break; + + case PT_LIST: + case PT_INTEGER: + m_XMLWriter.Write(CFmtStrMax("%s=\"%s\"", pOutputName, + pPropertyState->m_StringValue.Get())); + break; + + case PT_IGNORE: + break; + + default: + g_pVPC->VPCError( + "CProjectGenerator_Win32: WriteProperty, %s - not implemented", + pOutputName); + } + + return true; +} diff --git a/utils/vpc/projectgenerator_win32.h b/utils/vpc/projectgenerator_win32.h new file mode 100644 index 0000000..41a08ec --- /dev/null +++ b/utils/vpc/projectgenerator_win32.h @@ -0,0 +1,37 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_PROJECTGENERATOR_WIN32_H_ +#define VPC_PROJECTGENERATOR_WIN32_H_ + +#include "projectgenerator_vcproj.h" + +#define PROPERTYNAME(X, Y) X##_##Y, + +enum Win32Properties_e { +#include "projectgenerator_win32.inc" +}; + +class CProjectGenerator_Win32 : public IVCProjWriter { + public: + CProjectGenerator_Win32(); + IBaseProjectGenerator *GetProjectGenerator() { return m_pVCProjGenerator; } + + virtual bool Save(const char *pOutputFilename); + + private: + bool WriteToXML(); + + bool WriteFolder(CProjectFolder *pFolder); + bool WriteFile(CProjectFile *pFile); + bool WriteConfiguration(CProjectConfiguration *pConfig); + bool WriteProperty(const PropertyState_t *pPropertyState, + const char *pOutputName = NULL, const char *pValue = NULL); + bool WriteTool(const char *pToolName, const CProjectTool *pProjectTool); + bool WriteNULLTool(const char *pToolName, + const CProjectConfiguration *pConfig); + + CXMLWriter m_XMLWriter; + CVCProjGenerator *m_pVCProjGenerator; +}; + +#endif // VPC_PROJECTGENERATOR_WIN32_H_ diff --git a/utils/vpc/projectgenerator_win32.inc b/utils/vpc/projectgenerator_win32.inc new file mode 100644 index 0000000..0b9a167 --- /dev/null +++ b/utils/vpc/projectgenerator_win32.inc @@ -0,0 +1,253 @@ + +//========= Copyright 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Property Enumerations +// +//=====================================================================================// + +// Config +PROPERTYNAME( WIN32_GENERAL, ExcludedFromBuild ) +PROPERTYNAME( WIN32_GENERAL, OutputDirectory ) +PROPERTYNAME( WIN32_GENERAL, IntermediateDirectory ) +PROPERTYNAME( WIN32_GENERAL, ConfigurationType ) +PROPERTYNAME( WIN32_GENERAL, CharacterSet ) +PROPERTYNAME( WIN32_GENERAL, WholeProgramOptimization ) +PROPERTYNAME( WIN32_GENERAL, ExtensionsToDeleteOnClean ) +PROPERTYNAME( WIN32_GENERAL, BuildLogFile ) +PROPERTYNAME( WIN32_GENERAL, InheritedProjectPropertySheets ) +PROPERTYNAME( WIN32_GENERAL, UseOfMFC ) +PROPERTYNAME( WIN32_GENERAL, UseOfATL ) +PROPERTYNAME( WIN32_GENERAL, MinimizeCRTUseInATL ) + +// Debugging +PROPERTYNAME( WIN32_DEBUGGING, Command ) +PROPERTYNAME( WIN32_DEBUGGING, CommandArguments ) +PROPERTYNAME( WIN32_DEBUGGING, RemoteMachine ) +PROPERTYNAME( WIN32_DEBUGGING, WorkingDirectory ) +PROPERTYNAME( WIN32_DEBUGGING, Attach ) +PROPERTYNAME( WIN32_DEBUGGING, DebuggerType ) +PROPERTYNAME( WIN32_DEBUGGING, Environment ) +PROPERTYNAME( WIN32_DEBUGGING, MergeEnvironment ) +PROPERTYNAME( WIN32_DEBUGGING, SQLDebugging ) + +// Compiler +PROPERTYNAME( WIN32_COMPILER, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_COMPILER, AdditionalOptions ) +PROPERTYNAME( WIN32_COMPILER, Optimization ) +PROPERTYNAME( WIN32_COMPILER, InlineFunctionExpansion ) +PROPERTYNAME( WIN32_COMPILER, EnableIntrinsicFunctions ) +PROPERTYNAME( WIN32_COMPILER, FavorSizeOrSpeed ) +PROPERTYNAME( WIN32_COMPILER, EnableFiberSafeOptimizations ) +PROPERTYNAME( WIN32_COMPILER, WholeProgramOptimization ) +PROPERTYNAME( WIN32_COMPILER, AdditionalIncludeDirectories ) +PROPERTYNAME( WIN32_COMPILER, PreprocessorDefinitions ) +PROPERTYNAME( WIN32_COMPILER, IgnoreStandardIncludePath ) +PROPERTYNAME( WIN32_COMPILER, GeneratePreprocessedFile ) +PROPERTYNAME( WIN32_COMPILER, KeepComments ) +PROPERTYNAME( WIN32_COMPILER, EnableStringPooling ) +PROPERTYNAME( WIN32_COMPILER, EnableMinimalRebuild ) +PROPERTYNAME( WIN32_COMPILER, EnableCPPExceptions ) +PROPERTYNAME( WIN32_COMPILER, BasicRuntimeChecks ) +PROPERTYNAME( WIN32_COMPILER, SmallerTypeCheck ) +PROPERTYNAME( WIN32_COMPILER, RuntimeLibrary ) +PROPERTYNAME( WIN32_COMPILER, StructMemberAlignment ) +PROPERTYNAME( WIN32_COMPILER, BufferSecurityCheck ) +PROPERTYNAME( WIN32_COMPILER, EnableFunctionLevelLinking ) +PROPERTYNAME( WIN32_COMPILER, EnableEnhancedInstructionSet ) +PROPERTYNAME( WIN32_COMPILER, FloatingPointModel ) +PROPERTYNAME( WIN32_COMPILER, EnableFloatingPointExceptions ) +PROPERTYNAME( WIN32_COMPILER, LanguageStandard ) +PROPERTYNAME( WIN32_COMPILER, DisableLanguageExtensions ) +PROPERTYNAME( WIN32_COMPILER, DefaultCharUnsigned ) +PROPERTYNAME( WIN32_COMPILER, TreatWCHAR_TAsBuiltInType ) +PROPERTYNAME( WIN32_COMPILER, ForceConformanceInForLoopScope ) +PROPERTYNAME( WIN32_COMPILER, EnableRunTimeTypeInfo ) +PROPERTYNAME( WIN32_COMPILER, OpenMPSupport ) +PROPERTYNAME( WIN32_COMPILER, CreateUsePrecompiledHeader ) +PROPERTYNAME( WIN32_COMPILER, CreateUsePCHThroughFile ) +PROPERTYNAME( WIN32_COMPILER, PrecompiledHeaderFile ) +PROPERTYNAME( WIN32_COMPILER, ExpandAttributedSource ) +PROPERTYNAME( WIN32_COMPILER, AssemblerOutput ) +PROPERTYNAME( WIN32_COMPILER, ASMListLocation ) +PROPERTYNAME( WIN32_COMPILER, ObjectFileName ) +PROPERTYNAME( WIN32_COMPILER, ProgramDatabaseFileName ) +PROPERTYNAME( WIN32_COMPILER, GenerateXMLDocumentationFiles ) +PROPERTYNAME( WIN32_COMPILER, EnableBrowseInformation ) +PROPERTYNAME( WIN32_COMPILER, BrowseFile ) +PROPERTYNAME( WIN32_COMPILER, WarningLevel ) +PROPERTYNAME( WIN32_COMPILER, TreatWarningsAsErrors ) +PROPERTYNAME( WIN32_COMPILER, Detect64bitPortabilityIssues ) +PROPERTYNAME( WIN32_COMPILER, SuppressStartupBanner ) +PROPERTYNAME( WIN32_COMPILER, DebugInformationFormat ) +PROPERTYNAME( WIN32_COMPILER, CompileAs ) +PROPERTYNAME( WIN32_COMPILER, ForceIncludes ) +PROPERTYNAME( WIN32_COMPILER, ShowIncludes ) +PROPERTYNAME( WIN32_COMPILER, UndefineAllPreprocessorDefinitions ) +PROPERTYNAME( WIN32_COMPILER, UndefinePreprocessorDefinitions ) +PROPERTYNAME( WIN32_COMPILER, UseFullPaths ) +PROPERTYNAME( WIN32_COMPILER, OmitDefaultLibraryNames ) +PROPERTYNAME( WIN32_COMPILER, TrapIntegerDividesOptimization ) +PROPERTYNAME( WIN32_COMPILER, PreschedulingOptimization ) +PROPERTYNAME( WIN32_COMPILER, InlineAssemblyOptimization ) +PROPERTYNAME( WIN32_COMPILER, RegisterReservation ) +PROPERTYNAME( WIN32_COMPILER, Stalls ) +PROPERTYNAME( WIN32_COMPILER, CallAttributedProfiling ) +PROPERTYNAME( WIN32_COMPILER, XMLDocumentationFileName ) +PROPERTYNAME( WIN32_COMPILER, DisableSpecificWarnings ) +PROPERTYNAME( WIN32_COMPILER, ResolveUsingReferences ) +PROPERTYNAME( WIN32_COMPILER, OmitFramePointers ) +PROPERTYNAME( WIN32_COMPILER, CallingConvention ) +PROPERTYNAME( WIN32_COMPILER, ForceUsing ) +PROPERTYNAME( WIN32_COMPILER, ErrorReporting ) + +// Librarian +PROPERTYNAME( WIN32_LIBRARIAN, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_LIBRARIAN, AdditionalDependencies ) +PROPERTYNAME( WIN32_LIBRARIAN, OutputFile ) +PROPERTYNAME( WIN32_LIBRARIAN, AdditionalLibraryDirectories ) +PROPERTYNAME( WIN32_LIBRARIAN, SuppressStartupBanner ) +PROPERTYNAME( WIN32_LIBRARIAN, ModuleDefinitionFileName ) +PROPERTYNAME( WIN32_LIBRARIAN, IgnoreAllDefaultLibraries ) +PROPERTYNAME( WIN32_LIBRARIAN, IgnoreSpecificLibrary ) +PROPERTYNAME( WIN32_LIBRARIAN, ExportNamedFunctions ) +PROPERTYNAME( WIN32_LIBRARIAN, ForceSymbolReferences ) +PROPERTYNAME( WIN32_LIBRARIAN, LinkLibraryDependencies ) +PROPERTYNAME( WIN32_LIBRARIAN, AdditionalOptions ) + +// Linker +PROPERTYNAME( WIN32_LINKER, IgnoreImportLibrary ) +PROPERTYNAME( WIN32_LINKER, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_LINKER, AdditionalOptions ) +PROPERTYNAME( WIN32_LINKER, AdditionalDependencies ) +PROPERTYNAME( WIN32_LINKER, ShowProgress ) +PROPERTYNAME( WIN32_LINKER, OutputFile ) +PROPERTYNAME( WIN32_LINKER, Version ) +PROPERTYNAME( WIN32_LINKER, EnableIncrementalLinking ) +PROPERTYNAME( WIN32_LINKER, SuppressStartupBanner ) +PROPERTYNAME( WIN32_LINKER, AdditionalLibraryDirectories ) +PROPERTYNAME( WIN32_LINKER, GenerateManifest ) +PROPERTYNAME( WIN32_LINKER, IgnoreAllDefaultLibraries ) +PROPERTYNAME( WIN32_LINKER, IgnoreSpecificLibrary ) +PROPERTYNAME( WIN32_LINKER, ModuleDefinitionFile ) +PROPERTYNAME( WIN32_LINKER, GenerateDebugInfo ) +PROPERTYNAME( WIN32_LINKER, DebuggableAssembly ) +PROPERTYNAME( WIN32_LINKER, GenerateProgramDatabaseFile ) +PROPERTYNAME( WIN32_LINKER, GenerateMapFile ) +PROPERTYNAME( WIN32_LINKER, MapFileName ) +PROPERTYNAME( WIN32_LINKER, SubSystem ) +PROPERTYNAME( WIN32_LINKER, EnableLargeAddresses ) +PROPERTYNAME( WIN32_LINKER, MapExports ) +PROPERTYNAME( WIN32_LINKER, StackReserveSize ) +PROPERTYNAME( WIN32_LINKER, StackCommitSize ) +PROPERTYNAME( WIN32_LINKER, References ) +PROPERTYNAME( WIN32_LINKER, EnableCOMDATFolding ) +PROPERTYNAME( WIN32_LINKER, LinkTimeCodeGeneration ) +PROPERTYNAME( WIN32_LINKER, EntryPoint ) +PROPERTYNAME( WIN32_LINKER, NoEntryPoint ) +PROPERTYNAME( WIN32_LINKER, SetChecksum ) +PROPERTYNAME( WIN32_LINKER, BaseAddress ) +PROPERTYNAME( WIN32_LINKER, ImportLibrary ) +PROPERTYNAME( WIN32_LINKER, TargetMachine ) +PROPERTYNAME( WIN32_LINKER, FixedBaseAddress ) +PROPERTYNAME( WIN32_LINKER, ErrorReporting ) +PROPERTYNAME( WIN32_LINKER, FunctionOrder ) +PROPERTYNAME( WIN32_LINKER, LinkLibraryDependencies ) +PROPERTYNAME( WIN32_LINKER, UseLibraryDependencyInputs ) +PROPERTYNAME( WIN32_LINKER, ForceSymbolReferences ) +PROPERTYNAME( WIN32_LINKER, StripPrivateSymbols ) +PROPERTYNAME( WIN32_LINKER, ProfileGuidedDatabase ) +PROPERTYNAME( WIN32_LINKER, MergeSections ) +PROPERTYNAME( WIN32_LINKER, RegisterOutput ) +PROPERTYNAME( WIN32_LINKER, AddModuleToAssembly ) +PROPERTYNAME( WIN32_LINKER, EmbedManagedResourceFile ) +PROPERTYNAME( WIN32_LINKER, DelayLoadedDLLs ) +PROPERTYNAME( WIN32_LINKER, AssemblyLinkResource ) +PROPERTYNAME( WIN32_LINKER, ManifestFile ) +PROPERTYNAME( WIN32_LINKER, AdditionalManifestDependencies ) +PROPERTYNAME( WIN32_LINKER, AllowIsolation ) +PROPERTYNAME( WIN32_LINKER, HeapReserveSize ) +PROPERTYNAME( WIN32_LINKER, HeapCommitSize ) +PROPERTYNAME( WIN32_LINKER, TerminalServer ) +PROPERTYNAME( WIN32_LINKER, SwapRunFromCD ) +PROPERTYNAME( WIN32_LINKER, SwapRunFromNetwork ) +PROPERTYNAME( WIN32_LINKER, Driver ) +PROPERTYNAME( WIN32_LINKER, OptimizeForWindows98 ) +PROPERTYNAME( WIN32_LINKER, MIDLCommands ) +PROPERTYNAME( WIN32_LINKER, IgnoreEmbeddedIDL ) +PROPERTYNAME( WIN32_LINKER, MergeIDLBaseFileName ) +PROPERTYNAME( WIN32_LINKER, TypeLibrary ) +PROPERTYNAME( WIN32_LINKER, TypeLibResourceID ) +PROPERTYNAME( WIN32_LINKER, TurnOffAssemblyGeneration ) +PROPERTYNAME( WIN32_LINKER, DelayLoadedDLL ) +PROPERTYNAME( WIN32_LINKER, Profile ) +PROPERTYNAME( WIN32_LINKER, CLRThreadAttribute ) +PROPERTYNAME( WIN32_LINKER, CLRImageType ) +PROPERTYNAME( WIN32_LINKER, KeyFile ) +PROPERTYNAME( WIN32_LINKER, KeyContainer ) +PROPERTYNAME( WIN32_LINKER, DelaySign ) +PROPERTYNAME( WIN32_LINKER, CLRUnmanagedCodeCheck ) + +// Manifest +PROPERTYNAME( WIN32_MANIFESTTOOL, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_MANIFESTTOOL, SuppressStartupBanner ) +PROPERTYNAME( WIN32_MANIFESTTOOL, VerboseOutput ) +PROPERTYNAME( WIN32_MANIFESTTOOL, AssemblyIdentity ) +PROPERTYNAME( WIN32_MANIFESTTOOL, UseFAT32WorkAround ) +PROPERTYNAME( WIN32_MANIFESTTOOL, AdditionalManifestFiles ) +PROPERTYNAME( WIN32_MANIFESTTOOL, InputResourceManifests ) +PROPERTYNAME( WIN32_MANIFESTTOOL, EmbedManifest ) +PROPERTYNAME( WIN32_MANIFESTTOOL, OutputManifestFile ) +PROPERTYNAME( WIN32_MANIFESTTOOL, ManifestResourceFile ) +PROPERTYNAME( WIN32_MANIFESTTOOL, GenerateCatalogFiles ) +PROPERTYNAME( WIN32_MANIFESTTOOL, DependencyInformationFile ) +PROPERTYNAME( WIN32_MANIFESTTOOL, TypeLibraryFile ) +PROPERTYNAME( WIN32_MANIFESTTOOL, RegistrarScriptFile ) +PROPERTYNAME( WIN32_MANIFESTTOOL, ComponentFileName ) +PROPERTYNAME( WIN32_MANIFESTTOOL, ReplacementsFile ) +PROPERTYNAME( WIN32_MANIFESTTOOL, UpdateFileHashes ) +PROPERTYNAME( WIN32_MANIFESTTOOL, UpdateFileHashesSearchPath ) +PROPERTYNAME( WIN32_MANIFESTTOOL, AdditionalOptions ) + +// XML Document Generator +PROPERTYNAME( WIN32_XMLDOCUMENTGENERATOR, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_XMLDOCUMENTGENERATOR, SuppressStartupBanner ) +PROPERTYNAME( WIN32_XMLDOCUMENTGENERATOR, ValidateIntelliSense ) +PROPERTYNAME( WIN32_XMLDOCUMENTGENERATOR, AdditionalDocumentFiles ) +PROPERTYNAME( WIN32_XMLDOCUMENTGENERATOR, OutputDocumentFile ) +PROPERTYNAME( WIN32_XMLDOCUMENTGENERATOR, DocumentLibraryDependencies ) +PROPERTYNAME( WIN32_XMLDOCUMENTGENERATOR, AdditionalOptions ) + +// Browse Information +PROPERTYNAME( WIN32_BROWSEINFORMATION, SuppressStartupBanner ) +PROPERTYNAME( WIN32_BROWSEINFORMATION, OutputFile ) +PROPERTYNAME( WIN32_BROWSEINFORMATION, AdditionalOptions ) + +// Resources +PROPERTYNAME( WIN32_RESOURCES, PreprocessorDefinitions ) +PROPERTYNAME( WIN32_RESOURCES, Culture ) +PROPERTYNAME( WIN32_RESOURCES, AdditionalIncludeDirectories ) +PROPERTYNAME( WIN32_RESOURCES, IgnoreStandardIncludePath ) +PROPERTYNAME( WIN32_RESOURCES, ShowProgress ) +PROPERTYNAME( WIN32_RESOURCES, ResourceFileName ) +PROPERTYNAME( WIN32_RESOURCES, AdditionalOptions ) + +// Pre Build +PROPERTYNAME( WIN32_PREBUILDEVENT, Description ) +PROPERTYNAME( WIN32_PREBUILDEVENT, CommandLine ) +PROPERTYNAME( WIN32_PREBUILDEVENT, ExcludedFromBuild ) + +// Pre Link +PROPERTYNAME( WIN32_PRELINKEVENT, Description ) +PROPERTYNAME( WIN32_PRELINKEVENT, CommandLine ) +PROPERTYNAME( WIN32_PRELINKEVENT, ExcludedFromBuild ) + +// Post Build +PROPERTYNAME( WIN32_POSTBUILDEVENT, Description ) +PROPERTYNAME( WIN32_POSTBUILDEVENT, CommandLine ) +PROPERTYNAME( WIN32_POSTBUILDEVENT, ExcludedFromBuild ) + +// Custom Build +PROPERTYNAME( WIN32_CUSTOMBUILDSTEP, Description ) +PROPERTYNAME( WIN32_CUSTOMBUILDSTEP, CommandLine ) +PROPERTYNAME( WIN32_CUSTOMBUILDSTEP, AdditionalDependencies ) +PROPERTYNAME( WIN32_CUSTOMBUILDSTEP, Outputs ) diff --git a/utils/vpc/projectgenerator_win32_2010.cpp b/utils/vpc/projectgenerator_win32_2010.cpp new file mode 100644 index 0000000..0802e99 --- /dev/null +++ b/utils/vpc/projectgenerator_win32_2010.cpp @@ -0,0 +1,641 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "projectgenerator_win32_2010.h" +#include "projectgenerator_vcproj.h" + +#include "tier0/memdbgon.h" + +#undef PROPERTYNAME +#define PROPERTYNAME(X, Y) {X##_##Y, #X, #Y}, + +static PropertyName_t s_Win32PropertyNames_2010[] = { +#include "projectgenerator_win32_2010.inc" + {-1, NULL, NULL}}; + +IBaseProjectGenerator *GetWin32ProjectGenerator_2010() { + static CProjectGenerator_Win32_2010 *s_pProjectGenerator = NULL; + if (!s_pProjectGenerator) { + s_pProjectGenerator = new CProjectGenerator_Win32_2010(); + } + + return s_pProjectGenerator->GetProjectGenerator(); +} + +CProjectGenerator_Win32_2010::CProjectGenerator_Win32_2010() { + m_pVCProjGenerator = new CVCProjGenerator(); + m_pVCProjGenerator->SetupGeneratorDefinition(this, "win32_2010.def", + s_Win32PropertyNames_2010); +} + +enum TypeKeyNames_e { + TKN_LIBRARY = 0, + TKN_INCLUDE, + TKN_COMPILE, + TKN_RESOURCECOMPILE, + TKN_CUSTOMBUILD, + TKN_NONE, + TKN_MAX_COUNT, +}; + +static const char *s_TypeKeyNames[] = {"Library", "ClInclude", + "ClCompile", "ResourceCompile", + "CustomBuild", "None"}; + +const char *CProjectGenerator_Win32_2010::GetKeyNameForFile( + CProjectFile *pFile) { + static_assert(V_ARRAYSIZE(s_TypeKeyNames) == TKN_MAX_COUNT); + + const char *pExtension = V_GetFileExtension(pFile->m_Name.Get()); + + const char *pKeyName = s_TypeKeyNames[TKN_NONE]; + if (pExtension) { + if (pFile->m_Configs.Count() && pFile->m_Configs[0]->GetCustomBuildTool()) { + pKeyName = s_TypeKeyNames[TKN_CUSTOMBUILD]; + } else if (IsCFileExtension(pExtension)) { + pKeyName = s_TypeKeyNames[TKN_COMPILE]; + } else if (IsHFileExtension(pExtension)) { + pKeyName = s_TypeKeyNames[TKN_INCLUDE]; + } else if (!V_stricmp(pExtension, "lib")) { + pKeyName = s_TypeKeyNames[TKN_LIBRARY]; + } else if (!V_stricmp(pExtension, "rc")) { + pKeyName = s_TypeKeyNames[TKN_RESOURCECOMPILE]; + } + } + + return pKeyName; +} + +bool CProjectGenerator_Win32_2010::WritePropertyGroupTool( + CProjectTool *pProjectTool, CProjectConfiguration *pConfiguration) { + if (!pProjectTool) return true; + + for (intp i = 0; + i < pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + i++) { + intp sortedIndex = + pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (!pProjectTool->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty( + &pProjectTool->m_PropertyStates.m_Properties[sortedIndex], true, + pConfiguration->m_Name.Get())) + return false; + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteFile(CProjectFile *pFile, + const char *pFileTypeName) { + const char *pKeyName = GetKeyNameForFile(pFile); + if (V_stricmp(pFileTypeName, pKeyName)) { + // skip it + return true; + } + + if (!pFile->m_Configs.Count()) { + m_XMLWriter.Write( + CFmtStrMax("<%s Include=\"%s\" />", pKeyName, pFile->m_Name.Get())); + } else { + m_XMLWriter.PushNode(pKeyName, + CFmtStr("Include=\"%s\"", pFile->m_Name.Get())); + + for (int i = 0; i < pFile->m_Configs.Count(); i++) { + if (!WriteConfiguration(pFile->m_Configs[i])) return false; + } + + m_XMLWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteFolder(CProjectFolder *pFolder, + const char *pFileTypeName, + int nDepth) { + if (!nDepth) { + m_XMLWriter.PushNode("ItemGroup"); + } + + for (auto iIndex = pFolder->m_Files.Head(); + iIndex != pFolder->m_Files.InvalidIndex(); + iIndex = pFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pFolder->m_Files[iIndex], pFileTypeName)) return false; + } + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pFolder->m_Folders[iIndex], pFileTypeName, nDepth + 1)) + return false; + } + + if (!nDepth) { + m_XMLWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteConfiguration( + CProjectConfiguration *pConfig) { + if (!pConfig->m_bIsFileConfig) { + const char *pTargetPlatformName = + g_pVPC->IsPlatformDefined("win64") ? "x64" : "Win32"; + + m_XMLWriter.PushNode("PropertyGroup", + CFmtStr("Condition=\"'$(Configuration)|$(Platform)'=='" + "%s|%s'\" Label=\"Configuration\"", + pConfig->m_Name.Get(), pTargetPlatformName)); + + for (intp i = 0; + i < pConfig->m_PropertyStates.m_PropertiesInOutputOrder.Count(); i++) { + intp sortedIndex = pConfig->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (pConfig->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty(&pConfig->m_PropertyStates.m_Properties[sortedIndex])) + return false; + } + + m_XMLWriter.PopNode(true); + } else { + for (intp i = 0; + i < pConfig->m_PropertyStates.m_PropertiesInOutputOrder.Count(); i++) { + intp sortedIndex = pConfig->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (!WriteProperty(&pConfig->m_PropertyStates.m_Properties[sortedIndex], + true, pConfig->m_Name.Get())) + return false; + } + + if (!WriteTool("ClCompile", pConfig->GetCompilerTool(), pConfig)) + return false; + + if (!WriteTool("CustomBuildStep", pConfig->GetCustomBuildTool(), pConfig)) + return false; + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteTools(CProjectConfiguration *pConfig) { + const char *pTargetPlatformName = + g_pVPC->IsPlatformDefined("win64") ? "x64" : "Win32"; + + m_XMLWriter.PushNode( + "ItemDefinitionGroup", + CFmtStr("Condition=\"'$(Configuration)|$(Platform)'=='%s|%s'\"", + pConfig->m_Name.Get(), pTargetPlatformName)); + + if (!WriteTool("PreBuildEvent", pConfig->GetPreBuildEventTool(), pConfig)) + return false; + + if (!WriteTool("ClCompile", pConfig->GetCompilerTool(), pConfig)) + return false; + + if (!WriteTool("ResourceCompile", pConfig->GetResourcesTool(), pConfig)) + return false; + + if (!WriteTool("PreLinkEvent", pConfig->GetPreLinkEventTool(), pConfig)) + return false; + + if (!WriteTool("Link", pConfig->GetLinkerTool(), pConfig)) return false; + + if (!WriteTool("Lib", pConfig->GetLibrarianTool(), pConfig)) return false; + + if (!WriteTool("Manifest", pConfig->GetManifestTool(), pConfig)) return false; + + if (!WriteTool("Xdcmake", pConfig->GetXMLDocGenTool(), pConfig)) return false; + + if (!WriteTool("Bscmake", pConfig->GetBrowseInfoTool(), pConfig)) + return false; + + if (!WriteTool("PostBuildEvent", pConfig->GetPostBuildEventTool(), pConfig)) + return false; + + if (!WriteTool("CustomBuildStep", pConfig->GetCustomBuildTool(), pConfig)) + return false; + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Win32_2010::WritePrimaryXML( + const char *pOutputFilename) { + if (!m_XMLWriter.Open(pOutputFilename, true)) return false; + + const char *pTargetPlatformName = + g_pVPC->IsPlatformDefined("win64") ? "x64" : "Win32"; + + m_XMLWriter.PushNode( + "Project", + "DefaultTargets=\"Build\" ToolsVersion=\"4.0\" " + "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\""); + + m_XMLWriter.PushNode("ItemGroup", "Label=\"ProjectConfigurations\""); + CUtlVector configurationNames; + m_pVCProjGenerator->GetAllConfigurationNames(configurationNames); + const char *pPlatformString = "Win32"; + if (g_pVPC->IsPlatformDefined("WIN64")) pPlatformString = "x64"; + for (int i = 0; i < configurationNames.Count(); i++) { + m_XMLWriter.PushNode( + "ProjectConfiguration", + CFmtStr("Include=\"%s|%s\"", configurationNames[i].Get(), + pTargetPlatformName)); + m_XMLWriter.WriteLineNode("Configuration", "", configurationNames[i].Get()); + m_XMLWriter.WriteLineNode("Platform", "", + CFmtStr("%s", pTargetPlatformName)); + m_XMLWriter.PopNode(true); + } + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("PropertyGroup", "Label=\"Globals\""); + m_XMLWriter.WriteLineNode("ProjectName", "", + m_pVCProjGenerator->GetProjectName().Get()); + m_XMLWriter.WriteLineNode("ProjectGuid", "", + m_pVCProjGenerator->GetGUIDString().Get()); + if (g_pVPC->BUseP4SCC()) { + m_XMLWriter.WriteLineNode("SccProjectName", "", "Perforce Project"); + // it looks like 2k10 (at least) doesn't hook files in the project but not + // under the project root into source control, so make all the projects + // local paths the solution dir + char szCurrentDirectory[MAX_PATH]; + V_GetCurrentDirectory(szCurrentDirectory, V_ARRAYSIZE(szCurrentDirectory)); + char szRelativeFilename[MAX_PATH]; + if (!V_MakeRelativePath(g_pVPC->GetStartDirectory(), szCurrentDirectory, + szRelativeFilename, sizeof(szRelativeFilename))) + V_strncpy(szRelativeFilename, ".", V_ARRAYSIZE(szRelativeFilename)); + m_XMLWriter.WriteLineNode("SccLocalPath", "", szRelativeFilename); + m_XMLWriter.WriteLineNode("SccProvider", "", "MSSCCI:Perforce SCM"); + } + m_XMLWriter.PopNode(true); + + m_XMLWriter.Write( + ""); + + // When building 64 bit, use 64 bit toolchain (there is no 64 bit toolchain + // for 32 bit projects). This property is written early/specially to ensure it + // is written prior to Microsoft.Cpp.props + if (g_pVPC->IsPlatformDefined("win64") && !g_pVPC->BUse32BitTools()) { + m_XMLWriter.PushNode("PropertyGroup"); + m_XMLWriter.WriteLineNode("PreferredToolArchitecture", NULL, "x64"); + m_XMLWriter.PopNode(true); + } + + // write the root configurations + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + if (!WriteConfiguration(pConfiguration)) return false; + } + } + + m_XMLWriter.Write( + ""); + m_XMLWriter.PushNode("ImportGroup", "Label=\"ExtensionSettings\""); + m_XMLWriter.PopNode(true); + + for (int i = 0; i < configurationNames.Count(); i++) { + m_XMLWriter.PushNode( + "ImportGroup", + CFmtStr("Condition=\"'$(Configuration)|$(Platform)'=='%s|%s'\" " + "Label=\"PropertySheets\"", + configurationNames[i].Get(), pTargetPlatformName)); + m_XMLWriter.Write( + ""); + m_XMLWriter.PopNode(true); + } + + m_XMLWriter.Write(""); + + m_XMLWriter.PushNode("PropertyGroup"); + m_XMLWriter.WriteLineNode("_ProjectFileVersion", "", "10.0.30319.1"); + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + for (intp j = 0; + j < + pConfiguration->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + j++) { + intp sortedIndex = + pConfiguration->m_PropertyStates.m_PropertiesInOutputOrder[j]; + if (!pConfiguration->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty( + &pConfiguration->m_PropertyStates.m_Properties[sortedIndex], + true, pConfiguration->m_Name.Get())) + return false; + } + + if (!WritePropertyGroupTool(pConfiguration->GetPreBuildEventTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetPreLinkEventTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetLinkerTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetLibrarianTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetPostBuildEventTool(), + pConfiguration)) + return false; + } + } + m_XMLWriter.PopNode(true); + + // write the tool configurations + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + if (!WriteTools(pConfiguration)) return false; + } + } + + // write root folders + for (int i = 0; i < TKN_MAX_COUNT; i++) { + if (!WriteFolder(m_pVCProjGenerator->GetRootFolder(), s_TypeKeyNames[i], 0)) + return false; + } + + m_XMLWriter.Write( + ""); + m_XMLWriter.PushNode("ImportGroup", "Label=\"ExtensionTargets\""); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PopNode(true); + + m_XMLWriter.Close(); + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteFolderToSecondaryXML( + CProjectFolder *pFolder, const char *pParentPath) { + CUtlString parentPath = + CUtlString(CFmtStr("%s%s%s", pParentPath, pParentPath[0] ? "\\" : "", + pFolder->m_Name.Get())); + + MD5Context_t ctx; + unsigned char digest[MD5_DIGEST_LENGTH]; + V_memset(&ctx, 0, sizeof(ctx)); + V_memset(digest, 0, sizeof(digest)); + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char *)parentPath.Get(), V_strlen(parentPath.Get())); + MD5Final(digest, &ctx); + + char szMD5[64]; + V_binarytohex(digest, MD5_DIGEST_LENGTH, szMD5, sizeof(szMD5)); + V_strupr(szMD5); + + char szGUID[MAX_PATH]; + V_snprintf(szGUID, sizeof(szGUID), "{%8.8s-%4.4s-%4.4s-%4.4s-%12.12s}", szMD5, + &szMD5[8], &szMD5[12], &szMD5[16], &szMD5[20]); + + m_XMLFilterWriter.PushNode("Filter", + CFmtStr("Include=\"%s\"", parentPath.Get())); + m_XMLFilterWriter.WriteLineNode("UniqueIdentifier", "", szGUID); + m_XMLFilterWriter.PopNode(true); + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolderToSecondaryXML(pFolder->m_Folders[iIndex], + parentPath.Get())) + return false; + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteFileToSecondaryXML( + CProjectFile *pFile, const char *pParentPath, const char *pFileTypeName) { + const char *pKeyName = GetKeyNameForFile(pFile); + if (V_stricmp(pFileTypeName, pKeyName)) { + // skip it + return true; + } + + if (pParentPath) { + m_XMLFilterWriter.PushNode(pKeyName, + CFmtStr("Include=\"%s\"", pFile->m_Name.Get())); + m_XMLFilterWriter.WriteLineNode("Filter", "", pParentPath); + m_XMLFilterWriter.PopNode(true); + } else { + m_XMLFilterWriter.Write( + CFmtStr("<%s Include=\"%s\" />", pKeyName, pFile->m_Name.Get())); + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteFolderContentsToSecondaryXML( + CProjectFolder *pFolder, const char *pParentPath, const char *pFileTypeName, + int nDepth) { + CUtlString parentPath; + if (pParentPath) { + parentPath = CFmtStr("%s%s%s", pParentPath, pParentPath[0] ? "\\" : "", + pFolder->m_Name.Get()); + } + + if (!nDepth) { + m_XMLFilterWriter.PushNode("ItemGroup", NULL); + } + + for (auto iIndex = pFolder->m_Files.Head(); + iIndex != pFolder->m_Files.InvalidIndex(); + iIndex = pFolder->m_Files.Next(iIndex)) { + if (!WriteFileToSecondaryXML(pFolder->m_Files[iIndex], parentPath.Get(), + pFileTypeName)) + return false; + } + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolderContentsToSecondaryXML(pFolder->m_Folders[iIndex], + parentPath.Get(), pFileTypeName, + nDepth + 1)) + return false; + } + + if (!nDepth) { + m_XMLFilterWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteSecondaryXML( + const char *pOutputFilename) { + if (!m_XMLFilterWriter.Open(pOutputFilename, true)) return false; + + m_XMLFilterWriter.PushNode( + "Project", + "ToolsVersion=\"4.0\" " + "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\""); + + // write the root folders + m_XMLFilterWriter.PushNode("ItemGroup", NULL); + CProjectFolder *pRootFolder = m_pVCProjGenerator->GetRootFolder(); + for (auto iIndex = pRootFolder->m_Folders.Head(); + iIndex != pRootFolder->m_Folders.InvalidIndex(); + iIndex = pRootFolder->m_Folders.Next(iIndex)) { + if (!WriteFolderToSecondaryXML(pRootFolder->m_Folders[iIndex], "")) + return false; + } + m_XMLFilterWriter.PopNode(true); + + // write folder contents + for (int i = 0; i < TKN_MAX_COUNT; i++) { + if (!WriteFolderContentsToSecondaryXML(pRootFolder, NULL, s_TypeKeyNames[i], + 0)) + return false; + } + + m_XMLFilterWriter.PopNode(true); + + m_XMLFilterWriter.Close(); + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteTool(const char *pToolName, + const CProjectTool *pProjectTool, + CProjectConfiguration *pConfig) { + if (!pProjectTool) { + // not an error, some tools n/a for a config + return true; + } + + if (!pConfig->m_bIsFileConfig) { + m_XMLWriter.PushNode(pToolName, NULL); + } + + for (intp i = 0; + i < pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + i++) { + intp sortedIndex = + pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (!pConfig->m_bIsFileConfig) { + if (pProjectTool->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty( + &pProjectTool->m_PropertyStates.m_Properties[sortedIndex])) + return false; + } else { + if (!WriteProperty( + &pProjectTool->m_PropertyStates.m_Properties[sortedIndex], true, + pConfig->m_Name.Get())) + return false; + } + } + + if (!pConfig->m_bIsFileConfig) { + m_XMLWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Win32_2010::WriteProperty( + const PropertyState_t *pPropertyState, bool bEmitConfiguration, + const char *pConfigName, const char *pOutputName, + const char *pOutputValue) { + if (!pPropertyState) { + m_XMLWriter.WriteLineNode(pOutputName, "", pOutputValue); + return true; + } + + if (!pOutputName) { + pOutputName = pPropertyState->m_pToolProperty->m_OutputString.Get(); + if (!pOutputName[0]) { + pOutputName = pPropertyState->m_pToolProperty->m_ParseString.Get(); + if (pOutputName[0] == '$') { + pOutputName++; + } + } + } + + const char *pCondition = ""; + CUtlString conditionString; + if (bEmitConfiguration) { + const char *pTargetPlatformName = + g_pVPC->IsPlatformDefined("win64") ? "x64" : "Win32"; + + conditionString = + CFmtStr(" Condition=\"'$(Configuration)|$(Platform)'=='%s|%s'\"", + pConfigName, pTargetPlatformName); + pCondition = conditionString.Get(); + } + + switch (pPropertyState->m_pToolProperty->m_nType) { + case PT_BOOLEAN: { + bool bEnabled = Sys_StringToBool(pPropertyState->m_StringValue.Get()); + if (pPropertyState->m_pToolProperty->m_bInvertOutput) { + bEnabled ^= 1; + } + m_XMLWriter.WriteLineNode(pOutputName, pCondition, + bEnabled ? "true" : "false"); + } break; + + case PT_STRING: + m_XMLWriter.WriteLineNode( + pOutputName, pCondition, + m_XMLWriter.FixupXMLString(pPropertyState->m_StringValue.Get())); + break; + + case PT_LIST: + case PT_INTEGER: + m_XMLWriter.WriteLineNode(pOutputName, pCondition, + pPropertyState->m_StringValue.Get()); + break; + + case PT_IGNORE: + break; + + default: + g_pVPC->VPCError( + "CProjectGenerator_Win32_2010: WriteProperty, %s - not implemented", + pOutputName); + } + + return true; +} + +bool CProjectGenerator_Win32_2010::Save(const char *pOutputFilename) { + bool bValid = WritePrimaryXML(pOutputFilename); + if (bValid) { + bValid = WriteSecondaryXML(CFmtStr("%s.filters", pOutputFilename)); + if (!bValid) { + g_pVPC->VPCError("Cannot save to the specified project '%s'", + pOutputFilename); + } + } + + return bValid; +} diff --git a/utils/vpc/projectgenerator_win32_2010.h b/utils/vpc/projectgenerator_win32_2010.h new file mode 100644 index 0000000..af97132 --- /dev/null +++ b/utils/vpc/projectgenerator_win32_2010.h @@ -0,0 +1,59 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_PROJECTGENERATOR_WIN32_2010_H_ +#define VPC_PROJECTGENERATOR_WIN32_2010_H_ + +#include "projectgenerator_vcproj.h" + +#define PROPERTYNAME(X, Y) X##_##Y, + +enum Win32_2010_Properties_e { +#include "projectgenerator_win32_2010.inc" +}; + +class CProjectGenerator_Win32_2010 : public IVCProjWriter { + public: + CProjectGenerator_Win32_2010(); + IBaseProjectGenerator *GetProjectGenerator() { return m_pVCProjGenerator; } + + virtual bool Save(const char *pOutputFilename); + + private: + // primary XML - foo.vcxproj + bool WritePrimaryXML(const char *pOutputFilename); + bool WriteFolder(CProjectFolder *pFolder, const char *pFileTypeName, + int nDepth); + bool WriteFile(CProjectFile *pFile, const char *pFileTypeName); + bool WriteConfiguration(CProjectConfiguration *pConfig); + bool WriteTools(CProjectConfiguration *pConfig); + bool WriteProperty(const PropertyState_t *pPropertyState, + bool bEmitConfiguration = false, + const char *pConfigurationName = NULL, + const char *pOutputName = NULL, const char *pValue = NULL); + bool WriteTool(const char *pToolName, const CProjectTool *pProjectTool, + CProjectConfiguration *pConfig); + bool WriteNULLTool(const char *pToolName, + const CProjectConfiguration *pConfig); + bool WritePropertyGroupTool(CProjectTool *pProjectTool, + CProjectConfiguration *pConfiguration); + bool WritePropertyGroup(); + + // secondary XML - foo.vcxproj.filters + bool WriteSecondaryXML(const char *pOutputFilename); + bool WriteFolderToSecondaryXML(CProjectFolder *pFolder, + const char *pParentPath); + bool WriteFolderContentsToSecondaryXML(CProjectFolder *pFolder, + const char *pParentPath, + const char *pFileTypeName, int nDepth); + bool WriteFileToSecondaryXML(CProjectFile *pFile, const char *pParentPath, + const char *pFileTypeName); + + const char *GetKeyNameForFile(CProjectFile *pFile); + + CXMLWriter m_XMLWriter; + CXMLWriter m_XMLFilterWriter; + + CVCProjGenerator *m_pVCProjGenerator; +}; + +#endif // VPC_PROJECTGENERATOR_WIN32_2010_H_ diff --git a/utils/vpc/projectgenerator_win32_2010.inc b/utils/vpc/projectgenerator_win32_2010.inc new file mode 100644 index 0000000..1507003 --- /dev/null +++ b/utils/vpc/projectgenerator_win32_2010.inc @@ -0,0 +1,300 @@ + +//========= Copyright 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Property Enumerations +// +//=====================================================================================// + +// Config +PROPERTYNAME( WIN32_2010_GENERAL, ExcludedFromBuild ) +PROPERTYNAME( WIN32_2010_GENERAL, OutputDirectory ) +PROPERTYNAME( WIN32_2010_GENERAL, IntermediateDirectory ) +PROPERTYNAME( WIN32_2010_GENERAL, ConfigurationType ) +PROPERTYNAME( WIN32_2010_GENERAL, CharacterSet ) +PROPERTYNAME( WIN32_2010_GENERAL, WholeProgramOptimization ) +PROPERTYNAME( WIN32_2010_GENERAL, ExtensionsToDeleteOnClean ) +PROPERTYNAME( WIN32_2010_GENERAL, BuildLogFile ) +PROPERTYNAME( WIN32_2010_GENERAL, InheritedProjectPropertySheets ) +PROPERTYNAME( WIN32_2010_GENERAL, UseOfMFC ) +PROPERTYNAME( WIN32_2010_GENERAL, UseOfATL ) +PROPERTYNAME( WIN32_2010_GENERAL, MinimizeCRTUseInATL ) +PROPERTYNAME( WIN32_2010_GENERAL, TargetName ) +PROPERTYNAME( WIN32_2010_GENERAL, TargetExtension ) +PROPERTYNAME( WIN32_2010_GENERAL, PlatformToolset ) +PROPERTYNAME( WIN32_2010_GENERAL, ExecutableDirectories ) + +// Debugging +PROPERTYNAME( WIN32_2010_DEBUGGING, Command ) +PROPERTYNAME( WIN32_2010_DEBUGGING, CommandArguments ) +PROPERTYNAME( WIN32_2010_DEBUGGING, RemoteMachine ) +PROPERTYNAME( WIN32_2010_DEBUGGING, WorkingDirectory ) +PROPERTYNAME( WIN32_2010_DEBUGGING, Attach ) +PROPERTYNAME( WIN32_2010_DEBUGGING, DebuggerType ) +PROPERTYNAME( WIN32_2010_DEBUGGING, Environment ) +PROPERTYNAME( WIN32_2010_DEBUGGING, MergeEnvironment ) +PROPERTYNAME( WIN32_2010_DEBUGGING, SQLDebugging ) + +// Compiler +PROPERTYNAME( WIN32_2010_COMPILER, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_2010_COMPILER, AdditionalOptions ) +PROPERTYNAME( WIN32_2010_COMPILER, Optimization ) +PROPERTYNAME( WIN32_2010_COMPILER, InlineFunctionExpansion ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableIntrinsicFunctions ) +PROPERTYNAME( WIN32_2010_COMPILER, FavorSizeOrSpeed ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableFiberSafeOptimizations ) +PROPERTYNAME( WIN32_2010_COMPILER, WholeProgramOptimization ) +PROPERTYNAME( WIN32_2010_COMPILER, AdditionalIncludeDirectories ) +PROPERTYNAME( WIN32_2010_COMPILER, PreprocessorDefinitions ) +PROPERTYNAME( WIN32_2010_COMPILER, IgnoreStandardIncludePath ) +PROPERTYNAME( WIN32_2010_COMPILER, GeneratePreprocessedFile ) +PROPERTYNAME( WIN32_2010_COMPILER, KeepComments ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableStringPooling ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableMinimalRebuild ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableCPPExceptions ) +PROPERTYNAME( WIN32_2010_COMPILER, BasicRuntimeChecks ) +PROPERTYNAME( WIN32_2010_COMPILER, SmallerTypeCheck ) +PROPERTYNAME( WIN32_2010_COMPILER, RuntimeLibrary ) +PROPERTYNAME( WIN32_2010_COMPILER, StructMemberAlignment ) +PROPERTYNAME( WIN32_2010_COMPILER, BufferSecurityCheck ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableFunctionLevelLinking ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableEnhancedInstructionSet ) +PROPERTYNAME( WIN32_2010_COMPILER, FloatingPointModel ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableFloatingPointExceptions ) +PROPERTYNAME( WIN32_2010_COMPILER, LanguageStandard ) +PROPERTYNAME( WIN32_2010_COMPILER, DisableLanguageExtensions ) +PROPERTYNAME( WIN32_2010_COMPILER, DefaultCharUnsigned ) +PROPERTYNAME( WIN32_2010_COMPILER, TreatWCHAR_TAsBuiltInType ) +PROPERTYNAME( WIN32_2010_COMPILER, ForceConformanceInForLoopScope ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableRunTimeTypeInfo ) +PROPERTYNAME( WIN32_2010_COMPILER, OpenMPSupport ) +PROPERTYNAME( WIN32_2010_COMPILER, PrecompiledHeader ) +PROPERTYNAME( WIN32_2010_COMPILER, PrecompiledHeaderFile ) +PROPERTYNAME( WIN32_2010_COMPILER, PrecompiledHeaderOutputFile ) +PROPERTYNAME( WIN32_2010_COMPILER, ExpandAttributedSource ) +PROPERTYNAME( WIN32_2010_COMPILER, AssemblerOutput ) +PROPERTYNAME( WIN32_2010_COMPILER, ASMListLocation ) +PROPERTYNAME( WIN32_2010_COMPILER, ObjectFileName ) +PROPERTYNAME( WIN32_2010_COMPILER, ProgramDatabaseFileName ) +PROPERTYNAME( WIN32_2010_COMPILER, GenerateXMLDocumentationFiles ) +PROPERTYNAME( WIN32_2010_COMPILER, EnableBrowseInformation ) +PROPERTYNAME( WIN32_2010_COMPILER, BrowseFile ) +PROPERTYNAME( WIN32_2010_COMPILER, WarningLevel ) +PROPERTYNAME( WIN32_2010_COMPILER, TreatWarningsAsErrors ) +PROPERTYNAME( WIN32_2010_COMPILER, Detect64bitPortabilityIssues ) +PROPERTYNAME( WIN32_2010_COMPILER, SuppressStartupBanner ) +PROPERTYNAME( WIN32_2010_COMPILER, DebugInformationFormat ) +PROPERTYNAME( WIN32_2010_COMPILER, CompileAs ) +PROPERTYNAME( WIN32_2010_COMPILER, ForceIncludes ) +PROPERTYNAME( WIN32_2010_COMPILER, ShowIncludes ) +PROPERTYNAME( WIN32_2010_COMPILER, UndefineAllPreprocessorDefinitions ) +PROPERTYNAME( WIN32_2010_COMPILER, UndefinePreprocessorDefinitions ) +PROPERTYNAME( WIN32_2010_COMPILER, UseFullPaths ) +PROPERTYNAME( WIN32_2010_COMPILER, OmitDefaultLibraryNames ) +PROPERTYNAME( WIN32_2010_COMPILER, TrapIntegerDividesOptimization ) +PROPERTYNAME( WIN32_2010_COMPILER, PreschedulingOptimization ) +PROPERTYNAME( WIN32_2010_COMPILER, InlineAssemblyOptimization ) +PROPERTYNAME( WIN32_2010_COMPILER, RegisterReservation ) +PROPERTYNAME( WIN32_2010_COMPILER, Stalls ) +PROPERTYNAME( WIN32_2010_COMPILER, CallAttributedProfiling ) +PROPERTYNAME( WIN32_2010_COMPILER, XMLDocumentationFileName ) +PROPERTYNAME( WIN32_2010_COMPILER, DisableSpecificWarnings ) +PROPERTYNAME( WIN32_2010_COMPILER, ResolveUsingReferences ) +PROPERTYNAME( WIN32_2010_COMPILER, OmitFramePointers ) +PROPERTYNAME( WIN32_2010_COMPILER, CallingConvention ) +PROPERTYNAME( WIN32_2010_COMPILER, ForceUsing ) +PROPERTYNAME( WIN32_2010_COMPILER, ErrorReporting ) +PROPERTYNAME( WIN32_2010_COMPILER, CommonLanguageRuntimeSupport ) +PROPERTYNAME( WIN32_2010_COMPILER, MultiProcessorCompilation ) +PROPERTYNAME( WIN32_2010_COMPILER, UseUnicodeForAssemblerListing ) +PROPERTYNAME( WIN32_2010_COMPILER, IgnoreStandardIncludePaths ) +PROPERTYNAME( WIN32_2010_COMPILER, PreprocessToAFile ) +PROPERTYNAME( WIN32_2010_COMPILER, PreprocessSuppressLineNumbers ) +PROPERTYNAME( WIN32_2010_COMPILER, CreateHotpatchableImage ) +PROPERTYNAME( WIN32_2010_COMPILER, BrowseInformationFile ) +PROPERTYNAME( WIN32_2010_COMPILER, ForcedIncludeFile ) +PROPERTYNAME( WIN32_2010_COMPILER, ForcedUsingFile ) +PROPERTYNAME( WIN32_2010_COMPILER, OmitDefaultLibName ) +PROPERTYNAME( WIN32_2010_COMPILER, InternalCompilerErrorReporting ) +PROPERTYNAME( WIN32_2010_COMPILER, TreatSpecificWarningsAsErrors ) + +// Librarian +PROPERTYNAME( WIN32_2010_LIBRARIAN, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, AdditionalDependencies ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, OutputFile ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, AdditionalLibraryDirectories ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, SuppressStartupBanner ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, ModuleDefinitionFileName ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, IgnoreAllDefaultLibraries ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, IgnoreSpecificLibrary ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, ExportNamedFunctions ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, ForceSymbolReferences ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, LinkLibraryDependencies ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, TargetMachine ) +PROPERTYNAME( WIN32_2010_LIBRARIAN, AdditionalOptions ) + +// Linker +PROPERTYNAME( WIN32_2010_LINKER, IgnoreImportLibrary ) +PROPERTYNAME( WIN32_2010_LINKER, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_2010_LINKER, AdditionalOptions ) +PROPERTYNAME( WIN32_2010_LINKER, AdditionalDependencies ) +PROPERTYNAME( WIN32_2010_LINKER, ShowProgress ) +PROPERTYNAME( WIN32_2010_LINKER, OutputFile ) +PROPERTYNAME( WIN32_2010_LINKER, Version ) +PROPERTYNAME( WIN32_2010_LINKER, EnableIncrementalLinking ) +PROPERTYNAME( WIN32_2010_LINKER, SuppressStartupBanner ) +PROPERTYNAME( WIN32_2010_LINKER, AdditionalLibraryDirectories ) +PROPERTYNAME( WIN32_2010_LINKER, IgnoreSpecificDefaultLibraries ) +PROPERTYNAME( WIN32_2010_LINKER, GenerateManifest ) +PROPERTYNAME( WIN32_2010_LINKER, IgnoreAllDefaultLibraries ) +PROPERTYNAME( WIN32_2010_LINKER, IgnoreSpecificLibrary ) +PROPERTYNAME( WIN32_2010_LINKER, ModuleDefinitionFile ) +PROPERTYNAME( WIN32_2010_LINKER, GenerateDebugInfo ) +PROPERTYNAME( WIN32_2010_LINKER, DebuggableAssembly ) +PROPERTYNAME( WIN32_2010_LINKER, GenerateProgramDatabaseFile ) +PROPERTYNAME( WIN32_2010_LINKER, GenerateMapFile ) +PROPERTYNAME( WIN32_2010_LINKER, MapFileName ) +PROPERTYNAME( WIN32_2010_LINKER, SubSystem ) +PROPERTYNAME( WIN32_2010_LINKER, EnableLargeAddresses ) +PROPERTYNAME( WIN32_2010_LINKER, MapExports ) +PROPERTYNAME( WIN32_2010_LINKER, StackReserveSize ) +PROPERTYNAME( WIN32_2010_LINKER, StackCommitSize ) +PROPERTYNAME( WIN32_2010_LINKER, References ) +PROPERTYNAME( WIN32_2010_LINKER, EnableCOMDATFolding ) +PROPERTYNAME( WIN32_2010_LINKER, LinkTimeCodeGeneration ) +PROPERTYNAME( WIN32_2010_LINKER, EntryPoint ) +PROPERTYNAME( WIN32_2010_LINKER, NoEntryPoint ) +PROPERTYNAME( WIN32_2010_LINKER, SetChecksum ) +PROPERTYNAME( WIN32_2010_LINKER, BaseAddress ) +PROPERTYNAME( WIN32_2010_LINKER, ImportLibrary ) +PROPERTYNAME( WIN32_2010_LINKER, TargetMachine ) +PROPERTYNAME( WIN32_2010_LINKER, FixedBaseAddress ) +PROPERTYNAME( WIN32_2010_LINKER, ErrorReporting ) +PROPERTYNAME( WIN32_2010_LINKER, FunctionOrder ) +PROPERTYNAME( WIN32_2010_LINKER, LinkLibraryDependencies ) +PROPERTYNAME( WIN32_2010_LINKER, UseLibraryDependencyInputs ) +PROPERTYNAME( WIN32_2010_LINKER, ForceSymbolReferences ) +PROPERTYNAME( WIN32_2010_LINKER, StripPrivateSymbols ) +PROPERTYNAME( WIN32_2010_LINKER, ProfileGuidedDatabase ) +PROPERTYNAME( WIN32_2010_LINKER, MergeSections ) +PROPERTYNAME( WIN32_2010_LINKER, RegisterOutput ) +PROPERTYNAME( WIN32_2010_LINKER, AddModuleToAssembly ) +PROPERTYNAME( WIN32_2010_LINKER, EmbedManagedResourceFile ) +PROPERTYNAME( WIN32_2010_LINKER, DelayLoadedDLLs ) +PROPERTYNAME( WIN32_2010_LINKER, AssemblyLinkResource ) +PROPERTYNAME( WIN32_2010_LINKER, ManifestFile ) +PROPERTYNAME( WIN32_2010_LINKER, AdditionalManifestDependencies ) +PROPERTYNAME( WIN32_2010_LINKER, AllowIsolation ) +PROPERTYNAME( WIN32_2010_LINKER, HeapReserveSize ) +PROPERTYNAME( WIN32_2010_LINKER, HeapCommitSize ) +PROPERTYNAME( WIN32_2010_LINKER, TerminalServer ) +PROPERTYNAME( WIN32_2010_LINKER, SwapRunFromCD ) +PROPERTYNAME( WIN32_2010_LINKER, SwapRunFromNetwork ) +PROPERTYNAME( WIN32_2010_LINKER, Driver ) +PROPERTYNAME( WIN32_2010_LINKER, OptimizeForWindows98 ) +PROPERTYNAME( WIN32_2010_LINKER, MIDLCommands ) +PROPERTYNAME( WIN32_2010_LINKER, IgnoreEmbeddedIDL ) +PROPERTYNAME( WIN32_2010_LINKER, MergeIDLBaseFileName ) +PROPERTYNAME( WIN32_2010_LINKER, TypeLibrary ) +PROPERTYNAME( WIN32_2010_LINKER, TypeLibResourceID ) +PROPERTYNAME( WIN32_2010_LINKER, TurnOffAssemblyGeneration ) +PROPERTYNAME( WIN32_2010_LINKER, DelayLoadedDLL ) +PROPERTYNAME( WIN32_2010_LINKER, Profile ) +PROPERTYNAME( WIN32_2010_LINKER, CLRThreadAttribute ) +PROPERTYNAME( WIN32_2010_LINKER, CLRImageType ) +PROPERTYNAME( WIN32_2010_LINKER, KeyFile ) +PROPERTYNAME( WIN32_2010_LINKER, KeyContainer ) +PROPERTYNAME( WIN32_2010_LINKER, DelaySign ) +PROPERTYNAME( WIN32_2010_LINKER, CLRUnmanagedCodeCheck ) +PROPERTYNAME( WIN32_2010_LINKER, PerUserRedirection ) +PROPERTYNAME( WIN32_2010_LINKER, LinkStatus ) +PROPERTYNAME( WIN32_2010_LINKER, PreventDllBinding ) +PROPERTYNAME( WIN32_2010_LINKER, TreatLinkerWarningsAsErrors ) +PROPERTYNAME( WIN32_2010_LINKER, ForceFileOutput ) +PROPERTYNAME( WIN32_2010_LINKER, CreateHotpatchableImage ) +PROPERTYNAME( WIN32_2010_LINKER, SpecifySectionAttributes ) +PROPERTYNAME( WIN32_2010_LINKER, EnableUserAccountControl ) +PROPERTYNAME( WIN32_2010_LINKER, UACExecutionLevel ) +PROPERTYNAME( WIN32_2010_LINKER, UACBypassUIProtection ) +PROPERTYNAME( WIN32_2010_LINKER, MinimumRequiredVersion ) +PROPERTYNAME( WIN32_2010_LINKER, RandomizedBaseAddress ) +PROPERTYNAME( WIN32_2010_LINKER, DataExecutionPrevention ) +PROPERTYNAME( WIN32_2010_LINKER, UnloaddelayloadedDLL ) +PROPERTYNAME( WIN32_2010_LINKER, NobinddelayloadedDLL ) +PROPERTYNAME( WIN32_2010_LINKER, SectionAlignment ) +PROPERTYNAME( WIN32_2010_LINKER, PreserveLastErrorCodeforPInvokeCalls ) +PROPERTYNAME( WIN32_2010_LINKER, ImageHasSafeExceptionHandlers ) + +// Manifest +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, SuppressStartupBanner ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, VerboseOutput ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, AssemblyIdentity ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, UseFAT32WorkAround ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, AdditionalManifestFiles ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, InputResourceManifests ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, EmbedManifest ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, OutputManifestFile ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, ManifestResourceFile ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, GenerateCatalogFiles ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, DependencyInformationFile ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, TypeLibraryFile ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, RegistrarScriptFile ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, ComponentFileName ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, ReplacementsFile ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, UpdateFileHashes ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, UpdateFileHashesSearchPath ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, AdditionalOptions ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, GenerateManifestFromManagedAssembly ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, SuppressDependencyElement ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, GenerateCategoryTags ) +PROPERTYNAME( WIN32_2010_MANIFESTTOOL, EnableDPIAwareness ) + +// XML Document Generator +PROPERTYNAME( WIN32_2010_XMLDOCUMENTGENERATOR, UseUNICODEResponseFiles ) +PROPERTYNAME( WIN32_2010_XMLDOCUMENTGENERATOR, SuppressStartupBanner ) +PROPERTYNAME( WIN32_2010_XMLDOCUMENTGENERATOR, ValidateIntelliSense ) +PROPERTYNAME( WIN32_2010_XMLDOCUMENTGENERATOR, AdditionalDocumentFiles ) +PROPERTYNAME( WIN32_2010_XMLDOCUMENTGENERATOR, OutputDocumentFile ) +PROPERTYNAME( WIN32_2010_XMLDOCUMENTGENERATOR, DocumentLibraryDependencies ) +PROPERTYNAME( WIN32_2010_XMLDOCUMENTGENERATOR, AdditionalOptions ) + +// Browse Information +PROPERTYNAME( WIN32_2010_BROWSEINFORMATION, SuppressStartupBanner ) +PROPERTYNAME( WIN32_2010_BROWSEINFORMATION, OutputFile ) +PROPERTYNAME( WIN32_2010_BROWSEINFORMATION, AdditionalOptions ) +PROPERTYNAME( WIN32_2010_BROWSEINFORMATION, PreserveSBRFiles ) + +// Resources +PROPERTYNAME( WIN32_2010_RESOURCES, PreprocessorDefinitions ) +PROPERTYNAME( WIN32_2010_RESOURCES, Culture ) +PROPERTYNAME( WIN32_2010_RESOURCES, AdditionalIncludeDirectories ) +PROPERTYNAME( WIN32_2010_RESOURCES, IgnoreStandardIncludePath ) +PROPERTYNAME( WIN32_2010_RESOURCES, ShowProgress ) +PROPERTYNAME( WIN32_2010_RESOURCES, ResourceFileName ) +PROPERTYNAME( WIN32_2010_RESOURCES, AdditionalOptions ) + +// Pre Build +PROPERTYNAME( WIN32_2010_PREBUILDEVENT, Description ) +PROPERTYNAME( WIN32_2010_PREBUILDEVENT, CommandLine ) +PROPERTYNAME( WIN32_2010_PREBUILDEVENT, ExcludedFromBuild ) +PROPERTYNAME( WIN32_2010_PREBUILDEVENT, UseInBuild ) + +// Pre Link +PROPERTYNAME( WIN32_2010_PRELINKEVENT, Description ) +PROPERTYNAME( WIN32_2010_PRELINKEVENT, CommandLine ) +PROPERTYNAME( WIN32_2010_PRELINKEVENT, ExcludedFromBuild ) +PROPERTYNAME( WIN32_2010_PRELINKEVENT, UseInBuild ) + +// Post Build +PROPERTYNAME( WIN32_2010_POSTBUILDEVENT, Description ) +PROPERTYNAME( WIN32_2010_POSTBUILDEVENT, CommandLine ) +PROPERTYNAME( WIN32_2010_POSTBUILDEVENT, ExcludedFromBuild ) +PROPERTYNAME( WIN32_2010_POSTBUILDEVENT, UseInBuild ) + +// Custom Build +PROPERTYNAME( WIN32_2010_CUSTOMBUILDSTEP, Description ) +PROPERTYNAME( WIN32_2010_CUSTOMBUILDSTEP, CommandLine ) +PROPERTYNAME( WIN32_2010_CUSTOMBUILDSTEP, AdditionalDependencies ) +PROPERTYNAME( WIN32_2010_CUSTOMBUILDSTEP, Outputs ) +PROPERTYNAME( WIN32_2010_CUSTOMBUILDSTEP, ExecuteAfter ) +PROPERTYNAME( WIN32_2010_CUSTOMBUILDSTEP, ExecuteBefore ) diff --git a/utils/vpc/projectgenerator_xbox360.cpp b/utils/vpc/projectgenerator_xbox360.cpp new file mode 100644 index 0000000..f50f3f1 --- /dev/null +++ b/utils/vpc/projectgenerator_xbox360.cpp @@ -0,0 +1,317 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "projectgenerator_xbox360.h" + +#include "tier0/memdbgon.h" + +#undef PROPERTYNAME +#define PROPERTYNAME(X, Y) {X##_##Y, #X, #Y}, + +static PropertyName_t s_Xbox360PropertyNames[] = { +#include "projectgenerator_xbox360.inc" + {-1, NULL, NULL}}; + +IBaseProjectGenerator *GetXbox360ProjectGenerator() { + static CProjectGenerator_Xbox360 *s_pProjectGenerator = NULL; + if (!s_pProjectGenerator) { + s_pProjectGenerator = new CProjectGenerator_Xbox360(); + } + + return s_pProjectGenerator->GetProjectGenerator(); +} + +CProjectGenerator_Xbox360::CProjectGenerator_Xbox360() { + m_pVCProjGenerator = new CVCProjGenerator(); + m_pVCProjGenerator->SetupGeneratorDefinition(this, "xbox360.def", + s_Xbox360PropertyNames); +} + +bool CProjectGenerator_Xbox360::WriteFile(CProjectFile *pFile) { + m_XMLWriter.PushNode("File"); + m_XMLWriter.Write(CFmtStrMax("RelativePath=\"%s\"", pFile->m_Name.Get())); + m_XMLWriter.Write(">"); + + for (int i = 0; i < pFile->m_Configs.Count(); i++) { + if (!WriteConfiguration(pFile->m_Configs[i])) return false; + } + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Xbox360::WriteFolder(CProjectFolder *pFolder) { + m_XMLWriter.PushNode("Filter"); + // String() returns temporary object, so save name in var to prevent stale + // memory usage. + CUtlString name = m_XMLWriter.FixupXMLString(pFolder->m_Name.Get()); + m_XMLWriter.Write(CFmtStrMax("Name=\"%s\"", name.String())); + m_XMLWriter.Write(">"); + + for (auto iIndex = pFolder->m_Files.Head(); + iIndex != pFolder->m_Files.InvalidIndex(); + iIndex = pFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pFolder->m_Files[iIndex])) return false; + } + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pFolder->m_Folders[iIndex])) return false; + } + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Xbox360::WriteConfiguration( + CProjectConfiguration *pConfig) { + if (pConfig->m_bIsFileConfig) { + m_XMLWriter.PushNode("FileConfiguration"); + } else { + m_XMLWriter.PushNode("Configuration"); + } + + const char *pOutputName = "???"; + if (!V_stricmp(pConfig->m_Name.Get(), "debug")) { + pOutputName = "Debug|Xbox 360"; + } else if (!V_stricmp(pConfig->m_Name.Get(), "release")) { + pOutputName = "Release|Xbox 360"; + } else { + return false; + } + + m_XMLWriter.Write(CFmtStrMax("Name=\"%s\"", pOutputName)); + + // write configuration properties + for (int i = 0; + i < pConfig->m_PropertyStates.m_PropertiesInOutputOrder.Count(); i++) { + intp sortedIndex = pConfig->m_PropertyStates.m_PropertiesInOutputOrder[i]; + WriteProperty(&pConfig->m_PropertyStates.m_Properties[sortedIndex]); + } + + if (!pConfig->m_bIsFileConfig && + pConfig->m_PropertyStates.m_Properties.Count()) { + WriteProperty(NULL, "UseOfMFC", "-1"); + WriteProperty(NULL, "UseOfATL", "0"); + } + + m_XMLWriter.Write(">"); + + if (!WriteTool("VCPreBuildEventTool", pConfig->GetPreBuildEventTool())) + return false; + + if (!WriteTool("VCCustomBuildTool", pConfig->GetCustomBuildTool())) + return false; + + if (!WriteNULLTool("VCXMLDataGeneratorTool", pConfig)) return false; + + if (!WriteNULLTool("VCWebServiceProxyGeneratorTool", pConfig)) return false; + + if (!WriteNULLTool("VCMIDLTool", pConfig)) return false; + + if (!WriteTool("VCCLX360CompilerTool", pConfig->GetCompilerTool())) + return false; + + if (!WriteNULLTool("VCManagedResourceCompilerTool", pConfig)) return false; + + if (!WriteNULLTool("VCResourceCompilerTool", pConfig)) return false; + + if (!WriteTool("VCPreLinkEventTool", pConfig->GetPreLinkEventTool())) + return false; + + if (!WriteTool("VCX360LinkerTool", pConfig->GetLinkerTool())) return false; + + if (!WriteTool("VCLibrarianTool", pConfig->GetLibrarianTool())) return false; + + if (!WriteNULLTool("VCALinkTool", pConfig)) return false; + + if (!WriteTool("VCX360ImageTool", pConfig->GetXboxImageTool())) return false; + + if (!WriteTool("VCBscMakeTool", pConfig->GetBrowseInfoTool())) return false; + + if (!WriteTool("VCX360DeploymentTool", pConfig->GetXboxDeploymentTool())) + return false; + + if (!WriteTool("VCPostBuildEventTool", pConfig->GetPostBuildEventTool())) + return false; + + if (!pConfig->m_bIsFileConfig) { + m_XMLWriter.PushNode("DebuggerTool"); + m_XMLWriter.PopNode(false); + } + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Xbox360::WriteToXML() { + m_XMLWriter.PushNode("VisualStudioProject"); + m_XMLWriter.Write("ProjectType=\"Visual C++\""); + + if (g_pVPC->Is2008()) + m_XMLWriter.Write("Version=\"9.00\""); + else + m_XMLWriter.Write("Version=\"8.00\""); + + m_XMLWriter.Write( + CFmtStrMax("Name=\"%s\"", m_pVCProjGenerator->GetProjectName().Get())); + m_XMLWriter.Write(CFmtStrMax("ProjectGUID=\"%s\"", + m_pVCProjGenerator->GetGUIDString().Get())); + m_XMLWriter.Write(">"); + + m_XMLWriter.PushNode("Platforms"); + m_XMLWriter.PushNode("Platform"); + m_XMLWriter.Write("Name=\"Xbox 360\""); + m_XMLWriter.PopNode(false); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("ToolFiles"); + m_XMLWriter.PopNode(true); + + CUtlVector configurationNames; + m_pVCProjGenerator->GetAllConfigurationNames(configurationNames); + + // write the root configurations + m_XMLWriter.PushNode("Configurations"); + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + if (!WriteConfiguration(pConfiguration)) return false; + } + } + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("References"); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("Files"); + + CProjectFolder *pRootFolder = m_pVCProjGenerator->GetRootFolder(); + + for (auto iIndex = pRootFolder->m_Folders.Head(); + iIndex != pRootFolder->m_Folders.InvalidIndex(); + iIndex = pRootFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pRootFolder->m_Folders[iIndex])) return false; + } + + for (auto iIndex = pRootFolder->m_Files.Head(); + iIndex != pRootFolder->m_Files.InvalidIndex(); + iIndex = pRootFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pRootFolder->m_Files[iIndex])) return false; + } + + m_XMLWriter.PopNode(true); + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Xbox360::Save(const char *pOutputFilename) { + if (!m_XMLWriter.Open(pOutputFilename)) return false; + + bool bValid = WriteToXML(); + + m_XMLWriter.Close(); + + return bValid; +} + +bool CProjectGenerator_Xbox360::WriteNULLTool( + const char *pToolName, const CProjectConfiguration *pConfig) { + if (pConfig->m_bIsFileConfig) return true; + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write(CFmtStr("Name=\"%s\"", pToolName)); + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_Xbox360::WriteTool(const char *pToolName, + const CProjectTool *pProjectTool) { + if (!pProjectTool) { + // not an error, some tools n/a for a config + return true; + } + + m_XMLWriter.PushNode("Tool"); + + m_XMLWriter.Write(CFmtStr("Name=\"%s\"", pToolName)); + + for (int i = 0; + i < pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + i++) { + intp sortedIndex = + pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder[i]; + WriteProperty(&pProjectTool->m_PropertyStates.m_Properties[sortedIndex]); + } + + m_XMLWriter.PopNode(false); + + return true; +} + +bool CProjectGenerator_Xbox360::WriteProperty( + const PropertyState_t *pPropertyState, const char *pOutputName, + const char *pOutputValue) { + if (!pPropertyState) { + m_XMLWriter.Write(CFmtStrMax("%s=\"%s\"", pOutputName, pOutputValue)); + return true; + } + + if (!pOutputName) { + pOutputName = pPropertyState->m_pToolProperty->m_OutputString.Get(); + if (!pOutputName[0]) { + pOutputName = pPropertyState->m_pToolProperty->m_ParseString.Get(); + if (pOutputName[0] == '$') { + pOutputName++; + } + } + } + + switch (pPropertyState->m_pToolProperty->m_nType) { + case PT_BOOLEAN: { + bool bEnabled = Sys_StringToBool(pPropertyState->m_StringValue.Get()); + if (pPropertyState->m_pToolProperty->m_bInvertOutput) { + bEnabled ^= 1; + } + m_XMLWriter.Write( + CFmtStrMax("%s=\"%s\"", pOutputName, bEnabled ? "true" : "false")); + } break; + + case PT_STRING: { + // String() returns temporary object, so save in var to prevent stale + // memory usage. + CUtlString s = + m_XMLWriter.FixupXMLString(pPropertyState->m_StringValue.Get()); + m_XMLWriter.Write(CFmtStrMax("%s=\"%s\"", pOutputName, s.String())); + } break; + + case PT_LIST: + case PT_INTEGER: + m_XMLWriter.Write(CFmtStrMax("%s=\"%s\"", pOutputName, + pPropertyState->m_StringValue.Get())); + break; + + case PT_IGNORE: + break; + + default: + g_pVPC->VPCError( + "CProjectGenerator_Xbox360: WriteProperty, %s - not implemented", + pOutputName); + } + + return true; +} diff --git a/utils/vpc/projectgenerator_xbox360.h b/utils/vpc/projectgenerator_xbox360.h new file mode 100644 index 0000000..e755af9 --- /dev/null +++ b/utils/vpc/projectgenerator_xbox360.h @@ -0,0 +1,36 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_PROJECTGENERATOR_XBOX360_H_ +#define VPC_PROJECTGENERATOR_XBOX360_H_ + +#include "projectgenerator_vcproj.h" + +#define PROPERTYNAME(X, Y) X##_##Y, +enum Xbox360Properties_e { +#include "projectgenerator_xbox360.inc" +}; + +class CProjectGenerator_Xbox360 : public IVCProjWriter { + public: + CProjectGenerator_Xbox360(); + IBaseProjectGenerator *GetProjectGenerator() { return m_pVCProjGenerator; } + + virtual bool Save(const char *pOutputFilename); + + private: + bool WriteToXML(); + + bool WriteFolder(CProjectFolder *pFolder); + bool WriteFile(CProjectFile *pFile); + bool WriteConfiguration(CProjectConfiguration *pConfig); + bool WriteProperty(const PropertyState_t *pPropertyState, + const char *pOutputName = NULL, const char *pValue = NULL); + bool WriteTool(const char *pToolName, const CProjectTool *pProjectTool); + bool WriteNULLTool(const char *pToolName, + const CProjectConfiguration *pConfig); + + CXMLWriter m_XMLWriter; + CVCProjGenerator *m_pVCProjGenerator; +}; + +#endif // VPC_PROJECTGENERATOR_XBOX360_H_ diff --git a/utils/vpc/projectgenerator_xbox360.inc b/utils/vpc/projectgenerator_xbox360.inc new file mode 100644 index 0000000..d5fadba --- /dev/null +++ b/utils/vpc/projectgenerator_xbox360.inc @@ -0,0 +1,192 @@ + +//========= Copyright ?1996-2006, Valve Corporation, All rights reserved. ============// +// +// Property Enumerations +// +//=====================================================================================// + +// Config +PROPERTYNAME( XBOX360_GENERAL, ExcludedFromBuild ) +PROPERTYNAME( XBOX360_GENERAL, OutputDirectory ) +PROPERTYNAME( XBOX360_GENERAL, IntermediateDirectory ) +PROPERTYNAME( XBOX360_GENERAL, ConfigurationType ) +PROPERTYNAME( XBOX360_GENERAL, CharacterSet ) +PROPERTYNAME( XBOX360_GENERAL, WholeProgramOptimization ) +PROPERTYNAME( XBOX360_GENERAL, ExtensionsToDeleteOnClean ) +PROPERTYNAME( XBOX360_GENERAL, BuildLogFile ) +PROPERTYNAME( XBOX360_GENERAL, InheritedProjectPropertySheets ) + +// Debugging +PROPERTYNAME( XBOX360_DEBUGGING, Command ) +PROPERTYNAME( XBOX360_DEBUGGING, CommandArguments ) +PROPERTYNAME( XBOX360_DEBUGGING, RemoteMachine ) + +// Compiler +PROPERTYNAME( XBOX360_COMPILER, AdditionalOptions ) +PROPERTYNAME( XBOX360_COMPILER, Optimization ) +PROPERTYNAME( XBOX360_COMPILER, InlineFunctionExpansion ) +PROPERTYNAME( XBOX360_COMPILER, EnableIntrinsicFunctions ) +PROPERTYNAME( XBOX360_COMPILER, FavorSizeOrSpeed ) +PROPERTYNAME( XBOX360_COMPILER, EnableFiberSafeOptimizations ) +PROPERTYNAME( XBOX360_COMPILER, WholeProgramOptimization ) +PROPERTYNAME( XBOX360_COMPILER, AdditionalIncludeDirectories ) +PROPERTYNAME( XBOX360_COMPILER, PreprocessorDefinitions ) +PROPERTYNAME( XBOX360_COMPILER, IgnoreStandardIncludePath ) +PROPERTYNAME( XBOX360_COMPILER, GeneratePreprocessedFile ) +PROPERTYNAME( XBOX360_COMPILER, KeepComments ) +PROPERTYNAME( XBOX360_COMPILER, EnableStringPooling ) +PROPERTYNAME( XBOX360_COMPILER, EnableMinimalRebuild ) +PROPERTYNAME( XBOX360_COMPILER, EnableCPPExceptions ) +PROPERTYNAME( XBOX360_COMPILER, BasicRuntimeChecks ) +PROPERTYNAME( XBOX360_COMPILER, SmallerTypeCheck ) +PROPERTYNAME( XBOX360_COMPILER, RuntimeLibrary ) +PROPERTYNAME( XBOX360_COMPILER, StructMemberAlignment ) +PROPERTYNAME( XBOX360_COMPILER, BufferSecurityCheck ) +PROPERTYNAME( XBOX360_COMPILER, EnableFunctionLevelLinking ) +PROPERTYNAME( XBOX360_COMPILER, FloatingPointModel ) +PROPERTYNAME( XBOX360_COMPILER, EnableFloatingPointExceptions ) +PROPERTYNAME( XBOX360_COMPILER, DisableLanguageExtensions ) +PROPERTYNAME( XBOX360_COMPILER, DefaultCharUnsigned ) +PROPERTYNAME( XBOX360_COMPILER, TreatWCHAR_TAsBuiltInType ) +PROPERTYNAME( XBOX360_COMPILER, ForceConformanceInForLoopScope ) +PROPERTYNAME( XBOX360_COMPILER, EnableRunTimeTypeInfo ) +PROPERTYNAME( XBOX360_COMPILER, OpenMPSupport ) +PROPERTYNAME( XBOX360_COMPILER, CreateUsePrecompiledHeader ) +PROPERTYNAME( XBOX360_COMPILER, CreateUsePCHThroughFile ) +PROPERTYNAME( XBOX360_COMPILER, PrecompiledHeaderFile ) +PROPERTYNAME( XBOX360_COMPILER, ExpandAttributedSource ) +PROPERTYNAME( XBOX360_COMPILER, AssemblerOutput ) +PROPERTYNAME( XBOX360_COMPILER, ASMListLocation ) +PROPERTYNAME( XBOX360_COMPILER, ObjectFileName ) +PROPERTYNAME( XBOX360_COMPILER, ProgramDatabaseFileName ) +PROPERTYNAME( XBOX360_COMPILER, EnableBrowseInformation ) +PROPERTYNAME( XBOX360_COMPILER, BrowseFile ) +PROPERTYNAME( XBOX360_COMPILER, WarningLevel ) +PROPERTYNAME( XBOX360_COMPILER, TreatWarningsAsErrors ) +PROPERTYNAME( XBOX360_COMPILER, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_COMPILER, DebugInformationFormat ) +PROPERTYNAME( XBOX360_COMPILER, CompileAs ) +PROPERTYNAME( XBOX360_COMPILER, ForceIncludes ) +PROPERTYNAME( XBOX360_COMPILER, ShowIncludes ) +PROPERTYNAME( XBOX360_COMPILER, UndefineAllPreprocessorDefinitions ) +PROPERTYNAME( XBOX360_COMPILER, UndefinePreprocessorDefinitions ) +PROPERTYNAME( XBOX360_COMPILER, UseFullPaths ) +PROPERTYNAME( XBOX360_COMPILER, OmitDefaultLibraryNames ) +PROPERTYNAME( XBOX360_COMPILER, TrapIntegerDividesOptimization ) +PROPERTYNAME( XBOX360_COMPILER, PreschedulingOptimization ) +PROPERTYNAME( XBOX360_COMPILER, InlineAssemblyOptimization ) +PROPERTYNAME( XBOX360_COMPILER, RegisterReservation ) +PROPERTYNAME( XBOX360_COMPILER, Stalls ) +PROPERTYNAME( XBOX360_COMPILER, CallAttributedProfiling ) +PROPERTYNAME( XBOX360_COMPILER, UseUNICODEResponseFiles ) +PROPERTYNAME( XBOX360_COMPILER, GenerateXMLDocumentationFiles ) +PROPERTYNAME( XBOX360_COMPILER, XMLDocumentationFileName ) +PROPERTYNAME( XBOX360_COMPILER, DisableSpecificWarnings ) + +// Librarian +PROPERTYNAME( XBOX360_LIBRARIAN, OutputFile ) +PROPERTYNAME( XBOX360_LIBRARIAN, AdditionalDependencies ) +PROPERTYNAME( XBOX360_LIBRARIAN, AdditionalLibraryDirectories ) +PROPERTYNAME( XBOX360_LIBRARIAN, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_LIBRARIAN, ModuleDefinitionFileName ) +PROPERTYNAME( XBOX360_LIBRARIAN, IgnoreAllDefaultLibraries ) +PROPERTYNAME( XBOX360_LIBRARIAN, IgnoreSpecificLibrary ) +PROPERTYNAME( XBOX360_LIBRARIAN, ExportNamedFunctions ) +PROPERTYNAME( XBOX360_LIBRARIAN, ForceSymbolReferences ) +PROPERTYNAME( XBOX360_LIBRARIAN, UseUNICODEResponseFiles ) +PROPERTYNAME( XBOX360_LIBRARIAN, LinkLibraryDependencies ) +PROPERTYNAME( XBOX360_LIBRARIAN, AdditionalOptions ) + +// Linker +PROPERTYNAME( XBOX360_LINKER, IgnoreImportLibrary ) +PROPERTYNAME( XBOX360_LINKER, AdditionalOptions ) +PROPERTYNAME( XBOX360_LINKER, AdditionalDependencies ) +PROPERTYNAME( XBOX360_LINKER, ShowProgress ) +PROPERTYNAME( XBOX360_LINKER, OutputFile ) +PROPERTYNAME( XBOX360_LINKER, EnableIncrementalLinking ) +PROPERTYNAME( XBOX360_LINKER, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_LINKER, AdditionalLibraryDirectories ) +PROPERTYNAME( XBOX360_LINKER, IgnoreAllDefaultLibraries ) +PROPERTYNAME( XBOX360_LINKER, IgnoreSpecificLibrary ) +PROPERTYNAME( XBOX360_LINKER, ModuleDefinitionFile ) +PROPERTYNAME( XBOX360_LINKER, GenerateDebugInfo ) +PROPERTYNAME( XBOX360_LINKER, GenerateProgramDatabaseFile ) +PROPERTYNAME( XBOX360_LINKER, GenerateMapFile ) +PROPERTYNAME( XBOX360_LINKER, MapFileName ) +PROPERTYNAME( XBOX360_LINKER, MapExports ) +PROPERTYNAME( XBOX360_LINKER, StackReserveSize ) +PROPERTYNAME( XBOX360_LINKER, StackCommitSize ) +PROPERTYNAME( XBOX360_LINKER, References ) +PROPERTYNAME( XBOX360_LINKER, EnableCOMDATFolding ) +PROPERTYNAME( XBOX360_LINKER, LinkTimeCodeGeneration ) +PROPERTYNAME( XBOX360_LINKER, EntryPoint ) +PROPERTYNAME( XBOX360_LINKER, NoEntryPoint ) +PROPERTYNAME( XBOX360_LINKER, SetChecksum ) +PROPERTYNAME( XBOX360_LINKER, BaseAddress ) +PROPERTYNAME( XBOX360_LINKER, ImportLibrary ) +PROPERTYNAME( XBOX360_LINKER, FixedBaseAddress ) +PROPERTYNAME( XBOX360_LINKER, ErrorReporting ) +PROPERTYNAME( XBOX360_LINKER, FunctionOrder ) +PROPERTYNAME( XBOX360_LINKER, Version ) +PROPERTYNAME( XBOX360_LINKER, LinkLibraryDependencies ) +PROPERTYNAME( XBOX360_LINKER, UseLibraryDependencyInputs ) +PROPERTYNAME( XBOX360_LINKER, UseUNICODEResponseFiles ) +PROPERTYNAME( XBOX360_LINKER, ForceSymbolReferences ) +PROPERTYNAME( XBOX360_LINKER, StripPrivateSymbols ) +PROPERTYNAME( XBOX360_LINKER, ProfileGuidedDatabase ) +PROPERTYNAME( XBOX360_LINKER, MergeSections ) + +// Browse Information +PROPERTYNAME( XBOX360_BROWSEINFORMATION, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_BROWSEINFORMATION, OutputFile ) +PROPERTYNAME( XBOX360_BROWSEINFORMATION, AdditionalOptions ) + +// Pre Build +PROPERTYNAME( XBOX360_PREBUILDEVENT, Description ) +PROPERTYNAME( XBOX360_PREBUILDEVENT, CommandLine ) +PROPERTYNAME( XBOX360_PREBUILDEVENT, ExcludedFromBuild ) + +// Pre Link +PROPERTYNAME( XBOX360_PRELINKEVENT, Description ) +PROPERTYNAME( XBOX360_PRELINKEVENT, CommandLine ) +PROPERTYNAME( XBOX360_PRELINKEVENT, ExcludedFromBuild ) + +// Post Build +PROPERTYNAME( XBOX360_POSTBUILDEVENT, Description ) +PROPERTYNAME( XBOX360_POSTBUILDEVENT, CommandLine ) +PROPERTYNAME( XBOX360_POSTBUILDEVENT, ExcludedFromBuild ) + +// Custom Build +PROPERTYNAME( XBOX360_CUSTOMBUILDSTEP, Description ) +PROPERTYNAME( XBOX360_CUSTOMBUILDSTEP, CommandLine ) +PROPERTYNAME( XBOX360_CUSTOMBUILDSTEP, Outputs ) +PROPERTYNAME( XBOX360_CUSTOMBUILDSTEP, AdditionalDependencies ) + +// Image Conversion +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, OutputFile ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, ProjectDefaults ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, WorkspaceSize ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, ExportByName ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, OpticalDiscDriveMapping ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, PAL50Incompatible ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, TitleID ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, LANKey ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, BaseAddress ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, HeapSize ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, AdditionalSections ) +PROPERTYNAME( XBOX360_XBOX360IMAGECONVERSION, AdditionalOptions ) + +// Console Deployment +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, ExcludedFromBuild ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, DeploymentRoot ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, DeploymentFiles ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, Progress ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, ForceCopy ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, DeploymentType ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, EmulationType ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, Layout ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, LayoutRoot ) +PROPERTYNAME( XBOX360_CONSOLEDEPLOYMENT, AdditionalOptions ) + diff --git a/utils/vpc/projectgenerator_xbox360_2010.cpp b/utils/vpc/projectgenerator_xbox360_2010.cpp new file mode 100644 index 0000000..289e747 --- /dev/null +++ b/utils/vpc/projectgenerator_xbox360_2010.cpp @@ -0,0 +1,611 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#include "projectgenerator_xbox360_2010.h" + +#include "tier0/memdbgon.h" + +#undef PROPERTYNAME +#define PROPERTYNAME(X, Y) {X##_##Y, #X, #Y}, + +static PropertyName_t s_Xbox360PropertyNames_2010[] = { +#include "projectgenerator_xbox360_2010.inc" + {-1, NULL, NULL}}; + +IBaseProjectGenerator *GetXbox360ProjectGenerator_2010() { + static CProjectGenerator_Xbox360_2010 *s_pProjectGenerator = NULL; + if (!s_pProjectGenerator) { + s_pProjectGenerator = new CProjectGenerator_Xbox360_2010(); + } + + return s_pProjectGenerator->GetProjectGenerator(); +} + +CProjectGenerator_Xbox360_2010::CProjectGenerator_Xbox360_2010() { + m_pVCProjGenerator = new CVCProjGenerator(); + m_pVCProjGenerator->SetupGeneratorDefinition(this, "xbox360_2010.def", + s_Xbox360PropertyNames_2010); +} + +enum TypeKeyNames_e { + TKN_LIBRARY = 0, + TKN_INCLUDE, + TKN_COMPILE, + TKN_RESOURCECOMPILE, + TKN_CUSTOMBUILD, + TKN_NONE, + TKN_MAX_COUNT, +}; + +static const char *s_TypeKeyNames[] = {"Library", "ClInclude", + "ClCompile", "ResourceCompile", + "CustomBuild", "None"}; + +const char *CProjectGenerator_Xbox360_2010::GetKeyNameForFile( + CProjectFile *pFile) { + static_assert(V_ARRAYSIZE(s_TypeKeyNames) == TKN_MAX_COUNT); + + const char *pExtension = V_GetFileExtension(pFile->m_Name.Get()); + + const char *pKeyName = s_TypeKeyNames[TKN_NONE]; + if (pExtension) { + if (pFile->m_Configs.Count() && pFile->m_Configs[0]->GetCustomBuildTool()) { + pKeyName = s_TypeKeyNames[TKN_CUSTOMBUILD]; + } else if (IsCFileExtension(pExtension)) { + pKeyName = s_TypeKeyNames[TKN_COMPILE]; + } else if (IsHFileExtension(pExtension)) { + pKeyName = s_TypeKeyNames[TKN_INCLUDE]; + } else if (!V_stricmp(pExtension, "lib")) { + pKeyName = s_TypeKeyNames[TKN_LIBRARY]; + } else if (!V_stricmp(pExtension, "rc")) { + pKeyName = s_TypeKeyNames[TKN_RESOURCECOMPILE]; + } + } + + return pKeyName; +} + +bool CProjectGenerator_Xbox360_2010::WritePropertyGroupTool( + CProjectTool *pProjectTool, CProjectConfiguration *pConfiguration) { + if (!pProjectTool) return true; + + for (intp i = 0; + i < pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + i++) { + intp sortedIndex = + pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (!pProjectTool->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty( + &pProjectTool->m_PropertyStates.m_Properties[sortedIndex], true, + pConfiguration->m_Name.Get())) + return false; + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteFile(CProjectFile *pFile, + const char *pFileTypeName) { + const char *pKeyName = GetKeyNameForFile(pFile); + if (V_stricmp(pFileTypeName, pKeyName)) { + // skip it + return true; + } + + if (!pFile->m_Configs.Count()) { + m_XMLWriter.Write( + CFmtStrMax("<%s Include=\"%s\" />", pKeyName, pFile->m_Name.Get())); + } else { + m_XMLWriter.PushNode(pKeyName, + CFmtStr("Include=\"%s\"", pFile->m_Name.Get())); + + for (int i = 0; i < pFile->m_Configs.Count(); i++) { + if (!WriteConfiguration(pFile->m_Configs[i])) return false; + } + + m_XMLWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteFolder(CProjectFolder *pFolder, + const char *pFileTypeName, + int nDepth) { + if (!nDepth) { + m_XMLWriter.PushNode("ItemGroup"); + } + + for (auto iIndex = pFolder->m_Files.Head(); + iIndex != pFolder->m_Files.InvalidIndex(); + iIndex = pFolder->m_Files.Next(iIndex)) { + if (!WriteFile(pFolder->m_Files[iIndex], pFileTypeName)) return false; + } + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolder(pFolder->m_Folders[iIndex], pFileTypeName, nDepth + 1)) + return false; + } + + if (!nDepth) { + m_XMLWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteConfiguration( + CProjectConfiguration *pConfig) { + if (!pConfig->m_bIsFileConfig) { + m_XMLWriter.PushNode("PropertyGroup", + CFmtStr("Condition=\"'$(Configuration)|$(Platform)'=='" + "%s|Xbox 360'\" Label=\"Configuration\"", + pConfig->m_Name.Get())); + + for (intp i = 0; + i < pConfig->m_PropertyStates.m_PropertiesInOutputOrder.Count(); i++) { + intp sortedIndex = pConfig->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (pConfig->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty(&pConfig->m_PropertyStates.m_Properties[sortedIndex])) + return false; + } + + WriteProperty(NULL, false, NULL, "UseOfAtl", "false"); + + m_XMLWriter.PopNode(true); + } else { + for (intp i = 0; + i < pConfig->m_PropertyStates.m_PropertiesInOutputOrder.Count(); i++) { + intp sortedIndex = pConfig->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (!WriteProperty(&pConfig->m_PropertyStates.m_Properties[sortedIndex], + true, pConfig->m_Name.Get())) + return false; + } + + if (!WriteTool("ClCompile", pConfig->GetCompilerTool(), pConfig)) + return false; + + if (!WriteTool("CustomBuildStep", pConfig->GetCustomBuildTool(), pConfig)) + return false; + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteTools( + CProjectConfiguration *pConfig) { + m_XMLWriter.PushNode( + "ItemDefinitionGroup", + CFmtStr("Condition=\"'$(Configuration)|$(Platform)'=='%s|Xbox 360'\"", + pConfig->m_Name.Get())); + + if (!WriteTool("PreBuildEvent", pConfig->GetPreBuildEventTool(), pConfig)) + return false; + + if (!WriteTool("CustomBuildStep", pConfig->GetCustomBuildTool(), pConfig)) + return false; + + if (!WriteTool("ClCompile", pConfig->GetCompilerTool(), pConfig)) + return false; + + if (!WriteTool("PreLinkEvent", pConfig->GetPreLinkEventTool(), pConfig)) + return false; + + if (!WriteTool("Link", pConfig->GetLinkerTool(), pConfig)) return false; + + if (!WriteTool("Lib", pConfig->GetLibrarianTool(), pConfig)) return false; + + if (!WriteTool("ImageXex", pConfig->GetXboxImageTool(), pConfig)) + return false; + + if (!WriteTool("Bscmake", pConfig->GetBrowseInfoTool(), pConfig)) + return false; + + if (!WriteTool("Deploy", pConfig->GetXboxDeploymentTool(), pConfig)) + return false; + + if (!WriteTool("PostBuildEvent", pConfig->GetPostBuildEventTool(), pConfig)) + return false; + + m_XMLWriter.PopNode(true); + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WritePrimaryXML( + const char *pOutputFilename) { + if (!m_XMLWriter.Open(pOutputFilename, true)) return false; + + m_XMLWriter.PushNode( + "Project", + "DefaultTargets=\"Build\" ToolsVersion=\"4.0\" " + "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\""); + + m_XMLWriter.PushNode("ItemGroup", "Label=\"ProjectConfigurations\""); + CUtlVector configurationNames; + m_pVCProjGenerator->GetAllConfigurationNames(configurationNames); + for (int i = 0; i < configurationNames.Count(); i++) { + m_XMLWriter.PushNode( + "ProjectConfiguration", + CFmtStr("Include=\"%s|Xbox 360\"", configurationNames[i].Get())); + m_XMLWriter.WriteLineNode("Configuration", "", configurationNames[i].Get()); + m_XMLWriter.WriteLineNode("Platform", "", "Xbox 360"); + m_XMLWriter.PopNode(true); + } + m_XMLWriter.PopNode(true); + + m_XMLWriter.PushNode("PropertyGroup", "Label=\"Globals\""); + m_XMLWriter.WriteLineNode("ProjectName", "", + m_pVCProjGenerator->GetProjectName().Get()); + m_XMLWriter.WriteLineNode("ProjectGuid", "", + m_pVCProjGenerator->GetGUIDString().Get()); + m_XMLWriter.PopNode(true); + + m_XMLWriter.Write( + ""); + + // write the root configurations + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + if (!WriteConfiguration(pConfiguration)) return false; + } + } + + m_XMLWriter.Write( + ""); + m_XMLWriter.PushNode("ImportGroup", "Label=\"ExtensionSettings\""); + m_XMLWriter.PopNode(true); + + for (int i = 0; i < configurationNames.Count(); i++) { + m_XMLWriter.PushNode("ImportGroup", + CFmtStr("Condition=\"'$(Configuration)|$(Platform)'=='" + "%s|Xbox 360'\" Label=\"PropertySheets\"", + configurationNames[i].Get())); + m_XMLWriter.Write( + ""); + m_XMLWriter.PopNode(true); + } + + m_XMLWriter.Write(""); + + m_XMLWriter.PushNode("PropertyGroup"); + m_XMLWriter.WriteLineNode("_ProjectFileVersion", "", "10.0.30319.1"); + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + for (intp j = 0; + j < + pConfiguration->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + j++) { + intp sortedIndex = + pConfiguration->m_PropertyStates.m_PropertiesInOutputOrder[j]; + if (!pConfiguration->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty( + &pConfiguration->m_PropertyStates.m_Properties[sortedIndex], + true, pConfiguration->m_Name.Get())) + return false; + } + + if (!WritePropertyGroupTool(pConfiguration->GetPreBuildEventTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetPreLinkEventTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetLinkerTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetLibrarianTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetPostBuildEventTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetXboxImageTool(), + pConfiguration)) + return false; + + if (!WritePropertyGroupTool(pConfiguration->GetXboxDeploymentTool(), + pConfiguration)) + return false; + } + } + m_XMLWriter.PopNode(true); + + // write the tool configurations + for (int i = 0; i < configurationNames.Count(); i++) { + CProjectConfiguration *pConfiguration = NULL; + if (m_pVCProjGenerator->GetRootConfiguration(configurationNames[i].Get(), + &pConfiguration)) { + if (!WriteTools(pConfiguration)) return false; + } + } + + // write root folders + for (int i = 0; i < TKN_MAX_COUNT; i++) { + if (!WriteFolder(m_pVCProjGenerator->GetRootFolder(), s_TypeKeyNames[i], 0)) + return false; + } + + m_XMLWriter.Write( + ""); + m_XMLWriter.PushNode("ImportGroup", "Label=\"ExtensionTargets\""); + m_XMLWriter.PopNode(true); + + m_XMLWriter.PopNode(true); + + m_XMLWriter.Close(); + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteFolderToSecondaryXML( + CProjectFolder *pFolder, const char *pParentPath) { + CUtlString parentPath = + CUtlString(CFmtStr("%s%s%s", pParentPath, pParentPath[0] ? "\\" : "", + pFolder->m_Name.Get())); + + MD5Context_t ctx; + unsigned char digest[MD5_DIGEST_LENGTH]; + V_memset(&ctx, 0, sizeof(ctx)); + V_memset(digest, 0, sizeof(digest)); + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char *)parentPath.Get(), V_strlen(parentPath.Get())); + MD5Final(digest, &ctx); + + char szMD5[64]; + V_binarytohex(digest, MD5_DIGEST_LENGTH, szMD5, sizeof(szMD5)); + V_strupr(szMD5); + + char szGUID[MAX_PATH]; + V_snprintf(szGUID, sizeof(szGUID), "{%8.8s-%4.4s-%4.4s-%4.4s-%12.12s}", szMD5, + &szMD5[8], &szMD5[12], &szMD5[16], &szMD5[20]); + + m_XMLFilterWriter.PushNode("Filter", + CFmtStr("Include=\"%s\"", parentPath.Get())); + m_XMLFilterWriter.WriteLineNode("UniqueIdentifier", "", szGUID); + m_XMLFilterWriter.PopNode(true); + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolderToSecondaryXML(pFolder->m_Folders[iIndex], + parentPath.Get())) + return false; + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteFileToSecondaryXML( + CProjectFile *pFile, const char *pParentPath, const char *pFileTypeName) { + const char *pKeyName = GetKeyNameForFile(pFile); + if (V_stricmp(pFileTypeName, pKeyName)) { + // skip it + return true; + } + + if (pParentPath) { + m_XMLFilterWriter.PushNode(pKeyName, + CFmtStr("Include=\"%s\"", pFile->m_Name.Get())); + m_XMLFilterWriter.WriteLineNode("Filter", "", pParentPath); + m_XMLFilterWriter.PopNode(true); + } else { + m_XMLFilterWriter.Write( + CFmtStr("<%s Include=\"%s\" />", pKeyName, pFile->m_Name.Get())); + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteFolderContentsToSecondaryXML( + CProjectFolder *pFolder, const char *pParentPath, const char *pFileTypeName, + int nDepth) { + CUtlString parentPath; + if (pParentPath) { + parentPath = CFmtStr("%s%s%s", pParentPath, pParentPath[0] ? "\\" : "", + pFolder->m_Name.Get()); + } + + if (!nDepth) { + m_XMLFilterWriter.PushNode("ItemGroup", NULL); + } + + for (auto iIndex = pFolder->m_Files.Head(); + iIndex != pFolder->m_Files.InvalidIndex(); + iIndex = pFolder->m_Files.Next(iIndex)) { + if (!WriteFileToSecondaryXML(pFolder->m_Files[iIndex], parentPath.Get(), + pFileTypeName)) + return false; + } + + for (auto iIndex = pFolder->m_Folders.Head(); + iIndex != pFolder->m_Folders.InvalidIndex(); + iIndex = pFolder->m_Folders.Next(iIndex)) { + if (!WriteFolderContentsToSecondaryXML(pFolder->m_Folders[iIndex], + parentPath.Get(), pFileTypeName, + nDepth + 1)) + return false; + } + + if (!nDepth) { + m_XMLFilterWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteSecondaryXML( + const char *pOutputFilename) { + if (!m_XMLFilterWriter.Open(pOutputFilename, true)) return false; + + m_XMLFilterWriter.PushNode( + "Project", + "ToolsVersion=\"4.0\" " + "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\""); + + // write the root folders + m_XMLFilterWriter.PushNode("ItemGroup", NULL); + CProjectFolder *pRootFolder = m_pVCProjGenerator->GetRootFolder(); + for (auto iIndex = pRootFolder->m_Folders.Head(); + iIndex != pRootFolder->m_Folders.InvalidIndex(); + iIndex = pRootFolder->m_Folders.Next(iIndex)) { + if (!WriteFolderToSecondaryXML(pRootFolder->m_Folders[iIndex], "")) + return false; + } + m_XMLFilterWriter.PopNode(true); + + // write folder contents + for (int i = 0; i < TKN_MAX_COUNT; i++) { + if (!WriteFolderContentsToSecondaryXML(pRootFolder, NULL, s_TypeKeyNames[i], + 0)) + return false; + } + + m_XMLFilterWriter.PopNode(true); + + m_XMLFilterWriter.Close(); + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteTool(const char *pToolName, + const CProjectTool *pProjectTool, + CProjectConfiguration *pConfig) { + if (!pProjectTool) { + // not an error, some tools n/a for a config + return true; + } + + if (!pConfig->m_bIsFileConfig) { + m_XMLWriter.PushNode(pToolName, NULL); + } + + for (intp i = 0; + i < pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder.Count(); + i++) { + intp sortedIndex = + pProjectTool->m_PropertyStates.m_PropertiesInOutputOrder[i]; + if (!pConfig->m_bIsFileConfig) { + if (pProjectTool->m_PropertyStates.m_Properties[sortedIndex] + .m_pToolProperty->m_bEmitAsGlobalProperty) + continue; + + if (!WriteProperty( + &pProjectTool->m_PropertyStates.m_Properties[sortedIndex])) + return false; + } else { + if (!WriteProperty( + &pProjectTool->m_PropertyStates.m_Properties[sortedIndex], true, + pConfig->m_Name.Get())) + return false; + } + } + + if (!pConfig->m_bIsFileConfig) { + m_XMLWriter.PopNode(true); + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::WriteProperty( + const PropertyState_t *pPropertyState, bool bEmitConfiguration, + const char *pConfigName, const char *pOutputName, + const char *pOutputValue) { + if (!pPropertyState) { + m_XMLWriter.WriteLineNode(pOutputName, "", pOutputValue); + return true; + } + + if (!pOutputName) { + pOutputName = pPropertyState->m_pToolProperty->m_OutputString.Get(); + if (!pOutputName[0]) { + pOutputName = pPropertyState->m_pToolProperty->m_ParseString.Get(); + if (pOutputName[0] == '$') { + pOutputName++; + } + } + } + + const char *pCondition = ""; + CUtlString conditionString; + if (bEmitConfiguration) { + conditionString = + CFmtStr(" Condition=\"'$(Configuration)|$(Platform)'=='%s|Xbox 360'\"", + pConfigName); + pCondition = conditionString.Get(); + } + + switch (pPropertyState->m_pToolProperty->m_nType) { + case PT_BOOLEAN: { + bool bEnabled = Sys_StringToBool(pPropertyState->m_StringValue.Get()); + if (pPropertyState->m_pToolProperty->m_bInvertOutput) { + bEnabled ^= 1; + } + m_XMLWriter.WriteLineNode(pOutputName, pCondition, + bEnabled ? "true" : "false"); + } break; + + case PT_STRING: + m_XMLWriter.WriteLineNode( + pOutputName, pCondition, + m_XMLWriter.FixupXMLString(pPropertyState->m_StringValue.Get())); + break; + + case PT_LIST: + case PT_INTEGER: + m_XMLWriter.WriteLineNode(pOutputName, pCondition, + pPropertyState->m_StringValue.Get()); + break; + + case PT_IGNORE: + break; + + default: + g_pVPC->VPCError( + "CProjectGenerator_Xbox360_2010: WriteProperty, %s - not " + "implemented", + pOutputName); + } + + return true; +} + +bool CProjectGenerator_Xbox360_2010::Save(const char *pOutputFilename) { + bool bValid = WritePrimaryXML(pOutputFilename); + if (bValid) { + bValid = WriteSecondaryXML(CFmtStr("%s.filters", pOutputFilename)); + if (!bValid) { + g_pVPC->VPCError("Cannot save to the specified project '%s'", + pOutputFilename); + } + } + + return bValid; +} diff --git a/utils/vpc/projectgenerator_xbox360_2010.h b/utils/vpc/projectgenerator_xbox360_2010.h new file mode 100644 index 0000000..052f1f1 --- /dev/null +++ b/utils/vpc/projectgenerator_xbox360_2010.h @@ -0,0 +1,59 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_PROJECTGENERATOR_XBOX360_2010_H_ +#define VPC_PROJECTGENERATOR_XBOX360_2010_H_ + +#include "projectgenerator_vcproj.h" + +#define PROPERTYNAME(X, Y) X##_##Y, + +enum Xbox360_2010_Properties_e { +#include "projectgenerator_xbox360_2010.inc" +}; + +class CProjectGenerator_Xbox360_2010 : public IVCProjWriter { + public: + CProjectGenerator_Xbox360_2010(); + IBaseProjectGenerator *GetProjectGenerator() { return m_pVCProjGenerator; } + + virtual bool Save(const char *pOutputFilename); + + private: + // primary XML - foo.vcxproj + bool WritePrimaryXML(const char *pOutputFilename); + bool WriteFolder(CProjectFolder *pFolder, const char *pFileTypeName, + int nDepth); + bool WriteFile(CProjectFile *pFile, const char *pFileTypeName); + bool WriteConfiguration(CProjectConfiguration *pConfig); + bool WriteTools(CProjectConfiguration *pConfig); + bool WriteProperty(const PropertyState_t *pPropertyState, + bool bEmitConfiguration = false, + const char *pConfigurationName = NULL, + const char *pOutputName = NULL, const char *pValue = NULL); + bool WriteTool(const char *pToolName, const CProjectTool *pProjectTool, + CProjectConfiguration *pConfig); + bool WriteNULLTool(const char *pToolName, + const CProjectConfiguration *pConfig); + bool WritePropertyGroupTool(CProjectTool *pProjectTool, + CProjectConfiguration *pConfiguration); + bool WritePropertyGroup(); + + // secondary XML - foo.vcxproj.filters + bool WriteSecondaryXML(const char *pOutputFilename); + bool WriteFolderToSecondaryXML(CProjectFolder *pFolder, + const char *pParentPath); + bool WriteFolderContentsToSecondaryXML(CProjectFolder *pFolder, + const char *pParentPath, + const char *pFileTypeName, int nDepth); + bool WriteFileToSecondaryXML(CProjectFile *pFile, const char *pParentPath, + const char *pFileTypeName); + + const char *GetKeyNameForFile(CProjectFile *pFile); + + CXMLWriter m_XMLWriter; + CXMLWriter m_XMLFilterWriter; + + CVCProjGenerator *m_pVCProjGenerator; +}; + +#endif // VPC_PROJECTGENERATOR_XBOX360_2010_H_ diff --git a/utils/vpc/projectgenerator_xbox360_2010.inc b/utils/vpc/projectgenerator_xbox360_2010.inc new file mode 100644 index 0000000..11b35d1 --- /dev/null +++ b/utils/vpc/projectgenerator_xbox360_2010.inc @@ -0,0 +1,210 @@ + +//========= Copyright 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Property Enumerations +// +//=====================================================================================// + +// Config +PROPERTYNAME( XBOX360_2010_GENERAL, ExcludedFromBuild ) +PROPERTYNAME( XBOX360_2010_GENERAL, OutputDirectory ) +PROPERTYNAME( XBOX360_2010_GENERAL, IntermediateDirectory ) +PROPERTYNAME( XBOX360_2010_GENERAL, ConfigurationType ) +PROPERTYNAME( XBOX360_2010_GENERAL, CharacterSet ) +PROPERTYNAME( XBOX360_2010_GENERAL, WholeProgramOptimization ) +PROPERTYNAME( XBOX360_2010_GENERAL, ExtensionsToDeleteOnClean ) +PROPERTYNAME( XBOX360_2010_GENERAL, BuildLogFile ) +PROPERTYNAME( XBOX360_2010_GENERAL, PlatformToolset ) + +// Debugging +PROPERTYNAME( XBOX360_2010_DEBUGGING, Command ) +PROPERTYNAME( XBOX360_2010_DEBUGGING, CommandArguments ) +PROPERTYNAME( XBOX360_2010_DEBUGGING, RemoteMachine ) +PROPERTYNAME( XBOX360_2010_DEBUGGING, MapDVDDrive ) +PROPERTYNAME( XBOX360_2010_DEBUGGING, CheckUpToDate ) + +// Compiler +PROPERTYNAME( XBOX360_2010_COMPILER, AdditionalOptions ) +PROPERTYNAME( XBOX360_2010_COMPILER, Optimization ) +PROPERTYNAME( XBOX360_2010_COMPILER, InlineFunctionExpansion ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableIntrinsicFunctions ) +PROPERTYNAME( XBOX360_2010_COMPILER, FavorSizeOrSpeed ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableFiberSafeOptimizations ) +PROPERTYNAME( XBOX360_2010_COMPILER, WholeProgramOptimization ) +PROPERTYNAME( XBOX360_2010_COMPILER, AdditionalIncludeDirectories ) +PROPERTYNAME( XBOX360_2010_COMPILER, PreprocessorDefinitions ) +PROPERTYNAME( XBOX360_2010_COMPILER, IgnoreStandardIncludePaths ) +PROPERTYNAME( XBOX360_2010_COMPILER, PreprocessToAFile ) +PROPERTYNAME( XBOX360_2010_COMPILER, PreprocessSuppressLineNumbers ) +PROPERTYNAME( XBOX360_2010_COMPILER, KeepComments ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableStringPooling ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableMinimalRebuild ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableCPPExceptions ) +PROPERTYNAME( XBOX360_2010_COMPILER, BasicRuntimeChecks ) +PROPERTYNAME( XBOX360_2010_COMPILER, RuntimeLibrary ) +PROPERTYNAME( XBOX360_2010_COMPILER, StructMemberAlignment ) +PROPERTYNAME( XBOX360_2010_COMPILER, BufferSecurityCheck ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableFunctionLevelLinking ) +PROPERTYNAME( XBOX360_2010_COMPILER, FloatingPointModel ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableFloatingPointExceptions ) +PROPERTYNAME( XBOX360_2010_COMPILER, DisableLanguageExtensions ) +PROPERTYNAME( XBOX360_2010_COMPILER, TreatWCHAR_TAsBuiltInType ) +PROPERTYNAME( XBOX360_2010_COMPILER, ForceConformanceInForLoopScope ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableRunTimeTypeInfo ) +PROPERTYNAME( XBOX360_2010_COMPILER, OpenMPSupport ) +PROPERTYNAME( XBOX360_2010_COMPILER, PrecompiledHeader ) +PROPERTYNAME( XBOX360_2010_COMPILER, PrecompiledHeaderFile ) +PROPERTYNAME( XBOX360_2010_COMPILER, PrecompiledHeaderOutputFile ) +PROPERTYNAME( XBOX360_2010_COMPILER, ExpandAttributedSource ) +PROPERTYNAME( XBOX360_2010_COMPILER, AssemblerOutput ) +PROPERTYNAME( XBOX360_2010_COMPILER, ASMListLocation ) +PROPERTYNAME( XBOX360_2010_COMPILER, ObjectFileName ) +PROPERTYNAME( XBOX360_2010_COMPILER, ProgramDatabaseFileName ) +PROPERTYNAME( XBOX360_2010_COMPILER, EnableBrowseInformation ) +PROPERTYNAME( XBOX360_2010_COMPILER, BrowseInformationFile ) +PROPERTYNAME( XBOX360_2010_COMPILER, WarningLevel ) +PROPERTYNAME( XBOX360_2010_COMPILER, TreatWarningsAsErrors ) +PROPERTYNAME( XBOX360_2010_COMPILER, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_2010_COMPILER, DebugInformationFormat ) +PROPERTYNAME( XBOX360_2010_COMPILER, CompileAs ) +PROPERTYNAME( XBOX360_2010_COMPILER, ForcedIncludeFile ) +PROPERTYNAME( XBOX360_2010_COMPILER, ShowIncludes ) +PROPERTYNAME( XBOX360_2010_COMPILER, UndefineAllPreprocessorDefinitions ) +PROPERTYNAME( XBOX360_2010_COMPILER, UndefinePreprocessorDefinitions ) +PROPERTYNAME( XBOX360_2010_COMPILER, UseFullPaths ) +PROPERTYNAME( XBOX360_2010_COMPILER, OmitDefaultLibraryName ) +PROPERTYNAME( XBOX360_2010_COMPILER, TrapIntegerDividesOptimization ) +PROPERTYNAME( XBOX360_2010_COMPILER, PreschedulingOptimization ) +PROPERTYNAME( XBOX360_2010_COMPILER, InlineAssemblyOptimization ) +PROPERTYNAME( XBOX360_2010_COMPILER, RegisterReservation ) +PROPERTYNAME( XBOX360_2010_COMPILER, AnalyzeStalls ) +PROPERTYNAME( XBOX360_2010_COMPILER, CallAttributedProfiling ) +PROPERTYNAME( XBOX360_2010_COMPILER, SmallerTypeCheck ) +PROPERTYNAME( XBOX360_2010_COMPILER, DisableSpecificWarnings ) +PROPERTYNAME( XBOX360_2010_COMPILER, MultiProcessorCompilation ) +PROPERTYNAME( XBOX360_2010_COMPILER, UseUnicodeForAssemblerListing ) +PROPERTYNAME( XBOX360_2010_COMPILER, ForcedUsingFile ) +PROPERTYNAME( XBOX360_2010_COMPILER, DeduceVariableType ) +PROPERTYNAME( XBOX360_2010_COMPILER, CodeAnalysisForCCPP ) +PROPERTYNAME( XBOX360_2010_COMPILER, DisabledWarningDirectories ) + +// Librarian +PROPERTYNAME( XBOX360_2010_LIBRARIAN, UseUNICODEResponseFiles ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, AdditionalDependencies ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, OutputFile ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, AdditionalLibraryDirectories ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, ModuleDefinitionFileName ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, IgnoreAllDefaultLibraries ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, IgnoreSpecificDefaultLibraries ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, ExportNamedFunctions ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, ForceSymbolReferences ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, LinkLibraryDependencies ) +PROPERTYNAME( XBOX360_2010_LIBRARIAN, AdditionalOptions ) + +// Linker +PROPERTYNAME( XBOX360_2010_LINKER, IgnoreImportLibrary ) +PROPERTYNAME( XBOX360_2010_LINKER, AdditionalOptions ) +PROPERTYNAME( XBOX360_2010_LINKER, AdditionalDependencies ) +PROPERTYNAME( XBOX360_2010_LINKER, ShowProgress ) +PROPERTYNAME( XBOX360_2010_LINKER, OutputFile ) +PROPERTYNAME( XBOX360_2010_LINKER, Version ) +PROPERTYNAME( XBOX360_2010_LINKER, EnableIncrementalLinking ) +PROPERTYNAME( XBOX360_2010_LINKER, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_2010_LINKER, AdditionalLibraryDirectories ) +PROPERTYNAME( XBOX360_2010_LINKER, IgnoreAllDefaultLibraries ) +PROPERTYNAME( XBOX360_2010_LINKER, IgnoreSpecificDefaultLibraries ) +PROPERTYNAME( XBOX360_2010_LINKER, ModuleDefinitionFile ) +PROPERTYNAME( XBOX360_2010_LINKER, GenerateDebugInfo ) +PROPERTYNAME( XBOX360_2010_LINKER, GenerateProgramDatabaseFile ) +PROPERTYNAME( XBOX360_2010_LINKER, GenerateMapFile ) +PROPERTYNAME( XBOX360_2010_LINKER, MapFileName ) +PROPERTYNAME( XBOX360_2010_LINKER, MapExports ) +PROPERTYNAME( XBOX360_2010_LINKER, StackCommitSize ) +PROPERTYNAME( XBOX360_2010_LINKER, References ) +PROPERTYNAME( XBOX360_2010_LINKER, EnableCOMDATFolding ) +PROPERTYNAME( XBOX360_2010_LINKER, LinkTimeCodeGeneration ) +PROPERTYNAME( XBOX360_2010_LINKER, EntryPoint ) +PROPERTYNAME( XBOX360_2010_LINKER, NoEntryPoint ) +PROPERTYNAME( XBOX360_2010_LINKER, SetChecksum ) +PROPERTYNAME( XBOX360_2010_LINKER, BaseAddress ) +PROPERTYNAME( XBOX360_2010_LINKER, ImportLibrary ) +PROPERTYNAME( XBOX360_2010_LINKER, FixedBaseAddress ) +PROPERTYNAME( XBOX360_2010_LINKER, ErrorReporting ) +PROPERTYNAME( XBOX360_2010_LINKER, FunctionOrder ) +PROPERTYNAME( XBOX360_2010_LINKER, LinkLibraryDependencies ) +PROPERTYNAME( XBOX360_2010_LINKER, UseLibraryDependencyInputs ) +PROPERTYNAME( XBOX360_2010_LINKER, ForceSymbolReferences ) +PROPERTYNAME( XBOX360_2010_LINKER, StripPrivateSymbols ) +PROPERTYNAME( XBOX360_2010_LINKER, ProfileGuidedDatabase ) +PROPERTYNAME( XBOX360_2010_LINKER, MergeSections ) +PROPERTYNAME( XBOX360_2010_LINKER, AutomaticModuleDefinitionFile ) + +// Browse Information +PROPERTYNAME( XBOX360_2010_BROWSEINFORMATION, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_2010_BROWSEINFORMATION, OutputFile ) +PROPERTYNAME( XBOX360_2010_BROWSEINFORMATION, AdditionalOptions ) +PROPERTYNAME( XBOX360_2010_BROWSEINFORMATION, PreserveSBRFiles ) + +// Pre Build +PROPERTYNAME( XBOX360_2010_PREBUILDEVENT, Description ) +PROPERTYNAME( XBOX360_2010_PREBUILDEVENT, CommandLine ) +PROPERTYNAME( XBOX360_2010_PREBUILDEVENT, ExcludedFromBuild ) +PROPERTYNAME( XBOX360_2010_PREBUILDEVENT, UseInBuild ) + +// Pre Link +PROPERTYNAME( XBOX360_2010_PRELINKEVENT, Description ) +PROPERTYNAME( XBOX360_2010_PRELINKEVENT, CommandLine ) +PROPERTYNAME( XBOX360_2010_PRELINKEVENT, ExcludedFromBuild ) +PROPERTYNAME( XBOX360_2010_PRELINKEVENT, UseInBuild ) + +// Post Build +PROPERTYNAME( XBOX360_2010_POSTBUILDEVENT, Description ) +PROPERTYNAME( XBOX360_2010_POSTBUILDEVENT, CommandLine ) +PROPERTYNAME( XBOX360_2010_POSTBUILDEVENT, ExcludedFromBuild ) +PROPERTYNAME( XBOX360_2010_POSTBUILDEVENT, UseInBuild ) + +// Custom Build +PROPERTYNAME( XBOX360_2010_CUSTOMBUILDSTEP, Description ) +PROPERTYNAME( XBOX360_2010_CUSTOMBUILDSTEP, CommandLine ) +PROPERTYNAME( XBOX360_2010_CUSTOMBUILDSTEP, AdditionalDependencies ) +PROPERTYNAME( XBOX360_2010_CUSTOMBUILDSTEP, Outputs ) +PROPERTYNAME( XBOX360_2010_CUSTOMBUILDSTEP, ExecuteAfter ) +PROPERTYNAME( XBOX360_2010_CUSTOMBUILDSTEP, ExecuteBefore ) + +// Image Conversion +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, ConfigurationFile ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, OutputFile ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, TitleID ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, LANKey ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, BaseAddress ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, HeapSize ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, WorkspaceSize ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, AdditionalSections ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, ExportByName ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, OpticalDiscDriveMapping ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, PAL50Incompatible ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, MultidiscTitle ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, PreferBigButtonInput ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, CrossPlatformSystemLink ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, AllowAvatarGetMetadataByXUID ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, AllowControllerSwapping ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, RequireFullExperience ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, GameVoiceRequiredUI ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, KinectElevationControl ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, SkeletalTrackingRequirement ) +PROPERTYNAME( XBOX360_2010_XBOX360IMAGECONVERSION, AdditionalOptions ) + +// Console Deployment +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, ExcludedFromBuild ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, SuppressStartupBanner ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, DeploymentFiles ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, Progress ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, ForceCopy ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, DeploymentType ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, DeploymentRoot ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, EmulationType ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, LayoutFile ) +PROPERTYNAME( XBOX360_2010_CONSOLEDEPLOYMENT, AdditionalOptions ) + diff --git a/utils/vpc/projectscript.cpp b/utils/vpc/projectscript.cpp new file mode 100644 index 0000000..55b797c --- /dev/null +++ b/utils/vpc/projectscript.cpp @@ -0,0 +1,2286 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" +#include "tier1/utldict.h" +#include "tier1/keyvalues.h" +#include "baseprojectdatacollector.h" + +#include "tier0/memdbgon.h" + +#ifndef STEAM +template +bool V_StrSubstInPlace(char (&in_out)[in_out_size], const char *pMatch, + const char *pReplaceWith, bool bCaseSensitive) { + bool bRet = false; + char *pchT = new char[in_out_size]; + + if (V_StrSubst(in_out, pMatch, pReplaceWith, pchT, in_out_size, + bCaseSensitive)) { + V_strncpy(in_out, pchT, in_out_size); + bRet = true; + } + + delete[] pchT; + return bRet; +} +#endif + +void VPC_ParseFileSection(bool *pbHadConfigSection = NULL) { + if (pbHadConfigSection) *pbHadConfigSection = false; + + while (1) { + const char *pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "}")) { + // end of section + break; + } + if (!V_stricmp(pToken, "$configuration")) { + VPC_Keyword_FileConfiguration(); + if (pbHadConfigSection) *pbHadConfigSection = true; + } + } +} + +//----------------------------------------------------------------------------- +// VPC_TrackSchemaFile +// +//----------------------------------------------------------------------------- + +void VPC_TrackSchemaFile(const char *pName, bool bRemove, + const char *pFileFlag) { +#ifdef STEAM + return; +#else + if (!bRemove && (!pFileFlag || !V_stristr(pFileFlag, "schema"))) { + // adding something that's not schema - ignore + return; + } + + for (int i = 0; i < g_pVPC->m_SchemaFiles.Count(); i++) { + if (!g_pVPC->m_SchemaFiles[i].String()) continue; + + if (!V_stricmp(pName, g_pVPC->m_SchemaFiles[i].String())) { + if (bRemove) { + g_pVPC->m_SchemaFiles.Remove(i); + } + return; + } + } + + if (bRemove) { + // not found, nothing to do + return; + } + + g_pVPC->m_SchemaFiles.AddToTail(pName); + + // suppress building of schematized cpp files + // (they get #included by an auto-generated cpp that is built instead) + const char *pExt = V_GetFileExtension(pName); + if (pExt && !V_stricmp(pExt, "cpp")) { + CUtlVector configurationNames; + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames(configurationNames); + for (int i = 0; i < configurationNames.Count(); i++) { + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[i].String(), true); + g_pVPC->GetProjectGenerator()->FileIsSchema(true); + g_pVPC->GetProjectGenerator()->FileExcludedFromBuild(true); + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } + } + +#endif +} + +static char *ResolveCandidate(const char *pszFile, const char *pszPlatform) { + char szPath[MAX_PATH]; + char szPathExpanded[MAX_PATH]; + + V_ComposeFileName(g_pVPC->GetProjectPath(), pszFile, szPath, sizeof(szPath)); + Sys_ReplaceString(szPath, "$os", pszPlatform, szPathExpanded, + sizeof(szPathExpanded)); + V_FixSlashes(szPathExpanded); + V_RemoveDotSlashes(szPathExpanded); + V_FixDoubleSlashes(szPathExpanded); + + if (Sys_Exists(szPathExpanded)) { + char *pszResolvedFilename = (char *)malloc(MAX_PATH); + Sys_ReplaceString(pszFile, "$os", pszPlatform, pszResolvedFilename, + MAX_PATH); + return pszResolvedFilename; + } + + return NULL; +} + +// No particular order, but must not change without changing the +// arrPlatformChains matrix +const char *g_szArrPlatforms[] = {"win32", // 0 + "win64", // 1 + "osx32", // 2 + "osx64", // 3 + "linux32", // 4 + "linux64", // 5 + "cygwin", // 6 + "ps3", // 7 + "x360", // 8 + "win", // 9 + "osx", // 10 + "linux", // 11 + "posix", // 12 + "any", // 13 + NULL}; + +//----------------------------------------------------------------------------- +// ResolveFilename +// Utility to expand $OS if present +// Returns expanded filename to a file that exists on disk, or NULL +// otherwise vecBonusFiles will contain a list of valid OS files that +// are on disk but did not match the current target platform. +//----------------------------------------------------------------------------- +static char *ResolveFilename(const char *pszFile, + CUtlVector &vecBonusFiles) { + static const int k_lastRealPlatform = 8; // index, not count + static const int k_AnyPlatform = 13; // index + + static const int arrPlatformChains[][9] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8}, // the raw platforms + {9, 9, 10, 10, 11, 11, 12, 12, 9}, // first fallback + {13, 13, 12, 12, 12, 12, 13, 13, 13}, // 2nd fallback + {13, 13, 13, 13, 13, 13, 13, 13, 13}, // 3rd fallback + }; + + // Don't want the actual backing store to be const since we aren't + // compile-time init'ing, but it is single-instance initialized, so make a + // const ref to it that everyone except the init codes uses to make sure it + // doesn't get mucked with. + static bool bInited = false; + static char _szPlatformStore[128]; + static const char(&szPlatform)[128] = _szPlatformStore; + + if (!bInited) { + bInited = true; + const char *pszPlatform = g_pVPC->GetTargetPlatformName(); + V_strncpy(_szPlatformStore, pszPlatform, sizeof(szPlatform)); + V_strlower(_szPlatformStore); + } + + vecBonusFiles.RemoveAll(); + + int nPlatformColumn = -1; + for (int i = 0; i <= k_lastRealPlatform; i++) { + if (V_strcmp(szPlatform, g_szArrPlatforms[i]) == 0) { + nPlatformColumn = i; + break; + } + } + + if (nPlatformColumn < 0) { + g_pVPC->VPCWarning( + "Internal Error: Target Platform: '%s' unrecognized while expanding " + "$os!", + szPlatform); + return NULL; + } + + // Now walk the chain of potential platform matches from strongest to weakest + char *pszResolved = NULL; + int nPlatformToCheck = -1; + int nCurrentPlatformRow = -1; + do { + nCurrentPlatformRow++; + nPlatformToCheck = arrPlatformChains[nCurrentPlatformRow][nPlatformColumn]; + pszResolved = ResolveCandidate(pszFile, g_szArrPlatforms[nPlatformToCheck]); + if (pszResolved) break; + } while (arrPlatformChains[nCurrentPlatformRow][nPlatformColumn] != + k_AnyPlatform); + + // Now go pickup the any files that exist, but were non-matches + for (int i = 0; g_szArrPlatforms[i] != NULL; i++) { + // Don't pick up the actual found platform + if (i != nPlatformToCheck) { + CUtlString file(ResolveCandidate(pszFile, g_szArrPlatforms[i])); + if (!file.IsEmpty()) vecBonusFiles.AddToTail(file); + } + } + return pszResolved; +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_AddFile +// +//----------------------------------------------------------------------------- +void VPC_Keyword_AddFilesByPattern() { + CUtlVector files; + + while (1) { + const char *pToken = g_pVPC->GetScript().GetToken(false); + if (!pToken || !pToken[0]) break; + + // Is this a conditional expression? + if (pToken[0] == '[') { + if (files.Count() == 0) { + g_pVPC->VPCSyntaxError( + "Conditional specified on a $FilePattern without any pattern " + "preceding it."); + } + + if (!g_pVPC->EvaluateConditionalExpression(pToken)) { + // we did all that work for no reason, time to bail out + return; + } + } + + char szFilename[MAX_PATH]; + g_pVPC->ResolveMacrosInString(pToken, szFilename, sizeof(szFilename)); + + V_FixSlashes(szFilename); + + CUtlVector vecResults; + Sys_ExpandFilePattern(szFilename, vecResults); + + for (int i = 0; i < vecResults.Count(); i++) { + g_pVPC->VPCStatus(false, "glob: adding '%s' to project", + vecResults[i].String()); + g_pVPC->GetProjectGenerator()->StartFile(vecResults[i].String(), true); + g_pVPC->GetProjectGenerator()->EndFile(); + } + } +} + +// forward decl we need in the add file code +void VPC_Keyword_Folder(bool bUnity, + const folderConfig_t *pInheritedFolderConfig = NULL); + +//----------------------------------------------------------------------------- +// VPC_Keyword_AddFile +// +//----------------------------------------------------------------------------- +void VPC_Keyword_AddFile(const char *pFileFlag = NULL, + const folderConfig_t *pFolderConfig = NULL) { + bool bAllowNextLine = false; + CUtlVector files; + CUtlVector unbuiltFiles; + + bool bHasConditional = false; + while (1) { + const char *pToken = g_pVPC->GetScript().GetToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + + // Is this a conditional expression? + if (pToken[0] == '[') { + if (files.Count() == 0) { + g_pVPC->VPCSyntaxError( + "Conditional specified on a $File without any file preceding it."); + } + + if (!g_pVPC->EvaluateConditionalExpression(pToken)) { + // DO NOT INTEGRATE OR TAKE THIS TO STEAM + // Steam VPC differs in conditional handling inside grouped files + unbuiltFiles.AddToTail(files[files.Count() - 1]); + files.Remove(files.Count() - 1); + // unbuiltFiles.AddMultipleToTail( files.Count(), files.Base() ); + // files.RemoveAll(); + } + + bHasConditional = true; + continue; + } + + char szFilename[MAX_PATH]; + g_pVPC->ResolveMacrosInString(pToken, szFilename, sizeof(szFilename)); + + if (!V_stricmp(pToken, "\\")) { + bAllowNextLine = true; + continue; + } else { + bAllowNextLine = false; + } + + V_FixSlashes(szFilename); + + CUtlString string = szFilename; + files.AddToTail(string); + + // check for another optional file + pToken = g_pVPC->GetScript().PeekNextToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + } + + // check for optional section + bool bHasSection = false; + const char *pToken = g_pVPC->GetScript().PeekNextToken(true); + if (pToken && pToken[0] && !V_stricmp(pToken, "{")) { + bHasSection = true; + } + + // dynamic files need to opt out of strict file presence check + bool bDynamicFile = pFileFlag && V_stristr(pFileFlag, "dynamic"); + + // need to check files early to handle possible rejected section + if (g_pVPC->IsCheckFiles() && !bDynamicFile) { + for (int i = 0; i < files.Count(); i++) { + const char *pFilename = files[i].String(); + if (!Sys_Exists(pFilename) && !V_stristr(pFilename, "$os")) { +#if defined(POSIX) + // We have a _lot_ of vpc files that contain header files with the + // incorrect casing. So try to lowercase the filename here and if it + // exists, replace it and carry on. + // + CUtlString FileNameLower = files[i]; + + V_strlower(FileNameLower.Get()); + if (Sys_Exists(FileNameLower)) { + files[i] = FileNameLower; + continue; + } +#endif + + // Hack around all the libraries which are listed as $File instead of + // $Lib + const char *extension = strrchr(pFilename, '.'); + if (!extension || stricmp(extension, ".lib")) { + g_pVPC->VPCWarning("File '%s' does not exist. Not adding to project.", + pFilename); + g_pVPC->IncrementFileMissing(); + files.Remove(i); + } else { + // don't complain about libs that contain $(Configuration) in their + // path + if (!V_stristr(pFilename, "$(Configuration)")) + g_pVPC->VPCWarning( + "Library '%s' does not exist. Adding to project anyway.", + pFilename); + } + } + } + } + + if (g_pVPC->IsShowCaseIssues() && !bDynamicFile) { + for (int i = 0; i < files.Count(); i++) { + const char *pFilename = files[i].String(); + char actualFilename[MAX_PATH]; + if (!Sys_IsFilenameCaseConsistent(pFilename, actualFilename, + sizeof(actualFilename))) { + g_pVPC->VPCWarning( + "Case Consistency Issue! File '%s' specified in '%s' is " + "inconsistent with OS version '%s'.", + pFilename, g_pVPC->GetProjectName(), actualFilename); + + // need script stack to assist in tracking down missing file + g_pVPC->GetScript().SpewScriptStack(); + } + } + } + + if (!files.Count() && bHasSection) { + // optional section has been conditionally removed + g_pVPC->GetScript().SkipBracedSection(); + return; + } + + for (int k = 0; k < unbuiltFiles.Count(); k++) { + const char *pExcludedFilename = unbuiltFiles[k].String(); + + g_pVPC->GetProjectGenerator()->StartFile(pExcludedFilename, true); + CUtlVector configurationNames; + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames(configurationNames); + for (int j = 0; j < configurationNames.Count(); j++) { + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[j].String(), true); + g_pVPC->GetProjectGenerator()->FileExcludedFromBuild(true); + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } + g_pVPC->GetProjectGenerator()->EndFile(); + } + + if (bHasSection) { + // found optional section, parse opening brace + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) + g_pVPC->VPCSyntaxError(); + } + + // Handle $OS expansion + // save parser state + CScriptSource startingScriptSource = g_pVPC->GetScript().GetCurrentScript(); + + for (int k = 0; k < files.Count(); k++) { + const char *pFilename = files[k].String(); + + CUtlString filename; + if (!g_pVPC->m_bInMkSlnPass && V_stristr(pFilename, "$os")) { + CUtlVector vecExcludedFiles; + filename = ResolveFilename(pFilename, vecExcludedFiles); + + char rgchRejectList[4096]; + rgchRejectList[0] = '\0'; + + if (vecExcludedFiles.Count()) { + for (int j = 0; j < files.Count(); j++) { + V_strncat(rgchRejectList, files[j].String(), + V_ARRAYSIZE(rgchRejectList)); + V_strncat(rgchRejectList, ",", V_ARRAYSIZE(rgchRejectList)); + } + } + + g_pVPC->VPCStatus(false, "$OS: Resolved %s -> %s, rejected %s", pFilename, + filename.String(), rgchRejectList); + + if (filename.IsEmpty()) { + g_pVPC->VPCWarning( + "$File %s did not resolve to an existing file, skipping!", + pFilename); + continue; + } + + for (const auto &excludedFile : vecExcludedFiles) { + const char *pExcludedFilename = excludedFile.String(); + const char *pExcludedExtension = V_GetFileExtension(pExcludedFilename); + + if (!pExcludedExtension) pExcludedExtension = ""; + + if (!V_stricmp(pExcludedExtension, "cpp")) { + g_pVPC->VPCStatus(false, "excluding '%s' from build", + pExcludedFilename); + g_pVPC->GetProjectGenerator()->StartFile(pExcludedFilename, true); + + CUtlVector configurationNames; + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames( + configurationNames); + + for (int j = 0; j < configurationNames.Count(); j++) { + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[j].String(), true); + g_pVPC->GetProjectGenerator()->FileExcludedFromBuild(true); + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } + + g_pVPC->GetProjectGenerator()->EndFile(); + } + } + + pFilename = filename.String(); + } + + bool bAdded = g_pVPC->GetProjectGenerator()->StartFile(pFilename, true); + + if (bDynamicFile) { + CUtlVector configurationNames; + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames( + configurationNames); + + for (int j = 0; j < configurationNames.Count(); j++) { + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[j].String(), true); + g_pVPC->GetProjectGenerator()->FileIsDynamic(true); + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } + } + + // Lookup extension for a custom build script + const char *pExtension = V_GetFileExtension(pFilename); + if (!pExtension) pExtension = ""; + + if (!g_pVPC->m_sUnityCurrent.IsEmpty() && !V_stricmp(pExtension, "cpp") && + !bHasSection && !bHasConditional) { + bool bEmitUnityFiles = + (!g_pVPC->m_bInMkSlnPass && + (g_pVPC->IsForceGenerate() || + !g_pVPC->IsProjectCurrent(g_pVPC->GetOutputFilename(), false))); + + if (bEmitUnityFiles) { + // append to the unity file + g_pVPC->VPCStatus(false, "Unity: adding '%s' to unity file '%s'", + pFilename, g_pVPC->m_sUnityCurrent.String()); + FILE *fp = fopen(g_pVPC->m_sUnityCurrent.String(), "at"); + + if (!fp) { + g_pVPC->VPCError("Cannot open %s for appending", + g_pVPC->m_sUnityCurrent.String()); + } else { + char pFixedFilename[MAX_PATH]; + V_strncpy(pFixedFilename, pFilename, sizeof(pFixedFilename)); + V_FixSlashes(pFixedFilename, '/'); + fprintf(fp, "#include \"%s\"\n", pFixedFilename); + fclose(fp); + } + } + + g_pVPC->VPCStatus(false, "Unity: excluding '%s' from build", pFilename); + CUtlVector configurationNames; + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames( + configurationNames); + for (int j = 0; j < configurationNames.Count(); j++) { + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[j].String(), true); + g_pVPC->GetProjectGenerator()->FileExcludedFromBuild(true); + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } + } else { + bool bHadConfigSection = false; + + int index = g_pVPC->m_CustomBuildSteps.Find(pExtension); + if (g_pVPC->m_CustomBuildSteps.IsValidIndex(index)) { + CUtlString buildsteps = g_pVPC->m_CustomBuildSteps[index]; + const char *pBuffer = buildsteps.Get(); + + CUtlString scriptName; + scriptName = pExtension; + scriptName += " custom build step"; + + // save parser state + g_pVPC->GetScript().PushScript(g_pVPC->GetScript().GetName(), pBuffer); + + // parse injected buildstep + VPC_ParseFileSection(&bHadConfigSection); + + // restore parser state + g_pVPC->GetScript().PopScript(); + g_pVPC->GetScript().RestoreScript(startingScriptSource); + } + + // apply optional section to each file + if (bHasSection && bAdded) { + // restore parser state + g_pVPC->GetScript().RestoreScript(startingScriptSource); + VPC_ParseFileSection(&bHadConfigSection); + } + + // if this file doesn't have a file-specific config, but the folder we're + // in has a config + // apply that folder config to the file here + if (!bHadConfigSection && pFolderConfig) + VPC_ApplyFolderConfigurationToFile(*pFolderConfig); + } + + VPC_TrackSchemaFile(pFilename, false, pFileFlag); + + if (bAdded) g_pVPC->GetProjectGenerator()->EndFile(); + } +} + +// parse a list of filenames from the current token, handling conditionals, +// macro expansion, \\, fixslashes, etc +static void VPC_ParseFileList(CUtlStringList &files) { + bool bAllowNextLine = false; + + while (1) { + const char *pToken = g_pVPC->GetScript().GetToken(bAllowNextLine); + if (!pToken || !pToken[0]) g_pVPC->VPCSyntaxError(); + + const char *pNextToken = g_pVPC->GetScript().PeekNextToken(false); + if (!pNextToken || !pNextToken[0]) { + // current token is last token + // last token can be optional conditional, need to identify + // backup and reparse up to last token + if (pToken[0] == '[') { + if (files.Count() == 0) { + g_pVPC->VPCSyntaxError( + "Conditional specified on a file list without any file preceding " + "it."); + } + // last token is an optional conditional + bool bResult = g_pVPC->EvaluateConditionalExpression(pToken); + if (!bResult) // was conditional false? + { + files.PurgeAndDeleteElements(); + } + return; + } + } + + char szFilename[MAX_PATH]; + g_pVPC->ResolveMacrosInString(pToken, szFilename, sizeof(szFilename)); + V_FixSlashes(szFilename); + + if (!V_stricmp(pToken, "\\")) { + bAllowNextLine = true; + continue; + } else { + bAllowNextLine = false; + } + + files.CopyAndAddToTail(szFilename); + + // check for another optional file + pToken = g_pVPC->GetScript().PeekNextToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + } +} + +// add or remove .lib or dll import files, automatically adding prefixes and +// suffices to the name +static void VPC_HandleLibraryExpansion(char const *pDefaultPath, + char const *pFileNamePrefix, + char const *pSuffix, bool bRemove) { + if (!pFileNamePrefix) { + pFileNamePrefix = ""; + } + char szResolvedFilePrefix[MAX_PATH]; + g_pVPC->ResolveMacrosInString(pFileNamePrefix, szResolvedFilePrefix, + sizeof(szResolvedFilePrefix)); + + CUtlStringList impFiles; + VPC_ParseFileList(impFiles); + for (int i = 0; i < impFiles.Count(); i++) { + char szFilename[MAX_PATH * 2]; + char const *pPathPrefixToUse = pDefaultPath; + + // do not add the path prefix if the filename contains path information + // already, or if null was passed. + char impFile[MAX_PATH]; + V_strncpy(impFile, impFiles[i], sizeof(impFile)); + V_RemoveDotSlashes(impFile); + + char *pLastSlash = + (char *)MAX(strrchr(impFile, '\\'), strrchr(impFile, '/')); + if (pLastSlash) { + *pLastSlash = 0; + const char *pFilenamePart = pLastSlash + 1; + // don't prepend the prefix if the filename already starts with it + bool bAddPrefix = true; + if (V_strlen(szResolvedFilePrefix) && + !V_strnicmp(pFilenamePart, szResolvedFilePrefix, + V_strlen(szResolvedFilePrefix))) + bAddPrefix = false; + sprintf(szFilename, "%s/%s%s%s", impFile, + bAddPrefix ? pFileNamePrefix : "", pFilenamePart, pSuffix); + } else { + bool bAddPrefix = true; + if (V_strlen(szResolvedFilePrefix) && + !V_strnicmp(impFiles[i], szResolvedFilePrefix, + V_strlen(szResolvedFilePrefix))) + bAddPrefix = false; + sprintf(szFilename, "%s%s%s%s", pPathPrefixToUse, + bAddPrefix ? pFileNamePrefix : "", impFiles[i], pSuffix); + } + + char szFilename1[MAX_PATH]; + g_pVPC->ResolveMacrosInString(szFilename, szFilename1, sizeof(szFilename1)); + + // Replace forward slashes with backslashes regardless of target platform + V_FixSlashes(szFilename1); + V_RemoveDotSlashes(szFilename1); + + if (bRemove) { + bool bSucc = g_pVPC->GetProjectGenerator()->RemoveFile(szFilename1); + if (!bSucc) { + g_pVPC->VPCError( + "Broken $implib command. Failed to remove file %s from project.", + szFilename1); + } + } else { + bool bAdded = g_pVPC->GetProjectGenerator()->StartFile(szFilename1, true); + if (!bAdded) { + g_pVPC->VPCError("couldn't add %s", szFilename1); + } + g_pVPC->GetProjectGenerator()->EndFile(); + } + } +} + +enum EVPCKeywordFlag { + k_eVPCKeywordFlag_Remove = 0x00000001, + k_eVPCKeywordFlag_External = 0x00000002, +}; + +static void VPC_Keyword_ImportLibrary(uint32 Flags) { + char const *pSuffix = "$_IMPLIB_EXT"; + bool bRemove = !!(Flags & k_eVPCKeywordFlag_Remove); + + if ((Flags & k_eVPCKeywordFlag_External) && + g_pVPC->FindOrCreateMacro("_EXTERNAL_IMPLIB_EXT", false, NULL)) { + pSuffix = "$_EXTERNAL_IMPLIB_EXT"; + } + + VPC_HandleLibraryExpansion("$LIBPUBLIC\\", "$_IMPLIB_PREFIX", pSuffix, + bRemove); +} + +static void VPC_Keyword_LinkerLibrary(uint32 Flags) { + char const *pSuffix = "$_STATICLIB_EXT"; + bool bRemove = !!(Flags & k_eVPCKeywordFlag_Remove); + + if ((Flags & k_eVPCKeywordFlag_External) && + g_pVPC->FindOrCreateMacro("_EXTERNAL_STATICLIB_EXT", false, NULL)) { + pSuffix = "$_EXTERNAL_STATICLIB_EXT"; + } + + VPC_HandleLibraryExpansion("$LIBPUBLIC\\", "$_STATICLIB_PREFIX", pSuffix, + bRemove); +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_RemoveFile +// +//----------------------------------------------------------------------------- +void VPC_Keyword_RemoveFile() { + CUtlStringList filesToRemove; + VPC_ParseFileList(filesToRemove); + for (int i = 0; i < filesToRemove.Count(); i++) { + bool bSucc = g_pVPC->GetProjectGenerator()->RemoveFile(filesToRemove[i]); + if (!bSucc) { + g_pVPC->VPCWarning("Failed to remove file %s from project", + filesToRemove[i]); + } + + VPC_TrackSchemaFile(filesToRemove[i], true, NULL); + } +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_Folder +// +//----------------------------------------------------------------------------- +void VPC_Keyword_Folder( + bool bUnity, const folderConfig_t *pInheritedFolderConfig /* = NULL */) { + const char *pToken; + char folderName[MAX_PATH]; + folderConfig_t folderConfig; + + // by default, our active config is any config that we inherited from the + // parent. + const folderConfig_t *pActiveFolderConfig = pInheritedFolderConfig; + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, folderName, + sizeof(folderName))) { + g_pVPC->GetScript().SkipBracedSection(); + return; + } + + g_pVPC->GetProjectGenerator()->StartFolder(folderName); + + bool bEmitUnityFiles = + (!g_pVPC->m_bInMkSlnPass && + (g_pVPC->IsForceGenerate() || + !g_pVPC->IsProjectCurrent(g_pVPC->GetOutputFilename(), false))); + if (bUnity) { + // generate a .cpp file from the folderName + char unityName[MAX_PATH]; + V_snprintf(unityName, sizeof(unityName), "%s_%s_unity.cpp", folderName, + g_pVPC->GetProjectName()); + V_StrSubstInPlace(unityName, " ", "_", false); + + // make sure we have a unique file name, we don't want to tread on another + // projects unity + g_pVPC->m_sUnityCurrent = unityName; + int cAttempt = 1; + while (g_pVPC->m_UnityFilesSeen.Find(g_pVPC->m_sUnityCurrent) != + g_pVPC->m_UnityFilesSeen.InvalidIndex()) { + V_snprintf(unityName, sizeof(unityName), "%s_%s_unity_%d.cpp", folderName, + g_pVPC->GetProjectName(), ++cAttempt); + V_StrSubstInPlace(unityName, " ", "_", false); + g_pVPC->m_sUnityCurrent = unityName; + } + + g_pVPC->m_UnityStack.Push(g_pVPC->m_sUnityCurrent); + + if (bEmitUnityFiles) { + g_pVPC->VPCStatus(false, "Unity: emitting '%s' in project: '%s'", + unityName, g_pVPC->GetProjectName()); + + // always add the stdafx.h at the top (if we're using a precompiled + // header, what if we're not?) + FILE *fp = fopen(g_pVPC->m_sUnityCurrent.String(), "wt"); + if (!fp) { + g_pVPC->VPCError("Cannot open %s for writing", + g_pVPC->m_sUnityCurrent.String()); + } else { + const char *pStdAfx = g_pVPC->GetMacroValue("STDAFX"); + if (pStdAfx && *pStdAfx) { + fprintf(fp, "#include \"%s\"\n", pStdAfx); + } + fclose(fp); + } + } + + g_pVPC->m_UnityFilesSeen.Insert(g_pVPC->m_sUnityCurrent); + + // Msg( "pushing unity file %s\n", g_sUnityCurrent ); + g_pVPC->GetProjectGenerator()->StartFile(g_pVPC->m_sUnityCurrent, true); + g_pVPC->GetProjectGenerator()->EndFile(); + } + + // Now parse all the files and subfolders.. + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) g_pVPC->VPCSyntaxError(); + + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "}")) { + // pop + break; + } else if (!V_stricmp(pToken, "$file") || + !V_stricmp(pToken, "$schemafile") || + !V_stricmp(pToken, "$entschemafile")) { + // add file + VPC_Keyword_AddFile(NULL, pActiveFolderConfig); + } else if (!V_stricmp(pToken, "$DynamicFile")) { + // add file + VPC_Keyword_AddFile("dynamic", pActiveFolderConfig); + } else if (!V_stricmp(pToken, "$FilePattern")) { + // glob the given pattern, add all files + VPC_Keyword_AddFilesByPattern(); + } + // THIS IS A LEGACY INCORRECT IMPLEMENTATION + // else if ( !V_stricmp( pToken, "$schemafile" ) ) + // { + // // add file + // VPC_Keyword_AddFile( "schema" ); + // } + else if (!V_stricmp(pToken, "$implib") || + !V_stricmp(pToken, "$implibexternal") || + !V_stricmp(pToken, "-$implib") || + !V_stricmp(pToken, "-$implibexternal")) { + uint32 KeywordFlags = (pToken[0] == '-') ? k_eVPCKeywordFlag_Remove : 0; + + if (V_stristr(pToken, "external")) + KeywordFlags |= k_eVPCKeywordFlag_External; + + VPC_Keyword_ImportLibrary(KeywordFlags); + } else if (!V_stricmp(pToken, "$lib") || + !V_stricmp(pToken, "$libexternal") || + !V_stricmp(pToken, "-$lib") || + !V_stricmp(pToken, "-$libexternal")) { + uint32 KeywordFlags = (pToken[0] == '-') ? k_eVPCKeywordFlag_Remove : 0; + + if (V_stristr(pToken, "external")) + KeywordFlags |= k_eVPCKeywordFlag_External; + + VPC_Keyword_LinkerLibrary(KeywordFlags); + } else if (!V_stricmp(pToken, "-$file")) { + // remove file + VPC_Keyword_RemoveFile(); + } else if (!V_stricmp(pToken, "$folder")) { + // descend into subdirectory + VPC_Keyword_Folder(false, pActiveFolderConfig); + } else if (!V_stricmp(pToken, "$unity")) { + VPC_Keyword_Folder(g_pVPC->IsUnity(), pActiveFolderConfig); + } else if (!V_stricmp(pToken, "$configuration")) { + // read the folder-specific configuration. + VPC_Keyword_FolderConfiguration(&folderConfig); + + // if we found a new config section, make it active and apply it to + // subsequent files + if (folderConfig.BHasConfig()) + pActiveFolderConfig = &folderConfig; + else + pActiveFolderConfig = NULL; + } else { + g_pVPC->VPCSyntaxError(); + } + } + + if (bUnity) { + // Msg( "popping unity file %s\n", g_sUnityCurrent ); + g_pVPC->m_UnityStack.Pop(); + + if (g_pVPC->m_UnityStack.Count() == 0) + g_pVPC->m_sUnityCurrent = NULL; + else + g_pVPC->m_sUnityCurrent = g_pVPC->m_UnityStack.Top(); + } + g_pVPC->GetProjectGenerator()->EndFolder(); +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_Shaders +// +//----------------------------------------------------------------------------- +void VPC_Keyword_Shaders(int depth) { + const char *pToken; + char shadersName[MAX_PATH]; + CUtlBuffer vpcBuffer; + CUtlVector fxcList; + CUtlVector vshList; + CUtlVector pshList; + CUtlVector vfxList; + CUtlVector otherList; + int i; + bool bIgnoreRedundancyWarning; + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, shadersName, + sizeof(shadersName))) { + return; + } + + g_pVPC->VPCStatus(false, "Parsing: %s", shadersName); + g_pVPC->GetScript().PushScript(shadersName); + + // parse the shader list file into types (fxc,vsh,psh) + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) { + // end of file + break; + } + + if (V_stristr(pToken, ".fxc")) { + fxcList.AddToTail(pToken); + } else if (V_stristr(pToken, ".vsh")) { + vshList.AddToTail(pToken); + } else if (V_stristr(pToken, ".psh")) { + pshList.AddToTail(pToken); + } else if (V_stristr(pToken, ".vfx")) { + vfxList.AddToTail(pToken); + } else { + otherList.AddToTail(pToken); + } + } + + g_pVPC->GetScript().PopScript(); + + if (!fxcList.Count() && !vshList.Count() && !pshList.Count() && + !vfxList.Count() && !otherList.Count()) { + g_pVPC->VPCWarning("No shaders found in %s", shadersName); + return; + } + + // generate a vpc compatible file to generate the shader file hierarchy + vpcBuffer.SetBufferType(true, true); + vpcBuffer.Printf("$Folder \"Shader Source\" \n"); + vpcBuffer.Printf("{\n"); + + // add the shader file as a convienence + vpcBuffer.Printf("$file \"%s\"\n", shadersName); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$Configuration\n"); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$ExcludedFromBuild \"Yes\"\n"); + vpcBuffer.Printf("}\n"); + vpcBuffer.Printf("}\n"); + + // fxc files + if (fxcList.Count()) { + vpcBuffer.Printf("$Folder \"fxc\" \n"); + vpcBuffer.Printf("{\n"); + for (i = 0; i < fxcList.Count(); i++) { + vpcBuffer.Printf("$file \"%s\"\n", fxcList[i].String()); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$Configuration\n"); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$ExcludedFromBuild \"Yes\"\n"); + vpcBuffer.Printf("}\n"); + vpcBuffer.Printf("}\n"); + } + vpcBuffer.Printf("}\n"); + } + + // vsh files + if (vshList.Count()) { + vpcBuffer.Printf("$Folder \"vsh\" \n"); + vpcBuffer.Printf("{\n"); + for (i = 0; i < vshList.Count(); i++) { + vpcBuffer.Printf("$file \"%s\"\n", vshList[i].String()); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$Configuration\n"); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$ExcludedFromBuild \"Yes\"\n"); + vpcBuffer.Printf("}\n"); + vpcBuffer.Printf("}\n"); + } + vpcBuffer.Printf("}\n"); + } + + // psh files + if (pshList.Count()) { + vpcBuffer.Printf("$Folder \"psh\" \n"); + vpcBuffer.Printf("{\n"); + for (i = 0; i < pshList.Count(); i++) { + vpcBuffer.Printf("$file \"%s\"\n", pshList[i].String()); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$Configuration\n"); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$ExcludedFromBuild \"Yes\"\n"); + vpcBuffer.Printf("}\n"); + vpcBuffer.Printf("}\n"); + } + vpcBuffer.Printf("}\n"); + } + + // vfx files + if (vfxList.Count()) { + vpcBuffer.Printf("$Folder \"vfx\" \n"); + vpcBuffer.Printf("{\n"); + for (i = 0; i < vfxList.Count(); i++) { + vpcBuffer.Printf("$file \"%s\"\n", vfxList[i].String()); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$Configuration\n"); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$ExcludedFromBuild \"Yes\"\n"); + vpcBuffer.Printf("}\n"); + vpcBuffer.Printf("}\n"); + } + vpcBuffer.Printf("}\n"); + } + + // other files + if (otherList.Count()) { + // psh files + vpcBuffer.Printf("$Folder \"other\" \n"); + vpcBuffer.Printf("{\n"); + for (i = 0; i < otherList.Count(); i++) { + vpcBuffer.Printf("$file \"%s\"\n", otherList[i].String()); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$Configuration\n"); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf("$ExcludedFromBuild \"Yes\"\n"); + vpcBuffer.Printf("}\n"); + vpcBuffer.Printf("}\n"); + } + vpcBuffer.Printf("}\n"); + } + + // end of shader folder + vpcBuffer.Printf("}\n"); + + // save parser + bIgnoreRedundancyWarning = g_pVPC->IsIgnoreRedundancyWarning(); + g_pVPC->SetIgnoreRedundancyWarning(true); + + g_pVPC->GetScript().PushScript("Internal List", (char *)vpcBuffer.Base()); + + pToken = g_pVPC->GetScript().GetToken(true); + if (pToken && pToken[0] && !V_stricmp(pToken, "$folder")) { + VPC_Keyword_Folder(false); + } + + // restore parser + g_pVPC->GetScript().PopScript(); + g_pVPC->SetIgnoreRedundancyWarning(bIgnoreRedundancyWarning); +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_Macro +// +//----------------------------------------------------------------------------- +enum MacroType_t { VPC_MACRO_VALUE, VPC_MACRO_EMPTY_STRING }; +void VPC_Keyword_Macro(MacroType_t eMacroType) { + const char *pToken; + char macro[MAX_SYSTOKENCHARS]; + char value[MAX_SYSTOKENCHARS]; + + pToken = g_pVPC->GetScript().GetToken(false); + if (!pToken || !pToken[0]) g_pVPC->VPCSyntaxError(); + strcpy(macro, pToken); + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, value, sizeof(value))) { + return; + } + + char environmentValue[MAX_SYSTOKENCHARS]; + if (Sys_EvaluateEnvironmentExpression(value, "", environmentValue, + sizeof(environmentValue))) { + V_strncpy(value, environmentValue, sizeof(value)); + } + + g_pVPC->FindOrCreateMacro(macro, true, + (eMacroType == VPC_MACRO_VALUE) ? value : ""); +} + +//----------------------------------------------------------------------------- +// $MacroRequired [DefaultValue] [Condition] +// $MacroRequiredAllowEmpty [DefaultValue] [Condition] +// +// Forces a script to error if a macro that it depends on was not set. +// The Default will be used if the macro was not defined, otherwise error. +// This is to allow a required macro in a base script to have a concept +// of a default initialization value. +//----------------------------------------------------------------------------- +enum MacroRequiredType_t { + VPC_MACRO_REQUIRED_NOT_EMPTY, + VPC_MACRO_REQUIRED_ALLOW_EMPTY +}; +void VPC_Keyword_MacroRequired(MacroRequiredType_t eMacroRequiredType) { + char macroName[MAX_SYSTOKENCHARS]; + char macroDefaultValue[MAX_SYSTOKENCHARS]; + const char *pToken; + + macroDefaultValue[0] = '\0'; + + pToken = g_pVPC->GetScript().GetToken(false); + if (!pToken || !pToken[0]) { + g_pVPC->VPCSyntaxError(); + } + strcpy(macroName, pToken); + + // optional default macro value or conditional + pToken = g_pVPC->GetScript().PeekNextToken(false); + if (pToken && pToken[0]) { + if (pToken[0] == '[') { + // evaulate argument as conditional + if (!g_pVPC->EvaluateConditionalExpression(pToken)) { + return; + } + } else { + // argument is a default macro value + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, macroDefaultValue, + sizeof(macroDefaultValue))) { + return; + } + } + } + + // find macro, needs to be present and non-empty + macro_t *pMacro = g_pVPC->FindOrCreateMacro(macroName, false, NULL); + if (!pMacro || (eMacroRequiredType == VPC_MACRO_REQUIRED_NOT_EMPTY && + !strlen(pMacro->value.String()))) { + if (macroDefaultValue[0] || + (eMacroRequiredType == VPC_MACRO_REQUIRED_ALLOW_EMPTY)) { + g_pVPC->FindOrCreateMacro(macroName, true, macroDefaultValue); + } else { + // In case we're in mksln showing a pacifier of dots. Make sure to show + // the error on a new line. + g_pVPC->VPCSyntaxError("\n\nRequired Macro '%s', not defined or empty", + macroName); + } + } +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_LoadAddressMacro +// +// $LoadAddressMacro +// { +// +// } +// +// Specialized instruction to populate the load address macro based on a +// project name. +//----------------------------------------------------------------------------- +void VPC_Keyword_LoadAddressMacro(void) { + char szProjectName[MAX_SYSTOKENCHARS]; + char szMacroName[MAX_SYSTOKENCHARS]; + char szBaseAddress[MAX_SYSTOKENCHARS]; + const char *pToken; + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, szMacroName, + sizeof(szMacroName))) { + g_pVPC->GetScript().SkipBracedSection(); + return; + } + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) { + g_pVPC->VPCSyntaxError(); + } + + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) { + break; + } + strcpy(szProjectName, pToken); + + if (!V_stricmp(pToken, "}")) { + break; + } else { + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, szBaseAddress, + sizeof(szBaseAddress))) { + continue; + } + + if (!V_stricmp(szProjectName, g_pVPC->GetLoadAddressName())) { + // set Macro + g_pVPC->FindOrCreateMacro(szMacroName, true, szBaseAddress); + } + } + } +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_LoadAddressMacroAlias +// +// $LoadAddressMacroAlias +// { +// +// } +// +// When evaluating $LoadAddressMacro/$LoadAddressMacroAuto, substitute all +// listed entries with +//----------------------------------------------------------------------------- +void VPC_Keyword_LoadAddressMacroAlias(void) { + char szProjectName[MAX_SYSTOKENCHARS]; + char szAlias[MAX_SYSTOKENCHARS]; + const char *pToken; + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, szAlias, sizeof(szAlias))) { + g_pVPC->GetScript().SkipBracedSection(); + return; + } + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) { + g_pVPC->VPCSyntaxError(); + } + + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) { + break; + } + strcpy(szProjectName, pToken); + + if (!V_stricmp(pToken, "}")) { + break; + } else { + if (!V_stricmp(szProjectName, g_pVPC->GetProjectName())) { + // set Macro and alias + g_pVPC->FindOrCreateMacro("LOADADDRESSNAME", true, szAlias); + g_pVPC->SetLoadAddressName(szAlias); + } + } + } +} + +//----------------------------------------------------------------------------- +// Internal_LoadAddressMacroAuto +// +// bPad - Differentiate between $LoadAddressMacroAuto and +//$LoadAddressMacroAuto_Padded implementations +// +// Specialized instruction to populate the load address macro based on a +// project name. +//----------------------------------------------------------------------------- +void Internal_LoadAddressMacroAuto(bool bPad) { + char szProjectName[MAX_SYSTOKENCHARS]; + char szMacroName[MAX_SYSTOKENCHARS]; + char szBaseAddress[MAX_SYSTOKENCHARS]; + char szLength[MAX_SYSTOKENCHARS]; + const char *pToken; + + pToken = g_pVPC->GetScript().GetToken(false); + if (!pToken || !pToken[0]) { + g_pVPC->VPCSyntaxError(); + } + strcpy(szMacroName, pToken); + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, szBaseAddress, + sizeof(szBaseAddress))) { + g_pVPC->GetScript().SkipBracedSection(); + return; + } + unsigned int baseAddress = 0; + sscanf(szBaseAddress, "%x", &baseAddress); + unsigned int iInitialBaseAddress = baseAddress; + + macro_t *pMacro = NULL; + int iSetEntryNum = 0; + int iSetBaseAddress = 0; + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) { + g_pVPC->VPCSyntaxError(); + } + + int iEntryNum = 0; + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) { + break; + } + strcpy(szProjectName, pToken); + + if (!V_stricmp(szProjectName, g_pVPC->GetLoadAddressName())) { + // set Macro + char szMacroValue[MAX_SYSTOKENCHARS]; + sprintf(szMacroValue, "0x%8.8x", baseAddress); + + iSetEntryNum = iEntryNum; + iSetBaseAddress = baseAddress; + pMacro = g_pVPC->FindOrCreateMacro(szMacroName, true, szMacroValue); + } + + if (!V_stricmp(pToken, "}")) { + break; + } else { + unsigned int dllLength = 0; + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, szLength, + sizeof(szLength))) { + continue; + } + if (strchr(szLength, '.')) { + // assume float format + float fLength = 0; + sscanf(szLength, "%f", &fLength); + dllLength = static_cast(fLength * 1024.0f * 1024.0f); + } else { + sscanf(szLength, "%u", &dllLength); + } + + if (!bPad) { + // will align later when we actually set the darn thing + dllLength = AlignValue(dllLength, 64 * 1024); + } + + if (dllLength == 0) { + g_pVPC->VPCSyntaxError( + "$LoadAddressMacroAuto no longer supports 0 size dlls. Use " + "$LoadAddressMacroAlias to have two orthogonal projects load in " + "the same space"); + } + + baseAddress += dllLength; + } + + ++iEntryNum; + } + + if (bPad && pMacro) { + unsigned int iEndAddress; + if ((iInitialBaseAddress >= 0x82000000) && + (iInitialBaseAddress < 0x8C000000)) { + iEndAddress = 0x8BFFFFFF; + } else { + iEndAddress = 0x9BFFFFFF; + } + + // compute leftover unused address space + unsigned int iRemainingSpace = iEndAddress - baseAddress; + + int iPadPerEntry = iRemainingSpace / iEntryNum; + // iPadPerEntry = iPadPerEntry & ~(64 * 1024); //align DOWN to 64k + if (iPadPerEntry > 0) { + // set the base address again with padding added + iSetBaseAddress += iPadPerEntry * iSetEntryNum; + iSetBaseAddress = AlignValue(iSetBaseAddress, 64 * 1024); + + char szMacroValue[MAX_SYSTOKENCHARS]; + sprintf(szMacroValue, "0x%8.8x", iSetBaseAddress); + + pMacro->value = szMacroValue; + } + } +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_LoadAddressMacroAuto +// +// $LoadAddressMacroAuto +// { +// +// } +// +// Specialized instruction to populate the load address macro based on a +// project name. +//----------------------------------------------------------------------------- +void VPC_Keyword_LoadAddressMacroAuto(void) { + Internal_LoadAddressMacroAuto(false); +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_LoadAddressMacroAuto_Padded +// +// $LoadAddressMacroAuto_Padded +// { +// +// } +// +// Specialized instruction to populate the load address macro based on a +// project name. Assumes the contained list is minimally packed and has +// free reign of space up to the limit. Finds unused space spreads it out +// evenly between each project +//----------------------------------------------------------------------------- +void VPC_Keyword_LoadAddressMacroAuto_Padded(void) { + Internal_LoadAddressMacroAuto(true); +} + +//----------------------------------------------------------------------------- +// VPC_SharedKeyword_Conditional +// $Conditional "CONDITIONNAME" "VALUE" +// +// Sets a conditional that can be used later in the script to affect later +// keywords. This works in both project and group scripts. +//----------------------------------------------------------------------------- +void VPC_SharedKeyword_Conditional() { + const char *pToken = g_pVPC->GetScript().GetToken(false); + if (!pToken || !pToken[0]) g_pVPC->VPCSyntaxError(); + + char name[MAX_SYSTOKENCHARS]; + if (pToken[0] == '$') { + // being nice to users, quietly remove the unwanted conditional prefix '$' + pToken++; + } + strcpy(name, pToken); + + char value[MAX_SYSTOKENCHARS]; + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, value, sizeof(value))) { + return; + } + + conditional_t *pConditional = + g_pVPC->FindOrCreateConditional(name, true, CONDITIONAL_CUSTOM); + if (pConditional->type != CONDITIONAL_CUSTOM) { + // scripts cannot change any platform or game conditionals + g_pVPC->VPCSyntaxError("$Conditional cannot be used on the reserved '$%s'", + pConditional->upperCaseName.Get()); + } + + char environmentValue[MAX_SYSTOKENCHARS]; + if (Sys_EvaluateEnvironmentExpression(value, "0", environmentValue, + sizeof(environmentValue))) { + V_strncpy(value, environmentValue, sizeof(value)); + } + + g_pVPC->SetConditional(name, Sys_StringToBool(value)); +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_IgnoreRedundancyWarning +// +//----------------------------------------------------------------------------- +void VPC_Keyword_IgnoreRedundancyWarning(void) { + char value[MAX_SYSTOKENCHARS]; + + if (!g_pVPC->GetScript().ParsePropertyValue(NULL, value, sizeof(value))) { + return; + } + + bool bVal = Sys_StringToBool(value); + g_pVPC->SetIgnoreRedundancyWarning(bVal); +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_Linux +// +//----------------------------------------------------------------------------- +void VPC_Keyword_Linux(void) { + // always ignore everything in this block + // parsed and processed by a different tool + g_pVPC->GetScript().SkipBracedSection(); +} + +void VPC_PrepareToReadScript(const char *pInputScriptName, int depth, + bool bQuiet, char *&pScriptBuffer, + char szScriptName[MAX_PATH]) { + if (!depth) { + // startup initialization + g_pVPC->GetProjectGenerator()->StartProject(); + } + + V_strncpy(szScriptName, pInputScriptName, MAX_PATH); + V_FixSlashes(szScriptName); + + // always spew the root script + if (!bQuiet) { + bool bSpew = (depth == 0); + g_pVPC->VPCStatus(bSpew, "Parsing: %s", szScriptName); + } + + // parse the text script + if (!Sys_Exists(szScriptName)) { + g_pVPC->VPCError("Cannot open %s", szScriptName); + } + + // load it with the file expansions to compute it's CRC, so we notice if new + // matching files appear on disk and regenerate the project correctly. + size_t scriptLen = + Sys_LoadTextFileWithIncludes(szScriptName, &pScriptBuffer, true); + if (scriptLen == std::numeric_limits::max()) { + // unexpected due to existence check + g_pVPC->VPCError("Cannot open %s", szScriptName); + } + + g_pVPC->AddScriptToCRCCheck( + szScriptName, CRC32_ProcessSingleBuffer(pScriptBuffer, scriptLen)); + + // Allocated via new[]. + delete[] pScriptBuffer; + Sys_LoadTextFileWithIncludes(szScriptName, &pScriptBuffer, false); + + g_pVPC->GetScript().PushScript(szScriptName, pScriptBuffer); +} + +//----------------------------------------------------------------------------- +// Adds the current VPC file to the 'VPC Script' file, setting all the +// custom builds steps needed to verify the .vcproj is up to date +//----------------------------------------------------------------------------- +void VPC_AddCurrentVPCScriptToProjectFolder(bool bDoCRCCheck) { + // skip including VPC scripts if NOVPC is defined + if (g_pVPC->EvaluateConditionalExpression("$NOVPC")) return; + + g_pVPC->GetProjectGenerator()->StartFolder("VPC Scripts"); + g_pVPC->GetProjectGenerator()->StartFile(g_pVPC->GetScript().GetName(), + false); + + // only emit the extra information on windows, and only for the project vpc + if (bDoCRCCheck && g_pVPC->EvaluateConditionalExpression("$WINDOWS")) { + CUtlString sSentinel = + CFmtStr("$PROJECTDIR\\%s.sentinel", g_pVPC->GetScript().GetName()) + .Access(); + bool bShouldSkip; + + CUtlVector configurationNames; + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames(configurationNames); + char rgchCRCCheckExpanded[2048]; + rgchCRCCheckExpanded[0] = '\0'; + for (int i = 0; i < configurationNames.Count(); i++) { + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[i], true); + g_pVPC->GetProjectGenerator()->StartPropertySection( + KEYWORD_CUSTOMBUILDSTEP, &bShouldSkip); + // this will write a sentinel file so we have a clear build target so we + // can know the last time we checked + g_pVPC->GetProjectGenerator()->HandleProperty( + "$Description", CFmtStr("\"Running VPC CRC Check - %s\"", + g_pVPC->GetScript().GetName())); + g_pVPC->GetProjectGenerator()->HandleProperty( + "$CommandLine", + CFmtStr( + "\"rem IncrediBuild_AllowOverlap\n%s\necho crc_complete > %s\"", + g_pVPC->GetMacroValue("CRCCHECK"), sSentinel.Get())); + g_pVPC->GetProjectGenerator()->HandleProperty("$Outputs", sSentinel); + g_pVPC->GetProjectGenerator()->EndPropertySection( + KEYWORD_CUSTOMBUILDSTEP); + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } + } + + g_pVPC->GetProjectGenerator()->EndFile(); + g_pVPC->GetProjectGenerator()->EndFolder(); +} + +void VPC_HandleIncludeStatement(int depth, bool bQuiet, + void (*CallbackFn)(const char *pScriptName, + int depth, bool bQuiet)) { + char szBigBuffer[MAX_SYSTOKENCHARS]; + if (g_pVPC->GetScript().ParsePropertyValue(NULL, szBigBuffer, + sizeof(szBigBuffer))) { + // recurse into and run + char *pScriptBuffer; + char szFixedScriptName[MAX_PATH]; + VPC_PrepareToReadScript(szBigBuffer, depth + 1, bQuiet, pScriptBuffer, + szFixedScriptName); + + VPC_AddCurrentVPCScriptToProjectFolder(false); + + CallbackFn(szBigBuffer, depth + 1, bQuiet); + // Allocated via new[]. + delete[] pScriptBuffer; + + // restore state + g_pVPC->GetScript().PopScript(); + } +} + +void VPC_HandleProjectCommands(const char *pUnusedScriptName, int depth, + bool bQuiet) { + const char *pToken; + + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "}")) { + break; + } else if (!V_stricmp(pToken, "$include")) { + VPC_HandleIncludeStatement(depth, bQuiet, VPC_HandleProjectCommands); + } else if (!V_stricmp(pToken, "$Folder")) { + // root level folder + VPC_Keyword_Folder(false); + } else if (!V_stricmp(pToken, "$Unity")) { + // root level folder + VPC_Keyword_Folder(g_pVPC->IsUnity()); + } else if (!V_stricmp(pToken, "$File")) { + // add root level file + VPC_Keyword_AddFile(); + } else if (!V_stricmp(pToken, "$SchemaFile")) { + // add root level file + VPC_Keyword_AddFile("schema"); + } else if (!V_stricmp(pToken, "-$File")) { + // remove root level file + VPC_Keyword_RemoveFile(); + } else if (!V_stricmp(pToken, "$Shaders")) { + // add root level shaders folder + VPC_Keyword_Shaders(0); + } else if (!V_stricmp(pToken, "$macro")) { + VPC_Keyword_Macro(VPC_MACRO_VALUE); + } else { + g_pVPC->VPCSyntaxError(); + } + } +} + +void WriteCRCCheckFile(const char *pVCProjFilename) { + char szFilename[MAX_PATH]; + V_snprintf(szFilename, sizeof(szFilename), "%s." VPCCRCCHECK_FILE_EXTENSION, + pVCProjFilename); + + FILE *fp = fopen(szFilename, "wt"); + if (!fp) { + g_pVPC->VPCError("Unable to open %s to write CRCs into.", szFilename); + } + + fprintf(fp, "%s\n", VPCCRCCHECK_FILE_VERSION_STRING); + // add the executable crc + char vpcExeAbsPath[MAX_PATH]; + vpcExeAbsPath[0] = '\0'; + CRC32_t nCRCFromFileContents = 0; + if (Sys_GetExecutablePath(vpcExeAbsPath, sizeof(vpcExeAbsPath))) { + char *pBuffer; + int cbVPCExe = Sys_LoadFile(vpcExeAbsPath, (void **)&pBuffer); + + // Calculate the CRC from the contents of the file. + nCRCFromFileContents = CRC32_ProcessSingleBuffer(pBuffer, cbVPCExe); + // Allocated via malloc. + free(pBuffer); + } + + const char *vpcExePath = vpcExeAbsPath; + + char vpcExeRelPath[MAX_PATH]; + vpcExeRelPath[0] = '\0'; + if (V_MakeRelativePath(vpcExeAbsPath, g_pVPC->GetProjectPath(), vpcExeRelPath, + sizeof(vpcExeRelPath))) { + vpcExePath = vpcExeRelPath; + } + + fprintf(fp, "%8.8x %s\n", (unsigned int)nCRCFromFileContents, vpcExePath); + + // add the supplemental string crc + fprintf(fp, "%s\n", g_pVPC->GetCRCString()); + + CUtlDict filenameDict(k_eDictCompareTypeFilenames); + for (int i = 0; i < g_pVPC->m_ScriptList.Count(); i++) { + scriptList_t *pScript = &g_pVPC->m_ScriptList[i]; + + // Use the dictionary to prevent duplicate file CRCs being written in here. + if (filenameDict.Find(pScript->m_scriptName.String()) == + filenameDict.InvalidIndex()) { + filenameDict.Insert(pScript->m_scriptName.String(), 1); + + // [crc] [filename] + fprintf(fp, "%8.8x %s\n", (unsigned int)pScript->m_crc, + pScript->m_scriptName.Get()); + } + } + + fclose(fp); +} + +// +// This is called when it's done parsing a project. If there were any +// $SchemaFile entries in the project, then we will make this project depend on +// schemacompiler (via $AdditionalProjectDependencies). +// +// This fixes full rebuild problems where it's building a project that uses +// schemacompiler at the same time as it's building schemacompiler. This usually +// screws up when it tries to copy the new schemacompiler.exe to game\bin but +// it's in use. +// +void VPC_ForceAdditionalSchemaDependencies(const char *pProjectName) { + if (g_pVPC->m_SchemaFiles.Count() == 0) return; + + // Add "$BASE;SchemaCompiler" to $AdditionalProjectDependencies. + CUtlVector configurationNames; + g_pVPC->GetProjectGenerator()->GetAllConfigurationNames(configurationNames); + for (int i = 0; i < configurationNames.Count(); i++) { + g_pVPC->GetProjectGenerator()->StartConfigurationBlock( + configurationNames[i].String(), false); + g_pVPC->GetProjectGenerator()->StartPropertySection(KEYWORD_GENERAL, NULL); + + g_pVPC->GetProjectGenerator()->HandleProperty( + g_pOption_AdditionalProjectDependencies, "$BASE;SchemaCompiler"); + + g_pVPC->GetProjectGenerator()->EndPropertySection(KEYWORD_GENERAL); + g_pVPC->GetProjectGenerator()->EndConfigurationBlock(); + } +} + +//----------------------------------------------------------------------------- +// VPC_Keyword_Project +// +//----------------------------------------------------------------------------- +void VPC_Keyword_Project(int depth, bool bQuiet) { + char szProjectName[MAX_PATH]; + + // check for optional project name + const char *pToken = g_pVPC->GetScript().PeekNextToken(false); + + if (pToken && pToken[0] && V_stricmp(pToken, "{")) { + // get optional project name + pToken = g_pVPC->GetScript().GetToken(false); + if (!pToken || !pToken[0]) { + g_pVPC->VPCSyntaxError(); + } + + g_pVPC->ResolveMacrosInString(pToken, szProjectName, sizeof(szProjectName)); + + if (g_pVPC->IsDecorateProject()) { + g_pVPC->DecorateProjectName(szProjectName); + } + } else { + CUtlString strName = g_pVPC->GetProjectGenerator()->GetProjectName(); + V_strncpy(szProjectName, strName.String(), sizeof(szProjectName)); + } + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_stricmp(pToken, "{")) g_pVPC->VPCSyntaxError(); + + VPC_HandleProjectCommands(NULL, depth, bQuiet); + + // the unnamed project does not get written, once it is named it will be + // written on closing scope + if (V_stricmp(szProjectName, "UNNAMED")) { + VPC_ForceAdditionalSchemaDependencies(szProjectName); + + // change name + g_pVPC->GetProjectGenerator()->SetProjectName(szProjectName); + + // need to do this at the end of parsing the project, so we have all the + // macros needed + VPC_AddCurrentVPCScriptToProjectFolder(true); + + g_pVPC->GetProjectGenerator()->EndProject(); + g_pVPC->m_bGeneratedProject = true; + } +} + +static const char *fileTypes[] = {"c", "cpp", "cxx", "cc", "h", + "hh", "hxx", "hpp", ""}; +bool VPC_IsBuiltInFileType(const char *extension) { + for (auto *ft : fileTypes) { + if (!V_stricmp(ft, extension)) return true; + } + + return false; +} + +void VPC_Keyword_CustomBuildStep(void) { + bool bAllowNextLine = false; + CUtlVector extensions; + + const char *pToken = NULL; + while (1) { + pToken = g_pVPC->GetScript().GetToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + + // Is this a conditional expression? + if (pToken[0] == '[') { + if (extensions.Count() == 0) { + g_pVPC->VPCSyntaxError( + "Conditional specified on a $CustomBuildStep without any " + "extensions preceding it."); + } + + if (!g_pVPC->EvaluateConditionalExpression(pToken)) { + extensions.Remove(extensions.Count() - 1); + } + + continue; + } + + if (!V_stricmp(pToken, "\\")) { + bAllowNextLine = true; + continue; + } else { + bAllowNextLine = false; + } + + if (VPC_IsBuiltInFileType(pToken)) { + g_pVPC->VPCSyntaxError( + "Cannot define custom build steps for built in file type: %s", + pToken); + } + + CUtlString string = pToken; + extensions.AddToTail(string); + + // check for another optional file + pToken = g_pVPC->GetScript().PeekNextToken(bAllowNextLine); + if (!pToken || !pToken[0]) break; + } + + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0] || V_strcmp(pToken, "{")) { + g_pVPC->VPCSyntaxError("Missing section for CustomBuildStep"); + } else if (extensions.Count() == 0) { + g_pVPC->GetScript().SkipBracedSection(); + return; + } else { + const char *pScriptSave = g_pVPC->GetScript().GetData(); + while (1) { + pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) break; + + if (!V_stricmp(pToken, "}")) { + // end of section + break; + } + } + + CUtlString buildsteps; + if (g_pVPC->GetScript().GetData() > pScriptSave) { + CUtlString tempString; + tempString.SetDirect( + pScriptSave, int(g_pVPC->GetScript().GetData() - pScriptSave - 1)); + buildsteps += "$Configuration\n{\n$CustomBuildStep\n{"; + buildsteps += tempString + "}\n}\n"; + } + + if (!buildsteps.IsEmpty()) { + FOR_EACH_VEC(extensions, i) { + g_pVPC->m_CustomBuildSteps.Insert(extensions[i], buildsteps); + } + } + } +} + +void VPC_ParseProjectScriptParameters(const char *szScriptName, int depth, + bool bQuiet) { + while (1) { + const char *pToken = g_pVPC->GetScript().GetToken(true); + if (!pToken || !pToken[0]) { + // end of file + break; + } + + if (!V_stricmp(pToken, "$include")) { + VPC_HandleIncludeStatement(depth, bQuiet, + VPC_ParseProjectScriptParameters); + } else if (!V_stricmp(pToken, "$configuration")) { + VPC_Keyword_Configuration(); + } else if (!V_stricmp(pToken, "$project")) { + VPC_Keyword_Project(depth, bQuiet); + } else if (!V_stricmp(pToken, "$macro")) { + VPC_Keyword_Macro(VPC_MACRO_VALUE); + } else if (!V_stricmp(pToken, "$MacroEmptyString")) { + VPC_Keyword_Macro(VPC_MACRO_EMPTY_STRING); + } else if (!V_stricmp(pToken, "$MacroRequired")) { + VPC_Keyword_MacroRequired(VPC_MACRO_REQUIRED_NOT_EMPTY); + } else if (!V_stricmp(pToken, "$MacroRequiredAllowEmpty")) { + VPC_Keyword_MacroRequired(VPC_MACRO_REQUIRED_ALLOW_EMPTY); + } else if (!V_stricmp(pToken, "$LoadAddressMacro")) { + VPC_Keyword_LoadAddressMacro(); + } else if (!V_stricmp(pToken, "$LoadAddressMacroAlias")) { + VPC_Keyword_LoadAddressMacroAlias(); + } else if (!V_stricmp(pToken, "$LoadAddressMacroAuto")) { + VPC_Keyword_LoadAddressMacroAuto(); + } else if (!V_stricmp(pToken, "$LoadAddressMacroAuto_Padded")) { + VPC_Keyword_LoadAddressMacroAuto_Padded(); + } else if (!V_stricmp(pToken, "$IgnoreRedundancyWarning")) { + VPC_Keyword_IgnoreRedundancyWarning(); + } else if (!V_stricmp(pToken, "$Linux")) { + VPC_Keyword_Linux(); + } else if (!V_stricmp(pToken, "$CustomBuildStep")) { + VPC_Keyword_CustomBuildStep(); + } else if (!V_stricmp(pToken, "$Conditional")) { + VPC_SharedKeyword_Conditional(); + } else { + g_pVPC->VPCSyntaxError(); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVPC::ParseProjectScript(const char *pScriptName, int depth, bool bQuiet, + bool bWriteCRCCheckFile) { + char *pScriptBuffer; + char szScriptName[MAX_PATH]; + + VPC_PrepareToReadScript(pScriptName, depth, bQuiet, pScriptBuffer, + szScriptName); + + int cMissingFilesPreParse = g_pVPC->GetMissingFilesCount(); + + if (!depth) { + // create reserved $ROOTSCRIPT - tracks the root script + FindOrCreateMacro("ROOTSCRIPT", true, szScriptName); + + // create reserved $PROJECTNAME - tracks the undecorated pure project name + // $(ProjectName) can be auto-decorated, making it unusable by scripts + // expecting a pure project name + FindOrCreateMacro("PROJECTNAME", true, g_pVPC->GetProjectName()); + + // create reserved $LOADADDRESSNAME - defaults to project name but can be + // aliased with $LoadAddressMacroAlias + FindOrCreateMacro("LOADADDRESSNAME", true, g_pVPC->GetLoadAddressName()); + + // create reserved $CRCCHECK + // The CRCs themselves are dumped into theproject.vcproj.vpc_crc (in + // VPC_WriteCRCCheckFile), so all this does is point vpccrccheck.exe at it + // with "vpccrccheck.exe -crc2 theproject.vcproj" scripts add the terminal + // /n if they append, after referencing $CRCCHECK needs to be quoted to work + // with /dp which puts " (platform) " in project names, + + CUtlString crcCheckString; + crcCheckString = + "if exist $QUOTE$SRCDIR\\devtools\\bin\\" VPCCRCCHECK_EXE_FILENAME + "$QUOTE $QUOTE$SRCDIR\\devtools\\bin\\" VPCCRCCHECK_EXE_FILENAME + "$QUOTE -crc2 $QUOTE"; + crcCheckString += g_pVPC->GetOutputFilename(); + crcCheckString += "$QUOTE\nif ERRORLEVEL 1 exit /b 1"; + g_pVPC->FindOrCreateMacro("CRCCHECK", true, crcCheckString.Get()); + + // create reserved $PROJECTDIR + char szProjectRootPath[MAX_PATH]; + V_snprintf(szProjectRootPath, sizeof(szProjectRootPath), "%s", + g_pVPC->GetProjectPath()); + V_RemoveDotSlashes(szProjectRootPath); + SetMacro("PROJECTDIR", szProjectRootPath, true); + + // reset + g_pVPC->m_SchemaFiles.Purge(); + + // reset unity file tracking (it's per project) + g_pVPC->m_UnityFilesSeen.RemoveAll(); + g_pVPC->m_UnityStack.Clear(); + g_pVPC->m_sUnityCurrent = NULL; + } + + VPC_ParseProjectScriptParameters(szScriptName, depth, bQuiet); + + // Allocated via new[]. + delete[] pScriptBuffer; + + // for safety, force callers to restore to proper state + g_pVPC->GetScript().PopScript(); + + if (!depth) { + // at end of all processing, don't write crc checks if we're missing files + if (bWriteCRCCheckFile && + g_pVPC->GetMissingFilesCount() == cMissingFilesPreParse) { + // Finally write out the file with all the CRCs in it. This is referenced + // by the $CRCCHECK macro in the prebuild steps. + WriteCRCCheckFile(g_pVPC->GetOutputFilename()); + } + + g_pVPC->m_ScriptList.Purge(); + g_pVPC->RemoveScriptCreatedMacros(); // Remove any macros that came from + // the script file. + } + + return true; +} + +void CVPC::AddScriptToCRCCheck(const char *pScriptName, CRC32_t crc) { + for (intp i = 0; i < m_ScriptList.Count(); i++) { + if (!V_stricmp(m_ScriptList[i].m_scriptName, pScriptName)) { + // update + g_pVPC->m_ScriptList[i].m_crc = crc; + return; + } + } + + intp index = g_pVPC->m_ScriptList.AddToTail(); + g_pVPC->m_ScriptList[index].m_scriptName = pScriptName; + g_pVPC->m_ScriptList[index].m_crc = crc; +} + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +// +// Schema Script Generation +// +// Temporary - Once the schema workflow settles down, this schema-specific code +// should be +// at minimum moved into its own file, and ideally generalized so +// that VPC has a minimum of embedded schema-specific logic. +// +// +// +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// + +KeyValues *ConfigPreprocessorSettingsAsKV(CSpecificConfig *pConfig); +struct SchemaFileInfo_t { + bool bIsCppFile; + char szFile[MAX_PATH]; + char szGeneratedFile[MAX_PATH]; +}; + +void VPC_FakeKeyword_SchemaFolder(CBaseProjectDataCollector *pDataCollector) { + if (g_pVPC->m_SchemaFiles.Count() == 0) return; + + macro_t *pMacro = g_pVPC->FindOrCreateMacro("NOSCHEMACOMPILER", false, NULL); + if (pMacro && atoi(pMacro->value.Get())) return; + + CUtlString platformName = g_pVPC->GetTargetPlatformName(); + V_strlower(platformName.Get()); + const char *pPlatformName = platformName.String(); + + CUtlVector schemaFileInfos; + schemaFileInfos.AddMultipleToTail(g_pVPC->m_SchemaFiles.Count()); + + char schemaCompilerPath[MAX_PATH]; + g_pVPC->ResolveMacrosInString("$SRCDIR/devtools/bin/schemacompiler.dll", + schemaCompilerPath, sizeof(schemaCompilerPath)); + + CUtlString schemaInputs; + CUtlString schemaOutputs; + char szExt[MAX_PATH]; + + schemaInputs += schemaCompilerPath; + + for (int i = 0; i < g_pVPC->m_SchemaFiles.Count(); i++) { + if (!g_pVPC->m_SchemaFiles[i].String()) continue; + + schemaInputs += ";"; + schemaInputs += g_pVPC->m_SchemaFiles[i]; + + V_ExtractFileExtension(g_pVPC->m_SchemaFiles[i], szExt, sizeof(szExt)); + SchemaFileInfo_t &fileInfo = schemaFileInfos[i]; + + V_strncpy(fileInfo.szFile, g_pVPC->m_SchemaFiles[i], + sizeof(fileInfo.szFile)); + V_FixSlashes(fileInfo.szFile, '/'); + + char szNoExt[MAX_PATH]; + V_StripExtension(fileInfo.szFile, szNoExt, sizeof(szNoExt)); + char szBase[MAX_PATH]; + V_FileBase(fileInfo.szFile, szBase, sizeof(szBase)); + char szPath[MAX_PATH]; + V_ExtractFilePath(fileInfo.szFile, szPath, sizeof(szPath)); + + char szGenRelPath[MAX_PATH]; + + if (IsCFileExtension(szExt)) { + fileInfo.bIsCppFile = true; + V_snprintf(szGenRelPath, sizeof(szGenRelPath), + "generated_code_%s_%s/%s_schema_cpp_gen.cpp", + g_pVPC->GetProjectName(), pPlatformName, szBase); + } else { + fileInfo.bIsCppFile = false; + V_snprintf(szGenRelPath, sizeof(szGenRelPath), + "generated_code_%s_%s/%s_schema_gen.cpp", + g_pVPC->GetProjectName(), pPlatformName, szBase); + } + + V_strncpy(fileInfo.szGeneratedFile, szGenRelPath, + sizeof(fileInfo.szGeneratedFile)); + V_FixSlashes(fileInfo.szGeneratedFile, '/'); + + schemaOutputs += fileInfo.szGeneratedFile; + schemaOutputs += ";"; + } + + char szSchemaOutAnchorPath[MAX_PATH]; + V_snprintf(szSchemaOutAnchorPath, sizeof(szSchemaOutAnchorPath), + "generated_code_%s_%s/%s_schema_anchor.cpp", + g_pVPC->GetProjectName(), pPlatformName, g_pVPC->GetProjectName()); + V_FixSlashes(szSchemaOutAnchorPath, '/'); + schemaOutputs += szSchemaOutAnchorPath; + schemaOutputs += ";"; + + char szSchemaPath[MAX_PATH]; + V_snprintf(szSchemaPath, sizeof(szSchemaPath), "%s_%s.schproj", + g_pVPC->GetProjectName(), pPlatformName); + + ////////////////////////////////////////////////////////////////////////// + + CUtlBuffer vpcBuffer; + + char schemaCompilerExePath[MAX_PATH]; + g_pVPC->ResolveMacrosInString("$SRCDIR/devtools/bin/schemacompiler.exe", + schemaCompilerExePath, + sizeof(schemaCompilerExePath)); + V_FixSlashes(schemaCompilerExePath); + + vpcBuffer.SetBufferType(true, true); + vpcBuffer.Printf("$Folder \"Autogenerated Schema Files\" \n"); + vpcBuffer.Printf("{\n"); + vpcBuffer.Printf( + "$File \"%s\"\n" + "{\n" + " $Configuration\n" + " {\n" + " $CustomBuildStep\n" + " {\n" + " $CommandLine \"%s -schproj $(InputPath) -config " + "$(ConfigurationName)\"\n" + " $Description \"Schema Compiler for " + "[$(ConfigurationName)] $(InputName)\"\n" + " $Outputs \"%s\"\n" + " $AdditionalDependencies \"%s\"\n" + " }\n" + " }\n" + "}\n", + szSchemaPath, schemaCompilerExePath, schemaOutputs.Get(), + schemaInputs.Get()); + + static const char *schemaConfiguration = + "{\n" + " $Configuration\n" + " {\n" + " $Compiler\n" + " {\n" + //" $PreprocessorDefinitions + //\"NO_MEMOVERRIDE_NEW_DELETE\"\n" + " $Create/UsePrecompiledHeader \"Not Using " + "Precompiled Headers\"\n" + " }\n" + " }\n" + "}\n"; + + vpcBuffer.Printf("$Folder \"Cpp Schema Wrappers\" \n"); + { + vpcBuffer.Printf("{\n"); + + for (int i = 0; i < schemaFileInfos.Count(); ++i) { + if (schemaFileInfos[i].bIsCppFile) { + vpcBuffer.Printf("$File \"%s\"\n", schemaFileInfos[i].szGeneratedFile); + vpcBuffer.Printf(schemaConfiguration); + } + } + + vpcBuffer.Printf("}\n"); + } + + vpcBuffer.Printf("$Folder \"Header-Generated Files\" \n"); + { + vpcBuffer.Printf("{\n"); + + for (int i = 0; i < schemaFileInfos.Count(); ++i) { + if (!schemaFileInfos[i].bIsCppFile) { + vpcBuffer.Printf("$File \"%s\"\n", schemaFileInfos[i].szGeneratedFile); + vpcBuffer.Printf(schemaConfiguration); + } + } + + vpcBuffer.Printf("}\n"); + } + + vpcBuffer.Printf("$File \"%s\"\n", szSchemaOutAnchorPath); + vpcBuffer.Printf(schemaConfiguration); + vpcBuffer.Printf("}\n"); + + // save parser + bool bIgnoreRedundancyWarning = g_pVPC->IsIgnoreRedundancyWarning(); + g_pVPC->SetIgnoreRedundancyWarning(true); + g_pVPC->GetScript().PushScript("Internal List [Schema]", + (char *)vpcBuffer.Base()); + + const char *pToken = g_pVPC->GetScript().GetToken(true); + if (pToken && pToken[0] && !V_stricmp(pToken, "$folder")) { + VPC_Keyword_Folder(false); + } + + // restore parser + g_pVPC->GetScript().PopScript(); + g_pVPC->SetIgnoreRedundancyWarning(bIgnoreRedundancyWarning); + + ////////////////////////////////////////////////////////////////////////// + + KeyValues *pOutKeyValues = KeyValues::AutoDelete("schproj"); + pOutKeyValues->SetString("project_name", g_pVPC->GetProjectName()); + pOutKeyValues->SetString("platform_name", pPlatformName); + pOutKeyValues->SetString("anchor_path", szSchemaOutAnchorPath); + char dmeTargetFolder[MAX_PATH]; + g_pVPC->ResolveMacrosInString("$SRCDIR\\public", dmeTargetFolder, + sizeof(dmeTargetFolder)); + pOutKeyValues->SetString("dme_target_folder", dmeTargetFolder); + + KeyValues *pOutAllConfigs = new KeyValues("configs"); + pOutKeyValues->AddSubKey(pOutAllConfigs); + + for (int i = pDataCollector->m_BaseConfigData.m_Configurations.First(); + i != pDataCollector->m_BaseConfigData.m_Configurations.InvalidIndex(); + i = pDataCollector->m_BaseConfigData.m_Configurations.Next(i)) { + CSpecificConfig *pConfig = + pDataCollector->m_BaseConfigData.m_Configurations[i]; + pOutAllConfigs->AddSubKey(ConfigPreprocessorSettingsAsKV(pConfig)); + } + + char szNum[64]; + { + KeyValues *pOutInputs = new KeyValues("inputs"); + int nInput = 0; + pOutKeyValues->AddSubKey(pOutInputs); + for (int i = 0; i < schemaFileInfos.Count(); i++) { + V_snprintf(szNum, sizeof(szNum), "%03d", nInput++); + + SchemaFileInfo_t &fileInfo = schemaFileInfos[i]; + + KeyValues *pOutFile = new KeyValues(szNum); + pOutInputs->AddSubKey(pOutFile); + + pOutFile->SetString("generation_target", fileInfo.szGeneratedFile); + pOutFile->SetBool("is_cpp", fileInfo.bIsCppFile); + pOutFile->SetString("input", fileInfo.szFile); + } + } + CUtlBuffer tmpBuf; + tmpBuf.SetBufferType(true, true); + pOutKeyValues->RecursiveSaveToFile(tmpBuf, 0); + + FILE *fp = fopen(szSchemaPath, "wt"); + if (!fp) { + g_pVPC->VPCStatus(true, "Error Saving File: '%s'", szSchemaPath); + } else { + fwrite(tmpBuf.Base(), sizeof(char), tmpBuf.TellMaxPut(), fp); + fclose(fp); + } +} + +KeyValues *ConfigPreprocessorSettingsAsKV(CSpecificConfig *pConfig) { + KeyValues *pOutConfig = new KeyValues(pConfig->GetConfigName()); + + char szNum[64]; + KeyValues *pInConfigKV = pConfig->m_pKV; + + ////////////////////////////////////////////////////////////////////////// + // write defines + + { + KeyValues *pOutDefines = new KeyValues("defines"); + pOutConfig->AddSubKey(pOutDefines); + + CSplitString outStrings( + pInConfigKV->GetString(g_pOption_PreprocessorDefinitions), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + + int nDefine = 0; + + for (int i = 0; i < outStrings.Count(); i++) { + V_snprintf(szNum, sizeof(szNum), "%03d", nDefine++); + pOutDefines->SetString(szNum, outStrings[i]); + } + + // change #1001922 from source2 did the pBuf... + char pBuf[512]; + for (int i = 0; i < g_pVPC->m_Macros.Count(); i++) { + macro_t *pMacro = &g_pVPC->m_Macros[i]; + + if (pMacro->m_bSetupDefineInProjectFile) { + V_snprintf(szNum, sizeof(szNum), "%03d", nDefine++); + V_snprintf(pBuf, sizeof(pBuf), "%s=%s", pMacro->name.Get(), + pMacro->value.Get()); + pOutDefines->SetString(szNum, pBuf); + } + } + } + + ////////////////////////////////////////////////////////////////////////// + // write includes + + KeyValues *pOutIncludes = new KeyValues("includes"); + pOutConfig->AddSubKey(pOutIncludes); + + int nInclude = 0; + + CSplitString outStrings( + pInConfigKV->GetString(g_pOption_AdditionalIncludeDirectories), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < outStrings.Count(); i++) { + V_snprintf(szNum, sizeof(szNum), "%03d", nInclude++); + + char sDir[MAX_PATH]; + V_StrSubst(outStrings[i], "$(IntDir)", "$(OBJ_DIR)", sDir, sizeof(sDir)); + V_FixSlashes(sDir, '/'); + pOutIncludes->SetString(szNum, sDir); + } + + return pOutConfig; +} diff --git a/utils/vpc/scriptsource.cpp b/utils/vpc/scriptsource.cpp new file mode 100644 index 0000000..4a31acb --- /dev/null +++ b/utils/vpc/scriptsource.cpp @@ -0,0 +1,475 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" + +#include "tier0/memdbgon.h" + +#define MAX_SCRIPT_STACK_SIZE 32 + +CScript::CScript() { + m_ScriptName = "(empty)"; + m_bFreeScriptAtPop = false; + m_nScriptLine = 0; + m_pScriptData = NULL; + m_pScriptLine = &m_nScriptLine; + + m_Token[0] = '\0'; + m_PeekToken[0] = '\0'; +} + +const char *CScript::SkipWhitespace(const char *data, bool *pHasNewLines, + int *pNumLines) { + int c; + + while ((c = *data) <= ' ') { + if (c == '\n') { + if (pNumLines) { + (*pNumLines)++; + } + + if (pHasNewLines) { + *pHasNewLines = true; + } + } else if (!c) { + return (NULL); + } + + data++; + } + + return data; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CScript::SkipToValidToken(const char *data, bool *pHasNewLines, + int *pNumLines) { + int c; + + for (;;) { + data = SkipWhitespace(data, pHasNewLines, pNumLines); + + c = *data; + if (!c) { + break; + } + + if (c == '/' && data[1] == '/') { + // skip double slash comments + data += 2; + while (*data && *data != '\n') { + data++; + } + if (*data && *data == '\n') { + data++; + if (pNumLines) { + (*pNumLines)++; + } + if (pHasNewLines) { + *pHasNewLines = true; + } + } + } else if (c == '/' && data[1] == '*') { + // skip /* */ comments + data += 2; + while (*data && (*data != '*' || data[1] != '/')) { + if (*data == '\n') { + if (pNumLines) { + (*pNumLines)++; + } + if (pHasNewLines) { + *pHasNewLines = true; + } + } + data++; + } + + if (*data) { + data += 2; + } + } else { + break; + } + } + + return data; +} + +//----------------------------------------------------------------------------- +// The next token should be an open brace. +// Skips until a matching close brace is found. +// Internal brace depths are properly skipped. +//----------------------------------------------------------------------------- +void CScript::SkipBracedSection(const char **dataptr, int *numlines) { + const char *token; + int depth; + + depth = 0; + do { + token = GetToken(dataptr, true, numlines); + if (token[1] == '\0') { + if (token[0] == '{') + depth++; + else if (token[0] == '}') + depth--; + } + } while (depth && *dataptr); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CScript::SkipRestOfLine(const char **dataptr, int *numlines) { + const char *p; + int c; + + p = *dataptr; + while ((c = *p++) != '\0') { + if (c == '\n') { + if (numlines) (*numlines)++; + break; + } + } + *dataptr = p; +} + +//----------------------------------------------------------------------------- +// Does not corrupt results obtained with GetToken(). +//----------------------------------------------------------------------------- +const char *CScript::PeekNextToken(const char *dataptr, bool bAllowLineBreaks) { + // save the primary token, about to be corrupted + char savedToken[MAX_SYSTOKENCHARS]; + V_strncpy(savedToken, m_Token, MAX_SYSTOKENCHARS); + + const char *pSaved = dataptr; + const char *pToken = GetToken(&pSaved, bAllowLineBreaks, NULL); + + // restore + V_strncpy(m_PeekToken, pToken, MAX_SYSTOKENCHARS); + V_strncpy(m_Token, savedToken, MAX_SYSTOKENCHARS); + + return m_PeekToken; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CScript::GetToken(const char **dataptr, bool allowLineBreaks, + int *pNumLines) { + char c; + char endSymbol; + int len; + bool hasNewLines; + const char *data; + + c = 0; + data = *dataptr; + len = 0; + m_Token[0] = 0; + hasNewLines = false; + + // make sure incoming data is valid + if (!data) { + *dataptr = NULL; + return m_Token; + } + + for (;;) { + // skip whitespace + data = SkipWhitespace(data, &hasNewLines, pNumLines); + if (!data) { + *dataptr = NULL; + return m_Token; + } + + if (hasNewLines && !allowLineBreaks) { + *dataptr = data; + return m_Token; + } + + c = *data; + + if (c == '/' && data[1] == '/') { + // skip double slash comments + data += 2; + while (*data && *data != '\n') { + data++; + } + if (*data && *data == '\n') { + if (!allowLineBreaks) continue; + + data++; + if (pNumLines) { + (*pNumLines)++; + } + } + } else if (c == '/' && data[1] == '*') { + // skip /* */ comments + data += 2; + while (*data && (*data != '*' || data[1] != '/')) { + if (*data == '\n' && pNumLines) { + (*pNumLines)++; + } + data++; + } + + if (*data) { + data += 2; + } + } else + break; + } + + // handle scoped strings "???" [???] + if (c == '\"' || c == '<' || c == '[') { + bool bConditionalExpression = false; + endSymbol = '\0'; + switch (c) { + case '\"': + endSymbol = '\"'; + break; + case '<': + endSymbol = '>'; + break; + case '[': + bConditionalExpression = true; + endSymbol = ']'; + break; + } + + // want to preserve entire conditional expession [blah...blah...blah] + // maintain a conditional's open/close scope characters + if (!bConditionalExpression) { + // skip past scope character + data++; + } + + for (;;) { + c = *data++; + + if (c == endSymbol || !c) { + if (c == endSymbol && bConditionalExpression) { + // keep end symbol + m_Token[len++] = c; + } + + m_Token[len] = 0; + *dataptr = (char *)data; + return m_Token; + } + + if (len < MAX_SYSTOKENCHARS - 1) { + m_Token[len++] = c; + } + } + } + + // parse a regular word + do { + if (len < MAX_SYSTOKENCHARS) { + m_Token[len++] = c; + } + + data++; + c = *data; + } while (c > ' '); + + if (len >= MAX_SYSTOKENCHARS) { + len = 0; + } + + m_Token[len] = '\0'; + *dataptr = (char *)data; + + return m_Token; +} + +void CScript::PushScript(const char *file_name) { + // parse the text script + if (!Sys_Exists(file_name)) { + g_pVPC->VPCError("Cannot open %s", file_name); + } + + char *script; + Sys_LoadTextFileWithIncludes(file_name, &script, false); + + PushScript(file_name, script, 1, true); +} + +void CScript::PushScript(const char *pScriptName, const char *pScriptData, + int nScriptLine, bool bFreeScriptAtPop) { + if (m_ScriptStack.Count() > MAX_SCRIPT_STACK_SIZE) { + g_pVPC->VPCError("PushScript( scriptname=%s ) - stack overflow\n", + pScriptName); + } + + // Push the current state onto the stack. + m_ScriptStack.Push(GetCurrentScript()); + + // Set their state as the current state. + m_ScriptName = pScriptName; + m_pScriptData = pScriptData; + m_nScriptLine = nScriptLine; + m_bFreeScriptAtPop = bFreeScriptAtPop; +} + +void CScript::PushCurrentScript() { + PushScript(m_ScriptName.Get(), m_pScriptData, m_nScriptLine, + m_bFreeScriptAtPop); +} + +CScriptSource CScript::GetCurrentScript() { + return CScriptSource(m_ScriptName.Get(), m_pScriptData, m_nScriptLine, + m_bFreeScriptAtPop); +} + +void CScript::RestoreScript(const CScriptSource &scriptSource) { + m_ScriptName = scriptSource.GetName(); + m_pScriptData = scriptSource.GetData(); + m_nScriptLine = scriptSource.GetLine(); + m_bFreeScriptAtPop = scriptSource.IsFreeScriptAtPop(); +} + +void CScript::PopScript() { + if (m_ScriptStack.Count() == 0) { + g_pVPC->VPCError("PopScript(): stack is empty"); + } + + if (m_bFreeScriptAtPop && m_pScriptData) { + // Allocated via new[]. + delete[] m_pScriptData; + } + + // Restore the top entry on the stack and pop it off. + const CScriptSource &state = m_ScriptStack.Top(); + m_ScriptName = state.GetName(); + m_pScriptData = state.GetData(); + m_nScriptLine = state.GetLine(); + m_bFreeScriptAtPop = state.IsFreeScriptAtPop(); + + m_ScriptStack.Pop(); +} + +void CScript::EnsureScriptStackEmpty() { + if (m_ScriptStack.Count() != 0) { + g_pVPC->VPCError("EnsureScriptStackEmpty(): script stack is not empty!"); + } +} + +void CScript::SpewScriptStack() { + if (m_ScriptStack.Count()) { + CUtlString str; + + // emit stack with current at top + str += "Script Stack:\n"; + str += CFmtStr(" %s Line:%d\n", m_ScriptName.String(), m_nScriptLine); + for (int i = m_ScriptStack.Count() - 1; i >= 0; i--) { + if (i == 0 && !m_ScriptStack[i].GetData() && + m_ScriptStack[i].GetLine() <= 0) { + // ignore empty bottom of stack + break; + } + + str += CFmtStr(" %s Line:%d\n", m_ScriptStack[i].GetName(), + m_ScriptStack[i].GetLine()); + } + str += "\n"; + + Log_Msg(LOG_VPC, "%s", str.String()); + } +} + +const char *CScript::GetToken(bool bAllowLineBreaks) { + return GetToken(&m_pScriptData, bAllowLineBreaks, m_pScriptLine); +} + +const char *CScript::PeekNextToken(bool bAllowLineBreaks) { + return PeekNextToken(m_pScriptData, bAllowLineBreaks); +} + +void CScript::SkipRestOfLine() { + SkipRestOfLine(&m_pScriptData, m_pScriptLine); +} + +void CScript::SkipBracedSection() { + SkipBracedSection(&m_pScriptData, m_pScriptLine); +} + +void CScript::SkipToValidToken() { + m_pScriptData = SkipToValidToken(m_pScriptData, NULL, m_pScriptLine); +} + +//----------------------------------------------------------------------------- +// Handles expressions of the form <$BASE> ... [condition] +// Output is a concatenated string. +// +// Returns true if expression should be used, false if it should be ignored +//due to an optional condition that evaluated false. +//----------------------------------------------------------------------------- +bool CScript::ParsePropertyValue(const char *pBaseString, char *pOutBuff, + intp outBuffSize) { + const char **pScriptData = &m_pScriptData; + int *pScriptLine = m_pScriptLine; + + const char *pToken; + const char *pNextToken; + char *pOut = pOutBuff; + intp remaining = outBuffSize - 1; + intp len; + bool bAllowNextLine = false; + char buffer1[MAX_SYSTOKENCHARS]; + char buffer2[MAX_SYSTOKENCHARS]; + bool bResult = true; + + while (1) { + pToken = GetToken(pScriptData, bAllowNextLine, pScriptLine); + if (!pToken || !pToken[0]) g_pVPC->VPCSyntaxError(); + + pNextToken = PeekNextToken(*pScriptData, false); + if (!pNextToken || !pNextToken[0]) { + // current token is last token + // last token can be optional conditional, need to identify + // backup and reparse up to last token + if (pToken[0] == '[') { + // last token is an optional conditional + bResult = g_pVPC->EvaluateConditionalExpression(pToken); + break; + } + } + + if (!V_stricmp(pToken, "\\")) { + bAllowNextLine = true; + continue; + } else { + bAllowNextLine = false; + } + + if (!V_stricmp(pToken, "\\n")) { + pToken = "\n"; + } + + // handle reserved macro + if (!pBaseString) pBaseString = ""; + + strcpy(buffer1, pToken); + Sys_ReplaceString(buffer1, "$base", pBaseString, buffer2, sizeof(buffer2)); + + g_pVPC->ResolveMacrosInString(buffer2, buffer1, sizeof(buffer1)); + + len = V_strlen(buffer1); + if (remaining < len) len = remaining; + + if (len > 0) { + memcpy(pOut, buffer1, len); + pOut += len; + remaining -= len; + } + + pToken = PeekNextToken(*pScriptData, false); + if (!pToken || !pToken[0] || !V_stricmp(pNextToken, "}")) break; + } + + *pOut++ = '\0'; + + if (!pOutBuff[0]) g_pVPC->VPCSyntaxError(); + + return bResult; +} \ No newline at end of file diff --git a/utils/vpc/scriptsource.h b/utils/vpc/scriptsource.h new file mode 100644 index 0000000..f2a6fc4 --- /dev/null +++ b/utils/vpc/scriptsource.h @@ -0,0 +1,90 @@ +// Copyright Valve Corporation, All rights reserved. +// +// This module manages a stack of "script sources". + +#ifndef VPC_SCRIPTSOURCE_H_ +#define VPC_SCRIPTSOURCE_H_ + +#define MAX_SYSPRINTMSG 4096 +#define MAX_SYSTOKENCHARS 4096 + +class CScriptSource { + public: + CScriptSource() { Set("", NULL, 0, false); } + + CScriptSource(const char *pScriptName, const char *pScriptData, + int nScriptLine, bool bFreeScriptAtPop) { + Set(pScriptName, pScriptData, nScriptLine, bFreeScriptAtPop); + } + + void Set(const char *pScriptName, const char *pScriptData, int nScriptLine, + bool bFreeScriptAtPop) { + m_ScriptName = pScriptName; + m_pScriptData = pScriptData; + m_nScriptLine = nScriptLine; + m_bFreeScriptAtPop = bFreeScriptAtPop; + } + + const char *GetName() const { return m_ScriptName.Get(); } + const char *GetData() const { return m_pScriptData; } + int GetLine() const { return m_nScriptLine; } + bool IsFreeScriptAtPop() const { return m_bFreeScriptAtPop; } + + private: + CUtlString m_ScriptName; + const char *m_pScriptData; + int m_nScriptLine; + bool m_bFreeScriptAtPop; +}; + +class CScript { + public: + CScript(); + + void PushScript(const char *pFilename); + void PushScript(const char *pScriptName, const char *ppScriptData, + int nScriptLine = 1, bool bFreeScriptAtPop = false); + void PushCurrentScript(); + void PopScript(); + CScriptSource GetCurrentScript(); + void RestoreScript(const CScriptSource &scriptSource); + void EnsureScriptStackEmpty(); + void SpewScriptStack(); + + const char *GetName() const { return m_ScriptName.Get(); } + const char *GetData() const { return m_pScriptData; } + int GetLine() const { return m_nScriptLine; } + + const char *GetToken(bool bAllowLineBreaks); + const char *PeekNextToken(bool bAllowLineBreaks); + void SkipRestOfLine(); + void SkipBracedSection(); + void SkipToValidToken(); + + bool ParsePropertyValue(const char *pBaseString, char *pOutBuff, + intp outBuffSize); + + private: + const char *SkipWhitespace(const char *data, bool *pHasNewLines, + int *pNumLines); + const char *SkipToValidToken(const char *data, bool *pHasNewLines, + int *pNumLines); + void SkipBracedSection(const char **dataptr, int *numlines); + void SkipRestOfLine(const char **dataptr, int *numlines); + const char *PeekNextToken(const char *dataptr, bool bAllowLineBreaks); + const char *GetToken(const char **dataptr, bool allowLineBreaks, + int *pNumLines); + + CUtlStack m_ScriptStack; + + int m_nScriptLine; + int *m_pScriptLine; + const char *m_pScriptData; + CUtlString m_ScriptName; + bool m_bFreeScriptAtPop; + + char m_Token[MAX_SYSTOKENCHARS]; + char m_PeekToken[MAX_SYSTOKENCHARS]; +}; + +#endif // VPC_SCRIPTSOURCE_H_ diff --git a/utils/vpc/solutiongenerator_codelite.cpp b/utils/vpc/solutiongenerator_codelite.cpp new file mode 100644 index 0000000..f289e90 --- /dev/null +++ b/utils/vpc/solutiongenerator_codelite.cpp @@ -0,0 +1,317 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "dependencies.h" +#include "utlgraph.h" + +#include "tier0/memdbgon.h" + +class CSolutionGenerator_CodeLite : public IBaseSolutionGenerator { + public: + CSolutionGenerator_CodeLite() { + m_nIndent = 0; + m_fp = NULL; + } + + virtual void GenerateSolutionFile( + const char *pSolutionFilename, + CUtlVector &projects) { + char szSolutionName[MAX_PATH]; + V_FileBase(pSolutionFilename, szSolutionName, MAX_PATH); + + char szSolutionFileBaseName[MAX_PATH]; + + // Default extension. + char szTmpSolutionFilename[MAX_PATH]; + if (!V_GetFileExtension(pSolutionFilename)) { + V_strncpy(szSolutionFileBaseName, pSolutionFilename, + sizeof(szSolutionFileBaseName)); + V_snprintf(szTmpSolutionFilename, sizeof(szTmpSolutionFilename), + "%s.workspace", pSolutionFilename); + pSolutionFilename = szTmpSolutionFilename; + } else { + V_StripExtension(pSolutionFilename, szSolutionFileBaseName, + sizeof(szSolutionFileBaseName)); + } + + Msg("\nWriting CodeLite workspace %s.\n\n", pSolutionFilename); + + // Write the file. + m_fp = fopen(pSolutionFilename, "wt"); + if (!m_fp) + g_pVPC->VPCError("Can't open %s for writing.", pSolutionFilename); + + Write("\n"); + Write("\n", + szSolutionName, szSolutionFileBaseName); + + ++m_nIndent; + Write("\n", + szSolutionFileBaseName); + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + project_t *pProjectT = &g_pVPC->m_Projects[pCurProject->m_iProjectIndex]; + + char szProjectFileBaseName[MAX_PATH]; + V_StripExtension(pCurProject->m_ProjectFilename.String(), + szProjectFileBaseName, sizeof(szProjectFileBaseName)); + + Write("\n", + pProjectT->name.String(), szProjectFileBaseName); + } + + Write("\n"); + ++m_nIndent; + Write("\n"); + ++m_nIndent; + Write("\n"); + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + project_t *pProjectT = &g_pVPC->m_Projects[pCurProject->m_iProjectIndex]; + + Write("\n", + pProjectT->name.String()); + } + --m_nIndent; + Write("\n"); + + Write("\n"); + ++m_nIndent; + Write("\n"); + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + project_t *pProjectT = &g_pVPC->m_Projects[pCurProject->m_iProjectIndex]; + + Write("\n", + pProjectT->name.String()); + } + --m_nIndent; + Write("\n"); + --m_nIndent; + Write("\n"); + --m_nIndent; + Write("\n"); + + fclose(m_fp); + + WriteBuildOrderProject(szSolutionFileBaseName, projects); + } + + void WriteBuildOrderProject(const char *pszSolutionFileBaseName, + CUtlVector &projects) { + // write out a project with no files to encode the build order + // (dependencies) + char szProjectFileName[MAX_PATH]; + V_snprintf(szProjectFileName, sizeof(szProjectFileName), "%s.project", + pszSolutionFileBaseName); + + m_nIndent = 0; + m_fp = fopen(szProjectFileName, "wt"); + if (!m_fp) + g_pVPC->VPCError("Can't open %s for writing.", szProjectFileName); + + Write("\n"); + Write("\n"); + { + ++m_nIndent; + Write("\n"); + Write("\n"); + Write("\n"); + { + ++m_nIndent; + Write("\n"); + ++m_nIndent; + Write("\n"); + ++m_nIndent; + Write("\n"); + --m_nIndent; + Write("\n"); + Write("\n"); + ++m_nIndent; + Write("\n"); + --m_nIndent; + Write("\n"); + Write("\n"); + --m_nIndent; + Write("\n"); + + Write( + "\n"); +#if 0 + ++m_nIndent; + Write( "\n" ); + ++m_nIndent; + Write( "" ); + --m_nIndent; + --m_nIndent; +#endif + Write("\n"); + + Write( + "\n"); +#if 0 + ++m_nIndent; + Write( "\n" ); + ++m_nIndent; + Write( "" ); + --m_nIndent; + --m_nIndent; +#endif + Write("\n"); + + --m_nIndent; + } + Write("\n"); + + --m_nIndent; + + CUtlGraph dependencyGraph; + + // walk the project list building a dependency graph + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies(pCurProject, projects, + additionalProjectDependencies); + + // project_t *pProjectT = &g_projects[ pCurProject->m_iProjectIndex ]; + // printf( "%s depends on\n", pProjectT->name.String() ); + + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (i == iTestProject) continue; + + // do I depend on anyone? + CDependency_Project *pTestProject = projects[iTestProject]; + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pCurProject->DependsOn(pTestProject, dependsOnFlags) || + additionalProjectDependencies.Find(pTestProject) != + additionalProjectDependencies.InvalidIndex()) { + // add an edge from this project to the one it depends on + dependencyGraph.AddEdge(i, iTestProject, 1); + // printf( " %s -> %s\n", projects[ i ]->m_ProjectName.String(), + // projects[ iTestProject ]->m_ProjectName.String() + //); + } + } + } + + Write("\n"); + ++m_nIndent; + + CUtlVector visitedList; + for (int i = 0; i < projects.Count(); i++) { + TraverseFrom(projects, dependencyGraph, visitedList, i); + } + --m_nIndent; + Write("\n"); + + Write("\n"); + ++m_nIndent; + visitedList.Purge(); + for (int i = 0; i < projects.Count(); i++) { + TraverseFrom(projects, dependencyGraph, visitedList, i); + } + --m_nIndent; + Write("\n"); + } + Write("\n"); + fclose(m_fp); + } + + void TraverseFrom(CUtlVector &projects, + CUtlGraph &dependencyGraph, + CUtlVector &visitedList, int root) { + CUtlGraphVisitor visitor(dependencyGraph); + + if (visitedList.Find(root) != visitedList.InvalidIndex()) return; + + // this project has no dependencies, just emit it + if (!visitor.Begin(root)) { + Write("\n", projects[root]->m_ProjectName.String()); + visitedList.AddToTail(root); + return; + } + + // printf( "considering %i (%s)\n", root, projects[ root + // ]->m_ProjectName.String() ); + + while (visitor.Advance()) { + // printf( "%i (%s) depends on %i (%s)\n", root, projects[ root + // ]->m_ProjectName.String(), visitor.CurrentNode(), projects[ + // visitor.CurrentNode() ]->m_ProjectName.String() ); + TraverseFrom(projects, dependencyGraph, visitedList, + visitor.CurrentNode()); + } + + Write("\n", projects[root]->m_ProjectName.String()); + visitedList.AddToTail(root); + // printf( "emitting %i (%s)\n", root, projects[ root + // ]->m_ProjectName.String() ); + } + + void ResolveAdditionalProjectDependencies( + CDependency_Project *pCurProject, + CUtlVector &projects, + CUtlVector &additionalProjectDependencies) { + for (int i = 0; i < pCurProject->m_AdditionalProjectDependencies.Count(); + i++) { + const char *pLookingFor = + pCurProject->m_AdditionalProjectDependencies[i].String(); + + int j; + for (j = 0; j < projects.Count(); j++) { + if (V_stricmp(projects[j]->m_ProjectName.String(), pLookingFor) == 0) + break; + } + + if (j == projects.Count()) + g_pVPC->VPCError( + "Project %s lists '%s' in its $AdditionalProjectDependencies, but " + "there is no project by that name in the selected projects.", + pCurProject->GetName(), pLookingFor); + + additionalProjectDependencies.AddToTail(projects[j]); + } + } + + const char *FindInFile(const char *pFilename, const char *pFileData, + const char *pSearchFor) { + const char *pPos = V_stristr(pFileData, pSearchFor); + if (!pPos) g_pVPC->VPCError("Can't find ProjectGUID in %s.", pFilename); + + return pPos + V_strlen(pSearchFor); + } + + void Write(PRINTF_FORMAT_STRING const char *pMsg, ...) { + char sOut[8192]; + + va_list marker; + va_start(marker, pMsg); + V_vsnprintf(sOut, sizeof(sOut), pMsg, marker); + va_end(marker); + + for (int i = 0; i < m_nIndent; i++) fprintf(m_fp, " "); + + fprintf(m_fp, "%s", sOut); + } + + FILE *m_fp; + int m_nIndent; +}; + +static CSolutionGenerator_CodeLite g_SolutionGenerator_CodeLite; +IBaseSolutionGenerator *GetSolutionGenerator_CodeLite() { + return &g_SolutionGenerator_CodeLite; +} diff --git a/utils/vpc/solutiongenerator_makefile.cpp b/utils/vpc/solutiongenerator_makefile.cpp new file mode 100644 index 0000000..b1b2cc5 --- /dev/null +++ b/utils/vpc/solutiongenerator_makefile.cpp @@ -0,0 +1,374 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "dependencies.h" + +#include "tier0/memdbgon.h" + +extern void V_MakeAbsoluteCygwinPath(char *pOut, int outLen, + const char *pRelativePath); + +extern void MakeFriendlyProjectName(char *pchProject) { + intp strLen = V_strlen(pchProject); + for (intp j = 0; j < strLen; j++) { + if (pchProject[j] == ' ') pchProject[j] = '_'; + if (pchProject[j] == '(' || pchProject[j] == ')') { + V_memmove(pchProject + j, pchProject + j + 1, strLen - j); + strLen--; + } + } +} + +class CSolutionGenerator_Makefile : public IBaseSolutionGenerator { + private: + void GenerateProjectNames(CUtlVector &projNames, + CUtlVector &projects) { + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + char szFriendlyName[256]; + V_strncpy(szFriendlyName, pCurProject->m_ProjectName.String(), + sizeof(szFriendlyName)); + MakeFriendlyProjectName(szFriendlyName); + projNames[projNames.AddToTail()] = szFriendlyName; + } + } + + public: + virtual void GenerateSolutionFile( + const char *pSolutionFilename, + CUtlVector &projects) { + // Default extension. + char szTmpSolutionFilename[MAX_PATH]; + if (!V_GetFileExtension(pSolutionFilename)) { + V_snprintf(szTmpSolutionFilename, sizeof(szTmpSolutionFilename), "%s.mak", + pSolutionFilename); + pSolutionFilename = szTmpSolutionFilename; + } + + const char *pTargetPlatformName; + // forestw: if PLATFORM macro exists we should use its value, this + // accommodates overrides of PLATFORM in .vpc files + macro_t *pMacro = g_pVPC->FindOrCreateMacro("PLATFORM", false, NULL); + if (pMacro) + pTargetPlatformName = pMacro->value.String(); + else + pTargetPlatformName = g_pVPC->GetTargetPlatformName(); + + Msg("\nWriting master makefile %s.\n\n", pSolutionFilename); + + // Write the file. + FILE *fp = fopen(pSolutionFilename, "wt"); + if (!fp) g_pVPC->VPCError("Can't open %s for writing.", pSolutionFilename); + + fprintf(fp, "# VPC MASTER MAKEFILE\n\n"); + + fprintf(fp, + "# Disable built-in rules/variables. We don't depend on them, and " + "they slow down make processing.\n"); + fprintf(fp, "MAKEFLAGS += --no-builtin-rules --no-builtin-variables\n"); + fprintf(fp, "ifeq ($(MAKE_VERBOSE),)\n"); + fprintf(fp, "MAKEFLAGS += --no-print-directory\n"); + fprintf(fp, "endif\n\n"); + + fprintf(fp, "ifneq \"$(LINUX_TOOLS_PATH)\" \"\"\n"); + fprintf(fp, " TOOL_PATH = $(LINUX_TOOLS_PATH)/\n"); + fprintf(fp, " SHELL := $(TOOL_PATH)bash\n"); + fprintf(fp, "else\n"); + fprintf(fp, " SHELL := /bin/bash\n"); + fprintf(fp, "endif\n"); + + fprintf(fp, "ifndef NO_CHROOT\n"); + if (V_stristr(pTargetPlatformName, "64")) { + fprintf(fp, + " export CHROOT_NAME ?= $(subst /,_,$(dir $(abspath " + "$(lastword $(MAKEFILE_LIST)))))amd64\n"); + fprintf(fp, " RUNTIME_NAME ?= steamrt_scout_amd64\n"); + fprintf(fp, " CHROOT_PERSONALITY ?= linux\n"); + } else if (V_stristr(pTargetPlatformName, "32")) { + fprintf(fp, + " export CHROOT_NAME ?= $(subst /,_,$(dir $(abspath " + "$(lastword $(MAKEFILE_LIST)))))\n"); + fprintf(fp, " RUNTIME_NAME ?= steamrt_scout_i386\n"); + fprintf(fp, " CHROOT_PERSONALITY ?= linux32\n"); + } else { + g_pVPC->VPCError( + "TargetPlatform (%s) doesn't seem to be 32 or 64 bit, can't " + "configure chroot parameters", + pTargetPlatformName); + } + fprintf(fp, + " CHROOT_CONF := /etc/schroot/chroot.d/$(CHROOT_NAME).conf\n"); + fprintf(fp, + " CHROOT_DIR := $(abspath $(dir $(lastword " + "$(MAKEFILE_LIST)))/tools/runtime/linux)\n\n"); + + fprintf(fp, " export MAKE_CHROOT = 1\n"); + fprintf(fp, " ifneq (\"$(SCHROOT_CHROOT_NAME)\", \"$(CHROOT_NAME)\")\n"); + fprintf(fp, + " SHELL := schroot --chroot $(CHROOT_NAME) -- /bin/bash\n"); + fprintf(fp, " endif\n"); + fprintf(fp, "endif\n\n"); // NO_CHROOT + + fprintf(fp, "ECHO = $(TOOL_PATH)echo\n"); + fprintf(fp, "ETAGS = $(TOOL_PATH)etags\n"); + fprintf(fp, "FIND = $(TOOL_PATH)find\n"); + fprintf(fp, "UNAME = $(TOOL_PATH)uname\n"); + fprintf(fp, "XARGS = $(TOOL_PATH)xargs\n"); + fprintf(fp, "\n"); + + fprintf( + fp, + "# to control parallelism, set the MAKE_JOBS environment variable\n"); + fprintf(fp, "ifeq ($(strip $(MAKE_JOBS)),)\n"); + fprintf(fp, " ifeq ($(shell $(UNAME)),Darwin)\n"); + fprintf(fp, " CPUS := $(shell /usr/sbin/sysctl -n hw.ncpu)\n"); + fprintf(fp, " endif\n"); + fprintf(fp, " ifeq ($(shell $(UNAME)),Linux)\n"); + fprintf(fp, + " CPUS := $(shell $(TOOL_PATH)grep processor /proc/cpuinfo " + "| $(TOOL_PATH)wc -l)\n"); + fprintf(fp, " endif\n"); + fprintf(fp, " MAKE_JOBS := $(CPUS)\n"); + fprintf(fp, "endif\n\n"); + + fprintf(fp, "ifeq ($(strip $(MAKE_JOBS)),)\n"); + fprintf(fp, " MAKE_JOBS := 8\n"); + fprintf(fp, "endif\n\n"); + // Handle VALVE_NO_PROJECT_DEPS + fprintf(fp, + "# make VALVE_NO_PROJECT_DEPS 1 or empty (so " + "VALVE_NO_PROJECT_DEPS=0 works as expected)\n"); + fprintf(fp, "ifeq ($(strip $(VALVE_NO_PROJECT_DEPS)),1)\n"); + fprintf(fp, "\tVALVE_NO_PROJECT_DEPS := 1\n"); + fprintf(fp, "else\n"); + fprintf(fp, "\tVALVE_NO_PROJECT_DEPS :=\n"); + fprintf(fp, "endif\n\n"); + + // Handle VALVE_NO_PROJECT_DEPS + fprintf(fp, + "# make VALVE_NO_PROJECT_DEPS 1 or empty (so " + "VALVE_NO_PROJECT_DEPS=0 works as expected)\n"); + fprintf(fp, "ifeq ($(strip $(VALVE_NO_PROJECT_DEPS)),1)\n"); + fprintf(fp, "\tVALVE_NO_PROJECT_DEPS := 1\n"); + fprintf(fp, "else\n"); + fprintf(fp, "\tVALVE_NO_PROJECT_DEPS :=\n"); + fprintf(fp, "endif\n\n"); + + // First, make a target with all the project names. + fprintf(fp, "# All projects (default target)\n"); + fprintf(fp, "all: $(CHROOT_CONF)\n"); + fprintf(fp, + "\t$(MAKE) -f $(lastword $(MAKEFILE_LIST)) -j$(MAKE_JOBS) " + "all-targets\n\n"); + + fprintf(fp, "all-targets : "); + + CUtlVector projNames; + GenerateProjectNames(projNames, projects); + + for (int i = 0; i < projects.Count(); i++) { + fprintf(fp, "%s ", projNames[i].String()); + } + + fprintf(fp, "\n\n\n# Individual projects + dependencies\n\n"); + + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies(pCurProject, projects, + additionalProjectDependencies); + + fprintf(fp, "%s : $(if $(VALVE_NO_PROJECT_DEPS),,$(CHROOT_CONF) ", + projNames[i].String()); + + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (i == iTestProject) continue; + + CDependency_Project *pTestProject = projects[iTestProject]; + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pCurProject->DependsOn(pTestProject, dependsOnFlags) || + additionalProjectDependencies.Find(pTestProject) != + additionalProjectDependencies.InvalidIndex()) { + fprintf(fp, "%s ", projNames[iTestProject].String()); + } + } + + fprintf(fp, ")"); // Closing $(if) above + + // Now add the code to build this thing. + char sDirTemp[MAX_PATH], sDir[MAX_PATH]; + V_strncpy(sDirTemp, pCurProject->m_ProjectFilename.String(), + sizeof(sDirTemp)); + V_StripFilename(sDirTemp); + V_MakeAbsoluteCygwinPath(sDir, sizeof(sDir), sDirTemp); + + const char *pFilename = + V_UnqualifiedFileName(pCurProject->m_ProjectFilename.String()); + + fprintf(fp, "\n\t@echo \"Building: %s\"", projNames[i].String()); + fprintf(fp, + "\n\t@+cd %s && $(MAKE) -f %s $(SUBMAKE_PARAMS) $(CLEANPARAM)", + sDir, pFilename); + + fprintf(fp, "\n\n"); + } + + fprintf(fp, + "# this is a bit over-inclusive, but the alternative (actually " + "adding each referenced c/cpp/h file to\n"); + fprintf(fp, + "# the tags file) seems like more work than it's worth. feel free " + "to fix that up if it bugs you. \n"); + fprintf(fp, "TAGS:\n"); + fprintf(fp, "\t@rm -f TAGS\n"); + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + char sDirTemp[MAX_PATH], sDir[MAX_PATH]; + V_strncpy(sDirTemp, pCurProject->m_ProjectFilename.String(), + sizeof(sDirTemp)); + V_StripFilename(sDirTemp); + V_MakeAbsoluteCygwinPath(sDir, sizeof(sDir), sDirTemp); + fprintf(fp, + "\t@$(FIND) %s -name \'*.cpp\' -print0 | $(XARGS) -0 $(ETAGS) " + "--declarations --ignore-indentation --append\n", + sDir); + fprintf(fp, + "\t@$(FIND) %s -name \'*.h\' -print0 | $(XARGS) -0 $(ETAGS) " + "--language=c++ --declarations --ignore-indentation --append\n", + sDir); + fprintf(fp, + "\t@$(FIND) %s -name \'*.c\' -print0 | $(XARGS) -0 $(ETAGS) " + "--declarations --ignore-indentation --append\n", + sDir); + } + fprintf(fp, "\n\n"); + + fprintf(fp, + "\n# Mark all the projects as phony or else make will see the " + "directories by the same name and think certain targets \n\n"); + fprintf(fp, + ".PHONY: TAGS showtargets regen showregen clean cleantargets " + "cleanandremove relink "); + for (int i = 0; i < projects.Count(); i++) { + fprintf(fp, "%s ", projNames[i].String()); + } + fprintf(fp, "\n\n\n"); + + fprintf(fp, "\n# The standard clean command to clean it all out.\n"); + fprintf(fp, "\nclean: \n"); + fprintf(fp, + "\t@$(MAKE) -f $(lastword $(MAKEFILE_LIST)) -j$(MAKE_JOBS) " + "all-targets CLEANPARAM=clean\n\n\n"); + + fprintf(fp, "\n# clean targets, so we re-link next time.\n"); + fprintf(fp, "\ncleantargets: \n"); + fprintf(fp, + "\t@$(MAKE) -f $(lastword $(MAKEFILE_LIST)) -j$(MAKE_JOBS) " + "all-targets CLEANPARAM=cleantargets\n\n\n"); + + fprintf( + fp, + "\n# p4 edit and remove targets, so we get an entirely clean build.\n"); + fprintf(fp, "\ncleanandremove: \n"); + fprintf(fp, + "\t@$(MAKE) -f $(lastword $(MAKEFILE_LIST)) -j$(MAKE_JOBS) " + "all-targets CLEANPARAM=cleanandremove\n\n\n"); + + fprintf(fp, "\n#relink\n"); + fprintf(fp, "\nrelink: cleantargets \n"); + fprintf(fp, + "\t@$(MAKE) -f $(lastword $(MAKEFILE_LIST)) -j$(MAKE_JOBS) " + "all-targets\n\n\n"); + + // Create the showtargets target. + fprintf(fp, "\n# Here's a command to list out all the targets\n\n"); + fprintf(fp, "\nshowtargets: \n"); + fprintf(fp, "\t@$(ECHO) '-------------------' && \\\n"); + fprintf(fp, "\t$(ECHO) '----- TARGETS -----' && \\\n"); + fprintf(fp, "\t$(ECHO) '-------------------' && \\\n"); + fprintf(fp, "\t$(ECHO) 'clean' && \\\n"); + fprintf(fp, "\t$(ECHO) 'regen' && \\\n"); + fprintf(fp, "\t$(ECHO) 'showregen' && \\\n"); + for (int i = 0; i < projects.Count(); i++) { + fprintf(fp, "\t$(ECHO) '%s'", projNames[i].String()); + if (i != projects.Count() - 1) fprintf(fp, " && \\"); + fprintf(fp, "\n"); + } + fprintf(fp, "\n\n"); + + // Create the regen target. + fprintf(fp, "\n# Here's a command to regenerate this makefile\n\n"); + fprintf(fp, "\nregen: \n"); + fprintf(fp, "\t"); + ICommandLine *pCommandLine = CommandLine(); + for (int i = 0; i < pCommandLine->ParmCount(); i++) { + fprintf(fp, "%s ", pCommandLine->GetParm(i)); + } + fprintf(fp, "\n\n"); + + // Create the showregen target. + fprintf(fp, "\n# Here's a command to list out all the targets\n\n"); + fprintf(fp, "\nshowregen: \n"); + fprintf(fp, "\t@$(ECHO) "); + for (int i = 0; i < pCommandLine->ParmCount(); i++) { + fprintf(fp, "%s ", pCommandLine->GetParm(i)); + } + fprintf(fp, "\n\n"); + + // Auto-create the chroot if it's not there + fprintf(fp, + "ifdef CHROOT_CONF\n" + "$(CHROOT_CONF): $(CHROOT_DIR)/$(RUNTIME_NAME)/timestamp\n" + "$(CHROOT_CONF): SHELL = /bin/bash\n" + "$(CHROOT_DIR)/$(RUNTIME_NAME)/timestamp: " + "$(CHROOT_DIR)/$(RUNTIME_NAME).tar.xz\n" + "\t@echo \"Configuring schroot at $(CHROOT_DIR) (requires sudo)\"\n" + "\tsudo $(CHROOT_DIR)/configure_runtime.sh ${CHROOT_NAME} " + "$(RUNTIME_NAME) $(CHROOT_PERSONALITY)\n" + "endif\n"); + + fclose(fp); + } + + void ResolveAdditionalProjectDependencies( + CDependency_Project *pCurProject, + CUtlVector &projects, + CUtlVector &additionalProjectDependencies) { + for (int i = 0; i < pCurProject->m_AdditionalProjectDependencies.Count(); + i++) { + const char *pLookingFor = + pCurProject->m_AdditionalProjectDependencies[i].String(); + + int j; + for (j = 0; j < projects.Count(); j++) { + if (V_stricmp(projects[j]->m_ProjectName.String(), pLookingFor) == 0) + break; + } + + if (j == projects.Count()) + g_pVPC->VPCError( + "Project %s lists '%s' in its $AdditionalProjectDependencies, but " + "there is no project by that name in the selected projects.", + pCurProject->GetName(), pLookingFor); + + additionalProjectDependencies.AddToTail(projects[j]); + } + } + + const char *FindInFile(const char *pFilename, const char *pFileData, + const char *pSearchFor) { + const char *pPos = V_stristr(pFileData, pSearchFor); + if (!pPos) g_pVPC->VPCError("Can't find ProjectGUID in %s.", pFilename); + + return pPos + V_strlen(pSearchFor); + } +}; + +static CSolutionGenerator_Makefile g_SolutionGenerator_Makefile; +IBaseSolutionGenerator *GetMakefileSolutionGenerator() { + return &g_SolutionGenerator_Makefile; +} diff --git a/utils/vpc/solutiongenerator_win32.cpp b/utils/vpc/solutiongenerator_win32.cpp new file mode 100644 index 0000000..1442dcf --- /dev/null +++ b/utils/vpc/solutiongenerator_win32.cpp @@ -0,0 +1,387 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "dependencies.h" +#include "tier1/checksum_md5.h" + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#include +#endif + +#include "tier0/memdbgon.h" + +struct CVCProjInfo { + CUtlString m_ProjectName; + CUtlString m_ProjectGUID; +}; + +struct RegStartPoint { + HKEY baseKey; + const char *const baseDir; +}; + +class CSolutionGenerator_Win32 : public IBaseSolutionGenerator { + public: + void GetVCPROJSolutionGUID(char (&szSolutionGUID)[256]) { + if (g_pVPC->Is2022()) { + V_strncpy(szSolutionGUID, "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}", + ARRAYSIZE(szSolutionGUID)); + return; + } + + int firstVer; + const int lastVer = 14; // Handle up to VS 14, AKA VS 2015 + + if (g_pVPC->Is2010()) { + firstVer = 10; + } else if (g_pVPC->Is2008()) { + firstVer = 9; + } else { + firstVer = 8; + } + + for (int vsVer = firstVer; vsVer <= lastVer; ++vsVer) { + // Handle both VisualStudio and VCExpress (used by some SourceSDK + // customers) + RegStartPoint searchPoints[]{ + {HKEY_LOCAL_MACHINE, + // Visual Studio Professional + "Software\\Microsoft\\VisualStudio\\%d.0\\Projects"}, + {HKEY_LOCAL_MACHINE, + // Visual Studio Professional on x64 machine + "Software\\WOW6432Node\\Microsoft\\VisualStudio\\%d.0\\Projects"}, + {HKEY_LOCAL_MACHINE, + // VC Express 2010 and 2012 + "Software\\Microsoft\\VCExpress\\%d.0\\Projects"}, + {HKEY_CURRENT_USER, + // WinDev Express -- VS Express starting with VS 2013 + "Software\\Microsoft\\WDExpress\\%d.0_Config\\Projects"}, + }; + + for (const auto &searchPoint : searchPoints) { + char pRegKeyName[1000]; + V_snprintf(pRegKeyName, ARRAYSIZE(pRegKeyName), searchPoint.baseDir, + vsVer); + HKEY hKey; + LONG ret = + RegOpenKeyEx(searchPoint.baseKey, pRegKeyName, 0, KEY_READ, &hKey); + if (!hKey) continue; + + for (int i = 0; i < 200; i++) { + char szKeyName[MAX_PATH]; + DWORD dwKeyNameSize = sizeof(szKeyName); + ret = RegEnumKeyEx(hKey, i, szKeyName, &dwKeyNameSize, NULL, NULL, + NULL, NULL); + + if (ret == ERROR_NO_MORE_ITEMS) break; + + HKEY hSubKey; + ret = RegOpenKeyEx(hKey, szKeyName, 0, KEY_READ, &hSubKey); + if (ret == ERROR_SUCCESS) { + DWORD dwType; + char ext[MAX_PATH]; + DWORD dwExtLen = sizeof(ext); + ret = RegQueryValueEx(hSubKey, "DefaultProjectExtension", NULL, + &dwType, (BYTE *)ext, &dwExtLen); + RegCloseKey(hSubKey); + + // VS 2012 and beyond has the DefaultProjectExtension as vcxproj + // instead of vcproj + if (ret == ERROR_SUCCESS && dwType == REG_SZ && + (V_stricmp(ext, "vcproj") == 0 || + V_stricmp(ext, "vcxproj") == 0)) { + V_strncpy(szSolutionGUID, szKeyName, ARRAYSIZE(szSolutionGUID)); + RegCloseKey(hKey); + return; + } + } + } + + RegCloseKey(hKey); + } + } + + g_pVPC->VPCError( + "Unable to find RegKey for .vcproj or .vcxproj files in solutions."); + } + + virtual void GenerateSolutionFile( + const char *pSolutionFilename, + CUtlVector &projects) { + // Default extension. + char szTmpSolutionFilename[MAX_PATH]; + if (!V_GetFileExtension(pSolutionFilename)) { + V_snprintf(szTmpSolutionFilename, sizeof(szTmpSolutionFilename), "%s.sln", + pSolutionFilename); + pSolutionFilename = szTmpSolutionFilename; + } + + Msg("\nWriting solution file %s.\n\n", pSolutionFilename); + + char szSolutionGUID[256]; + GetVCPROJSolutionGUID(szSolutionGUID); + + CUtlVector vcprojInfos; + GetProjectInfos(projects, vcprojInfos); + + // Write the file. + FILE *fp = fopen(pSolutionFilename, "wt"); + if (!fp) { + g_pVPC->VPCError("Can't open %s for writing.", pSolutionFilename); + } + + if (g_pVPC->Is2022()) { + fprintf(fp, + "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format " + "Version 12.00\n"); // still on 12 + fprintf(fp, "# Visual Studio 2022\n"); + fprintf(fp, "MinimumVisualStudioVersion = 10.0.40219.1\n"); + } else if (g_pVPC->Is2015()) { + fprintf(fp, + "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format " + "Version 12.00\n"); // still on 12 + fprintf(fp, "# Visual Studio 2015\n"); + } else if (g_pVPC->Is2013()) { + fprintf( + fp, + "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format Version " + "12.00\n"); // Format didn't change from VS 2012 to VS 2013 + fprintf(fp, "# Visual Studio 2013\n"); + } else if (g_pVPC->Is2012()) { + fprintf(fp, + "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format " + "Version 12.00\n"); + fprintf(fp, "# Visual Studio 2012\n"); + } else if (g_pVPC->Is2010()) { + fprintf(fp, + "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format " + "Version 11.00\n"); + fprintf(fp, "# Visual Studio 2010\n"); + } else if (g_pVPC->Is2008()) { + fprintf(fp, + "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format " + "Version 10.00\n"); + fprintf(fp, "# Visual Studio 2008\n"); + } else { + fprintf(fp, + "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format " + "Version 9.00\n"); + fprintf(fp, "# Visual Studio 2005\n"); + } + fprintf(fp, "#\n"); + fprintf(fp, "# Automatically generated solution:\n"); + fprintf(fp, "# devtools\\bin\\vpc "); + for (int k = 1; k < __argc; ++k) fprintf(fp, "%s ", __argv[k]); + fprintf(fp, "\n"); + fprintf(fp, "#\n"); + fprintf(fp, "#\n"); + + if (!g_pVPC->Is2010()) { + // if /slnItems is passed on the command line, build a Solution + // Items project + const char *pSolutionItemsFilename = g_pVPC->GetSolutionItemsFilename(); + if (pSolutionItemsFilename[0] != '\0') { + fprintf(fp, + "Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = " + "\"Solution Items\", \"Solution Items\", " + "\"{AAAAAAAA-8B4A-11D0-8D11-90A07D6D6F7D}\"\n"); + fprintf(fp, "\tProjectSection(SolutionItems) = preProject\n"); + WriteSolutionItems(fp); + fprintf(fp, "\tEndProjectSection\n"); + fprintf(fp, "EndProject\n"); + } + } + + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + CVCProjInfo *pProjInfo = &vcprojInfos[i]; + + // Get a relative filename for the vcproj file. + const char *pFullProjectFilename = + pCurProject->m_ProjectFilename.String(); + char szRelativeFilename[MAX_PATH]; + if (!V_MakeRelativePath(pFullProjectFilename, g_pVPC->GetSourcePath(), + szRelativeFilename, sizeof(szRelativeFilename))) + g_pVPC->VPCError( + "Can't make a relative path (to the base source directory) for %s.", + pFullProjectFilename); + + fprintf(fp, "Project(\"%s\") = \"%s\", \"%s\", \"{%s}\"\n", + szSolutionGUID, pProjInfo->m_ProjectName.String(), + szRelativeFilename, pProjInfo->m_ProjectGUID.String()); + bool bHasDependencies = false; + + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (i == iTestProject) continue; + + CDependency_Project *pTestProject = projects[iTestProject]; + if (pCurProject->DependsOn(pTestProject, + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagRecurse) || + pCurProject->DependsOn(pTestProject, + k_EDependsOnFlagCheckAdditionalDependencies | + k_EDependsOnFlagTraversePastLibs)) { + if (!bHasDependencies) { + fprintf(fp, + "\tProjectSection(ProjectDependencies) = postProject\n"); + bHasDependencies = true; + } + fprintf(fp, "\t\t{%s} = {%s}\n", + vcprojInfos[iTestProject].m_ProjectGUID.String(), + vcprojInfos[iTestProject].m_ProjectGUID.String()); + } + } + if (bHasDependencies) fprintf(fp, "\tEndProjectSection\n"); + + fprintf(fp, "EndProject\n"); + } + + fclose(fp); + Sys_CopyToMirror(pSolutionFilename); + } + + const char *FindInFile(const char *pFilename, const char *pFileData, + const char *pSearchFor) { + const char *pPos = V_stristr(pFileData, pSearchFor); + if (!pPos) { + g_pVPC->VPCError("Can't find %s in %s.", pSearchFor, pFilename); + } + + return pPos + V_strlen(pSearchFor); + } + + void GetProjectInfos(CUtlVector &projects, + CUtlVector &vcprojInfos) { + for (int i = 0; i < projects.Count(); i++) { + CDependency_Project *pCurProject = projects[i]; + const char *pFilename = pCurProject->m_ProjectFilename.String(); + + CVCProjInfo vcprojInfo; + + char *pFileData; + int nResult = Sys_LoadFile(pFilename, (void **)&pFileData, false); + if (nResult == -1) + g_pVPC->VPCError("Can't open %s to get ProjectGUID.", pFilename); + + const char *pSearchFor; + if (g_pVPC->Is2010()) { + pSearchFor = "{"; + } else { + pSearchFor = "ProjectGUID=\"{"; + } + + const char *pPos = FindInFile(pFilename, pFileData, pSearchFor); + char szGuid[37]; + const char *pGuid = pPos; + V_strncpy(szGuid, pGuid, sizeof(szGuid)); + vcprojInfo.m_ProjectGUID = szGuid; + + const char *pEnd; + if (g_pVPC->Is2010()) { + pPos = FindInFile(pFilename, pFileData, ""); + pEnd = V_stristr(pPos, "<"); + } else { + pPos = FindInFile(pFilename, pFileData, "Name=\""); + pEnd = V_stristr(pPos, "\""); + } + + if (!pEnd || (pEnd - pPos) > 1024 || (pEnd - pPos) <= 0) + g_pVPC->VPCError("Can't find valid 'Name=' in %s.", pFilename); + + char szName[256]; + V_strncpy(szName, pPos, (pEnd - pPos) + 1); + vcprojInfo.m_ProjectName = szName; + + vcprojInfos.AddToTail(vcprojInfo); + + free(pFileData); + } + } + + // Parse g_SolutionItemsFilename, reading in filenames (including wildcards), + // and add them to the Solution Items project we're already writing. + void WriteSolutionItems(FILE *fp) { + char szFullSolutionItemsPath[MAX_PATH]; + if (V_IsAbsolutePath(g_pVPC->GetSolutionItemsFilename())) + V_strncpy(szFullSolutionItemsPath, g_pVPC->GetSolutionItemsFilename(), + sizeof(szFullSolutionItemsPath)); + else + V_ComposeFileName( + g_pVPC->GetStartDirectory(), g_pVPC->GetSolutionItemsFilename(), + szFullSolutionItemsPath, sizeof(szFullSolutionItemsPath)); + + g_pVPC->GetScript().PushScript(szFullSolutionItemsPath); + + int numSolutionItems = 0; + while (g_pVPC->GetScript().GetData()) { + // read a line + const char *pToken = g_pVPC->GetScript().GetToken(false); + + // strip out \r\n chars + char *end = V_strstr(pToken, "\n"); + if (end) { + *end = '\0'; + } + end = V_strstr(pToken, "\r"); + if (end) { + *end = '\0'; + } + + // bail on strings too small to be paths + if (V_strlen(pToken) < 3) continue; + + // compose an absolute path w/o any ../ + char szFullPath[MAX_PATH]; + if (V_IsAbsolutePath(pToken)) + V_strncpy(szFullPath, pToken, sizeof(szFullPath)); + else + V_ComposeFileName(g_pVPC->GetStartDirectory(), pToken, szFullPath, + sizeof(szFullPath)); + + if (!V_RemoveDotSlashes(szFullPath)) continue; + + if (V_strstr(szFullPath, "*") != NULL) { + // wildcard! + char szWildcardPath[MAX_PATH]; + V_strncpy(szWildcardPath, szFullPath, sizeof(szWildcardPath)); + V_StripFilename(szWildcardPath); + + struct _finddata32_t data; + intptr_t handle = _findfirst32(szFullPath, &data); + if (handle != -1L) { + do { + if ((data.attrib & _A_SUBDIR) == 0) { + // not a dir, just a filename - add it + V_ComposeFileName(szWildcardPath, data.name, szFullPath, + sizeof(szFullPath)); + + if (V_RemoveDotSlashes(szFullPath)) { + fprintf(fp, "\t\t%s = %s\n", szFullPath, szFullPath); + ++numSolutionItems; + } + } + } while (_findnext32(handle, &data) == 0); + + _findclose(handle); + } + } else { + // just a file - add it + fprintf(fp, "\t\t%s = %s\n", szFullPath, szFullPath); + ++numSolutionItems; + } + } + + g_pVPC->GetScript().PopScript(); + + Msg("Found %d solution files in %s\n", numSolutionItems, + g_pVPC->GetSolutionItemsFilename()); + } +}; + +static CSolutionGenerator_Win32 g_SolutionGenerator_Win32; +IBaseSolutionGenerator *GetSolutionGenerator_Win32() { + return &g_SolutionGenerator_Win32; +} diff --git a/utils/vpc/solutiongenerator_xcode.cpp b/utils/vpc/solutiongenerator_xcode.cpp new file mode 100644 index 0000000..c3c6fce --- /dev/null +++ b/utils/vpc/solutiongenerator_xcode.cpp @@ -0,0 +1,3436 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vpc.h" +#include "dependencies.h" +#include "baseprojectdatacollector.h" +#include "utlsortvector.h" +#include "checksum_md5.h" + +#include "tier0/memdbgon.h" + +#ifdef _WIN32 +#include +#define mkdir(dir, mode) _mkdir(dir) +#define getcwd _getcwd +#define snprintf _snprintf +typedef unsigned __int64 uint64_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +#else +#include +#endif + +static const int k_nShellScriptPhasesPerAggregateTarget = 8; + +#ifndef STEAM +template +bool V_StrSubstInPlace(char (&in_out)[in_out_size], const char *match, + const char *replace_with, bool case_sensitive) { + bool bRet = false; + char *pchT = new char[in_out_size]; + + if (V_StrSubst(in_out, match, replace_with, pchT, in_out_size, + case_sensitive)) { + V_strncpy(in_out, pchT, in_out_size); + bRet = true; + } + + delete[] pchT; + return bRet; +} + +bool V_StrSubstInPlace(char *in_out, int in_out_size, const char *pMatch, + const char *pReplaceWith, bool bCaseSensitive) { + bool bRet = false; + char *pchT = new char[in_out_size]; + + if (V_StrSubst(in_out, pMatch, pReplaceWith, pchT, in_out_size, + bCaseSensitive)) { + V_strncpy(in_out, pchT, in_out_size); + bRet = true; + } + + delete[] pchT; + return bRet; +} +#endif + +static const char *k_pchProjects = "Projects"; +static const char *k_pchLegacyTarget = "build with make"; +static const char *k_rgchConfigNames[] = {"Debug", "Release"}; + +static const char *g_pOption_AdditionalDependencies = "$AdditionalDependencies"; +static const char *g_pOption_BufferSecurityCheck = "$BufferSecurityCheck"; +static const char *g_pOption_BuildMultiArch = "$BuildMultiArch"; +static const char *g_pOption_BuildX64Only = "$BuildX64Only"; +static const char *g_pOption_CompileAs = "$CompileAs"; +static const char *g_pOption_PreBuildEventCommandLine = + "$PreBuildEvent/$CommandLine"; +static const char *g_pOption_CustomBuildStepCommandLine = + "$CustomBuildStep/$CommandLine"; +static const char *g_pOption_PostBuildEventCommandLine = + "$PostBuildEvent/$CommandLine"; +static const char *g_pOption_ConfigurationType = "$ConfigurationType"; +static const char *g_pOption_Description = "$Description"; +static const char *g_pOption_ExtraCompilerFlags = "$GCC_ExtraCompilerFlags"; +static const char *g_pOption_ExtraLinkerFlags = "$GCC_ExtraLinkerFlags"; +static const char *g_pOption_ForceInclude = "$ForceIncludes"; +static const char *g_pOption_LinkAsBundle = "$LinkAsBundle"; +static const char *g_pOption_LocalFrameworks = "$LocalFrameworks"; +static const char *g_pOption_LowerCaseFileNames = "$LowerCaseFileNames"; +static const char *g_pOption_OptimizerLevel = "$OptimizerLevel"; +static const char *g_pOption_OutputDirectory = "$OutputDirectory"; +static const char *g_pOption_Outputs = "$Outputs"; +static const char *g_pOption_PostBuildEvent = "$PostBuildEvent"; +static const char *g_pOption_PrecompiledHeader = "$Create/UsePrecompiledHeader"; +static const char *g_pOption_PrecompiledHeaderFile = "$PrecompiledHeaderFile"; +static const char *g_pOption_SymbolVisibility = "$SymbolVisibility"; +static const char *g_pOption_SystemFrameworks = "$SystemFrameworks"; +static const char *g_pOption_SystemLibraries = "$SystemLibraries"; +static const char *g_pOption_UsePCHThroughFile = "$Create/UsePCHThroughFile"; +static const char *g_pOption_AdditionalLibraryDirectories = + "$AdditionalLibraryDirectories"; +static const char *g_pOption_TargetCopies = "$TargetCopies"; +static const char *g_pOption_TreatWarningsAsErrors = "$TreatWarningsAsErrors"; + +// These are the only properties we care about for xcodeprojects. +static const char *g_pRelevantProperties[] = { + g_pOption_AdditionalDependencies, + g_pOption_AdditionalIncludeDirectories, + g_pOption_AdditionalLibraryDirectories, + g_pOption_BufferSecurityCheck, + g_pOption_CompileAs, + g_pOption_OptimizerLevel, + g_pOption_OutputFile, + g_pOption_GameOutputFile, + g_pOption_SymbolVisibility, + g_pOption_PreprocessorDefinitions, + g_pOption_ConfigurationType, + g_pOption_ImportLibrary, + g_pOption_LinkAsBundle, + g_pOption_PrecompiledHeader, + g_pOption_UsePCHThroughFile, + g_pOption_PrecompiledHeaderFile, + g_pOption_PreBuildEventCommandLine, + g_pOption_CustomBuildStepCommandLine, + g_pOption_PostBuildEventCommandLine, + g_pOption_OutputDirectory, + g_pOption_Outputs, + g_pOption_Description, + g_pOption_SystemLibraries, + g_pOption_SystemFrameworks, + g_pOption_LocalFrameworks, + g_pOption_BuildMultiArch, + g_pOption_BuildX64Only, + g_pOption_ExtraCompilerFlags, + g_pOption_ExtraLinkerFlags, + g_pOption_ForceInclude, + g_pOption_TargetCopies, + g_pOption_TreatWarningsAsErrors, +}; + +static CRelevantPropertyNames g_RelevantPropertyNames = { + g_pRelevantProperties, V_ARRAYSIZE(g_pRelevantProperties)}; + +static const char *k_rgchXCConfigFiles[] = { + "debug.xcconfig", "release.xcconfig", "base.xcconfig"}; + +static int k_oidBuildConfigList = 0xc0de; +static int k_rgOidBuildConfigs[] = {0x1c0de, 0x1c0e0}; + +class CProjectGenerator_Xcode : public CBaseProjectDataCollector { + typedef CBaseProjectDataCollector BaseClass; + + public: + CProjectGenerator_Xcode() : BaseClass(&g_RelevantPropertyNames) { + m_bIsCurrent = false; + m_nShellScriptPhases = 0; + m_nCustomBuildRules = 0; + m_nPreBuildEvents = 0; + } + + virtual void Setup() {} + + virtual const char *GetProjectFileExtension() { return ""; } + + virtual void EndProject() { + m_OutputFilename = g_pVPC->GetOutputFilename(); + // we need the "project file" to exist for crc checking + if (!Sys_Exists(m_OutputFilename)) Sys_Touch(m_OutputFilename); + + // remember if we needed rebuild according to vpc + m_bIsCurrent = g_pVPC->IsProjectCurrent(g_pVPC->GetOutputFilename(), false); + + // and update the mod time on the file if we needed rebuild + if (!m_bIsCurrent) Sys_Touch(m_OutputFilename); + + extern CUtlVector g_vecPGenerators; + g_vecPGenerators.AddToTail(this); + + extern IBaseProjectGenerator *g_pGenerator; + g_pVPC->SetProjectGenerator(new CProjectGenerator_Xcode()); + } + + bool m_bIsCurrent; + int m_nShellScriptPhases; + int m_nCustomBuildRules; + int m_nPreBuildEvents; + CUtlString m_OutputFilename; +}; + +// we assume (and assert) that the order of this vector is the same as the order +// of the projects vector +extern CUtlVector g_vecPGenerators; + +class CSolutionGenerator_Xcode : public IBaseSolutionGenerator { + public: + CSolutionGenerator_Xcode() : m_fp(NULL), m_nIndent(0) {} + virtual void GenerateSolutionFile( + const char *pSolutionFilename, + CUtlVector &projects); + + private: + void XcodeFileTypeFromFileName(const char *pszFileName, char *pchOutBuf, + int cchOutBuf); + void XcodeProductTypeFromFileName(const char *pszFileName, char *pchOutBuf, + int cchOutBuf); + void EmitBuildSettings(const char *pszProjectName, const char *pszProjectDir, + CUtlDict *pDictFiles, + KeyValues *pConfigKV, KeyValues *pReleaseKV, + bool bIsDebug); + void WriteFilesFolder(uint64_t oid, const char *pFolderName, + const char *pExtensions, + CBaseProjectDataCollector *pProject); + + void Write(PRINTF_FORMAT_STRING const char *pMsg, ...); + FILE *m_fp; + int m_nIndent; +}; + +enum EOIDType { + EOIDTypeProject = 0x00001d00, + EOIDTypeGroup, + EOIDTypeFileReference, + EOIDTypeBuildFile, + EOIDTypeSourcesBuildPhase, + EOIDTypeFrameworksBuildPhase, + EOIDTypeCopyFilesBuildPhase, + EOIDTypeHeadersBuildPhase, + EOIDTypePreBuildPhase, + EOIDTypeShellScriptBuildPhase, + EOIDTypePostBuildPhase, + EOIDTypeNativeTarget, + EOIDTypeAggregateTarget, + EOIDTypeTargetDependency, + EOIDTypeContainerItemProxy, + EOIDTypeBuildConfiguration, + EOIDTypeConfigurationList, + EOIDTypeCustomBuildRule, +}; + +// Make an OID from raw data. You probably want makeoid/makeoid2 below. +uint64_t makeoid_raw(const char *pData, int nDataLen, EOIDType type, + int16_t ordinal = 0) { + static unsigned int unOIDSalt = 0; + static bool bOIDSaltSet = false; + // Since the string passed to makeoid() doesn't change based on all parameters + // of the object it is representing, we need to regenerate them per-run. There + // isn't currently much value in perserving OIDs that refer to the same object + // in terms of xcode functionality. If we wanted deterministic OIDs, we + // should be hashing all parameters of the object that they represent, which + // is a non-trivial refactor. + if (!bOIDSaltSet) { + // Define this to generate xcode projects with deterministic OIDs (based on + // solution/project names, see makeoid callers). See above comment for why + // this is for debugging only. +#if defined(VPC_DETERMINISTIC_XCODE_OIDS) + unOIDSalt = 0; + Msg("DEBUG: Generating deterministic OIDs per project/solution name (salt " + "-> 0).\n"); +#else + // You'd think our random API would be better here, but it doesn't actually + // have a function to return a full-range int and uses the below code to + // seed itself. Except in tools where it is un-seeded without a warning that + // that is the case. + float flAppTime = static_cast(Plat_FloatTime()); + ThreadId_t threadId = ThreadGetCurrentId(); + static_assert(sizeof(flAppTime) <= sizeof(unOIDSalt)); + memcpy(&unOIDSalt, &flAppTime, sizeof(float)); + unOIDSalt ^= threadId; +#endif // defined( VPC_DETERMINISTIC_XCODE_OIDS ) + +#ifdef VPC_DEBUG_XCODE_OIDS + Msg("XCode Solution: Using random salt for OIDs of %u\n", unOIDSalt); +#endif + bOIDSaltSet = true; + } + + // Lower 32bits of OID is hash of identifier string + salt, upper 32bits is + // type and ordinal. + MD5Context_t md5Context; + unsigned char hash[MD5_DIGEST_LENGTH] = {0}; + MD5Init(&md5Context); + MD5Update(&md5Context, (const unsigned char *)&unOIDSalt, sizeof(unOIDSalt)); + MD5Update(&md5Context, (const unsigned char *)pData, nDataLen); + MD5Final(hash, &md5Context); + + // Take lower 32bits of md5 + static_assert(MD5_DIGEST_LENGTH >= sizeof(uint32_t)); + uint32_t lowerHash = 0; + memcpy(&lowerHash, hash, sizeof(lowerHash)); + + uint64_t oid = (uint64_t)lowerHash + ((uint64_t)type << 32) + + (((uint64_t)ordinal + 1) << 52); +#ifdef VPC_DEBUG_XCODE_OIDS + Msg("XCode Solution: Produced OID 0x%llx for \"%s\" with salt %u\n", oid, + pszIdentifier, unOIDSalt); +#endif + return oid; +} + +// Make an oid for a unique string identifier, per type, per ordinal +uint64_t makeoid(const char *pszIdentifier, EOIDType type, intp ordinal = 0) { + CFmtStr oidStr("oid1.%s", pszIdentifier); + return makeoid_raw(oidStr.Access(), oidStr.Length(), type, (int16_t)ordinal); +} + +// Make an oid for a unique string tuple, per type, per ordinal +uint64_t makeoid2(const char *pszIdentifierA, const char *pszIdentifierB, + EOIDType type, intp ordinal = 0) { + CFmtStr oidStr("oid2.%s.%s", pszIdentifierA, pszIdentifierB); + return makeoid_raw(oidStr.Access(), oidStr.Length(), type, (int16_t)ordinal); +} + +// Make an oid for a unique string tuple, per type, per ordinal +uint64_t makeoid3(const char *pszIdentifierA, const char *pszIdentifierB, + const char *pszIdentifierC, EOIDType type, int ordinal = 0) { + CFmtStr oidStr("oid3.%s.%s.%s", pszIdentifierA, pszIdentifierB, + pszIdentifierC); + return makeoid_raw(oidStr.Access(), oidStr.Length(), type, (int16_t)ordinal); +} + +static bool IsStaticLibrary(const char *pszFileName) { + const char *pchExtension = + V_GetFileExtension(V_UnqualifiedFileName(pszFileName)); + if (!pchExtension) + return false; + else if (!V_stricmp(pchExtension, "a")) + return true; + return false; +} + +static bool IsDynamicLibrary(const char *pszFileName) { + const char *pchExtension = + V_GetFileExtension(V_UnqualifiedFileName(pszFileName)); + if (!pchExtension) + return false; + else if (!V_stricmp(pchExtension, "dylib")) + return true; + return false; +} + +static void UsePOSIXSlashes(const char *pStr, char *pOut, intp nOutSize) { + intp len = V_strlen(pStr) + 2; + char *str = pOut; + AssertFatal(len <= nOutSize); + + V_strncpy(str, pStr, len); + for (intp i = 0; i < len; i++) { + if (str[i] == '\\') { + // allow escaping of bash special characters + if (i + 1 < len && (str[i + 1] != '"' && str[i + 1] != '$' && + str[i + 1] != '\'' && str[i + 1] != '\\')) { + str[i] = '/'; + } + } + if (str[i] == '\0') break; + } +} + +// Auto-allocating (not in-place) version. Caller is responsible for free'ing +// (or leaking) the allocated buffer. Most users leak. Is bad. :-/ +static char *UsePOSIXSlashes(const char *pStr) { + intp len = V_strlen(pStr) + 2; + char *str = (char *)malloc(len * sizeof(char)); + UsePOSIXSlashes(pStr, str, len * sizeof(char)); + return str; +} + +// Finds the file name component of a path, and prepends 'lib' to it if +// necesssary +// foo/bar/foo.a -> foo/bar/libfoo.a +// foo/bar/libfoo.a -> unchanged +static void EnforceLibPrefix(char *pInOutStr, int nOutSize) { + char *pFile = V_UnqualifiedFileName(pInOutStr); + if (pFile && V_strncmp(pFile, "lib", 3) != 0) { + char szOriginalName[MAX_PATH] = {0}; + V_strncpy(szOriginalName, pFile, sizeof(szOriginalName)); + + *pFile = '\0'; + V_strncat(pInOutStr, "lib", nOutSize); + V_strncat(pInOutStr, szOriginalName, nOutSize); + } +} + +// Get the output file with the output directory prepended +static CUtlString OutputFileWithDirectoryFromConfig(KeyValues *pConfigKV) { + char szOutputFile[MAX_PATH] = {0}; + char szOutputDir[MAX_PATH] = {0}; + UsePOSIXSlashes(pConfigKV->GetString(g_pOption_OutputFile, ""), szOutputFile, + sizeof(szOutputFile)); + UsePOSIXSlashes(pConfigKV->GetString(g_pOption_OutputDirectory, ""), + szOutputDir, sizeof(szOutputDir)); + + // Our output file is relative to BUILT_PRODUCTS_DIR already. This is a + // workaround for VPC files expecting Makefile semantics -- you shouldn't be + // using $() in VPC strings. + const char szObjDir[] = "$(OBJ_DIR)"; + V_StrSubstInPlace(szOutputDir, szObjDir, ".", true); + V_StrSubstInPlace(szOutputFile, szObjDir, ".", true); + + // VPC files have snuck in hard-coding of random variables expecting the + // Makefile backend to expand them. + if (V_strstr(szOutputDir, "$") || V_strstr(szOutputFile, "$")) { + g_pVPC->VPCWarning( + "$OutputDirectory '%s' or $OutputFile directive '%s' contains what " + "looks like a shell variable reference -- " + "this will be treated literally in most circumstances by the Xcode " + "build system and is likely not intended.", + szOutputDir, szOutputFile); + } + + char szFormat[MAX_PATH] = {0}; + V_ComposeFileName(szOutputDir, szOutputFile, szFormat, sizeof(szFormat)); + V_RemoveDotSlashes(szFormat); + + // For the output files, which are in the build/"Products" directory, Xcode + // expects static libs to be named "libfoo.a" so it can generate a "-lfoo" + // command to link with projects that depend upon them (vs passing just + // ./path/to/foo.a to the linker, which it doesn't want to do for static + // libraries generated as dependencies, but will do for random external static + // libs... I don't know either) + if (IsStaticLibrary(szFormat)) { + EnforceLibPrefix(szFormat, sizeof(szFormat)); + } + + // Some VPC files are giving output files that are trying to reference a path + // outside the build directory -- this is what GameOutputFile is for. This + // actually doesn't seem to break xcode, but isn't really proper and might + // break later. + if (V_IsAbsolutePath(szFormat) || V_strncmp(szFormat, "../", 3) == 0) { + g_pVPC->VPCWarning( + "Final output file '%s' (composited from $OutputDirectory '%s' and " + "$OutputFile '%s') escapes the relative " + "BUILT_PRODUCTS_DIR used by Xcode -- this may break things, and should " + "be achieved by using $GameOutputFile " + "to copy built output to the proper tree location", + szFormat, szOutputDir, szOutputFile); + } + + CUtlString ret(szFormat); + return ret; +} + +static CUtlString GameOutputFileFromConfig(KeyValues *pConfigKV) { + char szGameOutputFile[MAX_PATH] = {0}; + UsePOSIXSlashes(pConfigKV->GetString(g_pOption_GameOutputFile, ""), + szGameOutputFile, sizeof(szGameOutputFile)); + V_RemoveDotSlashes(szGameOutputFile); + + // VPC files have snuck in hard-coding of random variables expecting the + // Makefile backend to expand them. + if (V_strstr(szGameOutputFile, "$")) { + g_pVPC->VPCWarning( + "$GameOutputFile '%s' contains what looks like a shell variable " + "reference -- " + "this will be treated literally in most circumstances by the Xcode " + "build system and is likely not intended.", + szGameOutputFile); + } + + CUtlString ret(szGameOutputFile); + return ret; +} + +static const char *SkipLeadingWhitespace(const char *pStr) { + if (!pStr) return NULL; + while (*pStr != '\0' && isspace(*pStr)) pStr++; + return pStr; +} + +static bool ProjectProducesBinary( + const CBaseProjectDataCollector *pProjectDataCollector) { + return ( + pProjectDataCollector->m_BaseConfigData.m_Configurations[0]->GetOption( + g_pOption_OutputFile) || + pProjectDataCollector->m_BaseConfigData.m_Configurations[0]->GetOption( + g_pOption_GameOutputFile)); +} + +static bool IsSourceFile(const char *pszFileName) { + const char *pchExtension = + V_GetFileExtension(V_UnqualifiedFileName(pszFileName)); + if (!pchExtension) + return false; + else if (!V_stricmp(pchExtension, "cpp") || !V_stricmp(pchExtension, "cxx") || + !V_stricmp(pchExtension, "c") || !V_stricmp(pchExtension, "m") || + !V_stricmp(pchExtension, "mm") || !V_stricmp(pchExtension, "cc")) + return true; + return false; +} + +static bool FileBuildsWithCustomBuildRule( + const CBaseProjectDataCollector *pProjectDataCollector, + CFileConfig *pFileConfig) { + if (!pFileConfig || !pProjectDataCollector) return false; + CSpecificConfig *pFileSpecificData = pFileConfig->GetOrCreateConfig( + pProjectDataCollector->m_BaseConfigData.m_Configurations[1] + ->GetConfigName(), + pProjectDataCollector->m_BaseConfigData.m_Configurations[1]); + return (pFileSpecificData->GetOption(g_pOption_CustomBuildStepCommandLine) != + NULL && + pFileSpecificData->GetOption(g_pOption_AdditionalDependencies) == + NULL); +} + +static bool AppearsInSourcesBuildPhase( + const CBaseProjectDataCollector *pProjectDataCollector, + CFileConfig *pFileConfig) { + if (!pFileConfig) return false; + return (IsSourceFile(pFileConfig->m_Filename.String()) || + (FileBuildsWithCustomBuildRule(pProjectDataCollector, pFileConfig) && + ProjectProducesBinary(pProjectDataCollector))); +} + +static bool IsCreatedByCustomBuildStep( + const CBaseProjectDataCollector *pProjectDataCollector, + CFileConfig *pDynamicFileConfig) { + for (int i = pProjectDataCollector->m_Files.First(); + i != pProjectDataCollector->m_Files.InvalidIndex(); + i = pProjectDataCollector->m_Files.Next(i)) { + CFileConfig *pFileConfig = pProjectDataCollector->m_Files[i]; + CSpecificConfig *pFileSpecificData = pFileConfig->GetOrCreateConfig( + pProjectDataCollector->m_BaseConfigData.m_Configurations[1] + ->GetConfigName(), + pProjectDataCollector->m_BaseConfigData.m_Configurations[1]); + + if (!pFileSpecificData->GetOption(g_pOption_Outputs)) continue; + + CUtlString sOutputFiles; + sOutputFiles.SetLength(MAX_PATH); + CUtlString sInputFile; + sInputFile.SetLength(MAX_PATH); + V_snprintf(sInputFile.Get(), MAX_PATH, "%s/%s", + pProjectDataCollector->m_ProjectName.String(), + UsePOSIXSlashes(pDynamicFileConfig->m_Filename.String())); + V_RemoveDotSlashes(sInputFile.Get()); + + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + pFileSpecificData->GetOption(g_pOption_Outputs), sInputFile, + sOutputFiles.Get(), MAX_PATH); + V_StrSubstInPlace(sOutputFiles.Get(), MAX_PATH, "$(OBJ_DIR)", + "${OBJECT_FILE_DIR_normal}", false); + + if (V_stristr(sOutputFiles, + V_UnqualifiedFileName(pDynamicFileConfig->GetName())) && + !pFileSpecificData->GetOption(g_pOption_AdditionalDependencies)) { + g_pVPC->VPCWarning( + "Not adding '%s' to the build sources list in project '%s', it's a " + "dyanmic file and seems to be created by building '%s'\n", + pDynamicFileConfig->GetName(), + pProjectDataCollector->m_ProjectName.String(), + pFileConfig->GetName()); + return true; + } + } + return false; +} + +void ResolveAdditionalProjectDependencies( + CDependency_Project *pCurProject, + CUtlVector &projects, + CUtlVector &additionalProjectDependencies) { + for (int i = 0; i < pCurProject->m_AdditionalProjectDependencies.Count(); + i++) { + const char *pLookingFor = + pCurProject->m_AdditionalProjectDependencies[i].String(); + + int j; + for (j = 0; j < projects.Count(); j++) { + if (V_stricmp(projects[j]->m_ProjectName.String(), pLookingFor) == 0) + break; + } + + if (j == projects.Count()) + g_pVPC->VPCError( + "Project %s lists '%s' in its $AdditionalProjectDependencies, but " + "there is no project by that name.", + pCurProject->GetName(), pLookingFor); + + additionalProjectDependencies.AddToTail(projects[j]); + } +} + +void CSolutionGenerator_Xcode::WriteFilesFolder( + uint64_t oid, const char *pFolderName, const char *pExtensions, + CBaseProjectDataCollector *pProject) { + const CUtlDict &files = pProject->m_Files; + + CUtlVector extensions; + V_SplitString(pExtensions, ";", extensions); + + Write("%024llX /* %s */ = {\n", oid, pFolderName); + ++m_nIndent; + Write("isa = PBXGroup;\n"); + Write("children = (\n"); + ++m_nIndent; + + for (int i = files.First(); i != files.InvalidIndex(); i = files.Next(i)) { + const char *pFileName = files[i]->GetName(); + + // Check for duplicates -- projects may reference a file twice, and xcode + // does not enjoy any oid being a member of a group twice. + int idxDupe = files.Find(pFileName); + if (files.IsValidIndex(idxDupe) && idxDupe != i) { + continue; + } + + // Make sure this file's extension is one of the extensions they're asking + // for. + bool bValidExt = false; + const char *pFileExtension = + V_GetFileExtension(V_UnqualifiedFileName(pFileName)); + if (pFileExtension) { + for (int iExt = 0; iExt < extensions.Count(); iExt++) { + const char *pTestExt = extensions[iExt]; + + if (pTestExt[0] == '*' && pTestExt[1] == '.' && + V_stricmp(pTestExt + 2, pFileExtension) == 0) { + bValidExt = true; + break; + } + } + } + + if (bValidExt) { + Write("%024llX /* %s in %s */,\n", + makeoid2(pProject->GetProjectName(), pFileName, + EOIDTypeFileReference), + UsePOSIXSlashes(pFileName), pProject->GetProjectName().String()); + } + } + + --m_nIndent; + Write(");\n"); + Write("name = \"%s\";\n", pFolderName); + Write("sourceTree = \"\";\n"); + --m_nIndent; + Write("};\n"); +} + +void CSolutionGenerator_Xcode::XcodeFileTypeFromFileName( + const char *pszFileName, char *pchOutBuf, int cchOutBuf) { + const char *pchExtension = + V_GetFileExtension(V_UnqualifiedFileName(pszFileName)); + if (!pchExtension) + snprintf(pchOutBuf, cchOutBuf, "compiled.mach-o.executable"); + else if (!V_stricmp(pchExtension, "cpp") || !V_stricmp(pchExtension, "cxx") || + !V_stricmp(pchExtension, "cc") || !V_stricmp(pchExtension, "h") || + !V_stricmp(pchExtension, "hxx")) + snprintf(pchOutBuf, cchOutBuf, "sourcecode.cpp.%s", pchExtension); + else if (!V_stricmp(pchExtension, "c")) + snprintf(pchOutBuf, cchOutBuf, "sourcecode.cpp.cpp"); + else if (!V_stricmp(pchExtension, "m") || !V_stricmp(pchExtension, "mm")) + snprintf(pchOutBuf, cchOutBuf, "sourcecode.objc.%s", pchExtension); + else if (!V_stricmp(pchExtension, "a")) + snprintf(pchOutBuf, cchOutBuf, "archive.ar"); + else if (!V_stricmp(pchExtension, "dylib")) { + const char *pszLibName = V_UnqualifiedFileName(pszFileName); + if (pszLibName[0] == 'l' && pszLibName[1] == 'i' && pszLibName[2] == 'b') + snprintf(pchOutBuf, cchOutBuf, "compiled.mach-o.dylib"); + else + snprintf(pchOutBuf, cchOutBuf, "compiled.mach-o.bundle"); + } else if (!V_stricmp(pchExtension, "pl")) + snprintf(pchOutBuf, cchOutBuf, "text.script.perl"); + else + snprintf(pchOutBuf, cchOutBuf, "text.plain"); +} + +void CSolutionGenerator_Xcode::XcodeProductTypeFromFileName( + const char *pszFileName, char *pchOutBuf, int cchOutBuf) { + const char *pchExtension = + V_GetFileExtension(V_UnqualifiedFileName(pszFileName)); + if (!pchExtension) + snprintf(pchOutBuf, cchOutBuf, "com.apple.product-type.tool"); + else if (!V_stricmp(pchExtension, "a")) + snprintf(pchOutBuf, cchOutBuf, "com.apple.product-type.library.static"); + else if (!V_stricmp(pchExtension, "dylib")) { + snprintf(pchOutBuf, cchOutBuf, "com.apple.product-type.library.dynamic"); +#if 0 + const char *pszLibName = V_UnqualifiedFileName( pszFileName ); + if ( pszLibName[0] != 'l' || pszLibName[1] != 'i' || pszLibName[2] != 'b' ) + snprintf( pchOutBuf, cchOutBuf, "com.apple.product-type.bundle" ); +#endif + } else + snprintf(pchOutBuf, cchOutBuf, "com.apple.product-type.unknown"); +} + +// GetBool only groks 1/0, we want more flexibility +bool IsTrue(const char *str) { + if (V_strlen(str) && (!V_stricmp(str, "yes") || !V_stricmp(str, "true") || + !V_stricmp(str, "1"))) + return true; + return false; +} + +void CSolutionGenerator_Xcode::EmitBuildSettings( + const char *pszProjectName, const char *pszProjectDir, + CUtlDict *pDictFiles, KeyValues *pConfigKV, + KeyValues *pFirstConfigKV, bool bIsDebug) { + if (!pConfigKV) { + Write("PRODUCT_NAME = \"%s\";\n", pszProjectName); + return; + } + + // KeyValuesDumpAsDevMsg( pConfigKV, 0, 0 ); + + // Write( "CC = + // \"$(SOURCE_ROOT)/devtools/bin/osx32/xcode_ccache_wrapper\";\n" ); Write( + // "LDPLUSPLUS = \"$(DT_TOOLCHAIN_DIR)/usr/bin/clang++\";\n" ); + + Write("ARCHS = (\n"); + { + ++m_nIndent; + bool bBuildX64 = IsTrue(pConfigKV->GetString(g_pOption_BuildX64Only, "")) || + IsTrue(pConfigKV->GetString(g_pOption_BuildMultiArch, "")); + bool bBuildi386 = !IsTrue(pConfigKV->GetString(g_pOption_BuildX64Only, "")); + if (bBuildi386) Write("i386,\n"); + if (bBuildX64) { + Write("x86_64,\n"); + } + --m_nIndent; + } + Write(");\n"); + + // We do not handle Output file names changing between configurations, and use + // the first configuration for such things all over the generator. I think we + // would need to duplicate all the targets to be release and debug targets. + // Instead, when generating configurations, just warn that this isn't + // supported. + CUtlString sBuildOutputFile = + OutputFileWithDirectoryFromConfig(pFirstConfigKV); + CUtlString sGameOutputFile = GameOutputFileFromConfig(pFirstConfigKV); + for (int iConfig = 1; iConfig < V_ARRAYSIZE(k_rgchXCConfigFiles); iConfig++) { + CUtlString sConfigOutputFile = OutputFileWithDirectoryFromConfig(pConfigKV); + CUtlString sConfigGameOutputFile = GameOutputFileFromConfig(pConfigKV); + if (sConfigOutputFile != sBuildOutputFile || + sConfigGameOutputFile != sGameOutputFile) { + g_pVPC->VPCWarning( + "Config '%s' for project '%s' has effective output files:\n" + " $OutputDirectory/$OutputFile: %s\n" + " $GameOutputFile: %s\n" + " This differs from the first configuration's output files:\n" + " $OutputDirectory/$OutputFile: %s\n" + " $GameOutputFile: %s\n" + " XCode does not support having differing product names per " + "config,\n" + " the first configuration's names will be used for all configs.", + k_rgchXCConfigFiles[iConfig], pszProjectName, + sConfigOutputFile.String(), sConfigGameOutputFile.String(), + sBuildOutputFile.String(), sGameOutputFile.String()); + } + } + + if (sGameOutputFile.Length()) { + Write("PRODUCT_NAME = \"%s\";\n", pszProjectName); + Write("EXECUTABLE_NAME = \"%s\";\n", sBuildOutputFile.String()); + + if (V_strlen(pConfigKV->GetString(g_pOption_ExtraLinkerFlags, ""))) + Write("OTHER_LDFLAGS = \"%s\";\n", + pConfigKV->GetString(g_pOption_ExtraLinkerFlags)); + + CUtlString sOtherCompilerCFlags = "OTHER_CFLAGS = \"$(OTHER_CFLAGS) "; + CUtlString sOtherCompilerCPlusFlags = + "OTHER_CPLUSPLUSFLAGS = \"$(OTHER_CPLUSPLUSFLAGS) "; + + // Buffer overflow checks default to on so only change things + // if we need to turn them off. + bool bBufferSecurityCheck = Sys_StringToBool( + pConfigKV->GetString(g_pOption_BufferSecurityCheck, "Yes")); + if (!bBufferSecurityCheck) { + sOtherCompilerCFlags += "-fno-stack-protector "; + sOtherCompilerCPlusFlags += "-fno-stack-protector "; + } + + if (V_strlen(pConfigKV->GetString(g_pOption_ExtraCompilerFlags, ""))) { + sOtherCompilerCFlags += + pConfigKV->GetString(g_pOption_ExtraCompilerFlags); + sOtherCompilerCPlusFlags += + pConfigKV->GetString(g_pOption_ExtraCompilerFlags); + } + + if (V_strlen(pConfigKV->GetString(g_pOption_ForceInclude, ""))) { + CSplitString outStrings(pConfigKV->GetString(g_pOption_ForceInclude), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < outStrings.Count(); i++) { + if (V_strlen(outStrings[i]) > 2) { + // char sIncludeDir[ MAX_PATH ]; + char szIncludeLine[MAX_PATH]; + /*V_snprintf( sIncludeDir, sizeof( sIncludeDir ), "%s/%s", + pszProjectDir, outStrings[i] ); V_FixSlashes( sIncludeDir, '/' ); + V_RemoveDotSlashes( sIncludeDir ); + #ifdef STEAM + V_StripPrecedingAndTrailingWhitespace( sIncludeDir ); + #endif */ + V_snprintf(szIncludeLine, sizeof(szIncludeLine), " -include %s", + UsePOSIXSlashes(outStrings[i])); + sOtherCompilerCFlags += szIncludeLine; + sOtherCompilerCPlusFlags += szIncludeLine; + } + } + } + + sOtherCompilerCFlags += "\";\n"; + sOtherCompilerCPlusFlags += "\";\n"; + + Write(sOtherCompilerCFlags); + Write(sOtherCompilerCPlusFlags); + + if (IsDynamicLibrary(sGameOutputFile)) { + char szBaseName[MAX_PATH] = {0}; + V_StripExtension(V_UnqualifiedFileName(sGameOutputFile), szBaseName, + sizeof(szBaseName)); + + if (Sys_StringToBool( + pConfigKV->GetString(g_pOption_LinkAsBundle, "No"))) { + Write("MACH_O_TYPE = mh_bundle;\n"); + // Bundles can't have versions and they're defaulted to 1 + // so make sure we have our own no-version properties. + Write("DYLIB_COMPATIBILITY_VERSION = \"\";\n"); + Write("DYLIB_CURRENT_VERSION = \"\";\n"); + } else if (szBaseName[0] != 'l' || szBaseName[1] != 'i' || + szBaseName[2] == 'b') { + // if ( !pConfigKV->GetString( g_pOption_LocalFrameworks, NULL ) ) + // Write( "OTHER_LDFLAGS = \"-flat_namespace\";\n" ); + // Write( "MACH_O_TYPE = mh_bundle;\n" ); + // Write( "EXECUTABLE_EXTENSION = dylib;\n" ); + // Write( "OTHER_LDFLAGS = \"-flat_namespace -undefined suppress\";\n" + // ); + } else { + Write("MACH_O_TYPE = mh_dylib;\n"); + } + + Write("LD_DYLIB_INSTALL_NAME = \"@loader_path/%s.dylib\";\n", szBaseName); + } + + if (IsStaticLibrary(sGameOutputFile)) { + Write("DEBUG_INFORMATION_FORMAT = dwarf;\n"); + } + } else + Write("PRODUCT_NAME = \"%s\";\n", pszProjectName); + + // add our header search paths + CSplitString outStrings( + pConfigKV->GetString(g_pOption_AdditionalIncludeDirectories), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + if (outStrings.Count()) { + char sIncludeDir[MAX_PATH]; + + // start the iquote list with the project directory + V_snprintf(sIncludeDir, sizeof(sIncludeDir), "%s", pszProjectDir); + V_FixSlashes(sIncludeDir, '/'); + V_RemoveDotSlashes(sIncludeDir); +#ifdef STEAM + V_StripPrecedingAndTrailingWhitespace(sIncludeDir); +#endif + + Write("USER_HEADER_SEARCH_PATHS = (\n"); + ++m_nIndent; +#ifdef STEAM + Write("\"%s\",\n", sIncludeDir); +#endif + for (int i = 0; i < outStrings.Count(); i++) { + char sExpandedOutString[MAX_PATH]; + + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + outStrings[i], CFmtStr("%s/dummy.txt", pszProjectDir).Access(), + sExpandedOutString, sizeof(sExpandedOutString)); + V_StrSubstInPlace(sExpandedOutString, "$(OBJ_DIR)", + "\"${OBJECT_FILE_DIR_normal}\"", false); + V_StrSubstInPlace(sExpandedOutString, "\"", "\\\"", false); + + if (V_IsAbsolutePath(sExpandedOutString) || + V_strncmp(sExpandedOutString, "$", 1) == 0) + V_snprintf(sIncludeDir, sizeof(sExpandedOutString), "%s", + sExpandedOutString); + else { + V_snprintf(sIncludeDir, sizeof(sExpandedOutString), "%s/%s", + pszProjectDir, sExpandedOutString); + V_RemoveDotSlashes(sIncludeDir); + } +#ifdef STEAM + V_StripPrecedingAndTrailingWhitespace(sIncludeDir); +#endif + Write("\"%s\",\n", sIncludeDir); + } + --m_nIndent; + Write(");\n"); + } + + // add local frameworks we link against to the compiler framework search paths + CSplitString localFrameworks(pConfigKV->GetString(g_pOption_LocalFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + if (localFrameworks.Count()) { + Write("FRAMEWORK_SEARCH_PATHS = (\n"); + ++m_nIndent; + { + Write("\"$(inherited)\",\n"); + for (int i = 0; i < localFrameworks.Count(); i++) { + char rgchFrameworkPath[MAX_PATH]; + V_snprintf(rgchFrameworkPath, sizeof(rgchFrameworkPath), "%s/%s", + pszProjectDir, localFrameworks[i]); + rgchFrameworkPath[V_strlen(rgchFrameworkPath) - + V_strlen(V_UnqualifiedFileName(localFrameworks[i]))] = + '\0'; + V_RemoveDotSlashes(rgchFrameworkPath); + + Write("\"%s\",\n", rgchFrameworkPath); + } + } + --m_nIndent; + Write(");\n"); + } + + // add our needed preprocessor definitions + CSplitString preprocessorDefines( + pConfigKV->GetString(g_pOption_PreprocessorDefinitions), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + CUtlVector vpcMacroDefines; + g_pVPC->GetMacrosMarkedForCompilerDefines(vpcMacroDefines); + if (preprocessorDefines.Count() || vpcMacroDefines.Count()) { + Write("GCC_PREPROCESSOR_DEFINITIONS = (\n"); + ++m_nIndent; + { + Write("\"$(GCC_PREPROCESSOR_DEFINITIONS)\",\n"); + for (intp i = 0; i < preprocessorDefines.Count(); i++) { + Write("\"%s\",\n", preprocessorDefines[i]); + } + for (intp i = 0; i < vpcMacroDefines.Count(); i++) { + Write("\"%s=%s\",\n", vpcMacroDefines[i]->name.String(), + vpcMacroDefines[i]->value.String()); + } + } + --m_nIndent; + Write(");\n"); + } + + bool bTreatWarningsAsErrors = Sys_StringToBool( + pConfigKV->GetString(g_pOption_TreatWarningsAsErrors, "false")); + Write("GCC_TREAT_WARNINGS_AS_ERRORS = %s;\n", + bTreatWarningsAsErrors ? "YES" : "NO"); + + CUtlMap librarySearchPaths(StringLessThan); + if (pDictFiles) { + // libraries we consume (specified in our files list) + for (int i = pDictFiles->First(); i != pDictFiles->InvalidIndex(); + i = pDictFiles->Next(i)) { + const char *pFileName = (*pDictFiles)[i]->m_Filename.String(); + if (IsStaticLibrary(pFileName) || IsDynamicLibrary(pFileName)) { + char rgchLibPath[MAX_PATH]; + V_snprintf(rgchLibPath, sizeof(rgchLibPath), "%s/%s", pszProjectDir, + pFileName); + V_RemoveDotSlashes(rgchLibPath); + V_StripFilename(rgchLibPath); + int nIndex = librarySearchPaths.Find(rgchLibPath); + if (nIndex == librarySearchPaths.InvalidIndex()) { + char *pszLibPath = new char[MAX_PATH]; + V_strncpy(pszLibPath, rgchLibPath, MAX_PATH); + nIndex = librarySearchPaths.Insert(pszLibPath); + } + } + } + } + + // add additional library search paths + CSplitString additionalLibraryDirectories( + pConfigKV->GetString(g_pOption_AdditionalLibraryDirectories), + (const char **)g_IncludeSeparators, V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < additionalLibraryDirectories.Count(); i++) { + int nIndex = librarySearchPaths.Find(additionalLibraryDirectories[i]); + if (nIndex == librarySearchPaths.InvalidIndex()) { + // we need to dup the string so the map can free it later + char *pszLibPath = new char[MAX_PATH]; + V_strncpy(pszLibPath, additionalLibraryDirectories[i], MAX_PATH); + nIndex = librarySearchPaths.Insert(pszLibPath); + } + } + if (librarySearchPaths.Count()) { + char sIncludeDir[MAX_PATH]; + + // add the library path we know we need to reference + Write("LIBRARY_SEARCH_PATHS = (\n"); + ++m_nIndent; + { + Write("\"$(inherited)\",\n"); + for (unsigned short i = 0; (librarySearchPaths).IsUtlMap && + i < (librarySearchPaths).MaxElement(); + ++i) + if (!(librarySearchPaths).IsValidIndex(i)) + continue; + else { + char sExpandedOutString[MAX_PATH]; + V_strncpy(sExpandedOutString, librarySearchPaths.Key(i), + sizeof(sExpandedOutString)); + V_StrSubstInPlace(sExpandedOutString, "\"", "\\\"", false); + + if (V_IsAbsolutePath(sExpandedOutString) || + V_strncmp(sExpandedOutString, "$", 1) == 0) + V_snprintf(sIncludeDir, sizeof(sExpandedOutString), "%s", + sExpandedOutString); + else { + V_snprintf(sIncludeDir, sizeof(sExpandedOutString), "%s/%s", + pszProjectDir, sExpandedOutString); + V_RemoveDotSlashes(sIncludeDir); + } + + Write("\"%s\",\n", sIncludeDir); + } + } + --m_nIndent; + Write(");\n"); + } + + while (librarySearchPaths.Count()) { + const char *key = librarySearchPaths.Key(librarySearchPaths.FirstInorder()); + librarySearchPaths.Remove(key); + delete[] key; + } +} + +class CStringLess { + public: + bool Less(const char *lhs, const char *rhs, void *pCtx) { + return (strcmp(lhs, rhs) < 0 ? true : false); + } +}; + +void CSolutionGenerator_Xcode::GenerateSolutionFile( + const char *pSolutionFilename, + CUtlVector &projects) { + CFmtStr oidStrSolutionRoot("solutionroot.%s", pSolutionFilename); + CFmtStr oidStrProjectsRoot("projectsroot.%s", pSolutionFilename); + + Assert(projects.Count() == g_vecPGenerators.Count()); + + char sPbxProjFile[MAX_PATH]; + sprintf(sPbxProjFile, "%s.xcodeproj", pSolutionFilename); + mkdir(sPbxProjFile, 0777); + sprintf(sPbxProjFile, "%s.xcodeproj/project.pbxproj", pSolutionFilename); + + char sProjProjectListFile[MAX_PATH]; + V_snprintf(sProjProjectListFile, sizeof(sProjProjectListFile), "%s.projects", + sPbxProjFile); + + bool bUpToDate = !g_pVPC->IsForceGenerate() && Sys_Exists(sPbxProjFile); + int64 llSize = 0, llModTime = 0, llLastModTime = 0; + + FOR_EACH_VEC(projects, iProject) { + AssertFatal(!V_strcmp(projects[iProject]->m_ProjectName, + g_vecPGenerators[iProject]->GetProjectName())); + // the solution is up-to-date only if all the projects in it were up to date + // and the solution was built after the mod time on all the project outputs + CProjectGenerator_Xcode &pProjGen = + dynamic_cast(*g_vecPGenerators[iProject]); + bUpToDate &= pProjGen.m_bIsCurrent; + if (bUpToDate && + Sys_FileInfo(pProjGen.m_OutputFilename, llSize, llModTime) && + llModTime > llLastModTime) { + llLastModTime = llModTime; + } + } + + // regenerate pbxproj if it is older than the latest of the project output + // files + if (bUpToDate && (!Sys_FileInfo(sPbxProjFile, llSize, llModTime) || + llModTime < llLastModTime)) { + bUpToDate = false; + } + + // now go see if our project list agrees with the one on disk + if (bUpToDate) { + FILE *fp = fopen(sProjProjectListFile, "r+t"); + if (!fp) bUpToDate = false; + + char line[2048]; + char *pLine; + if (bUpToDate) { + pLine = fgets(line, sizeof(line), fp); + if (!pLine) bUpToDate = false; + if (stricmp(line, VPCCRCCHECK_FILE_VERSION_STRING "\n")) + bUpToDate = false; + } + + int cProjectsPreviously = 0; + while (bUpToDate) { + pLine = fgets(line, sizeof(line), fp); + if (!pLine) break; + + ++cProjectsPreviously; + + intp len = V_strlen(line) - 1; + while (line[len] == '\n' || line[len] == '\r') { + line[len] = '\0'; + len--; + } + + // N^2 sucks, but N is small + bool bProjectFound = false; + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + CProjectGenerator_Xcode *pGenerator = + (CProjectGenerator_Xcode *)g_vecPGenerators[iGenerator]; + if (stricmp(pGenerator->m_ProjectName.String(), pLine) == 0) { + bProjectFound = true; + break; + } + } + if (!bProjectFound) { + // fprintf( stderr, "%s has vanished from the project, + // regenerating...\n", pLine ); + bUpToDate = false; + break; + } + } + if (g_vecPGenerators.Count() != cProjectsPreviously) { + // fprintf( stderr, "Project count has changed (%d/%d), + // regenerating...\n", cProjectsPreviously, g_vecPGenerators.Count() ); + bUpToDate = false; + } + if (fp) fclose(fp); + } + + if (bUpToDate) { + g_pVPC->VPCStatus( + true, "Xcode Project %s.xcodeproj looks up-to-date, not generating", + pSolutionFilename); + return; + } + + m_fp = fopen(sPbxProjFile, "wt"); + m_nIndent = 0; + + Msg("\nWriting master Xcode project %s.xcodeproj.\n\n", pSolutionFilename); + + /** header **/ + Write("// !$*UTF8*$!\n{\n"); + ++m_nIndent; + { + /** + ** + ** preamble + ** + **/ + Write("archiveVersion = 1;\n"); + Write("classes = {\n"); + Write("};\n"); + Write("objectVersion = 44;\n"); + Write("objects = {\n"); + { + /** + ** + ** buildfiles - any file that's involved in, or the output of, a build + *phase + ** + **/ + Write("\n/* Begin PBXBuildFile section */"); + ++m_nIndent; + { + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + // poke into the project we're looking @ in the dependency projects + // vector to figure out it's location on disk + char rgchProjectDir[MAX_PATH]; + rgchProjectDir[0] = '\0'; + V_strncpy(rgchProjectDir, + projects[iGenerator]->m_ProjectFilename.String(), + sizeof(rgchProjectDir)); + V_StripFilename(rgchProjectDir); + + // the files this project references + for (int i = g_vecPGenerators[iGenerator]->m_Files.First(); + i != g_vecPGenerators[iGenerator]->m_Files.InvalidIndex(); + i = g_vecPGenerators[iGenerator]->m_Files.Next(i)) { + char rgchFilePath[MAX_PATH]; + V_snprintf( + rgchFilePath, sizeof(rgchFilePath), "%s/%s", rgchProjectDir, + g_vecPGenerators[iGenerator]->m_Files[i]->m_Filename.String()); + V_RemoveDotSlashes(rgchFilePath); + + CFileConfig *pFileConfig = g_vecPGenerators[iGenerator]->m_Files[i]; + const char *pFileName = pFileConfig->m_Filename.String(); + + bool bExcluded = true; + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchConfigNames); + iConfig++) { + bExcluded &= + (pFileConfig->IsExcludedFrom(k_rgchConfigNames[iConfig])); + } + + if (bExcluded) { + g_pVPC->VPCStatus(false, "xcode: excluding File %s\n", pFileName); + continue; + } + + // dynamic files - generated as part of the build - may be + // automatically added to the build set by xcode, if we add them + // twice, bad things (duplicate symbols) happen. + bool bIsDynamicFile = + pFileConfig->IsDynamicFile(k_rgchConfigNames[1]); + + // if we have a custom build step, we need to include this file in + // the build set + if ((!bIsDynamicFile || + !IsCreatedByCustomBuildStep(g_vecPGenerators[iGenerator], + pFileConfig)) && + AppearsInSourcesBuildPhase( + g_vecPGenerators[iGenerator], + g_vecPGenerators[iGenerator]->m_Files[i])) { + Write("\n"); + CUtlString sCompilerFlags = NULL; + // on mac we can only globally specify common (debug and release) + // per-file compiler flags + for (int k = pFileConfig->m_Configurations.First(); + k != pFileConfig->m_Configurations.InvalidIndex(); + k = pFileConfig->m_Configurations.Next(k)) { + sCompilerFlags += + pFileConfig->m_Configurations[k]->m_pKV->GetString( + g_pOption_ExtraCompilerFlags); + } + // File reference OIDs are unique per project per file + Write( + "%024llX /* %s in Sources */ = {isa = PBXBuildFile; fileRef " + "= %024llX /* %s */; ", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pFileName, EOIDTypeBuildFile), + V_UnqualifiedFileName(pFileName), + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pFileName, EOIDTypeFileReference), + pFileName); + if (!sCompilerFlags.IsEmpty()) { + Write("settings = { COMPILER_FLAGS = \"%s\"; };", + sCompilerFlags.String()); + } + Write(" };"); + } + + if (IsDynamicLibrary(pFileName)) { + Write("\n"); + Write( + "%024llX /* %s in Frameworks */ = {isa = PBXBuildFile; " + "fileRef = %024llX /* %s */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pFileName, EOIDTypeBuildFile), + V_UnqualifiedFileName(pFileName), + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pFileName, EOIDTypeFileReference), + pFileName); + } + + if (IsStaticLibrary(pFileName)) { + Write("\n"); + Write( + "%024llX /* %s in Frameworks */ = {isa = PBXBuildFile; " + "fileRef = %024llX /* %s */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pFileName, EOIDTypeBuildFile), + pFileName, + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pFileName, EOIDTypeFileReference), + pFileName); + } + } + + // system libraries we link against + KeyValues *pKV = g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + CSplitString libs(pKV->GetString(g_pOption_SystemLibraries), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < libs.Count(); i++) { + Write("\n"); + Write( + "%024llX /* lib%s.dylib in Frameworks */ = {isa = " + "PBXBuildFile; fileRef = %024llX /* lib%s.dylib */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemLibraries), + EOIDTypeBuildFile, i), + libs[i], + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemLibraries), + EOIDTypeFileReference, i), + libs[i]); + } + + // system frameworks we link against + CSplitString sysFrameworks(pKV->GetString(g_pOption_SystemFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < sysFrameworks.Count(); i++) { + Write("\n"); + Write( + "%024llX /* %s.framework in Frameworks */ = {isa = " + "PBXBuildFile; fileRef = %024llX /* %s.framework */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemFrameworks), + EOIDTypeBuildFile, i), + sysFrameworks[i], + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemFrameworks), + EOIDTypeFileReference, i), + sysFrameworks[i]); + } + + // local frameworks we link against + CSplitString localFrameworks( + pKV->GetString(g_pOption_LocalFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < localFrameworks.Count(); i++) { + char rgchFrameworkName[MAX_PATH]; + V_StripExtension(V_UnqualifiedFileName(localFrameworks[i]), + rgchFrameworkName, sizeof(rgchFrameworkName)); + + Write("\n"); + Write( + "%024llX /* %s.framework in Frameworks */ = {isa = " + "PBXBuildFile; fileRef = %024llX /* %s.framework */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_LocalFrameworks), + EOIDTypeBuildFile, i), + rgchFrameworkName, + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_LocalFrameworks), + EOIDTypeFileReference, i), + rgchFrameworkName); + } + + // look at everyone who depends on us, and emit a build file pointing + // at our output file for each of them to depend upon. We use the + // OutputFile (products directory) so XCode's dependency/linking logic + // works right. + // + // The oid has the project in question as the ordinal, so we have a + // unique build file OID for each project that wants to depend on us + // -- they all point to the same file reference. + CDependency_Project *pCurProject = projects[iGenerator]; + CUtlString sGameOutputFile = GameOutputFileFromConfig(pKV); + CUtlString sOutputFile = OutputFileWithDirectoryFromConfig(pKV); + + if (sOutputFile.Length() && + (IsStaticLibrary(sOutputFile) || IsDynamicLibrary(sOutputFile))) { + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (iGenerator == iTestProject) continue; + + CDependency_Project *pTestProject = projects[iTestProject]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies( + pTestProject, projects, additionalProjectDependencies); + + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pTestProject->DependsOn(pCurProject, dependsOnFlags) || + additionalProjectDependencies.Find(pCurProject) != + additionalProjectDependencies.InvalidIndex()) { + Write("\n"); + Write( + "%024llX /* (lib)%s */ = {isa = PBXBuildFile; fileRef = " + "%024llX /* (lib)%s - depended on by %s */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sOutputFile, EOIDTypeBuildFile, iTestProject), + sOutputFile.String(), + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sOutputFile, EOIDTypeFileReference), + sOutputFile.String(), pTestProject->m_ProjectName.String()); + } + } + } + + // Add our our output file and game output file -1 OID for ourselves. + if (sGameOutputFile.Length()) { + Write("\n"); + Write( + "%024llX /* %s */ = {isa = PBXBuildFile; fileRef = %024llX /* " + "%s */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sGameOutputFile, EOIDTypeBuildFile, -1), + sGameOutputFile.String(), + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sGameOutputFile, EOIDTypeFileReference), + sGameOutputFile.String()); + } + + if (sOutputFile.Length()) { + Write("\n"); + Write( + "%024llX /* %s in Products */ = {isa = PBXBuildFile; fileRef = " + "%024llX /* %s */; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sOutputFile, EOIDTypeBuildFile, -1), + sOutputFile.String(), + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sOutputFile, EOIDTypeFileReference), + sOutputFile.String()); + } + } + } + --m_nIndent; + Write("\n/* End PBXBuildFile section */\n"); + + Write("\n/*Begin PBXBuildRule section */\n"); + ++m_nIndent; + { + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + CProjectGenerator_Xcode *pGenerator = + (CProjectGenerator_Xcode *)g_vecPGenerators[iGenerator]; + + if (!ProjectProducesBinary(pGenerator)) continue; + + char rgchProjectDir[MAX_PATH]; + rgchProjectDir[0] = '\0'; + V_strncpy(rgchProjectDir, + projects[iGenerator]->m_ProjectFilename.String(), + sizeof(rgchProjectDir)); + V_StripFilename(rgchProjectDir); + + // we don't have an output file - wander the list of files, looking + // for custom build steps if we find any, magic up shell scripts to + // run them + for (int i = g_vecPGenerators[iGenerator]->m_Files.First(); + i != g_vecPGenerators[iGenerator]->m_Files.InvalidIndex(); + i = g_vecPGenerators[iGenerator]->m_Files.Next(i)) { + CFileConfig *pFileConfig = g_vecPGenerators[iGenerator]->m_Files[i]; + CSpecificConfig *pFileSpecificData = pFileConfig->GetOrCreateConfig( + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1] + ->GetConfigName(), + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1]); + + // custom build rules with additional dependencies don't map to + // pbxbuildrules, we handle them as custom script phases + if (pFileSpecificData->GetOption(g_pOption_AdditionalDependencies)) + continue; + + CUtlString sCustomBuildCommandLine = pFileSpecificData->GetOption( + g_pOption_CustomBuildStepCommandLine); + CUtlString sOutputFiles = + pFileSpecificData->GetOption(g_pOption_Outputs); + CUtlString sCommand; + + if (sOutputFiles.Length() && !sCustomBuildCommandLine.IsEmpty()) { + CUtlString sInputFile; + sInputFile.SetLength(MAX_PATH); + + int cCommand = + MAX(sCustomBuildCommandLine.Length() * 2, 8 * 1024); + sCommand.SetLength(cCommand); + + Write("\n"); + Write("%024llX /* PBXbuildRule */ = {\n", + makeoid(projects[iGenerator]->m_ProjectName, + EOIDTypeCustomBuildRule, + pGenerator->m_nCustomBuildRules++)); + ++m_nIndent; + { + Write("isa = PBXBuildRule;\n"); + Write("compilerSpec = com.apple.compilers.proxy.script;\n"); + + // DoStandardVisualStudioReplacements needs to know where the + // file is, so make sure it's got a path on it + if (V_IsAbsolutePath( + UsePOSIXSlashes(pFileConfig->m_Filename.String()))) + V_snprintf(sInputFile.Get(), MAX_PATH, "%s", + UsePOSIXSlashes(pFileConfig->m_Filename.String())); + else { + V_snprintf(sInputFile.Get(), MAX_PATH, "%s/%s", + rgchProjectDir, + UsePOSIXSlashes(pFileConfig->m_Filename.String())); + V_RemoveDotSlashes(sInputFile.Get()); + } + Write("filePatterns = \"%s\";\n", sInputFile.String()); + Write("fileType = pattern.proxy;\n"); + Write("isEditable = 1;\n"); + + Write("outputFiles = (\n"); + ++m_nIndent; + { + CSplitString outFiles(sOutputFiles, ";"); + for (int k = 0; k < outFiles.Count(); k++) { + CUtlString sOutputFile; + sOutputFile.SetLength(MAX_PATH); + CBaseProjectDataCollector:: + DoStandardVisualStudioReplacements( + outFiles[k], sInputFile, sOutputFile.Get(), + MAX_PATH); + V_StrSubstInPlace(sOutputFile.Get(), MAX_PATH, "$(OBJ_DIR)", + "${OBJECT_FILE_DIR_normal}", false); + + CUtlString sOutputPath; + sOutputPath.SetLength(MAX_PATH); + + if (V_IsAbsolutePath(sOutputFile) || + V_strncmp(outFiles[k], "$", 1) == 0) + V_snprintf(sOutputPath.Get(), MAX_PATH, "%s", + sOutputFile.String()); + else { + V_snprintf(sOutputPath.Get(), MAX_PATH, "%s/%s", + rgchProjectDir, sOutputFile.String()); + V_RemoveDotSlashes(sOutputPath.Get()); + } + Write("\"%s\",\n", sOutputPath.String()); + } + } + --m_nIndent; + Write(");\n"); + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + sCustomBuildCommandLine, sInputFile, sCommand.Get(), + cCommand); + V_StrSubstInPlace(sCommand.Get(), cCommand, "$(OBJ_DIR)", + "\"${OBJECT_FILE_DIR_normal}\"", false); + V_StrSubstInPlace(sCommand.Get(), cCommand, ";", ";\\n", false); + V_StrSubstInPlace(sCommand.Get(), cCommand, "\"", "\\\"", + false); + + Write( + "script = \"#!/bin/bash\\n" + "cd %s\\n" + "%s\\n" + "exit $?\";\n", + rgchProjectDir, sCommand.String()); + } + --m_nIndent; + Write("};"); + } + } + } + } + --m_nIndent; + Write("\n/*End PBXBuildRule section */\n"); + + /** + ** + ** file references - any file that appears in the project browser + ** + **/ + Write("\n/* Begin PBXFileReference section */"); + ++m_nIndent; + { + // include the xcconfig files + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchXCConfigFiles); + iConfig++) { + char rgchFilePath[MAX_PATH]; + V_snprintf(rgchFilePath, sizeof(rgchFilePath), + "%s.xcodeproj/../devtools/%s", pSolutionFilename, + k_rgchXCConfigFiles[iConfig]); + V_RemoveDotSlashes(rgchFilePath); + + Write("\n"); + Write( + "%024llX /* %s */ = {isa = PBXFileReference; fileEncoding = 4; " + "lastKnownFileType = text.xcconfig; name = \"%s\"; path = " + "\"%s\"; sourceTree = \"\"; };", + makeoid2(oidStrSolutionRoot, k_rgchXCConfigFiles[iConfig], + EOIDTypeFileReference), + k_rgchXCConfigFiles[iConfig], k_rgchXCConfigFiles[iConfig], + rgchFilePath); + } + + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + // find the project we're looking @ in the dependency projects vector + // to figure out it's location on disk + char rgchProjectDir[MAX_PATH]; + rgchProjectDir[0] = '\0'; + V_strncpy(rgchProjectDir, + projects[iGenerator]->m_ProjectFilename.String(), + sizeof(rgchProjectDir)); + V_StripFilename(rgchProjectDir); + + for (int i = g_vecPGenerators[iGenerator]->m_Files.First(); + i != g_vecPGenerators[iGenerator]->m_Files.InvalidIndex(); + i = g_vecPGenerators[iGenerator]->m_Files.Next(i)) { + char rgchFilePath[MAX_PATH]; + const char *file_name = + g_vecPGenerators[iGenerator]->m_Files[i]->m_Filename.String(); + + V_snprintf( + rgchFilePath, sizeof(rgchFilePath), "%s/%s", rgchProjectDir, file_name); + V_RemoveDotSlashes(rgchFilePath); + + const char *pFileName = V_UnqualifiedFileName(file_name); + + char rgchFileType[MAX_PATH]; + + // Can't support compiling as different types in different + // configurations, but that would be insane anyway, right!? Grab the + // Release settings. + CSpecificConfig *pFileSpecificData = + g_vecPGenerators[iGenerator]->m_Files[i]->GetOrCreateConfig( + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1] + ->GetConfigName(), + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1]); + const char *pCompileAsOption = + pFileSpecificData->GetOption(g_pOption_CompileAs); + if (pCompileAsOption && + strstr(pCompileAsOption, "(/TC)")) // Compile as C Code (/TC) + { + strcpy(rgchFileType, "sourcecode.c.c"); + } else { + XcodeFileTypeFromFileName(pFileName, rgchFileType, + sizeof(rgchFileType)); + } + + Write("\n"); + Write( + "%024llX /* %s */ = {isa = PBXFileReference; fileEncoding = 4; " + "explicitFileType = \"%s\"; name = \"%s\"; path = \"%s\"; " + "sourceTree = \"\"; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + g_vecPGenerators[iGenerator]->m_Files[i]->m_Filename, + EOIDTypeFileReference), + pFileName, rgchFileType, pFileName, rgchFilePath); + } + KeyValues *pKV = g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + + // system libraries we link against + CSplitString libs(pKV->GetString(g_pOption_SystemLibraries), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < libs.Count(); i++) { + Write("\n"); + Write( + "%024llX /* lib%s.dylib */ = {isa = PBXFileReference; " + "lastKnownFileType = \"compiled.mach-o.dylib\"; name = " + "\"lib%s.dylib\"; path = \"usr/lib/lib%s.dylib\"; sourceTree = " + "SDKROOT; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemLibraries), + EOIDTypeFileReference, i), + libs[i], libs[i], libs[i]); + } + + // system frameworks we link against + CSplitString sysFrameworks(pKV->GetString(g_pOption_SystemFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < sysFrameworks.Count(); i++) { + Write("\n"); + Write( + "%024llX /* %s.framework */ = {isa = PBXFileReference; " + "lastKnownFileType = wrapper.framework; name = " + "\"%s.framework\"; path = " + "\"System/Library/Frameworks/%s.framework\"; sourceTree = " + "SDKROOT; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemFrameworks), + EOIDTypeFileReference, i), + sysFrameworks[i], sysFrameworks[i], sysFrameworks[i]); + } + + // local frameworks we link against + CSplitString localFrameworks( + pKV->GetString(g_pOption_LocalFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < localFrameworks.Count(); i++) { + char rgchFrameworkName[MAX_PATH]; + V_StripExtension(V_UnqualifiedFileName(localFrameworks[i]), + rgchFrameworkName, sizeof(rgchFrameworkName)); + + char rgchFrameworkPath[MAX_PATH]; + V_snprintf(rgchFrameworkPath, sizeof(rgchFrameworkPath), "%s/%s", + rgchProjectDir, localFrameworks[i]); + V_RemoveDotSlashes(rgchFrameworkPath); + + Write("\n"); + Write( + "%024llX /* %s.framework */ = {isa = PBXFileReference; " + "lastKnownFileType = wrapper.framework; name = " + "\"%s.framework\"; path = \"%s\"; sourceTree = \"\"; " + "};", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_LocalFrameworks), + EOIDTypeFileReference, i), + rgchFrameworkName, rgchFrameworkName, rgchFrameworkPath); + } + + // include the output files (build products) We don't support these + // changing between configs -- We check for and warn about this in + // EmitBuildSettings + KeyValues *pConfigKV = g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + CUtlString sOutputFile = OutputFileWithDirectoryFromConfig(pConfigKV); + if (sOutputFile.Length()) { + char rgchFileType[MAX_PATH]; + XcodeFileTypeFromFileName(sOutputFile, rgchFileType, + sizeof(rgchFileType)); + + Write("\n"); + Write( + "%024llX /* %s */ = {isa = PBXFileReference; explicitFileType " + "= \"%s\"; includeInIndex = 0; path = \"%s\"; sourceTree = " + "BUILT_PRODUCTS_DIR; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sOutputFile, EOIDTypeFileReference), + sOutputFile.String(), rgchFileType, sOutputFile.String()); + } + + // and the gameoutputfile + CUtlString sGameOutputFile = GameOutputFileFromConfig(pKV); + if (sGameOutputFile.Length()) { + char rgchFilePath[MAX_PATH]; + V_snprintf(rgchFilePath, sizeof(rgchFilePath), "%s/%s", + rgchProjectDir, sGameOutputFile.String()); + V_RemoveDotSlashes(rgchFilePath); + + char rgchFileType[MAX_PATH]; + XcodeFileTypeFromFileName(sGameOutputFile, rgchFileType, + sizeof(rgchFileType)); + + Write("\n"); + Write( + "%024llX /* %s */ = {isa = PBXFileReference; explicitFileType " + "= \"%s\"; includeInIndex = 0; path = \"%s\"; sourceTree = " + "\"\"; };", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sGameOutputFile, EOIDTypeFileReference), + sGameOutputFile.String(), rgchFileType, rgchFilePath); + } + } + } + --m_nIndent; + Write("\n/* End PBXFileReference section */\n"); + + /** + ** + ** groups - the file hierarchy displayed in the project + ** + **/ + Write("\n/* Begin PBXGroup section */\n"); + ++m_nIndent; + { + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + CUtlVector folderNames; + V_SplitString("Source Files;Header Files;Resources;VPC Files", ";", + folderNames); + + static const char *folderExtensions[] = { + "*.c;*.C;*.cc;*.cpp;*.cp;*.cxx;*.c++;*.prg;*.pas;*.dpr;*.asm;*.s;" + "*.bas;*.java;*.cs;*.sc;*.e;*.cob;*.html;*.tcl;*.py;*.pl;*.m;*." + "mm", + "*.h;*.H;*.hh;*.hpp;*.hxx;*.inc;*.sh;*.cpy;*.if", + "*.plist;*.strings;*.xib;*.rc;*.proto;*.nut", "*.vpc"}; + + FOR_EACH_VEC(folderNames, iFolder) { + WriteFilesFolder( + makeoid(g_vecPGenerators[iGenerator]->m_ProjectName, + EOIDTypeGroup, iFolder + 1), + folderNames[iFolder], folderExtensions[iFolder], + g_vecPGenerators[iGenerator]); + } + + Write("%024llX /* %s */ = {\n", + makeoid(g_vecPGenerators[iGenerator]->m_ProjectName, + EOIDTypeGroup), + g_vecPGenerators[iGenerator]->GetProjectName().String()); + ++m_nIndent; + { + Write("isa = PBXGroup;\n"); + Write("children = (\n"); + + ++m_nIndent; + { + FOR_EACH_VEC(folderNames, iFolder) { + Write("%024llX /* %s */,\n", + makeoid(g_vecPGenerators[iGenerator]->m_ProjectName, + EOIDTypeGroup, iFolder + 1), + folderNames[iFolder]); + } + + // XCode does not easily support having differing + // membership/output names per config. We'll only output the file + // names for release, then warn below that they are not shifting. + KeyValues *pKV = g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + + // system libraries we link against + CSplitString libs(pKV->GetString(g_pOption_SystemLibraries), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < libs.Count(); i++) { + Write("%024llX /* lib%s.dylib (system library) */,\n", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemLibraries), + EOIDTypeFileReference, i), + libs[i]); + } + + // system frameworks we link against + CSplitString sysFrameworks( + pKV->GetString(g_pOption_SystemFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < sysFrameworks.Count(); i++) { + Write("%024llX /* %s.framework (system framework) */,\n", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_SystemFrameworks), + EOIDTypeFileReference, i), + sysFrameworks[i]); + } + + // local frameworks we link against + CSplitString localFrameworks( + pKV->GetString(g_pOption_LocalFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < localFrameworks.Count(); i++) { + char rgchFrameworkName[MAX_PATH]; + V_StripExtension(V_UnqualifiedFileName(localFrameworks[i]), + rgchFrameworkName, sizeof(rgchFrameworkName)); + + Write("%024llX /* %s.framework (local framework) */,\n", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + pKV->GetString(g_pOption_LocalFrameworks), + EOIDTypeFileReference, i), + rgchFrameworkName); + } + + // libraries we consume (specified in our files list) + for (int i = g_vecPGenerators[iGenerator]->m_Files.First(); + i != g_vecPGenerators[iGenerator]->m_Files.InvalidIndex(); + i = g_vecPGenerators[iGenerator]->m_Files.Next(i)) { + CUtlString sFileName = + UsePOSIXSlashes(g_vecPGenerators[iGenerator] + ->m_Files[i] + ->m_Filename.String()); + bool bInclude = IsDynamicLibrary(sFileName); + if (IsStaticLibrary(sFileName)) { + char szAbsoluteFileName[MAX_PATH] = {0}; + V_MakeAbsolutePath( + szAbsoluteFileName, sizeof(szAbsoluteFileName), + UsePOSIXSlashes(g_vecPGenerators[iGenerator] + ->m_Files[i] + ->m_Filename.String()), + projects[iGenerator]->m_szStoredCurrentDirectory); + + bInclude = true; + FOR_EACH_VEC(g_vecPGenerators, iGenerator2) { + // don't include static libs generated by other projects - + // we'll pull them out of the built products tree + KeyValues *kv = g_vecPGenerators[iGenerator2] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + char szAbsoluteGameOutputFile[MAX_PATH] = {0}; + V_MakeAbsolutePath( + szAbsoluteGameOutputFile, + sizeof(szAbsoluteGameOutputFile), + GameOutputFileFromConfig(kv).String(), + projects[iGenerator2]->m_szStoredCurrentDirectory); + if (!V_stricmp(szAbsoluteFileName, + szAbsoluteGameOutputFile)) { + bInclude = false; + break; + } + } + } + + if (bInclude) { + Write( + "%024llX /* %s in Frameworks (explicit) */,\n", + makeoid2( + g_vecPGenerators[iGenerator]->GetProjectName(), + g_vecPGenerators[iGenerator]->m_Files[i]->m_Filename, + EOIDTypeFileReference), + sFileName.String()); + } + } + + CUtlString sOutputFile = OutputFileWithDirectoryFromConfig(pKV); + if (sOutputFile.Length()) + Write("%024llX /* %s */,\n", + makeoid2(g_vecPGenerators[iGenerator]->GetProjectName(), + sOutputFile.String(), EOIDTypeFileReference), + sOutputFile.String()); + } + + --m_nIndent; + + Write(");\n"); + Write("name = \"%s\";\n", + g_vecPGenerators[iGenerator]->GetProjectName().String()); + Write("sourceTree = \"\";\n"); + } + --m_nIndent; + Write("};\n"); + } + + // root group - the top of the displayed hierarchy + Write("%024llX = {\n", makeoid(oidStrProjectsRoot, EOIDTypeGroup)); + ++m_nIndent; + { + Write("isa = PBXGroup;\n"); + Write("children = (\n"); + + // sort the projects by name before we emit the list + CUtlSortVector vecSortedProjectNames; + FOR_EACH_VEC(g_vecPGenerators, iGen) { + // fprintf( stderr, "inserting %s (%p)\n", + // g_vecPGenerators[iGen]->GetProjectName().String(), + // &g_vecPGenerators[iGen]->GetProjectName() ); + vecSortedProjectNames.Insert( + g_vecPGenerators[iGen]->GetProjectName()); + } + + ++m_nIndent; + { + FOR_EACH_VEC(vecSortedProjectNames, iProjectName) { + // fprintf( stderr, "looking for %s\n", + // vecSortedProjectNames[iProjectName].String() ); and each + // project's group (of groups) + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + if (strcmp(g_vecPGenerators[iGenerator]->m_ProjectName.String(), + vecSortedProjectNames[iProjectName])) { + // fprintf( stderr, " skipping '%s' (%p) != '%s' (%p) (%d, + // %d)\n", + // g_vecPGenerators[iGenerator]->GetProjectName().String(), + // g_vecPGenerators[iGenerator]->GetProjectName().String(), + // vecSortedProjectNames[iProjectName].String(), + // vecSortedProjectNames[iProjectName].String(), iGenerator, + // iProjectName ); + continue; + } + // fprintf( stderr, "emitting %s (%d, %d)\n", + // g_vecPGenerators[iGenerator]->GetProjectName().String(), + // iGenerator, iProjectName ); + + Write("%024llX /* %s */,\n", + makeoid(g_vecPGenerators[iGenerator]->m_ProjectName, + EOIDTypeGroup), + g_vecPGenerators[iGenerator]->GetProjectName().String()); + break; + } + } + + // add the build config (.xcconfig) files + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchXCConfigFiles); + iConfig++) { + Write("%024llX /* %s */, \n", + makeoid2(oidStrSolutionRoot, k_rgchXCConfigFiles[iConfig], + EOIDTypeFileReference), + k_rgchXCConfigFiles[iConfig]); + } + } + --m_nIndent; + Write(");\n"); + Write("sourceTree = \"\";\n"); + // make the project follow our coding standards, and use tabs. + Write("usesTabs = 1;\n"); + } + --m_nIndent; + Write("};"); + } + m_nIndent--; + Write("\n/* End PBXGroup section */\n"); + + /** + ** + ** the sources build phases - each target that compiles source references + *on of these, it in turn references the source files to be compiled + ** + **/ + Write("\n/* Begin PBXSourcesBuildPhase section */"); + ++m_nIndent; + FOR_EACH_VEC(projects, iProject) { + Write("\n"); + Write("%024llX /* Sources */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeSourcesBuildPhase)); + ++m_nIndent; + { + Write("isa = PBXSourcesBuildPhase;\n"); + Write("buildActionMask = 2147483647;\n"); + Write("files = (\n"); + ++m_nIndent; + { + for (int i = g_vecPGenerators[iProject]->m_Files.First(); + i != g_vecPGenerators[iProject]->m_Files.InvalidIndex(); + i = g_vecPGenerators[iProject]->m_Files.Next(i)) { + const char *pFileName = + g_vecPGenerators[iProject]->m_Files[i]->m_Filename.String(); + CFileConfig *pFileConfig = g_vecPGenerators[iProject]->m_Files[i]; + + if (AppearsInSourcesBuildPhase(g_vecPGenerators[iProject], + pFileConfig)) { + Write("%024llX /* %s in Sources */,\n", + makeoid2(g_vecPGenerators[iProject]->GetProjectName(), + pFileName, EOIDTypeBuildFile), + V_UnqualifiedFileName(UsePOSIXSlashes(pFileName))); + } + } + } + --m_nIndent; + Write(");\n"); + Write("runOnlyForDeploymentPostprocessing = 0;\n"); + } + --m_nIndent; + Write("};"); + } + --m_nIndent; + Write("\n/* End PBXSourcesBuildPhase section */\n"); + + /** + ** + ** the frameworks build phases - each target that links libraries + *(static, dyamic, framework) has one of these, it references the linked + *thing + ** + **/ + Write("\n/* Begin PBXFrameworksBuildPhase section */"); + ++m_nIndent; + FOR_EACH_VEC(projects, iProject) { + Write("\n"); + Write("%024llX /* Frameworks */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeFrameworksBuildPhase)); + ++m_nIndent; + { + Write("isa = PBXFrameworksBuildPhase;\n"); + Write("buildActionMask = 2147483647;\n"); + Write("files = (\n"); + ++m_nIndent; + { + // libraries we consume (specified in our files list) + for (int i = g_vecPGenerators[iProject]->m_Files.First(); + i != g_vecPGenerators[iProject]->m_Files.InvalidIndex(); + i = g_vecPGenerators[iProject]->m_Files.Next(i)) { + const char *pFileName = + g_vecPGenerators[iProject]->m_Files[i]->m_Filename.String(); + if (IsStaticLibrary(UsePOSIXSlashes(pFileName)) || + IsDynamicLibrary(UsePOSIXSlashes(pFileName))) { + char szAbsoluteFileName[MAX_PATH] = {0}; + V_MakeAbsolutePath( + szAbsoluteFileName, sizeof(szAbsoluteFileName), + UsePOSIXSlashes(g_vecPGenerators[iProject] + ->m_Files[i] + ->m_Filename.String()), + projects[iProject]->m_szStoredCurrentDirectory); + bool bInclude = true; + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + // Don't include libs generated by other projects - we'll pull + // them out of the built products tree. Resolve the absolute + // path of both, since they are relative to different + // projects. + KeyValues *pKV = g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + char szAbsoluteGameOutputFile[MAX_PATH] = {0}; + V_MakeAbsolutePath( + szAbsoluteGameOutputFile, + sizeof(szAbsoluteGameOutputFile), + GameOutputFileFromConfig(pKV).String(), + projects[iGenerator]->m_szStoredCurrentDirectory); + + if (!V_stricmp(szAbsoluteFileName, + szAbsoluteGameOutputFile)) { + bInclude = false; + break; + } + } + + if (bInclude) { + Write("%024llX /* %s in Frameworks (explicit) */,\n", + makeoid2(g_vecPGenerators[iProject]->GetProjectName(), + pFileName, EOIDTypeBuildFile), + pFileName); + } + } + } + + // libraries from projects we depend on + CDependency_Project *pCurProject = projects[iProject]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies(pCurProject, projects, + additionalProjectDependencies); + + for (intp iTestProject = projects.Count() - 1; iTestProject >= 0; + --iTestProject) { + if (iProject == iTestProject) continue; + + CDependency_Project *pTestProject = projects[iTestProject]; + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pCurProject->DependsOn(pTestProject, dependsOnFlags) || + additionalProjectDependencies.Find(pTestProject) != + additionalProjectDependencies.InvalidIndex()) { + // In the PBXBuildFile section each of our dependencies + // generated an OID pointing to their output file with our + // project index as the ordinal. We use the PRODUCTS directory + // build file as depending on the final GameOutputFile confuses + // XCode's linker logic, and it should not matter (since + // GameOutputFile is just copying it to a final destination, so + // we can depend/link on the products directory intermediate) + KeyValues *pKV = g_vecPGenerators[iTestProject] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + CUtlString sOutputFile = OutputFileWithDirectoryFromConfig(pKV); + if (sOutputFile.Length() && (IsStaticLibrary(sOutputFile) || + IsDynamicLibrary(sOutputFile))) { + // The project in question will have generated a BuildFile + // dependency for us under its name with ordinal set to our + // index + Write( + "%024llX /* (lib)%s (dependency) */,\n", + makeoid2(g_vecPGenerators[iTestProject]->GetProjectName(), + sOutputFile, EOIDTypeBuildFile, iProject), + sOutputFile.String()); + } + } + } + + KeyValues *pKV = g_vecPGenerators[iProject] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + + // local frameworks we link against + CSplitString localFrameworks( + pKV->GetString(g_pOption_LocalFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < localFrameworks.Count(); i++) { + char rgchFrameworkName[MAX_PATH]; + V_StripExtension(V_UnqualifiedFileName(localFrameworks[i]), + rgchFrameworkName, sizeof(rgchFrameworkName)); + + Write("%024llX /* %s in Frameworks (local framework) */,\n", + makeoid2(g_vecPGenerators[iProject]->GetProjectName(), + pKV->GetString(g_pOption_LocalFrameworks), + EOIDTypeBuildFile, i), + rgchFrameworkName); + } + + // system frameworks we link against + CSplitString sysFrameworks( + pKV->GetString(g_pOption_SystemFrameworks), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < sysFrameworks.Count(); i++) { + Write("%024llX /* %s in Frameworks (system framework) */,\n", + makeoid2(g_vecPGenerators[iProject]->GetProjectName(), + pKV->GetString(g_pOption_SystemFrameworks), + EOIDTypeBuildFile, i), + sysFrameworks[i]); + } + + // system libraries we link against + CSplitString libs(pKV->GetString(g_pOption_SystemLibraries), + (const char **)g_IncludeSeparators, + V_ARRAYSIZE(g_IncludeSeparators)); + for (int i = 0; i < libs.Count(); i++) { + Write("%024llX /* %s in Frameworks (system library) */,\n", + makeoid2(g_vecPGenerators[iProject]->GetProjectName(), + pKV->GetString(g_pOption_SystemLibraries), + EOIDTypeBuildFile, i), + libs[i]); + } + } + --m_nIndent; + Write(");\n"); + Write("runOnlyForDeploymentPostprocessing = 0;\n"); + } + --m_nIndent; + Write("};"); + } + --m_nIndent; + Write("\n/* End PBXFrameworksBuildPhase section */\n"); + + /** + ** + ** the shell script (pre/post build step) build phases - each target that + *generates a "gameoutputfile" has one of these, + ** to p4 edit the target and copy the build result there. + ** + **/ + Write("\n/* Begin PBXShellScriptBuildPhase section */"); + ++m_nIndent; + { + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + CProjectGenerator_Xcode *pGenerator = + (CProjectGenerator_Xcode *)g_vecPGenerators[iGenerator]; + char rgchProjectDir[MAX_PATH]; + rgchProjectDir[0] = '\0'; + V_strncpy(rgchProjectDir, + projects[iGenerator]->m_ProjectFilename.String(), + sizeof(rgchProjectDir)); + V_StripFilename(rgchProjectDir); + + CUtlString sPreBuildCommandLine = + g_vecPGenerators[iGenerator] + ->m_Files[0] + ->GetOrCreateConfig( + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1] + ->GetConfigName(), + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1]) + ->GetOption(g_pOption_PreBuildEventCommandLine); + if (sPreBuildCommandLine.Length()) { + CUtlString sCommand; + int cCommand = MAX(sPreBuildCommandLine.Length() * 2, 8 * 1024); + sCommand.SetLength(cCommand); + + Write("\n"); + Write("%024llX /* ShellScript */ = {\n", + makeoid(projects[iGenerator]->m_ProjectName, + EOIDTypePreBuildPhase, + pGenerator->m_nPreBuildEvents++)); + ++m_nIndent; + { + Write("isa = PBXShellScriptBuildPhase;\n"); + Write("buildActionMask = 2147483647;\n"); + Write("files = (\n"); + Write(");\n"); + Write("inputPaths = (\n);\n"); + Write("name = \"%s\";\n", + CFmtStr("PreBuild Event for %s", + projects[iGenerator]->m_ProjectName.String()) + .Access()); + Write("outputPaths = (\n);\n"); + Write("runOnlyForDeploymentPostprocessing = 0;\n"); + Write("shellPath = /bin/bash;\n"); + + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + sPreBuildCommandLine, + CFmtStr("%s/dummy.txt", rgchProjectDir).Access(), + sCommand.Get(), cCommand); + V_StrSubstInPlace(sCommand.Get(), cCommand, "$(OBJ_DIR)", + "\"${OBJECT_FILE_DIR_normal}\"", false); + V_StrSubstInPlace(sCommand.Get(), cCommand, ";", ";\\n", false); + V_StrSubstInPlace(sCommand.Get(), cCommand, "\"", "\\\"", false); + + // xcode wants to run your custom shell scripts anytime the + // pbxproj has changed (which makes some sense - the script might + // be different) - we can't and don't want to early out in this + // case. + Write( + "shellScript = \"cd %s\\n" + "%s\";\n", + rgchProjectDir, sCommand.String()); + } + --m_nIndent; + Write("};"); + } + // we don't have an output file - wander the list of files, looking + // for custom build steps if we find any, magic up shell scripts to + // run them + for (int i = g_vecPGenerators[iGenerator]->m_Files.First(); + i != g_vecPGenerators[iGenerator]->m_Files.InvalidIndex(); + i = g_vecPGenerators[iGenerator]->m_Files.Next(i)) { + CFileConfig *pFileConfig = g_vecPGenerators[iGenerator]->m_Files[i]; + CSpecificConfig *pFileSpecificData = pFileConfig->GetOrCreateConfig( + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1] + ->GetConfigName(), + g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1]); + + CUtlString sCustomBuildCommandLine = pFileSpecificData->GetOption( + g_pOption_CustomBuildStepCommandLine); + CUtlString sOutputFiles = + pFileSpecificData->GetOption(g_pOption_Outputs); + CUtlString sAdditionalDeps = + pFileSpecificData->GetOption(g_pOption_AdditionalDependencies); + CUtlString sCommand; + + // if the project produces a binary, it's a native target and we'll + // handle this custom build step as a build rule unless the custom + // build has additional dependencies + if (ProjectProducesBinary(pGenerator) && !sAdditionalDeps.Length()) + continue; + + if (sOutputFiles.Length() && !sCustomBuildCommandLine.IsEmpty()) { + CUtlString sInputFile; + sInputFile.SetLength(MAX_PATH); + + int cCommand = + MAX(sCustomBuildCommandLine.Length() * 2, 8 * 1024); + sCommand.SetLength(cCommand); + + Write("\n"); + Write("%024llX /* ShellScript */ = {\n", + makeoid(projects[iGenerator]->m_ProjectName, + EOIDTypeShellScriptBuildPhase, + pGenerator->m_nShellScriptPhases++)); + ++m_nIndent; + { + Write("isa = PBXShellScriptBuildPhase;\n"); + Write("buildActionMask = 2147483647;\n"); + Write("files = (\n"); + Write(");\n"); + Write("inputPaths = (\n"); + ++m_nIndent; + { + // DoStandardVisualStudioReplacements needs to know where the + // file is, so make sure it's got a path on it + if (V_IsAbsolutePath( + UsePOSIXSlashes(pFileConfig->m_Filename.String()))) + V_snprintf( + sInputFile.Get(), MAX_PATH, "%s", + UsePOSIXSlashes(pFileConfig->m_Filename.String())); + else { + V_snprintf( + sInputFile.Get(), MAX_PATH, "%s/%s", rgchProjectDir, + UsePOSIXSlashes(pFileConfig->m_Filename.String())); + V_RemoveDotSlashes(sInputFile.Get()); + } + Write("\"%s\",\n", sInputFile.String()); + + CSplitString additionalDeps(sAdditionalDeps, ";"); + FOR_EACH_VEC(additionalDeps, k) { + const char *pchOneFile = additionalDeps[k]; + if (*pchOneFile != '\0') { + char szDependency[MAX_PATH]; + // DoStandardVisualStudioReplacements needs to know where + // the file is, so make sure it's got a path on it + if (V_IsAbsolutePath(UsePOSIXSlashes(pchOneFile))) + V_snprintf(szDependency, MAX_PATH, "%s", + UsePOSIXSlashes(pchOneFile)); + else { + V_snprintf(szDependency, MAX_PATH, "%s/%s", + rgchProjectDir, UsePOSIXSlashes(pchOneFile)); + V_RemoveDotSlashes(szDependency); + } + Write("\"%s\",\n", szDependency); + } + } + } + --m_nIndent; + Write(");\n"); + + CUtlString sDescription; + if (pFileSpecificData->GetOption(g_pOption_Description)) { + intp cDescription = V_strlen(pFileSpecificData->GetOption( + g_pOption_Description)) * + 2; + sDescription.SetLength(cDescription); + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + pFileSpecificData->GetOption(g_pOption_Description), + sInputFile, sDescription.Get(), cDescription); + } else + sDescription = CFmtStr("Custom Build Step for %s", + pFileConfig->m_Filename.String()) + .Access(); + + Write("name = \"%s\";\n", sDescription.String()); + + Write("outputPaths = (\n"); +#define TELL_XCODE_ABOUT_OUTPUT_FILES 1 +#ifdef TELL_XCODE_ABOUT_OUTPUT_FILES + // telling xcode about the output files used to cause it's + // dependency evaluation to assume that those files had changed + // anytime the script had run, even if the script doesn't change + // them, which caused us to rebuild a bunch of stuff we didn't + // need to rebuild but testing with Xcode 6 suggests they fixed + // that bug, and lying less to the build system is generally + // good. + ++m_nIndent; + { + CSplitString outFiles(sOutputFiles, ";"); + for (int k = 0; k < outFiles.Count(); k++) { + CUtlString sOutputFile; + sOutputFile.SetLength(MAX_PATH); + CBaseProjectDataCollector:: + DoStandardVisualStudioReplacements( + outFiles[k], sInputFile, sOutputFile.Get(), + MAX_PATH); + V_StrSubstInPlace(sOutputFile.Get(), MAX_PATH, "$(OBJ_DIR)", + "${OBJECT_FILE_DIR_normal}", false); + + CUtlString sOutputPath; + sOutputPath.SetLength(MAX_PATH); + + if (V_IsAbsolutePath(sOutputFile) || + V_strncmp(outFiles[k], "$", 1) == 0) + V_snprintf(sOutputPath.Get(), MAX_PATH, "%s", + sOutputFile.String()); + else { + V_snprintf(sOutputPath.Get(), MAX_PATH, "%s/%s", + rgchProjectDir, sOutputFile.String()); + V_RemoveDotSlashes(sOutputPath.Get()); + } + Write("\"%s\",\n", sOutputPath.String()); + } + } + --m_nIndent; +#endif + Write(");\n"); + Write("runOnlyForDeploymentPostprocessing = 0;\n"); + Write("shellPath = /bin/bash;\n"); + + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + sCustomBuildCommandLine, sInputFile, sCommand.Get(), + cCommand); + V_StrSubstInPlace(sCommand.Get(), cCommand, "$(OBJ_DIR)", + "\"${OBJECT_FILE_DIR_normal}\"", false); + V_StrSubstInPlace(sCommand.Get(), cCommand, ";", ";\\n", false); + V_StrSubstInPlace(sCommand.Get(), cCommand, "\"", "\\\"", + false); + + // this is something of a dirty ugly hack. it seems that xcode + // wants to run your custom shell scripts anytime the pbxproj + // has changed (which makes some sense - the script might be + // different) since we generate one big project, any vpc change + // means we'll run all the custom build steps again, which will + // generate code, and link code, and generally take time so if + // this project was up-to-date (i.e. no vpc changes), add an + // early out that checks if all the output files are newer than + // the input files and early out if that's the case + CUtlString sConditionalBlock = + CFmtStr("export CANARY_FILE=\\\"%s\\\";\\n", + pGenerator->m_OutputFilename.String()) + .Access(); + // uncomment this line to debug the embedded shell script + // sConditionalBlock += "set -x\\n"; + sConditionalBlock += + "EARLY_OUT=1\\n" + "let LI=$SCRIPT_INPUT_FILE_COUNT-1\\n" + "let LO=$SCRIPT_OUTPUT_FILE_COUNT-1\\n" + "for j in $(seq 0 $LO); do\\n" + " OUTPUT=SCRIPT_OUTPUT_FILE_$j\\n" + " if [ \\\"${CANARY_FILE}\\\" -nt \\\"${!OUTPUT}\\\" ]; " + "then\\n" + " EARLY_OUT=0\\n" + " break\\n" + " fi\\n" + " for i in $(seq 0 $LI); do\\n" + " INPUT=SCRIPT_INPUT_FILE_$i\\n" + " if [ \\\"${!INPUT}\\\" -nt \\\"${!OUTPUT}\\\" ]; " + "then\\n" + " EARLY_OUT=0\\n" + " break 2\\n" + " fi\\n" + " done\\n" + "done\\n"; + + sConditionalBlock += + "if [ $EARLY_OUT -eq 1 ]; then\\n" + " echo \\\"outputs are newer than input, skipping " + "execution...\\\"\\n" + " exit 0\\n" + "fi\\n"; + Write( + "shellScript = \"cd %s\\n" + "%s" + "%s\";\n", + rgchProjectDir, sConditionalBlock.String(), + sCommand.String()); + } + --m_nIndent; + Write("};"); + } + } + + KeyValues *pDebugKV = g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + CUtlString sDebugGameOutputFile = GameOutputFileFromConfig(pDebugKV); + + KeyValues *pReleaseKV = g_vecPGenerators[iGenerator] + ->m_BaseConfigData.m_Configurations[1] + ->m_pKV; + CUtlString sReleaseGameOutputFile = + GameOutputFileFromConfig(pReleaseKV); + + if (sDebugGameOutputFile.Length() || + sReleaseGameOutputFile.Length()) { + char rgchDebugFilePath[MAX_PATH]; + V_snprintf(rgchDebugFilePath, sizeof(rgchDebugFilePath), "%s/%s", + rgchProjectDir, sDebugGameOutputFile.String()); + V_RemoveDotSlashes(rgchDebugFilePath); + + char rgchReleaseFilePath[MAX_PATH]; + V_snprintf(rgchReleaseFilePath, sizeof(rgchReleaseFilePath), + "%s/%s", rgchProjectDir, + sReleaseGameOutputFile.String()); + V_RemoveDotSlashes(rgchReleaseFilePath); + + Write("\n"); + Write("%024llX /* ShellScript */ = {\n", + makeoid(projects[iGenerator]->m_ProjectName, + EOIDTypePostBuildPhase, 0)); + ++m_nIndent; + { + Write("isa = PBXShellScriptBuildPhase;\n"); + Write("buildActionMask = 2147483647;\n"); + Write("files = (\n"); + Write(");\n"); + Write("inputPaths = (\n"); + ++m_nIndent; + { Write("\"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}\",\n"); } + --m_nIndent; + Write(");\n"); + Write("name = \"Post-Build Step\";\n"); + Write("outputPaths = (\n"); + ++m_nIndent; + { + V_StrSubstInPlace(rgchDebugFilePath, "/lib/osx32/debug/", + "/lib/osx32/${CONFIGURATION}/", false); + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + rgchDebugFilePath, rgchDebugFilePath, rgchDebugFilePath, + sizeof(rgchDebugFilePath)); + V_StrSubstInPlace(rgchReleaseFilePath, "/lib/osx32/release/", + "/lib/osx32/${CONFIGURATION}/", false); + CBaseProjectDataCollector::DoStandardVisualStudioReplacements( + rgchReleaseFilePath, rgchReleaseFilePath, + rgchReleaseFilePath, sizeof(rgchReleaseFilePath)); + + Write("\"%s\",\n", rgchDebugFilePath); + if (V_strcmp(rgchDebugFilePath, rgchReleaseFilePath)) + Write("\"%s\",\n", rgchReleaseFilePath); + } + --m_nIndent; + Write(");\n"); + Write("runOnlyForDeploymentPostprocessing = 0;\n"); + Write("shellPath = /bin/bash;\n"); + + CUtlString strScript( + CFmtStr("shellScript = \"cd %s;\\n", rgchProjectDir)); + + CUtlString strScriptExtra; + bool bHasReleasePostBuildCmd = + V_strlen(SkipLeadingWhitespace(pReleaseKV->GetString( + g_pOption_PostBuildEventCommandLine, ""))) > 0; + bool bHasDebugPostBuildCmd = + V_strlen(SkipLeadingWhitespace(pDebugKV->GetString( + g_pOption_PostBuildEventCommandLine, ""))) > 0; + strScriptExtra.Format( + "if [ -z \\\"$CONFIGURATION\\\" -a -n \\\"$BUILD_STYLE\\\" " + "]; then\\n" + " CONFIGURATION=${BUILD_STYLE}\\n" + "fi\\n" + "if [ -z \\\"$CONFIGURATION\\\" ]; then\\n" + " echo \\\"Could not determine build configuration.\\\";\\n" + " exit 1; \\n" + "fi\\n" + "CONFIGURATION=$(echo $CONFIGURATION | tr [A-Z] [a-z])\\n" + "OUTPUTFILE=\\\"%s\\\"\\n" + "if [ -z \\\"$VALVE_NO_AUTO_P4\\\" ]; then\\n" + " P4_EDIT_CHANGELIST_CMD=\\\"p4 changes -c $(p4 client -o | " + "grep ^Client | cut -f 2) -s pending | fgrep 'POSIX Auto " + "Checkout' | cut -d' ' -f 2 | tail -n 1\\\"\\n" + " P4_EDIT_CHANGELIST=$(eval " + "\\\"$P4_EDIT_CHANGELIST_CMD\\\")\\n" + " if [ -z \\\"$P4_EDIT_CHANGELIST\\\" ]; then\\n" + " P4_EDIT_CHANGELIST=$(echo -e \\\"Change: " + "new\\\\nDescription: POSIX Auto Checkout\\\" | p4 change -i " + "| cut -f 2 -d ' ')\\n" + " fi\\n" + "fi\\n" + "if [ -f \\\"$OUTPUTFILE\\\" -o -d \\\"$OUTPUTFILE.dSYM\\\" " + "]; then\\n" + " if [ -z \\\"$VALVE_NO_AUTO_P4\\\" ]; then\\n" + " p4 edit -c $P4_EDIT_CHANGELIST \\\"$OUTPUTFILE...\\\" | " + "grep -v \\\"also opened\\\"\\n" + " else\\n" + " if [ -f \\\"$OUTPUTFILE\\\" ]; then\\n" + " chmod -f +w \\\"$OUTPUTFILE\\\"\\n" + " fi\\n" + " if [ -d \\\"$OUTPUTFILE.dSYM\\\" ]; then\\n" + " chmod -R -f +w \\\"$OUTPUTFILE.dSYM\\\"\\n" + " fi\\n" + " fi\\n" + "fi\\n" + "set -eu\\n" + "if [ -d " + "\\\"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}.dSYM\\\" ]; " + "then\\n" + " echo \\\"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}.dSYM -> " + "${OUTPUTFILE}.dSYM\\\"\\n" + " rm -rf \\\"${OUTPUTFILE}.dSYM\\\"\\n" + " cp -pR " + "\\\"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}.dSYM\\\" " + "\\\"${OUTPUTFILE}.dSYM\\\"\\n" + "fi\\n" + "cp -pv \\\"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}\\\" " + "\\\"$OUTPUTFILE\\\"\\n" + "# POST-BUILD:\\n" + "set -e\\n" + "if [ ${CONFIGURATION} == \\\"release\\\" ]; then\\n" + " %s\\n" + "elif [ ${CONFIGURATION} == \\\"debug\\\" ]; then\\n" + " %s\\n" + "fi\\n" + "\";\n", + rgchReleaseFilePath, + bHasReleasePostBuildCmd + ? UsePOSIXSlashes(pReleaseKV->GetString( + g_pOption_PostBuildEventCommandLine, "true")) + : "true", + bHasDebugPostBuildCmd + ? UsePOSIXSlashes(pDebugKV->GetString( + g_pOption_PostBuildEventCommandLine, "true")) + : "true"); + + strScript += strScriptExtra; + Write(strScript.Get()); + } + --m_nIndent; + Write("};"); + } + } + } + --m_nIndent; + Write("\n/* End PBXShellScriptBuildPhase section */\n"); + + /** + ** + ** nativetargets section - build targets, which ultimately reference + *build phases + ** + **/ + Write("\n/* Begin PBXNativeTarget section */"); + ++m_nIndent; + FOR_EACH_VEC(projects, iProject) { + CProjectGenerator_Xcode *pGenerator = + (CProjectGenerator_Xcode *)g_vecPGenerators[iProject]; + + KeyValues *pKV = g_vecPGenerators[iProject] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + CUtlString sGameOutputFile = GameOutputFileFromConfig(pKV); + if (!sGameOutputFile.Length()) continue; + + Write("\n"); + Write("%024llX /* %s */ = {\n", + makeoid(projects[iProject]->m_ProjectName, EOIDTypeNativeTarget), + projects[iProject]->m_ProjectName.String()); + ++m_nIndent; + { + Write("isa = PBXNativeTarget;\n"); + + Write( + "buildConfigurationList = %024llX /* Build configuration list " + "for PBXNativeTarget \"%s\" */;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeConfigurationList), + projects[iProject]->m_ProjectName.String()); + Write("buildPhases = (\n"); + ++m_nIndent; + { + for (int i = 0; i < pGenerator->m_nPreBuildEvents; i++) + Write("%024llX /* PreBuildEvent */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypePreBuildPhase, i)); + for (int i = 0; i < pGenerator->m_nShellScriptPhases; i++) + Write("%024llX /* ShellScript */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeShellScriptBuildPhase, i)); + Write("%024llX /* Sources */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeSourcesBuildPhase)); + Write("%024llX /* Frameworks */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeFrameworksBuildPhase)); + Write("%024llX /* PostBuildPhase */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypePostBuildPhase, 0)); + } + --m_nIndent; + Write(");\n"); + Write("buildRules = (\n"); + ++m_nIndent; + { + for (int i = 0; i < pGenerator->m_nCustomBuildRules; i++) + Write("%024llX /* PBXBuildRule */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeCustomBuildRule, i)); + } + --m_nIndent; + Write(");\n"); + Write("dependencies = (\n"); + ++m_nIndent; + { + // these dependencies point to the dependency objects, which + // reference other projects through the container item proxy objects + CDependency_Project *pCurProject = projects[iProject]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies(pCurProject, projects, + additionalProjectDependencies); + + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (iProject == iTestProject) continue; + + CDependency_Project *pTestProject = projects[iTestProject]; + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pCurProject->DependsOn(pTestProject, dependsOnFlags) || + additionalProjectDependencies.Find(pTestProject) != + additionalProjectDependencies.InvalidIndex()) { + Write("%024llX /* %s */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeTargetDependency, (uint16_t)iTestProject), + pTestProject->GetName()); + } + } + } + --m_nIndent; + Write(");\n"); + Write("productName = \"%s\";\n", + projects[iProject]->m_ProjectName.String()); + Write("name = \"%s\";\n", projects[iProject]->m_ProjectName.String()); + + if (sGameOutputFile.Length()) { + Write("productReference = %024llX /* %s */;\n", + makeoid2(projects[iProject]->m_ProjectName, sGameOutputFile, + EOIDTypeFileReference), + sGameOutputFile.String()); + } + + char rgchProductType[MAX_PATH]; + XcodeProductTypeFromFileName(V_UnqualifiedFileName(sGameOutputFile), + rgchProductType, + sizeof(rgchProductType)); + Write("productType = \"%s\";\n", rgchProductType); + } + --m_nIndent; + Write("};"); + } + --m_nIndent; + Write("\n/* End PBXNativeTarget section */\n"); + + /** + ** + ** aggregate targets - for targets that have no output files (i.e. are + *scripts) + ** and the "all" target + ** + **/ + Write("\n/* Begin PBXAggregateTarget section */\n"); + ++m_nIndent; + { + Write("%024llX /* All */ = {\n", + makeoid(oidStrSolutionRoot, EOIDTypeAggregateTarget)); + ++m_nIndent; + { + Write("isa = PBXAggregateTarget;\n"); + Write( + "buildConfigurationList = %024llX /* Build configuration list " + "for PBXAggregateTarget \"All\" */;\n", + makeoid(oidStrSolutionRoot, EOIDTypeConfigurationList, 1)); + Write("buildPhases = (\n"); + Write(");\n"); + Write("dependencies = (\n"); + ++m_nIndent; + { + FOR_EACH_VEC(projects, iProject) { + // note the sneaky -1 ordinal here, is we can later generate a + // dependency block for the target thats not tied to any other + // targets dependency. + Write("%024llX /* PBXProjectDependency */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeTargetDependency, -1)); + } + } + --m_nIndent; + Write(");\n"); + Write("name = All;\n"); + Write("productName = All;\n"); + } + --m_nIndent; + Write("};\n"); + + FOR_EACH_VEC(projects, iProject) { + CProjectGenerator_Xcode *pGenerator = + (CProjectGenerator_Xcode *)g_vecPGenerators[iProject]; + KeyValues *pKV = g_vecPGenerators[iProject] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + CUtlString sOutputFile = OutputFileWithDirectoryFromConfig(pKV); + if (sOutputFile.Length()) continue; + + // NOTE: the use of EOIDTypeNativeTarget here is intentional - a + // project will never appear as both, and this makes things link up + // without having to special case in dependencies and aggregate + // targets NOTE: the driving loop is the number of shell script phases + // we have - aggregate targets with > 1 shell script phase get broken + // into N aggregate targets, each with 1 shell script phase so xcode + // can execute them in parallel + int cSubAggregateTargets = + (pGenerator->m_nShellScriptPhases / + k_nShellScriptPhasesPerAggregateTarget) + + (pGenerator->m_nShellScriptPhases % + k_nShellScriptPhasesPerAggregateTarget + ? 1 + : 0); + for (int i = 0; i < cSubAggregateTargets; i++) { + Write("%024llX /* %s_%d */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeNativeTarget, i), + projects[iProject]->m_ProjectName.String(), i); + ++m_nIndent; + { + Write("isa = PBXAggregateTarget;\n"); + + Write( + "buildConfigurationList = %024llX /* Build configuration " + "list for PBXAggregateTarget \"%s\" */;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeConfigurationList), + projects[iProject]->m_ProjectName.String()); + Write("buildPhases = (\n"); + ++m_nIndent; + { + for (int j = 0; j < k_nShellScriptPhasesPerAggregateTarget; j++) + Write("%024llX /* ShellScript %d/%d*/,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeShellScriptBuildPhase, + i * k_nShellScriptPhasesPerAggregateTarget + j), + i * k_nShellScriptPhasesPerAggregateTarget + j + 1, + pGenerator->m_nShellScriptPhases); + } + --m_nIndent; + Write(");\n"); + + Write("buildRules = (\n"); + ++m_nIndent; + { + // Aggregate targets don't get build rules + } + --m_nIndent; + Write(");\n"); + Write("dependencies = (\n"); + ++m_nIndent; + { + // these dependencies point to the dependency objects, which + // reference other projects through the container item proxy + // objects + CDependency_Project *pCurProject = projects[iProject]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies( + pCurProject, projects, additionalProjectDependencies); + + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (iProject == iTestProject) { + // the "parent" aggregate depends on all the subaggregates, + // so the vpc dependency structure doesn't need to change. + if (i == 0) + for (int j = 1; j < cSubAggregateTargets; j++) + // the 0-(j+1) is to avoid colliding with the All + // aggregate dependency at -1 + Write("%024llX /* %s_%d (subproject) */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeTargetDependency, 0 - (j + 1)), + pCurProject->m_ProjectName.String(), j); + continue; + } + + CDependency_Project *pTestProject = projects[iTestProject]; + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pCurProject->DependsOn(pTestProject, dependsOnFlags) || + additionalProjectDependencies.Find(pTestProject) != + additionalProjectDependencies.InvalidIndex()) { + Write("%024llX /* %s */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeTargetDependency, + (uint16_t)iTestProject), + pTestProject->GetName()); + } + } + } + --m_nIndent; + Write(");\n"); + if (i == 0) { + Write("name = \"%s\";\n", + projects[iProject]->m_ProjectName.String()); + Write("productName = \"%s\";\n", + projects[iProject]->m_ProjectName.String()); + } else { + Write("name = \"%s_%d\";\n", + projects[iProject]->m_ProjectName.String(), i); + Write("productName = \"%s_%d\";\n", + projects[iProject]->m_ProjectName.String(), i); + } + } + --m_nIndent; + Write("};\n"); + } + } + } + --m_nIndent; + Write("\n/* End PBXAggregateTarget section */\n"); + + /** + ** + ** project section - the top-level object that ties all the bits + *(targets, groups, ...) together + ** + **/ + Write("\n/* Begin PBXProject section */\n"); + ++m_nIndent; + Write("%024llX /* project object */ = {\n", + makeoid(oidStrSolutionRoot, EOIDTypeProject)); + ++m_nIndent; + { + Write("isa = PBXProject;\n"); + Write("attributes = {\n"); + ++m_nIndent; + { Write("BuildIndependentTargetsInParallel = YES;\n"); } + --m_nIndent; + Write("};\n"); + Write( + "buildConfigurationList = %024llX /* Build configuration list for " + "PBXProject \"%s\" */;\n", + makeoid(oidStrSolutionRoot, EOIDTypeConfigurationList), + V_UnqualifiedFileName(UsePOSIXSlashes(pSolutionFilename))); + Write("compatibilityVersion = \"Xcode 3.0\";\n"); + Write("hasScannedForEncodings = 0;\n"); + Write("mainGroup = %024llX;\n", + makeoid(oidStrProjectsRoot, EOIDTypeGroup)); + Write("productRefGroup = %024llX /* Products */;\n", + makeoid(oidStrSolutionRoot, EOIDTypeGroup)); + Write("projectDirPath = \"\";\n"); + Write("projectRoot = \"\";\n"); + Write("targets = (\n"); + ++m_nIndent; + { + Write("%024llX /* All */,\n", + makeoid(oidStrSolutionRoot, EOIDTypeAggregateTarget)); + // sort the projects by name before we emit the list + CUtlSortVector vecSortedProjectNames; + FOR_EACH_VEC(g_vecPGenerators, iGen) { + vecSortedProjectNames.Insert( + g_vecPGenerators[iGen]->GetProjectName()); + } + FOR_EACH_VEC(vecSortedProjectNames, iProjectName) { + FOR_EACH_VEC(projects, iProject) { + if (strcmp(projects[iProject]->m_ProjectName.String(), + vecSortedProjectNames[iProjectName])) { + continue; + } + Write("%024llX /* %s */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeNativeTarget), + projects[iProject]->m_ProjectName.String()); + // if this is an aggregate target with more than one shell script, + // emit the "child" aggregates + if (!ProjectProducesBinary(( + (CProjectGenerator_Xcode *)g_vecPGenerators[iProject]))) { + int cSubAggregateTargets = + (((CProjectGenerator_Xcode *)g_vecPGenerators[iProject]) + ->m_nShellScriptPhases / + k_nShellScriptPhasesPerAggregateTarget) + + (((CProjectGenerator_Xcode *)g_vecPGenerators[iProject]) + ->m_nShellScriptPhases % + k_nShellScriptPhasesPerAggregateTarget + ? 1 + : 0); + for (int i = 1; i < cSubAggregateTargets; i++) + Write("%024llX /* %s_%d */,\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeNativeTarget, i), + projects[iProject]->m_ProjectName.String(), i); + } + } + } + } + --m_nIndent; + Write(");\n"); + } + --m_nIndent; + Write("};"); + Write("\n/* End PBXProject section */\n"); + + /** + ** + ** container item proxies (no clue, I just work here...) - they sit + *between projects when expressing dependencies + ** + **/ + Write("\n/* Begin PBXContainerItemProxy section */"); + { + FOR_EACH_VEC(projects, iProject) { + // for the aggregate target + Write("\n"); + Write("%024llX /* PBXContainerItemProxy */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeContainerItemProxy, -1)); + ++m_nIndent; + { + Write("isa = PBXContainerItemProxy;\n"); + // it looks like if you cross ref between xcodeprojs, this is the + // oid for the other xcode proj + Write("containerPortal = %024llX; /* Project object */\n", + makeoid(oidStrSolutionRoot, EOIDTypeProject)); + Write("proxyType = 1;\n"); + Write("remoteGlobalIDString = %024llX;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeNativeTarget)); + Write("remoteInfo = \"%s\";\n", + projects[iProject]->m_ProjectName.String()); + } + --m_nIndent; + Write("};"); + + // for each project, figure out what projects it depends on, and spit + // out a containeritemproxy for that dependency of particular note is + // that there are many item proxies for a given project, so we make + // their oids with the ordinal of the project they depend on - this + // must be consistent within the generated solution + CDependency_Project *pCurProject = projects[iProject]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies(pCurProject, projects, + additionalProjectDependencies); + + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (iProject == iTestProject) { + int cSubAggregateTargets = + (((CProjectGenerator_Xcode *)g_vecPGenerators[iProject]) + ->m_nShellScriptPhases / + k_nShellScriptPhasesPerAggregateTarget) + + (((CProjectGenerator_Xcode *)g_vecPGenerators[iProject]) + ->m_nShellScriptPhases % + k_nShellScriptPhasesPerAggregateTarget + ? 1 + : 0); + for (int i = 1; i < cSubAggregateTargets; i++) { + Write("\n"); + Write("%024llX /* PBXContainerItemProxy (subproject) */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeContainerItemProxy, 0 - (i + 1))); + ++m_nIndent; + { + Write("isa = PBXContainerItemProxy;\n"); + // it looks like if you cross ref between xcodeprojs, this is + // the oid for the other xcode proj + Write("containerPortal = %024llX; /* Project object */\n", + makeoid(oidStrSolutionRoot, EOIDTypeProject)); + Write("proxyType = 1;\n"); + Write("remoteGlobalIDString = %024llX;\n", + makeoid(projects[iTestProject]->m_ProjectName, + EOIDTypeNativeTarget, i)); + Write("remoteInfo = \"%s\";\n", + projects[iTestProject]->m_ProjectName.String()); + } + --m_nIndent; + Write("};"); + } + continue; + } + + CDependency_Project *pTestProject = projects[iTestProject]; + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pCurProject->DependsOn(pTestProject, dependsOnFlags) || + additionalProjectDependencies.Find(pTestProject) != + additionalProjectDependencies.InvalidIndex()) { + Write("\n"); + Write( + "%024llX /* PBXContainerItemProxy */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeContainerItemProxy, (uint16_t)iTestProject)); + ++m_nIndent; + { + Write("isa = PBXContainerItemProxy;\n"); + // it looks like if you cross ref between xcodeprojs, this is + // the oid for the other xcode proj + Write("containerPortal = %024llX; /* Project object */\n", + makeoid(oidStrSolutionRoot, EOIDTypeProject)); + Write("proxyType = 1;\n"); + Write("remoteGlobalIDString = %024llX;\n", + makeoid(projects[iTestProject]->m_ProjectName, + EOIDTypeNativeTarget)); + Write("remoteInfo = \"%s\";\n", + projects[iTestProject]->m_ProjectName.String()); + } + --m_nIndent; + Write("};"); + } + } + } + } + Write("\n/* End PBXContainerItemProxy section */\n"); + + /** + ** + ** target dependencies - referenced by each project, in turn references + *the proxy container objects to express dependencies between targets + ** + **/ + Write("\n/* Begin PBXTargetDependency section */"); + FOR_EACH_VEC(projects, iProject) { + Write("\n"); + Write("%024llX /* PBXTargetDependency */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeTargetDependency, -1)); + ++m_nIndent; + { + Write("isa = PBXTargetDependency;\n"); + Write( + "target = %024llX /* %s */;\n", + makeoid(projects[iProject]->m_ProjectName, EOIDTypeNativeTarget), + projects[iProject]->m_ProjectName.String()); + Write("targetProxy = %024llX /* PBXContainerItemProxy */;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeContainerItemProxy, -1)); + } + --m_nIndent; + Write("};"); + + CDependency_Project *pCurProject = projects[iProject]; + + CUtlVector additionalProjectDependencies; + ResolveAdditionalProjectDependencies(pCurProject, projects, + additionalProjectDependencies); + + for (int iTestProject = 0; iTestProject < projects.Count(); + iTestProject++) { + if (iProject == iTestProject) { + for (int i = 1; + i < ((CProjectGenerator_Xcode *)g_vecPGenerators[iProject]) + ->m_nShellScriptPhases; + i++) { + Write("\n"); + Write("%024llX /* PBXTargetDependency */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeTargetDependency, 0 - (i + 1))); + ++m_nIndent; + { + Write("isa = PBXTargetDependency;\n"); + Write("target = %024llX /* %s_%d */;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeNativeTarget, i), + projects[iProject]->m_ProjectName.String(), i); + Write("targetProxy = %024llX /* PBXContainerItemProxy */;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeContainerItemProxy, 0 - (i + 1))); + } + --m_nIndent; + Write("};"); + } + continue; + } + + CDependency_Project *pTestProject = projects[iTestProject]; + int dependsOnFlags = k_EDependsOnFlagTraversePastLibs | + k_EDependsOnFlagCheckNormalDependencies | + k_EDependsOnFlagRecurse; + if (pCurProject->DependsOn(pTestProject, dependsOnFlags) || + additionalProjectDependencies.Find(pTestProject) != + additionalProjectDependencies.InvalidIndex()) { + // project_t *pTestProjectT = &g_projects[ + // pTestProject->m_iProjectIndex ]; + Write("\n"); + Write("%024llX /* PBXTargetDependency */ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeTargetDependency, (uint16_t)iTestProject)); + ++m_nIndent; + { + Write("isa = PBXTargetDependency;\n"); + Write("target = %024llX /* %s */;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeNativeTarget), + projects[iProject]->m_ProjectName.String()); + Write( + "targetProxy = %024llX /* PBXContainerItemProxy */;\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeContainerItemProxy, (uint16_t)iTestProject)); + } + --m_nIndent; + Write("};"); + } + } + } + --m_nIndent; + Write("\n/* End PBXTargetDependency section */\n"); + + /** + ** + ** build configurations - each target (and the project) has a set of + *build configurations (one release, one debug), each with their own set + *of build settings + ** the "baseConfigurationReference" points back to the appropriate + *.xcconfig file that gets referenced by the project and has all the + *non-target specific settings + ** + **/ + + Write("\n/* Begin XCBuildConfiguration section */"); + ++m_nIndent; + { + // project and aggregate "all" target + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchConfigNames); + iConfig++) { + bool bIsDebug = !V_stristr(k_rgchConfigNames[iConfig], "release"); + + Write("\n"); + Write("%024llX /* %s */ = {\n", + makeoid2(oidStrSolutionRoot, k_rgchConfigNames[iConfig], + EOIDTypeBuildConfiguration), + k_rgchConfigNames[iConfig]); + ++m_nIndent; + { + Write("isa = XCBuildConfiguration;\n"); + Write("baseConfigurationReference = %024llX /* %s */;\n", + makeoid2(oidStrSolutionRoot, k_rgchXCConfigFiles[iConfig], + EOIDTypeFileReference), + k_rgchXCConfigFiles[iConfig]); + Write("buildSettings = {\n"); + ++m_nIndent; + { EmitBuildSettings("All", NULL, NULL, NULL, NULL, bIsDebug); } + --m_nIndent; + Write("};\n"); + Write("name = \"%s\";\n", k_rgchConfigNames[iConfig]); + } + --m_nIndent; + Write("};"); + + Write("\n"); + Write("%024llX /* %s */ = {\n", + makeoid2(oidStrSolutionRoot, k_rgchConfigNames[iConfig], + EOIDTypeBuildConfiguration, 1), + k_rgchConfigNames[iConfig]); + ++m_nIndent; + { + Write("isa = XCBuildConfiguration;\n"); + Write("baseConfigurationReference = %024llX /* %s */;\n", + makeoid2(oidStrSolutionRoot, k_rgchXCConfigFiles[iConfig], + EOIDTypeFileReference), + k_rgchXCConfigFiles[iConfig]); + Write("buildSettings = {\n"); + ++m_nIndent; + { EmitBuildSettings("All", NULL, NULL, NULL, NULL, bIsDebug); } + --m_nIndent; + Write("};\n"); + Write("name = \"%s\";\n", k_rgchConfigNames[iConfig]); + } + --m_nIndent; + Write("};"); + } + + FOR_EACH_VEC(projects, iProject) { + KeyValues *pReleaseKV = g_vecPGenerators[iProject] + ->m_BaseConfigData.m_Configurations[0] + ->m_pKV; + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchConfigNames); + iConfig++) { + bool bIsDebug = !V_stristr(k_rgchConfigNames[iConfig], "release"); + + Write("\n"); + Write("%024llX /* %s */ = {\n", + makeoid3( + oidStrSolutionRoot, projects[iProject]->m_ProjectName, + k_rgchConfigNames[iConfig], EOIDTypeBuildConfiguration), + k_rgchConfigNames[iConfig]); + ++m_nIndent; + { + Write("isa = XCBuildConfiguration;\n"); + Write("baseConfigurationReference = %024llX /* %s */;\n", + makeoid2(oidStrSolutionRoot, k_rgchXCConfigFiles[iConfig], + EOIDTypeFileReference), + k_rgchXCConfigFiles[iConfig]); + Write("buildSettings = {\n"); + ++m_nIndent; + { + KeyValues *pConfigKV = + g_vecPGenerators[iProject] + ->m_BaseConfigData.m_Configurations[iConfig] + ->m_pKV; + char rgchProjectDir[MAX_PATH]; + V_strncpy(rgchProjectDir, + projects[iProject]->m_ProjectFilename.String(), + sizeof(rgchProjectDir)); + V_StripFilename(rgchProjectDir); + + EmitBuildSettings(projects[iProject]->m_ProjectName, + rgchProjectDir, + &(g_vecPGenerators[iProject]->m_Files), + pConfigKV, pReleaseKV, bIsDebug); + } + --m_nIndent; + Write("};\n"); + Write("name = \"%s\";\n", k_rgchConfigNames[iConfig]); + } + --m_nIndent; + Write("};"); + } + } + } + --m_nIndent; + Write("\n/* End XCBuildConfiguration section */\n"); + + /** + ** + ** configuration lists - aggregates the build configurations above into + *sets, which are referenced by the individual targets. + ** + **/ + Write("\n/* Begin XCConfigurationList section */\n"); + ++m_nIndent; + { + Write( + "%024llX /* Build configuration list for PBXProject \"%s\" */ = " + "{\n", + makeoid(oidStrSolutionRoot, EOIDTypeConfigurationList), + V_UnqualifiedFileName(UsePOSIXSlashes(pSolutionFilename))); + ++m_nIndent; + { + Write("isa = XCConfigurationList;\n"); + Write("buildConfigurations = (\n"); + ++m_nIndent; + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchConfigNames); + iConfig++) { + Write("%024llX /* %s */,\n", + makeoid2(oidStrSolutionRoot, k_rgchConfigNames[iConfig], + EOIDTypeBuildConfiguration), + k_rgchConfigNames[iConfig]); + } + --m_nIndent; + Write(");\n"); + Write("defaultConfigurationIsVisible = 0;\n"); + Write("defaultConfigurationName = \"%s\";\n", k_rgchConfigNames[0]); + } + --m_nIndent; + Write("};\n"); + + Write( + "%024llX /* Build configuration list for PBXAggregateTarget " + "\"All\" */ = {\n", + makeoid(oidStrSolutionRoot, EOIDTypeConfigurationList, 1)); + ++m_nIndent; + { + Write("isa = XCConfigurationList;\n"); + Write("buildConfigurations = (\n"); + ++m_nIndent; + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchConfigNames); + iConfig++) { + Write("%024llX /* %s */,\n", + makeoid2(oidStrSolutionRoot, k_rgchConfigNames[iConfig], + EOIDTypeBuildConfiguration, 1), + k_rgchConfigNames[iConfig]); + } + --m_nIndent; + Write(");\n"); + Write("defaultConfigurationIsVisible = 0;\n"); + Write("defaultConfigurationName = \"%s\";\n", k_rgchConfigNames[0]); + } + --m_nIndent; + Write("};"); + + FOR_EACH_VEC(projects, iProject) { + Write("\n"); + Write( + "%024llX /* Build configuration list for PBXNativeTarget \"%s\" " + "*/ = {\n", + makeoid(projects[iProject]->m_ProjectName, + EOIDTypeConfigurationList), + projects[iProject]->m_ProjectName.String()); + ++m_nIndent; + { + Write("isa = XCConfigurationList;\n"); + Write("buildConfigurations = (\n"); + ++m_nIndent; + for (int iConfig = 0; iConfig < V_ARRAYSIZE(k_rgchConfigNames); + iConfig++) { + Write("%024llX /* %s */,\n", + makeoid3( + oidStrSolutionRoot, projects[iProject]->m_ProjectName, + k_rgchConfigNames[iConfig], EOIDTypeBuildConfiguration), + k_rgchConfigNames[iConfig]); + } + --m_nIndent; + Write(");\n"); + Write("defaultConfigurationIsVisible = 0;\n"); + Write("defaultConfigurationName = \"%s\";\n", k_rgchConfigNames[0]); + } + --m_nIndent; + Write("};"); + } + } + --m_nIndent; + Write("\n/* End XCConfigurationList section */\n"); + } + Write("};\n"); // objects = { ... + + /** + ** + ** root object in the graph + ** + **/ + Write("rootObject = %024llX /* Project object */;\n", + makeoid(oidStrSolutionRoot, EOIDTypeProject)); + } + --m_nIndent; + + Write("}\n"); + fclose(m_fp); + + // and now write a .projects file inside the xcode project so we can detect + // the list of projects changing (specifically a vpc project dissapearing from + // our target list) + FILE *fp = fopen(sProjProjectListFile, "wt"); + if (!fp) { + g_pVPC->VPCError("Unable to open %s to write projects into.", + sProjProjectListFile); + } + // we don't need to be quite as careful as project script, as we're only + // looking to catch cases where the rest of VPC thinks we're up-to-date + fprintf(fp, "%s\n", VPCCRCCHECK_FILE_VERSION_STRING); + FOR_EACH_VEC(g_vecPGenerators, iGenerator) { + CProjectGenerator_Xcode *pGenerator = + (CProjectGenerator_Xcode *)g_vecPGenerators[iGenerator]; + + fprintf(fp, "%s\n", pGenerator->m_ProjectName.String()); + } + fclose(fp); +} + +void CSolutionGenerator_Xcode::Write(PRINTF_FORMAT_STRING const char *pMsg, + ...) { + for (int i = 0; i < m_nIndent; i++) fprintf(m_fp, "\t"); + + va_list marker; + va_start(marker, pMsg); + vfprintf(m_fp, pMsg, marker); + va_end(marker); +} + +static CSolutionGenerator_Xcode g_SolutionGenerator_Xcode; +IBaseSolutionGenerator *GetXcodeSolutionGenerator() { + return &g_SolutionGenerator_Xcode; +} + +static CProjectGenerator_Xcode g_ProjectGenerator_Xcode; +IBaseProjectGenerator *GetXcodeProjectGenerator() { + return &g_ProjectGenerator_Xcode; +} diff --git a/utils/vpc/sys_utils.cpp b/utils/vpc/sys_utils.cpp new file mode 100644 index 0000000..d2f29ea --- /dev/null +++ b/utils/vpc/sys_utils.cpp @@ -0,0 +1,675 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" + +#ifdef STEAM +#include "tier1/utlintrusivelist.h" +#endif + +#ifdef POSIX +#define _O_RDONLY O_RDONLY +#define _open open +#include +#define _lseek lseek +#define _read read +#define _close close +#define _stat stat +#include +#else +#include "winlite.h" +#include +#include +#endif + +#ifdef OSX +#include +#endif + +#include "tier0/memdbgon.h" + +CXMLWriter::CXMLWriter() { + m_fp = NULL; + m_b2010Format = false; +} + +bool CXMLWriter::Open(const char *pFilename, bool b2010Format) { + m_FilenameString = pFilename; + m_b2010Format = b2010Format; + + m_fp = fopen(pFilename, "wt"); + if (!m_fp) return false; + + if (b2010Format) { + Write("\xEF\xBB\xBF"); + } else { + // 2005 format + Write("\n"); + } + + return true; +} + +void CXMLWriter::Close() { + if (!m_fp) return; + fclose(m_fp); + + Sys_CopyToMirror(m_FilenameString.Get()); + + m_fp = NULL; + m_FilenameString = NULL; +} + +void CXMLWriter::PushNode(const char *pName) { + Indent(); + + char *pNewName = _strdup(pName); + m_Nodes.Push(pNewName); + + fprintf(m_fp, "<%s%s\n", pName, m_Nodes.Count() == 2 ? ">" : ""); +} + +void CXMLWriter::PushNode(const char *pName, const char *pString) { + Indent(); + + char *pNewName = _strdup(pName); + m_Nodes.Push(pNewName); + + fprintf(m_fp, "<%s%s%s>\n", pName, pString ? " " : "", + pString ? pString : ""); +} + +void CXMLWriter::WriteLineNode(const char *pName, const char *pExtra, + const char *pString) { + Indent(); + + fprintf(m_fp, "<%s%s>%s\n", pName, pExtra ? pExtra : "", pString, pName); +} + +void CXMLWriter::PopNode(bool bEmitLabel) { + char *pName; + m_Nodes.Pop(pName); + + Indent(); + if (bEmitLabel) { + fprintf(m_fp, "\n", pName); + } else { + fprintf(m_fp, "/>\n"); + } + + free(pName); +} + +void CXMLWriter::Write(const char *p) { + if (m_fp) { + Indent(); + fprintf(m_fp, "%s\n", p); + } +} + +struct XMLFixup_t { + const char *m_pFrom; + const char *m_pTo; + bool m_b2010Only; +}; + +CUtlString CXMLWriter::FixupXMLString(const char *pInput) { + // these tokens are not allowed in xml vcproj and be be escaped per msdev docs + XMLFixup_t xmlFixups[] = { + {"&", "&", false}, + {"\"", """, false}, + {"\'", "'", false}, + {"\n", " ", false}, + {">", ">", false}, + {"<", "<", false}, + {"$(InputFileName)", "%(Filename)%(Extension)", true}, + {"$(InputName)", "%(Filename)", true}, + {"$(InputPath)", "%(FullPath)", true}, + {"$(InputDir)", "%(RootDir)%(Directory)", true}, + }; + + bool bNeedsFixups = false; + CUtlVector needsFixups; + CUtlString outString; + + needsFixups.SetCount(ARRAYSIZE(xmlFixups)); + for (int i = 0; i < ARRAYSIZE(xmlFixups); i++) { + needsFixups[i] = false; + + if (!m_b2010Format && xmlFixups[i].m_b2010Only) continue; + + if (V_stristr(pInput, xmlFixups[i].m_pFrom)) { + needsFixups[i] = true; + bNeedsFixups = true; + } + } + + if (!bNeedsFixups) { + outString = pInput; + } else { + int flip = 0; + char bigBuffer[2][8192]; + V_strncpy(bigBuffer[flip], pInput, sizeof(bigBuffer[0])); + + for (int i = 0; i < ARRAYSIZE(xmlFixups); i++) { + if (!needsFixups[i]) continue; + + if (!V_StrSubst(bigBuffer[flip], xmlFixups[i].m_pFrom, xmlFixups[i].m_pTo, + bigBuffer[flip ^ 1], sizeof(bigBuffer[0]), false)) { + g_pVPC->VPCError("XML overflow - Increase big buffer"); + } + flip ^= 1; + } + outString = bigBuffer[flip]; + } + + return outString; +} + +void CXMLWriter::Indent() { + for (int i = 0; i < m_Nodes.Count(); i++) { + if (m_b2010Format) { + fprintf(m_fp, " "); + } else { + fprintf(m_fp, "\t"); + } + } +} + +// Sys_LoadFile +int Sys_LoadFile(const char *filename, void **bufferptr, bool bText) { + *bufferptr = nullptr; + + if (!Sys_Exists(filename)) return -1; + + int flags = _O_RDONLY; + +#if !defined(POSIX) + flags |= (bText ? _O_TEXT : _O_BINARY); +#endif + + int handle{_open(filename, flags)}; + if (handle == -1) + Sys_Error("Sys_LoadFile(): Error opening %s: %s", filename, + strerror(errno)); + + long length{_lseek(handle, 0, SEEK_END)}; + _lseek(handle, 0, SEEK_SET); + char *buffer = (char *)malloc(length + 1); + + int bytesRead = _read(handle, buffer, length); + if (!bText && bytesRead != length) + Sys_Error("Sys_LoadFile(): read truncated failure"); + + _close(handle); + + // text mode is truncated, add null for parsing + buffer[bytesRead] = '\0'; + + *bufferptr = (void *)buffer; + + return length; +} + +// Sys_LoadFileIntoBuffer +bool Sys_LoadFileIntoBuffer(const char *pchFileIn, CUtlBuffer &buf, + bool bText) { + buf.SetBufferType(bText, bText); + +#ifndef _WIN64 + struct stat statBuf; + if (::stat(pchFileIn, &statBuf) != 0) return false; +#else + struct _stat64 statBuf; + if (::_stat64(pchFileIn, &statBuf) != 0) return false; +#endif + + buf.EnsureCapacity(statBuf.st_size + 1); + if (!buf.IsValid()) return false; + + FILE *f{fopen(pchFileIn, "rb")}; + if (!f) return false; + + char *pBuffer = (char *)buf.Base(); + const decltype(statBuf.st_size) nBytesRead = + statBuf.st_size * fread(pBuffer, statBuf.st_size, 1, f); + + fclose(f); + + buf.SeekPut(CUtlBuffer::SEEK_HEAD, nBytesRead); + // terminate buffer without changing put size + pBuffer[statBuf.st_size] = '\0'; + + return nBytesRead == statBuf.st_size; +} + +// Sys_FileLength +long Sys_FileLength(const char *filename, bool bText) { + if (filename) { + int flags = _O_RDONLY; + +#if !defined(POSIX) + flags |= (bText ? _O_TEXT : _O_BINARY); +#endif + + int handle{_open(filename, flags)}; + // file does not exist + if (handle == -1) return -1; + + long length{_lseek(handle, 0, SEEK_END)}; + + _close(handle); + + return length; + } + + return -1; +} + +// Sys_StripPath +// +// Removes path portion from a fully qualified name, leaving filename and +// extension. +void Sys_StripPath(const char *inpath, char *outpath) { + const char *src{inpath + strlen(inpath)}; + + while ((src != inpath) && (*(src - 1) != '\\') && (*(src - 1) != '/') && + (*(src - 1) != ':')) + src--; + + strcpy(outpath, src); +} + +// Sys_Exists +// +// Returns TRUE if file exists. +bool Sys_Exists(const char *filename) { +#ifdef _WIN32 + // dimhotepus: Fast way to check file exists. + return ::GetFileAttributesA(filename) != INVALID_FILE_ATTRIBUTES; +#else + if (FILE *test = fopen(filename, "rb")) { + fclose(test); + return true; + } + + return false; +#endif +} + +// Sys_Touch +// +// Returns TRUE if the file could be accessed for write +bool Sys_Touch(const char *filename) { + if (FILE *test = fopen(filename, "wb")) { + fclose(test); + return true; + } + + return false; +} + +// Sys_FileInfo +bool Sys_FileInfo(const char *pFilename, int64 &nFileSize, int64 &nModifyTime) { + struct _stat statData; + int rt = _stat(pFilename, &statData); + + if (rt != 0) return false; + + nFileSize = statData.st_size; + nModifyTime = statData.st_mtime; + + return true; +} + +// Ignores allowable trailing characters. +bool Sys_StringToBool(const char *pString) { + if (!V_strnicmp(pString, "no", 2) || !V_strnicmp(pString, "off", 3) || + !V_strnicmp(pString, "false", 5) || !V_strnicmp(pString, "not set", 7) || + !V_strnicmp(pString, "disabled", 8) || !V_strnicmp(pString, "0", 1)) { + // false + return false; + } + + if (!V_strnicmp(pString, "yes", 3) || !V_strnicmp(pString, "on", 2) || + !V_strnicmp(pString, "true", 4) || !V_strnicmp(pString, "set", 3) || + !V_strnicmp(pString, "enabled", 7) || !V_strnicmp(pString, "1", 1)) { + // true + return true; + } + + // unknown boolean expression + g_pVPC->VPCSyntaxError("Unknown boolean expression '%s'", pString); +} + +bool Sys_ReplaceString(const char *pStream, const char *pSearch, + const char *pReplace, char *pOutBuff, int outBuffSize) { + const char *pFind; + const char *pStart = pStream; + char *pOut = pOutBuff; + intp len; + bool bReplaced = false; + + while (true) { + // find sub string + pFind = V_stristr(pStart, pSearch); + if (!pFind) { + /// end of string + len = V_strlen(pStart); + pFind = pStart + len; + memcpy(pOut, pStart, len); + pOut += len; + break; + } else { + bReplaced = true; + } + + // copy up to sub string + len = pFind - pStart; + memcpy(pOut, pStart, len); + pOut += len; + + // substitute new string + len = V_strlen(pReplace); + memcpy(pOut, pReplace, len); + pOut += len; + + // advance past sub string + pStart = pFind + strlen(pSearch); + } + + *pOut = '\0'; + + return bReplaced; +} + +// string match with wildcards. '?' = match any char +bool Sys_StringPatternMatch(char const *pSrcPattern, char const *pString) { + for (;;) { + char nPat = *(pSrcPattern++); + char nString = *(pString++); + + if (!((nPat == nString) || ((nPat == '?') && nString))) return false; + if (!nString) return true; // end of string + } +} + +bool Sys_EvaluateEnvironmentExpression(const char *pExpression, + const char *pDefault, char *pOutBuff, + int nOutBuffSize) { + char *pEnvVarName = (char *)StringAfterPrefix(pExpression, "$env("); + // not an environment specification + if (!pEnvVarName) return false; + + char *pLastChar = &pEnvVarName[V_strlen(pEnvVarName) - 1]; + if (!*pEnvVarName || *pLastChar != ')') { + g_pVPC->VPCSyntaxError("$env() must have a closing ')' in \"%s\"\n", + pExpression); + } + + // get the contents of the $env( blah..blah ) expressions + // handles expresions that could have whitepsaces + g_pVPC->GetScript().PushScript(pExpression, pEnvVarName); + const char *pToken = g_pVPC->GetScript().GetToken(false); + g_pVPC->GetScript().PopScript(); + + if (pToken && pToken[0]) { + const char *pResolve = getenv(pToken); + if (!pResolve) { + // not defined, use default + pResolve = pDefault ? pDefault : ""; + } + + V_strncpy(pOutBuff, pResolve, nOutBuffSize); + } + + return true; +} + +bool Sys_ExpandFilePattern(const char *pPattern, + CUtlVector &vecResults) { +#if defined(_WIN32) + char rgchPathPart[MAX_PATH]; + V_strncpy(rgchPathPart, pPattern, V_ARRAYSIZE(rgchPathPart)); + V_StripFilename(rgchPathPart); + if (V_strlen(rgchPathPart)) + V_strncat(rgchPathPart, "\\", V_ARRAYSIZE(rgchPathPart)); + + WIN32_FIND_DATA findData; + HANDLE hFind = FindFirstFile(pPattern, &findData); + if (hFind != INVALID_HANDLE_VALUE) { + vecResults.AddToTail( + CFmtStr("%s%s", rgchPathPart, findData.cFileName).Access()); + BOOL bMore = TRUE; + + while (bMore) { + bMore = FindNextFile(hFind, &findData); + if (bMore) + vecResults.AddToTail( + CFmtStr("%s%s", rgchPathPart, findData.cFileName).Access()); + } + + FindClose(hFind); + } +#elif defined(POSIX) + glob_t gr; + if (glob(pPattern, 0, NULL, &gr) == 0) { + for (int i = 0; i < gr.gl_pathc; i++) { + vecResults.AddToTail(gr.gl_pathv[i]); + } + globfree(&gr); + } +#else +#error +#endif + return vecResults.Count() > 0; +} + +bool Sys_GetExecutablePath(char *pBuf, int cbBuf) { +#if defined(_WIN32) + return (0 != GetModuleFileNameA(NULL, pBuf, cbBuf)); +#elif defined(OSX) + uint32_t _nBuff = cbBuf; + bool bSuccess = _NSGetExecutablePath(pBuf, &_nBuff) == 0; + pBuf[cbBuf - 1] = '\0'; + return bSuccess; +#elif defined LINUX + ssize_t nRead = readlink("/proc/self/exe", pBuf, cbBuf - 1); + if (nRead != -1) { + pBuf[nRead] = 0; + return true; + } + + pBuf[0] = 0; + return false; +#else +#error Sys_GetExecutablePath +#endif +} + +void Sys_CreatePath(const char *path) { +#if defined(_WIN32) + char pFullPath[MAX_PATH]; + V_MakeAbsolutePath(pFullPath, sizeof(pFullPath), path); + + // If Sys_CreatePath is called with a filename, all is well. + // If it is called with a folder name, it must have a trailing slash: + if (!V_GetFileExtension(pFullPath)) + V_AppendSlash(pFullPath, sizeof(pFullPath)); + + char *ptr; + + // skip past the drive path, but don't strip + if (pFullPath[1] == ':') { + ptr = strchr(pFullPath, '\\'); + } else { + ptr = pFullPath; + } + + while (ptr) { + ptr = strchr(ptr + 1, '\\'); + + if (ptr) { + *ptr = '\0'; + +#if defined(_WIN32) || defined(WIN32) + CreateDirectory(pFullPath, NULL); +#else +#error Sys_CreatePath: this mkdir is probably correct but has not been tested + mkdir(pFullPath, 0777); +#endif + + *ptr = '\\'; + } + } +#endif +} + +// Given some arbitrary case filename, provides what the OS thinks it is. +// Windows specific. Returns false if file cannot be resolved (i.e. does not +// exist). +bool Sys_GetActualFilenameCase(const char *pFilename, char *pOutputBuffer, + int nOutputBufferSize) { +#if defined(_WINDOWS) + char filenameBuffer[MAX_PATH]; + V_strncpy(filenameBuffer, pFilename, sizeof(filenameBuffer)); + V_FixSlashes(filenameBuffer); + V_RemoveDotSlashes(filenameBuffer); + + intp nFilenameLength = V_strlen(filenameBuffer); + + CUtlString actualFilename; + + // march along filename, resolving up to next seperator + intp nLastComponentStart = 0; + bool bAddSeparator = false; + intp i = 0; + while (i < nFilenameLength) { + // cannot resolve these, emit as-is + if (!V_strnicmp(filenameBuffer + i, ".\\", 2)) { + i += 2; + actualFilename += CUtlString(".\\"); + continue; + } + + // cannot resolve these, emit as-is + if (!V_strnicmp(filenameBuffer + i, "..\\", 3)) { + i += 3; + actualFilename += CUtlString("..\\"); + continue; + } + + // skip until path separator + while (i < nFilenameLength && filenameBuffer[i] != '\\') { + ++i; + } + + bool bFoundSeparator = (i < nFilenameLength); + + // truncate at separator, windows resolves each component in pieces + filenameBuffer[i] = 0; + + SHFILEINFOA info = {0}; + DWORD_PTR hr{SHGetFileInfoA(filenameBuffer, 0, &info, sizeof(info), + SHGFI_DISPLAYNAME)}; + if (hr != 0) { + // reassemble based on actual component + if (bAddSeparator) actualFilename += CUtlString("\\"); + + actualFilename += CUtlString(info.szDisplayName); + } else { + return false; + } + + // restore path separator + if (bFoundSeparator) { + filenameBuffer[i] = '\\'; + } + + ++i; + nLastComponentStart = i; + bAddSeparator = true; + } + + V_strncpy(pOutputBuffer, actualFilename.Get(), nOutputBufferSize); + return true; +#else + return false; +#endif +} + +// Given some arbitrary case filename, determine if OS version matches. +bool Sys_IsFilenameCaseConsistent(const char *pFilename, char *pOutputBuffer, + int nOutputBufferSize) { + V_strncpy(pOutputBuffer, pFilename, nOutputBufferSize); + + // normalize the provided filename separators + CUtlString filename = pFilename; + V_FixSlashes(filename.Get()); + V_RemoveDotSlashes(filename.Get()); + + if (!Sys_GetActualFilenameCase(filename.Get(), pOutputBuffer, + nOutputBufferSize)) + return false; + + if (!V_strcmp(filename.Get(), pOutputBuffer)) return true; + + return false; +} + +bool Sys_CopyToMirror(const char *pFilename) { + if (!pFilename || !pFilename[0]) return false; + + const char *pMirrorPath = g_pVPC->GetOutputMirrorPath(); + if (!pMirrorPath || !pMirrorPath[0]) return false; + + char absolutePathToOriginal[MAX_PATH]; + if (V_IsAbsolutePath(pFilename)) { + V_strncpy(absolutePathToOriginal, pFilename, + sizeof(absolutePathToOriginal)); + } else { + // need to determine where file resides for mirroring + char currentDirectory[MAX_PATH] = {0}; + V_GetCurrentDirectory(currentDirectory, sizeof(currentDirectory)); + V_ComposeFileName(currentDirectory, pFilename, absolutePathToOriginal, + sizeof(absolutePathToOriginal)); + } + + if (!Sys_Exists(absolutePathToOriginal)) { + g_pVPC->VPCWarning("Cannot mirror '%s', cannot resolve to expected '%s'", + pFilename, absolutePathToOriginal); + return false; + } + + const char *pTargetPath = + StringAfterPrefix(absolutePathToOriginal, g_pVPC->GetSourcePath()); + if (!pTargetPath || !pTargetPath[0]) { + g_pVPC->VPCWarning( + "Cannot mirror '%s', missing expected prefix '%s' in '%s'", pFilename, + g_pVPC->GetSourcePath(), absolutePathToOriginal); + return false; + } + + // supply the mirror path head + char absolutePathToMirror[MAX_PATH]; + if (pTargetPath[0] == '\\') pTargetPath++; + + V_ComposeFileName(pMirrorPath, pTargetPath, absolutePathToMirror, + sizeof(absolutePathToMirror)); + +#ifdef _WIN32 + Sys_CreatePath(absolutePathToMirror); + + if (!CopyFile(absolutePathToOriginal, absolutePathToMirror, FALSE)) { + g_pVPC->VPCWarning("Cannot mirror '%s' to '%s'", absolutePathToOriginal, + absolutePathToMirror); + return false; + } else { + g_pVPC->VPCStatus(true, "Mirror: '%s' to '%s'", absolutePathToOriginal, + absolutePathToMirror); + } +#endif + + return true; +} \ No newline at end of file diff --git a/utils/vpc/sys_utils.h b/utils/vpc/sys_utils.h new file mode 100644 index 0000000..eea4bea --- /dev/null +++ b/utils/vpc/sys_utils.h @@ -0,0 +1,140 @@ +// Copyright Valve Corporation, All rights reserved. +// +// File Utilities. + +#ifndef VPC_SYS_UTILS_H_ +#define VPC_SYS_UTILS_H_ + +#define _CRT_SECURE_NO_DEPRECATE 1 + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef POSIX +#include +#endif + +#if defined(LINUX) || defined(_LINUX) +#include +#endif + +#include "tier0/platform.h" +#include "../vpccrccheck/crccheck_shared.h" + +template +class CSimplePointerStack { + public: + inline CSimplePointerStack() { m_nCount = 0; } + + inline void Purge() { + for (int i = 0; i < m_nCount; i++) m_Values[i] = (NullType)NULL; + m_nCount = 0; + } + + inline int Count() { return m_nCount; } + + inline T &Top() { + Assert(m_nCount > 0); + return m_Values[m_nCount - 1]; + } + + inline void Pop(T &val) { + Assert(m_nCount > 0); + --m_nCount; + val = m_Values[m_nCount]; + m_Values[m_nCount] = (NullType)NULL; + } + + inline void Pop() { + Assert(m_nCount > 0); + --m_nCount; + m_Values[m_nCount] = (NullType)NULL; + } + + inline void Push(T &val) { + Assert(m_nCount + 1 < nMax); + m_Values[m_nCount] = val; + ++m_nCount; + } + + public: + T m_Values[nMax]; + int m_nCount; +}; + +class CXMLWriter { + public: + CXMLWriter(); + + bool Open(const char *pFilename, bool bIs2010Format = false); + void Close(); + + void PushNode(const char *pName); + void PopNode(bool bEmitLabel); + + void WriteLineNode(const char *pName, const char *pExtra, + const char *pString); + void PushNode(const char *pName, const char *pString); + + void Write(const char *p); + CUtlString FixupXMLString(const char *pInput); + + private: + void Indent(); + + bool m_b2010Format; + FILE *m_fp; + + CUtlString m_FilenameString; + + CSimplePointerStack m_Nodes; +}; + +long Sys_FileLength(const char *filename, bool bText = false); +int Sys_LoadFile(const char *filename, void **bufferptr, bool bText = false); +bool Sys_LoadFileIntoBuffer(const char *pchFileIn, CUtlBuffer &buf, bool bText); +void Sys_StripPath(const char *path, char *outpath); +bool Sys_Exists(const char *filename); +bool Sys_Touch(const char *filename); +bool Sys_FileInfo(const char *pFilename, int64 &nFileSize, int64 &nModifyTime); + +bool Sys_StringToBool(const char *pString); +bool Sys_ReplaceString(const char *pStream, const char *pSearch, + const char *pReplace, char *pOutBuff, int outBuffSize); +bool Sys_StringPatternMatch(char const *pSrcPattern, char const *pString); + +bool Sys_EvaluateEnvironmentExpression(const char *pExpression, + const char *pDefault, char *pOutBuff, + int nOutBuffSize); + +bool Sys_ExpandFilePattern(const char *pPattern, + CUtlVector &vecResults); +bool Sys_GetExecutablePath(char *pBuf, int cbBuf); + +bool Sys_CopyToMirror(const char *pFilename); +inline bool IsCFileExtension(const char *pExtension) { + if (!pExtension) return false; + + return !V_stricmp(pExtension, "cpp") || !V_stricmp(pExtension, "cxx") || + !V_stricmp(pExtension, "cc") || !V_stricmp(pExtension, "c"); +} + +bool Sys_GetActualFilenameCase(const char *pFilename, char *pOutputBuffer, + int nOutputBufferSize); +bool Sys_IsFilenameCaseConsistent(const char *pFilename, char *pOutputBuffer, + int nOutputBufferSize); +inline bool IsHFileExtension(const char *pExtension) { + if (!pExtension) return false; + + return !V_stricmp(pExtension, "hpp") || !V_stricmp(pExtension, "hxx") || + !V_stricmp(pExtension, "hh") || !V_stricmp(pExtension, "h"); +} + +#endif // VPC_SYS_UTILS_H_ diff --git a/utils/vpc/vpc.cpp b/utils/vpc/vpc.cpp new file mode 100644 index 0000000..777e5ce --- /dev/null +++ b/utils/vpc/vpc.cpp @@ -0,0 +1,2519 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: VPC + +#include "vpc.h" +#include "dependencies.h" + +#if !defined(NO_PERFORCE) +#include "p4sln.h" +#endif + +#include "ilaunchabledll.h" +#include +#include + +#ifdef _WIN32 +#include +#include "winlite.h" +#endif + +#include "tier0/memdbgon.h" + +CVPC *g_pVPC; + +class CBaseProjectDataCollector; +CUtlVector g_vecPGenerators; + +// Stuff that we might encounter in a vpc file that parts of vpc care about +const char *g_pOption_ImportLibrary = "$ImportLibrary"; +const char *g_pOption_OutputFile = "$OutputFile"; +const char *g_pOption_GameOutputFile = "$GameOutputFile"; +const char *g_pOption_AdditionalIncludeDirectories = + "$AdditionalIncludeDirectories"; +const char *g_pOption_AdditionalProjectDependencies = + "$AdditionalProjectDependencies"; +const char *g_pOption_AdditionalOutputFiles = "$AdditionalOutputFiles"; +const char *g_pOption_PreprocessorDefinitions = "$PreprocessorDefinitions"; +const char *g_IncludeSeparators[2] = {";", ","}; + +#ifdef POSIX +#define _unlink unlink +#define _stat stat +#endif + +CVPC::CVPC() { + m_pP4Module = nullptr; + m_pFilesystemModule = nullptr; + + m_nArgc = 0; + m_ppArgv = nullptr; + + m_bVerbose = false; + m_bQuiet = false; + m_bUsageOnly = false; + m_bHelp = false; + m_bSpewPlatforms = false; + m_bSpewGames = false; + m_bSpewGroups = false; + m_bSpewProjects = false; + m_bIgnoreRedundancyWarning = false; + m_bSpewProperties = false; + m_bTestMode = false; + m_bGeneratedProject = false; + m_bAnyProjectQualified = false; + m_bForceGenerate = false; + m_bNoPosixPCH = false; + m_bEnableVpcGameMacro = true; + m_bDecorateProject = false; + m_bShowDeps = false; + m_bP4AutoAdd = false; + m_bP4SlnCheckEverything = false; + m_bDedicatedBuild = false; + m_bAppendSrvToDedicated = false; + m_bUseValveBinDir = false; + m_bInMkSlnPass = false; + m_bShowCaseIssues = false; + m_bVerboseMakefile = false; + m_bP4SCC = false; + m_b32BitTools = false; + +#ifdef VPC_SCC_INTEGRATION + m_bP4SCC = true; +#endif + if (getenv("VPC_SRCCTL") != nullptr) { + m_bP4SCC = V_atoi(getenv("VPC_SRCCTL")) != 0; + } + +#ifdef WIN32 + m_eVSVersion = k_EVSVersion_2022; + m_bUseVS2010FileFormat = true; + m_bUseUnity = false; +#else + m_eVSVersion = k_EVSVersion_Invalid; + m_bUseVS2010FileFormat = false; + m_bUseUnity = false; +#endif + + m_FilesMissing = 0; + + // need to check files by default, otherwise dependency failure (due to + // missing file) cause needles rebuilds + m_bCheckFiles = true; + + m_pProjectGenerator = nullptr; + m_pSolutionGenerator = nullptr; + +#ifdef OSX + m_bForceIterate = true; +#else + m_bForceIterate = false; +#endif + + m_pPhase1Projects = nullptr; +} + +bool CVPC::Init(int argc, const char **argv) { + Assert(argc >= 0); + Assert(argv); + + m_nArgc = argc; + m_ppArgv = argv; + + // vpc operates tersely by preferred company opinion verbosity necessary for + // debugging + m_bVerbose = + (HasCommandLineParameter("/v") || HasCommandLineParameter("/verbose")); + m_bQuiet = + (HasCommandLineParameter("/q") || HasCommandLineParameter("/quiet") || + (getenv("VPC_QUIET") && V_stricmp(getenv("VPC_QUIET"), "0"))); + +#ifndef STEAM + LoggingSystem_PushLoggingState(); + + m_LoggingListener.m_bQuietPrintf = m_bQuiet; + LoggingSystem_RegisterLoggingListener(&m_LoggingListener); +#endif + + // needs to occur early and before any other expensive setup, a crc check just + // exits with an error code used by caller + InProcessCRCCheck(); + + LoadPerforceInterface(); + + // vpc may have been run from wrong location, restart self + bool is_restart_child{false}; + if (RestartFromCorrectLocation(&is_restart_child)) { + // successfully ran under restart condition, all done + return false; + } + + if (is_restart_child) { + // this process is the restart child, cull the internal private restart + // guard option otherwise it gets confused as a build option + m_nArgc--; + } + + Log_Msg(LOG_VPC, "VPC - Valve Project Creator For "); + Log_Msg(LOG_VPC, "Visual Studio, Xbox 360, PlayStation 3, "); + Log_Msg(LOG_VPC, "Xcode and Make (Build: %s %s)\n", __DATE__, __TIME__); + Log_Msg(LOG_VPC, + "(C) Copyright 1996-2024, Valve Corporation, All rights reserved.\n"); + Log_Msg(LOG_VPC, "\n"); + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::Shutdown(bool bHasError) { + if (!bHasError) { + GetScript().EnsureScriptStackEmpty(); + } + + if (!m_TempGroupScriptFilename.IsEmpty()) { + const char *temp_path{m_TempGroupScriptFilename.Get()}; + + // delete temp work file + if (_unlink(temp_path)) { + VPCWarning("Unlinking temp file %s failed: %s", temp_path, + strerror(errno)); + } + + m_TempGroupScriptFilename.Clear(); + } + + UnloadPerforceInterface(); + +#ifndef STEAM + LoggingSystem_UnregisterLoggingListener(&m_LoggingListener); + + LoggingSystem_PopLoggingState(); +#endif +} + +// Usually you have no Perforce version control system. +#if defined(STANDALONE_VPC) && !defined(NO_PERFORCE) +class CP4; +extern CP4 s_p4; +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVPC::LoadPerforceInterface() { + if (p4) { + // already loaded + return true; + } + +#if defined(STANDALONE_VPC) +#if !defined(NO_PERFORCE) + p4 = (IP4 *)&s_p4; + return (p4 != NULL); +#else + return false; +#endif +#else + + // + // Try to load p4lib.dll and the filesystem since the p4lib relies on it + // + char p4libdll[MAX_PATH]; + char filesystemdll[MAX_PATH]; + +#ifdef _WIN32 + // Don't require them to have game\bin in their path. Since we know where + // vpc.exe is, point directly to p4lib.dll in its rightful place. + char szModuleBinPath[MAX_PATH]; + GetModuleFileName(NULL, szModuleBinPath, sizeof(szModuleBinPath)); + V_ExtractFilePath(szModuleBinPath, p4libdll, sizeof(p4libdll)); + V_AppendSlash(p4libdll, sizeof(p4libdll)); + V_strncpy(filesystemdll, p4libdll, sizeof(filesystemdll)); + V_strncat(p4libdll, "..\\..\\..\\game\\bin\\p4lib.dll", sizeof(p4libdll)); + V_strncat(filesystemdll, "..\\..\\..\\game\\bin\\filesystem_stdio.dll", + sizeof(filesystemdll)); +#else + V_strncpy(p4libdll, "p4lib", sizeof(p4libdll)); + V_strncpy(filesystemdll, "filesystem_stdio", sizeof(filesystemdll)); +#endif + + if (!Sys_LoadInterface(p4libdll, P4_INTERFACE_VERSION, &m_pP4Module, + (void **)&p4)) { +#ifdef _WIN32 + // This always fails on non-Windows build machines -- the warning is + // annoying and not helpful. + VPCWarning("Unable to get Perforce interface from p4lib.dll."); +#endif + return false; + } + + // Let the P4 module get its interface to the filesystem - hate this + + // This method is not available in portal2, but is in source2. + // p4->SetVerbose( false ); + m_pFilesystemModule = Sys_LoadModule(filesystemdll); + p4->Connect(Sys_GetFactory(m_pFilesystemModule)); + + return true; +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::UnloadPerforceInterface() { + // Unload P4 if it was loaded + if (m_pP4Module) { + Sys_UnloadModule(m_pP4Module); + m_pP4Module = NULL; + } + + if (m_pFilesystemModule) { + Sys_UnloadModule(m_pFilesystemModule); + m_pFilesystemModule = NULL; + } +} + +bool VPC_Config_IgnoreOption(const char *pPropertyName) { + char buff[MAX_SYSTOKENCHARS]; + g_pVPC->GetScript().ParsePropertyValue(NULL, buff, sizeof(buff)); + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +[[noreturn]] void CVPC::VPCError(PRINTF_FORMAT_STRING const char *format, ...) { + va_list argptr; + char msg[MAX_SYSPRINTMSG]; + + va_start(argptr, format); + vsprintf(msg, format, argptr); + va_end(argptr); + + // spew in red + Log_Warning(LOG_VPC, Color(255, 0, 0, 255), "ERROR: %s\n", msg); + + // dump the script stack to assist in user understading of the include chain + GetScript().SpewScriptStack(); + + // do proper shutdown in an error context + Shutdown(true); + + // errors are expected to be fatal by all calling code + // otherwise it would have been a warning + exit(1); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +[[noreturn]] void CVPC::VPCSyntaxError(PRINTF_FORMAT_STRING const char *format, + ...) { + va_list argptr; + char msg[MAX_SYSPRINTMSG]; + + va_start(argptr, format); + if (format) { + vsprintf(msg, format, argptr); + } + va_end(argptr); + + if (format) { + Log_Warning(LOG_VPC, Color(255, 0, 0, 255), "Bad Syntax: %s\n", msg); + } + + // syntax errors are fatal + VPCError("Bad Syntax in '%s' line:%d\n", GetScript().GetName(), + GetScript().GetLine()); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::VPCWarning(PRINTF_FORMAT_STRING const char *format, ...) { + va_list argptr; + char msg[MAX_SYSPRINTMSG]; + + va_start(argptr, format); + vsprintf(msg, format, argptr); + va_end(argptr); + + if (m_bIgnoreRedundancyWarning) { + if (V_stristr(msg, "matches default setting")) return; + if (V_stristr(msg, "already exists in project")) return; + } + + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), "WARNING: %s\n", msg); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::VPCStatus(bool bAlwaysSpew, PRINTF_FORMAT_STRING const char *format, + ...) { + if (m_bQuiet) return; + + va_list argptr; + char msg[MAX_SYSPRINTMSG]; + + va_start(argptr, format); + vsprintf(msg, format, argptr); + va_end(argptr); + + if (bAlwaysSpew || m_bVerbose) { + Log_Msg(LOG_VPC, "%s\n", msg); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +intp CVPC::GetProjectsInGroup(CUtlVector &projectList, + const char *pGroupName) { + projectList.RemoveAll(); + + // Find the specified group + groupTagIndex_t groupTagIndex = + VPC_Group_FindOrCreateGroupTag(pGroupName, false); + + if (groupTagIndex != INVALID_INDEX) { + FOR_EACH_VEC(m_GroupTags[groupTagIndex].groups, m) { + FOR_EACH_VEC(m_Groups[m_GroupTags[groupTagIndex].groups[m]].projects, n) { + projectList.AddToTail( + m_Groups[m_GroupTags[groupTagIndex].groups[m]].projects[n]); + } + } + } + + return projectList.Count(); +} + +//----------------------------------------------------------------------------- +// Checks to ensure the bin path is in the same tree as the vpc_scripts +// Returns true if bin path valid +//----------------------------------------------------------------------------- +#if !defined(POSIX) +bool CVPC::CheckBinPath(char *pOutBinPath, int outBinPathSize) { + char szScriptPath[MAX_PATH]; + char szDirectory[MAX_PATH]; + char szLastDirectory[MAX_PATH]; + + // non destructively determine the vpc_scripts directory + bool bFound = false; + szLastDirectory[0] = '\0'; + szScriptPath[0] = '\0'; + V_GetCurrentDirectory(szDirectory, sizeof(szDirectory)); + while (1) { + V_ComposeFileName(szDirectory, "vpc_scripts", szScriptPath, + sizeof(szScriptPath)); + struct _stat statBuf; + if (_stat(szScriptPath, &statBuf) != -1) { + bFound = true; + break; + } + + // previous dir + V_ComposeFileName(szDirectory, "..", szScriptPath, sizeof(szScriptPath)); + + char fullPath[MAX_PATH]; + if (_fullpath(fullPath, szScriptPath, sizeof(fullPath))) { + V_strncpy(szDirectory, fullPath, sizeof(szDirectory)); + } + + if (!V_stricmp(szDirectory, szLastDirectory)) { + // can back up no further + break; + } + strcpy(szLastDirectory, szDirectory); + } + + if (!bFound) { + VPCError( + "Failed to determine source directory from current path. Expecting " + "'vpc_scripts' in source path."); + } + + char szSourcePath[MAX_PATH]; + strcpy(szSourcePath, szDirectory); + + // check to ensure that executeable and src directory are in the same tree + // executeable needs to be tightly bound to its vpc_scripts + char szModuleBinPath[MAX_PATH]; + GetModuleFileName(NULL, szModuleBinPath, sizeof(szModuleBinPath)); + + // cannot trust output from GetModuleFileName(), occasionally has ./ or ../ + // screwing up comparisons + V_RemoveDotSlashes(szModuleBinPath, '\\'); + V_strlower(szModuleBinPath); + V_strncpy(pOutBinPath, szModuleBinPath, outBinPathSize); + + // allowed to run from a root "devbin", for use with junctions + if (Sys_StringPatternMatch("?:\\devbin\\vpc.exe", szModuleBinPath)) + return true; + + char *pString = V_stristr(szModuleBinPath, "\\devtools\\bin\\"); + if (pString) { + // source dirs should match + char chSave = *pString; + *pString = '\0'; + bool bSame = V_stricmp(szSourcePath, szModuleBinPath) == 0; + *pString = chSave; + + if (bSame) { + return true; + } + } else { + VPCError( + "Executable not running from 'devtools/bin' but from unexpected " + "directory '%s'", + szModuleBinPath); + } + + // mismatched, wierd bin patch could have been a result of user's environment + // path use expected source path which is based on user's cwd to get the real + // bin path + V_strncpy(pOutBinPath, szSourcePath, outBinPathSize); + V_strncat(pOutBinPath, "\\devtools\\bin\\vpc.exe", outBinPathSize); + struct _stat statBuf; + if (_stat(pOutBinPath, &statBuf) == -1) { + VPCError("Correct executeable missing, should be at '%s'", pOutBinPath); + } + + // yikes, wrong executeable was started, agreed behavior was to restart based + // on user's cwd REALLY want users to see this, it indicates a possible hazard + // of using the wrong vpc + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "****************************************************************" + "****************\n"); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "Wrong Executable '%s' Running!\nRestarting at '%s'\n", + szModuleBinPath, pOutBinPath); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "****************************************************************" + "****************\n"); + + return false; +} +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::DetermineSourcePath() { + char source_path[MAX_PATH]; + char last_directory[MAX_PATH]; + + char old_path[MAX_PATH]; + V_GetCurrentDirectory(old_path, sizeof(old_path)); + + // find vpc_scripts from cwd + last_directory[0] = '\0'; + bool is_found{false}; + + while (1) { + V_GetCurrentDirectory(source_path, sizeof(source_path)); + if (!V_stricmp(source_path, last_directory)) { + // can back up no further + break; + } + + V_strncpy(last_directory, source_path, sizeof(last_directory)); + + char test_directory[MAX_PATH]; + V_ComposeFileName(source_path, "vpc_scripts", test_directory, + sizeof(test_directory)); + + struct _stat statBuf; + if (_stat(test_directory, &statBuf) != -1) { + is_found = true; + break; + } + + // previous dir + char prev_directory[MAX_PATH]; + V_ComposeFileName(source_path, "..", prev_directory, + sizeof(prev_directory)); + V_SetCurrentDirectory(prev_directory); + } + + if (!is_found) { + VPCError( + "Failed to determine source directory from current path. Expecting " + "'vpc_scripts' in source path."); + } + + // Remember the source path and restore the path to where it was. + m_SourcePath = source_path; + V_SetCurrentDirectory(old_path); + + // always emit source path, identifies MANY redundant user problems + // users can easily run from an unintended place due to botched path, mangled + // directories, etc + Log_Msg(LOG_VPC, "Source Path: %s\n", m_SourcePath.Get()); +} + +//----------------------------------------------------------------------------- +// Sets the working directory to .../vpc_scripts as all scripts are +// guaranteed relative to the vpc script directory. +//----------------------------------------------------------------------------- +void CVPC::SetDefaultSourcePath() { V_SetCurrentDirectory(m_SourcePath.Get()); } + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVPC::IsProjectCurrent(const char *pOutputFilename, bool bSpewStatus) { + // default is project is stale + if (!Sys_Exists(pOutputFilename)) { + return false; + } + + if (Is2010() && !Sys_Exists(CFmtStr("%s.filters", pOutputFilename))) { + return false; + } + + char error[1024]; + bool is_crc_valid{VPC_CheckProjectDependencyCRCs( + pOutputFilename, m_SupplementalCRCString.Get(), error)}; + + if (bSpewStatus) { + if (is_crc_valid) { + VPCStatus(true, "Valid: '%s' Passes CRC Checks.", pOutputFilename); + } else { + VPCStatus(true, "Stale: '%s' Requires Rebuild. [%s]", pOutputFilename, + error); + } + } + + return is_crc_valid; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::SpewUsage(void) { + // skip header if user requested specific detail + bool bNoHeader = m_bSpewPlatforms || m_bSpewGames || m_bSpewProjects || + m_bSpewGroups || m_bSpewProperties || + m_BuildCommands.Count(); + + if (!bNoHeader) { + Log_Msg(LOG_VPC, "\n"); + + if (!m_bHelp) { + // terse + Log_Msg(LOG_VPC, "Type vpc /h for help...\n"); + } else { + // verbose + Log_Msg(LOG_VPC, "usage: vpc [options] <+/-/*project or group>\n"); + + Log_Msg(LOG_VPC, "\n"); + Log_Msg(LOG_VPC, "Examples:\n"); + + Log_Msg(LOG_VPC, "\n"); + Log_Msg(LOG_VPC, " Single .vcproj generation:\n"); + Log_Msg(LOG_VPC, + " vpc +client /hl2 <-- Creates a Win32 .vcproj for the " + "HL2 client.\n"); + Log_Msg(LOG_VPC, + " vpc +shaderapi /x360 <-- Creates a Xbox360 .vcproj for the " + "shaderapi.\n"); + + Log_Msg(LOG_VPC, "\n"); + Log_Msg(LOG_VPC, + " Multiple .vcproj generation - Multiple Projects for Games and " + "Platforms:\n"); + Log_Msg(LOG_VPC, + " vpc +client /hl2 /tf <-- Creates ALL the Win32 " + ".vcprojs for the HL2 and TF client.\n"); + Log_Msg(LOG_VPC, + " vpc +gamedlls /allgames <-- Creates ALL the Win32 " + ".vcprojs for client and server for all GAMES.\n"); + Log_Msg(LOG_VPC, + " vpc +tools -tier0 /win32 /x360 <-- Creates ALL the Win32 " + "and Xbox360 .vcprojs for the tool projects but not the tier0 " + "project.\n"); + + Log_Msg(LOG_VPC, "\n"); + Log_Msg(LOG_VPC, " Use +/- to add or remove projects or groups.\n"); + Log_Msg( + LOG_VPC, + " Use * to add a project and all projects that depend on it.\n"); + Log_Msg( + LOG_VPC, + " Use @ to add a project and all projects that it depends on.\n"); + Log_Msg( + LOG_VPC, + " Use /h spew final target build set only (no .vcproj created).\n"); + Log_Msg(LOG_VPC, "\n"); + Log_Msg( + LOG_VPC, + " Further details can be found on Valve Internal Wiki on VPC.\n"); + + Log_Msg(LOG_VPC, "\n--- OPTIONS ---\n"); + Log_Msg(LOG_VPC, + "[/q]: Quiet mode (quiet mode is automatically on if " + "the VPC_QUIET environment variable is set)\n"); + Log_Msg(LOG_VPC, "[/v]: Verbose\n"); + Log_Msg( + LOG_VPC, + "[/f]: Force generate .vcproj, otherwise use crc checks\n"); + Log_Msg(LOG_VPC, "[/dp]: Decorate project names with platform\n"); + Log_Msg(LOG_VPC, + "[/testmode]: Override output .vcproj file to be named " + "'test.vcproj'\n"); +#ifdef VPC_SCC_INTEGRATION + Log_Msg(LOG_VPC, + "[/nosrcctl]: Disable P4SCC source control integration - can " + "also set environment variable VPC_SRCCTL to 0\n"); +#else + Log_Msg(LOG_VPC, + "[/srcctl]: Enable P4SCC source control integration - can " + "also set environment variable VPC_SRCCTL to 1\n"); +#endif + Log_Msg(LOG_VPC, + "[/mirror]: - Mirror output files to specified path. " + "Used for A:B testing.\n"); + Log_Msg(LOG_VPC, + "[/2022]: Generate projects and solutions for Visual " + "Studio 2022 [default]\n"); + Log_Msg(LOG_VPC, + "[/2015]: Generate projects and solutions for Visual " + "Studio 2015\n"); + Log_Msg(LOG_VPC, + "[/2013]: Generate projects and solutions for Visual " + "Studio 2013\n"); + Log_Msg(LOG_VPC, + "[/2012]: Generate projects and solutions for Visual " + "Studio 2012\n"); + Log_Msg(LOG_VPC, + "[/2010]: Generate projects and solutions for Visual " + "Studio 2010\n"); + Log_Msg(LOG_VPC, + "[/2005]: Generate projects and solutions for Visual " + "Studio 2005\n"); + Log_Msg(LOG_VPC, + "[/2008]: Generate projects and solutions for Visual " + "Studio 2008\n"); + Log_Msg(LOG_VPC, + "[/windows]: Generate projects for both Win32 and Win64\n"); + Log_Msg(LOG_VPC, "[/unity]: Enable unity file generation\n"); + Log_Msg(LOG_VPC, + "[/32bittools]: Specify 32-bit toolchain in VC++ even when " + "compiling 64 bit target\n"); + + Log_Msg(LOG_VPC, "\n--- Help ---\n"); + Log_Msg(LOG_VPC, "[/h]: Help\n"); + Log_Msg(LOG_VPC, "[/?]: Help\n"); + Log_Msg(LOG_VPC, "[/platforms]: Spew Platforms\n"); + Log_Msg(LOG_VPC, "[/games]: Spew Games\n"); + Log_Msg(LOG_VPC, "[/projects]: Spew Projects\n"); + Log_Msg(LOG_VPC, "[/groups]: Spew Groups\n"); + Log_Msg(LOG_VPC, "[/properties]: Spew VS2005 Properties\n"); + + Log_Msg(LOG_VPC, "\n--- Conditionals ---\n"); + Log_Msg(LOG_VPC, "[/profile]: Set Reserved $PROFILE=1\n"); + Log_Msg(LOG_VPC, "[/retail]: Set Reserved $RETAIL=1\n"); + Log_Msg(LOG_VPC, "[/callcap]: Set Reserved $CALLCAP=1\n"); + Log_Msg(LOG_VPC, "[/fastcap]: Set Reserved $FASTCAP=1\n"); + Log_Msg(LOG_VPC, "[/memtest]: Set Reserved $MEMTEST=1\n"); + Log_Msg(LOG_VPC, "[/nofpo]: Set Reserved $NOFPO=1\n"); + Log_Msg(LOG_VPC, "[/lv]: Set Reserved $LV=1\n"); + Log_Msg(LOG_VPC, "[/demo]: Set Reserved $DEMO=1\n"); + Log_Msg(LOG_VPC, "[/no_steam]: Set Reserved $NO_STEAM=1\n"); + Log_Msg(LOG_VPC, "[/qtdebug]: Set Reserved $QTDEBUG=1\n"); + Log_Msg(LOG_VPC, "[/no_ceg]: Set Reserved $NO_CEG=1\n"); + Log_Msg(LOG_VPC, "[/upload_ceg]: Set Reserved $UPLOAD_CEG=1\n"); + + Log_Msg(LOG_VPC, "\n--- Other ---\n"); + Log_Msg(LOG_VPC, + "[/mksln]: <.sln filename> - make a solution file\n"); + Log_Msg(LOG_VPC, + "[/p4sln]: <.sln filename> - make a " + "solution file based on\n"); + Log_Msg(LOG_VPC, + " the changelist. Changelists can be specific " + "numbers, 0 or \"default\"\n"); + Log_Msg(LOG_VPC, + " for the default changelist, or \"all\" for all " + "active changelists.\n"); + Log_Msg( + LOG_VPC, + "[/nop4add]: Don't automatically add project files to Perforce\n"); + Log_Msg(LOG_VPC, + "[/slnitems]: - adds all files listed in " + "to generated\n"); + Log_Msg(LOG_VPC, " solutions\n"); + Log_Msg(LOG_VPC, + "[/showdeps]: Show an example dependency chain for each " + "project that depends\n"); + Log_Msg(LOG_VPC, + " on your p4 change list(s). Use with /p4sln.\n"); + Log_Msg(LOG_VPC, + "[/checkfiles]: Check for the existence of files in $file " + "commands. For debugging vpc files.\n"); + Log_Msg(LOG_VPC, + " Only works if the currrent directory is the " + "project directory.\n"); + // Log_Msg( LOG_VPC, "[/novpcgame]: Disable + // reserved vpc macro $VPCGAME and $VPCGAMECAPS.\n" ); + // Log_Msg( LOG_VPC, " By default if a single game is specified on command + // line, then that specified\n" ); Log_Msg( + // LOG_VPC, " game name will be used as a value for $VPCGAME + // and $VPCGAMECAPS macros.\n" ); + Log_Msg(LOG_VPC, + "[/define:xxx]: Enable a custom conditional $XXX to use for " + "quick testing in VPC files.\n"); + } + } + + if (m_Conditionals.Count() && m_bSpewPlatforms) { + bool bFirstDefine = false; + for (auto &&c : m_Conditionals) { + if (c.type != CONDITIONAL_PLATFORM) continue; + + if (!bFirstDefine) { + Log_Msg(LOG_VPC, "\n--- PLATFORMS ---\n"); + bFirstDefine = true; + } + + Log_Msg(LOG_VPC, "%s%s\n", c.upperCaseName.String(), + c.m_bDefined ? " = 1" : ""); + } + } + + if (m_Conditionals.Count() && m_bSpewGames) { + bool bFirstGame = false; + for (auto &&c : m_Conditionals) { + if (c.type != CONDITIONAL_GAME) continue; + + if (!bFirstGame) { + Log_Msg(LOG_VPC, "\n--- GAMES ---\n"); + bFirstGame = true; + } + + Log_Msg(LOG_VPC, "%s%s\n", c.upperCaseName.String(), + c.m_bDefined ? " = 1" : ""); + } + } + + if (m_Projects.Count() && m_bSpewProjects) { + // spew all sorted projects + Log_Msg(LOG_VPC, "\n--- PROJECTS ---\n"); + CUtlRBTree sorted(0, 0, CaselessStringLessThan); + for (auto &&p : m_Projects) { + sorted.Insert(p.name.String()); + } + for (auto i = sorted.FirstInorder(); i != sorted.InvalidIndex(); + i = sorted.NextInorder(i)) { + Log_Msg(LOG_VPC, "[+/-] %s\n", sorted[i]); + } + } + + if (g_pVPC->m_GroupTags.Count() && m_bSpewGroups) { + // spew all sorted groups + Log_Msg(LOG_VPC, "\n--- GROUPS ---\n"); + CUtlRBTree sorted(0, 0, CaselessStringLessThan); + for (auto &> : g_pVPC->m_GroupTags) { + if (!gt.bSameAsProject) { + sorted.Insert(gt.name.String()); + } + } + for (auto i = sorted.FirstInorder(); i != sorted.InvalidIndex(); + i = sorted.NextInorder(i)) { + Log_Msg(LOG_VPC, "[+/-] %s\n", sorted[i]); + } + } + +#if 0 +#if defined(_WIN32) + if ( m_bSpewProperties ) + { + for ( int i = 0; i < KEYWORD_MAX; i++ ) + { + VPC_Config_SpewProperties( (configKeyword_e)i ); + } + } +#endif +#endif + + if (m_BuildCommands.Count()) { + // spew details about each command + Log_Msg(LOG_VPC, "\nUser Build Commands:\n"); + Log_Msg(LOG_VPC, "--------------------\n"); + for (auto &&c : m_BuildCommands) { + Log_Msg(LOG_VPC, "%s\n", c.String()); + + groupTagIndex_t groupTagIndex = + VPC_Group_FindOrCreateGroupTag(c.Get() + 1, false); + if (groupTagIndex == INVALID_INDEX) { + Log_Msg(LOG_VPC, " ??? (Unknown Group)\n"); + } else { + const groupTag_t &groupTag = g_pVPC->m_GroupTags[groupTagIndex]; + + for (auto &&g : groupTag.groups) { + const group_t &group = m_Groups[g]; + + for (auto &&p : group.projects) { + Log_Msg(LOG_VPC, " %s\n", m_Projects[p].name.String()); + } + } + } + } + + Log_Msg(LOG_VPC, "\nTarget Projects:\n"); + Log_Msg(LOG_VPC, "----------------\n"); + + if (m_TargetProjects.Count()) { + for (auto &&p : m_TargetProjects) { + Log_Msg(LOG_VPC, "%s\n", m_Projects[p].name.String()); + } + } else { + Log_Msg(LOG_VPC, "Empty Set (no output)\n"); + } + + Log_Msg(LOG_VPC, "\nTarget Games:\n"); + Log_Msg(LOG_VPC, "-------------\n"); + + bool bHasDefine = false; + for (auto &&c : m_Conditionals) { + if (c.type != CONDITIONAL_GAME) continue; + + if (c.m_bDefined) { + Log_Msg(LOG_VPC, "$%s = 1\n", c.upperCaseName.String()); + bHasDefine = true; + } + } + + if (!bHasDefine) { + Log_Msg(LOG_VPC, "No Game Set!\n"); + } + + Log_Msg(LOG_VPC, "\nTarget Platforms:\n"); + Log_Msg(LOG_VPC, "-----------------\n"); + + bHasDefine = false; + for (auto &&c : m_Conditionals) { + if (c.type != CONDITIONAL_PLATFORM) continue; + + if (c.m_bDefined) { + Log_Msg(LOG_VPC, "$%s = 1\n", c.upperCaseName.String()); + bHasDefine = true; + } + } + + if (!bHasDefine) { + Log_Msg(LOG_VPC, "No Platform Set!\n"); + } + + Log_Msg(LOG_VPC, "\nCustom Conditionals:\n"); + Log_Msg(LOG_VPC, "---------------------\n"); + + bHasDefine = false; + for (auto &&c : m_Conditionals) { + if (c.type != CONDITIONAL_CUSTOM) continue; + + if (c.m_bDefined) { + Log_Msg(LOG_VPC, "$%s = 1\n", c.upperCaseName.String()); + bHasDefine = true; + } + } + + if (!bHasDefine) { + Log_Msg(LOG_VPC, "No Custom Defines Set!\n"); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::HandleSingleCommandLineArg(const char *pArg) { + if ((pArg[0] == '-') || (pArg[0] == '/')) { + // skip past arg prefix + const char *pArgName = pArg + 1; + + // check options + if (!V_stricmp(pArgName, "h") || !V_stricmp(pArgName, "?") || + !V_stricmp(pArgName, "help")) { + m_bHelp = true; + m_bUsageOnly = true; + } else if (!V_stricmp(pArgName, "v") || !V_stricmp(pArgName, "verbose")) { + m_bVerbose = true; + } else if (!V_stricmp(pArgName, "testmode") || + !V_stricmp(pArgName, "test")) { + m_bTestMode = true; + } else if (!V_stricmp(pArgName, "f") || !V_stricmp(pArgName, "force")) { + m_bForceGenerate = true; + } else if (!V_stricmp(pArgName, "no_posix_pch")) { + // Not implemented for win32 generators since those just pass through raw + // options :-/ + m_bNoPosixPCH = true; + // Ensure this changes CRC. Ideally it would only change if anything ended + // up using the PCH conditional + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "novpcgame")) { + m_bEnableVpcGameMacro = false; + } else if (!V_stricmp(pArgName, "checkfiles")) { + m_bCheckFiles = true; + } else if (!V_stricmp(pArgName, "nocheckfiles")) { + m_bCheckFiles = false; + } else if (!V_stricmp(pArgName, "showcaseissues") || + !V_stricmp(pArgName, "showcase")) { + m_bShowCaseIssues = true; + } else if (!V_stricmp(pArgName, "dp")) { + m_bDecorateProject = true; + } else if (char const *szActualDecorateName = + StringAfterPrefix(pArgName, "decorate:")) { + m_bDecorateProject = true; + m_strDecorate = szActualDecorateName; + } else if (!V_stricmp(pArgName, "dedicated")) { + m_bDedicatedBuild = true; + m_bAppendSrvToDedicated = true; + m_bUseValveBinDir = true; + } else if (!V_stricmp(pArgName, "use_valve_bin")) { + m_bUseValveBinDir = true; + } else if (!V_stricmp(pArgName, "platforms") || + !V_stricmp(pArgName, "plats")) { + m_bSpewPlatforms = true; + m_bUsageOnly = true; + } else if (!V_stricmp(pArgName, "games")) { + m_bSpewGames = true; + m_bUsageOnly = true; + } else if (!V_stricmp(pArgName, "projects")) { + m_bSpewProjects = true; + m_bUsageOnly = true; + } else if (!V_stricmp(pArgName, "groups")) { + m_bSpewGroups = true; + m_bUsageOnly = true; + } else if (!V_stricmp(pArgName, "properties")) { + m_bSpewProperties = true; + m_bUsageOnly = true; + } else if (!V_stricmp(pArgName, "allgames")) { + // shortcut for all games defined + for (int j = 0; j < m_Conditionals.Count(); j++) { + if (m_Conditionals[j].type == CONDITIONAL_GAME) { + m_Conditionals[j].m_bDefined = true; + } + } + } else if (!V_stricmp(pArgName, "showdeps")) { + m_bShowDeps = true; + } else if (!V_stricmp(pArgName, "nop4add")) { + m_bP4AutoAdd = false; + } else if (!V_stricmp(pArgName, "2005")) { + m_eVSVersion = k_EVSVersion_2005; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "2008")) { + m_eVSVersion = k_EVSVersion_2008; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "2010")) { + m_eVSVersion = k_EVSVersion_2010; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "2012")) { + m_eVSVersion = k_EVSVersion_2012; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "2013")) { + m_eVSVersion = k_EVSVersion_2013; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "2015")) { + m_eVSVersion = k_EVSVersion_2015; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "2022")) { + m_eVSVersion = k_EVSVersion_2022; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "nounity")) { + m_bUseUnity = false; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "unity")) { + m_bUseUnity = true; + m_ExtraOptionsCRCString += pArgName; + } else if (!V_stricmp(pArgName, "verbosemakefile")) { + m_bVerboseMakefile = true; + } else if (char const *szActualDefineName = + StringAfterPrefix(pArgName, "define:")) { + // allow setting custom defines straight from command line + conditional_t *pConditional = + FindOrCreateConditional(szActualDefineName, true, CONDITIONAL_CUSTOM); + if (pConditional) { + pConditional->m_bDefined = true; + + m_ExtraOptionsCRCString += + "/define:"; // force this into additional CRC string + m_ExtraOptionsCRCString += + pConditional->name.Get(); // force this into additional CRC string + } + } else if (!V_stricmp(pArgName, "32bittools")) { + m_b32BitTools = + true; // use 32-bit toolchain even when building 64-bit targets + m_ExtraOptionsCRCString += + pArgName; // Make sure these options affect the CRC. + } else if (!V_stricmp(pArgName, "nosrcctl")) { + m_bP4SCC = false; + m_ExtraOptionsCRCString += + pArgName; // Make sure these options affect the CRC. + } else if (!V_stricmp(pArgName, "srcctl")) { + m_bP4SCC = true; + m_ExtraOptionsCRCString += + pArgName; // Make sure these options affect the CRC. + } else { + // not a recognized option, try conditionals + // find in list of conditionals + conditional_t *pConditional = + FindOrCreateConditional(pArgName, false, CONDITIONAL_NULL); + if (!pConditional) { + // not a recognized conditional, add to build commands + intp index = m_BuildCommands.AddToTail(); + m_BuildCommands[index] = pArg; + } else { + // found conditional, mark as defined + pConditional->m_bDefined = true; + } + } + } else if (pArg[0] == '+' || pArg[0] == '*' || pArg[0] == '@') { + // add to build commands + intp index = m_BuildCommands.AddToTail(); + m_BuildCommands[index] = pArg; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::ParseBuildOptions(int argc, const char *argv[]) { + m_bDedicatedBuild = false; + m_bAppendSrvToDedicated = false; + m_bUseValveBinDir = false; + + // parse options + // prefer +??? or -??? prefix syntax for groups and /??? for options because + // less confusing for new vpc users for ease we will support -??? prefix + // syntax for matched options as well + for (int i = 1; i < argc; i++) { + const char *pArg = argv[i]; + + if (!V_stricmp(pArg, "/mksln")) { + if (!m_P4SolutionFilename.IsEmpty()) { + VPCError("Can't use /mksln with /p4sln."); + } + + if ((i + 1) >= argc) { + VPCError("/mksln requires a filename after it."); + } + + // If the next parameter is a standard + or - or / or * parameter, then we + // take that to be the name of the solution file. So vpc /mksln +engine + // would generate engine.sln. + if (argv[i + 1][0] == '+' || argv[i + 1][0] == '-' || + argv[i + 1][0] == '/' || argv[i + 1][0] == '*' || + argv[i + 1][0] == '@') { + m_MKSolutionFilename = &argv[i + 1][1]; + } else { + m_MKSolutionFilename = argv[i + 1]; + ++i; + } + } else if (!V_stricmp(pArg, "/p4sln")) { + if (!m_MKSolutionFilename.IsEmpty()) { + VPCError("Can't use /mksln with /p4sln."); + } + + // Get the solution filename. + ++i; + if (i >= argc || argv[i][0] == '+' || argv[i][0] == '-' || + argv[i][0] == '/' || argv[i][0] == '*' || argv[i][0] == '@') { + VPCError( + "%s [ [restrict_to_group] " + "].", + pArg); + } + + m_P4SolutionFilename = argv[i]; + + // Get the changelist number. + while (1) { + ++i; + + // No more args? + if (i >= argc) break; + + // Special syntax for including all changelists. + if (V_stricmp(argv[i], "all") == 0) { + m_iP4Changelists.AddToTail(-1); + continue; + } + + // Special syntax for including default changelists. + if (V_stricmp(argv[i], "default") == 0) { + m_iP4Changelists.AddToTail(0); + continue; + } + + // This arg isn't a changelist number? + if (argv[i][0] < '0' || argv[i][0] > '9') { + --i; + break; + } + + // Add the changelist number. + m_iP4Changelists.AddToTail(atoi(argv[i])); + } + + // Make sure at least one changelist number was specified. + if (m_iP4Changelists.Count() == 0) { + VPCError( + "%s [additional changelist " + "numbers] [ [restrict_to_group] ].", + pArg); + } + + // Get the group restriction. + while (1) { + ++i; + + // No more args? + if (i >= argc) break; + + if (argv[i][0] != '[' || argv[i][V_strlen(argv[i]) - 1] != ']') { + // This arg isn't a group name + --i; + break; + } + + // strip the braces + CUtlString groupName = argv[i]; + intp nLastChar = groupName.Length() - 1; + groupName = groupName.Slice(1, nLastChar); + + // Add the restricted group name + m_P4GroupRestrictions.AddToTail(groupName); + } + } else if (!V_stricmp(pArg, "/slnitems")) { + // Get the solution items filename + ++i; + if (i >= argc || argv[i][0] == '+' || argv[i][0] == '-' || + argv[i][0] == '/' || argv[i][0] == '*' || argv[i][0] == '@') { + VPCError("/slnitems ."); + } + + m_SolutionItemsFilename = argv[i]; + } else if (!V_stricmp(pArg, "/mirror")) { + // force an output mirror, used for A:B comparison runs + ++i; + if (i >= argc || argv[i][0] == '+' || argv[i][0] == '-' || + argv[i][0] == '/' || argv[i][0] == '*' || argv[i][0] == '@') { + VPCError("/mirror ."); + } + + m_OutputMirrorString = argv[i]; + if (!m_OutputMirrorString.IsEmpty() && + !V_IsAbsolutePath(m_OutputMirrorString.Get())) { + VPCError("/mirror requires an absolute path specification."); + } + } else { + HandleSingleCommandLineArg(pArg); + } + } + + // If they did /p4sln but didn't specify any build commands, then have it + // check everything. + if (m_iP4Changelists.Count() > 0 && m_BuildCommands.Count() == 0) { + m_bP4SlnCheckEverything = true; + } + + CheckForInstalledXDK(); +} + +//----------------------------------------------------------------------------- +// Generate a string supplemental to CRC data, derived from command-line +// options, so varying certain command-line options can cause .VCPROJ +// rebuilds. +//----------------------------------------------------------------------------- +void CVPC::GenerateOptionsCRCString() { + m_SupplementalCRCString = "_"; + + conditional_t *pConditional = + FindOrCreateConditional("PROFILE", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Pr"; + } + + pConditional = FindOrCreateConditional("RETAIL", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Rt"; + } + + pConditional = FindOrCreateConditional("CALLCAP", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Cc"; + } + + pConditional = FindOrCreateConditional("FASTCAP", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Fc"; + } + + pConditional = FindOrCreateConditional("MEMTEST", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Mt"; + } + + pConditional = FindOrCreateConditional("NOFPO", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Nf"; + } + + pConditional = FindOrCreateConditional("LV", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Lv"; + } + + pConditional = FindOrCreateConditional("DEMO", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Dm"; + } + + pConditional = FindOrCreateConditional("NO_STEAM", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Ns"; + } + + pConditional = FindOrCreateConditional("QTDEBUG", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Qt"; + } + + pConditional = FindOrCreateConditional("NO_CEG", false, CONDITIONAL_NULL); + + // + // !!NEVER INTEGRATE THIS CHANGE TO REL BRANCHES!! + // !!THIS TURNS OF CEG FOR NON-REL BRANCHES!! + // -Jeep & AaronN + // + // if ( pConditional && pConditional->m_bDefined ) + if (pConditional) { + pConditional->m_bDefined = true; + m_SupplementalCRCString += "Nc"; + } + + pConditional = FindOrCreateConditional("UPLOAD_CEG", false, CONDITIONAL_NULL); + if (pConditional && pConditional->m_bDefined) { + m_SupplementalCRCString += "Uc"; + } + + if (!m_ExtraOptionsCRCString.IsEmpty()) { + m_SupplementalCRCString += CFmtStr("_%s_", m_ExtraOptionsCRCString.Get()); + } +} + +//----------------------------------------------------------------------------- +// Restart self from correct location and re-run. Returns FALSE if not +// applicable, otherwise TRUE if restart occurred. +//----------------------------------------------------------------------------- +bool CVPC::RestartFromCorrectLocation(bool *is_restart_child) { +#if defined(POSIX) + return false; +#else + // recursive restart guard + // restart is a hidden internal param, always the last argument + // presence identifies spawned process + bool is_restart{!V_stricmp(m_ppArgv[m_nArgc - 1], "/restart")}; + *is_restart_child = is_restart; + + char bin_path[MAX_PATH]; + if (!CheckBinPath(bin_path, sizeof(bin_path))) { + if (is_restart) { + VPCError("Cyclical Restart: Tell A Programmer!, Aborting."); + } + + // replicate arguments, add -restart as a recursion guard for the new + // process + const char *new_argv[128]; + if (m_nArgc >= static_cast(std::size(new_argv)) - 2) { + VPCError("Excessive Arguments: Tell A Programmer!, Aborting."); + } + + int i; + for (i = 0; i < m_nArgc; i++) { + new_argv[i] = m_ppArgv[i]; + } + new_argv[i++] = "/restart"; + new_argv[i++] = nullptr; + + // restart using synchronous semantic, async semantic causes wierd hang + intptr_t status{_spawnv(_P_WAIT, bin_path, new_argv)}; + + // called process exited normally + if (status == 0) return true; + + // called process exited with error, pass it along + if (status > 0) exit((int)status); + + // called process could not be started + VPCError("Restart of '%s' failed: %s\n", bin_path, strerror(errno)); + } + + // process is running from correct location + return false; +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::CheckForInstalledXDK() { +#ifndef POSIX + if (!IsPlatformDefined("X360")) { + // caller not doing any 360 work, so ignore + return; + } + + // quick and dirty early check for 360 XDK ability + // can only detect simplistic condition, VPC can't validate a perfect + // XDK/MSDEV installation + bool bHasXDK = false; + const char *pXDK = getenv("XEDK"); + if (pXDK && pXDK[0]) { + // look for expected compiler + char fullPath[MAX_PATH]; + V_strncpy(fullPath, pXDK, sizeof(fullPath)); + V_AppendSlash(fullPath, sizeof(fullPath)); + V_strncat(fullPath, "bin\\win32\\cl.exe", sizeof(fullPath)); + int fileSize = Sys_FileLength(fullPath, false); + if (fileSize > 0) { + bHasXDK = true; + } + } + if (!bHasXDK) { + VPCError( + "Cannot Build For Xbox 360, XDK is missing or damaged. Remove /x360 " + "from command line."); + } +#endif +} + +void CVPC::CreateOutputFilename(project_t *pProject, const char *pchPlatform, + const char *pchPhase, const char *pGameName, + const char *pchExtension) { + const char *pProjectFileNamePrefix = + m_bTestMode ? "test" : pProject->name.String(); + + m_OutputFilename = pProjectFileNamePrefix; + + if (pchPlatform && pchPlatform[0]) { + // non-pc platforms get decorated + m_OutputFilename += "_"; + m_OutputFilename += pchPlatform; + } + + if (m_bAppendSrvToDedicated) { + // Add _srv to the temp lib path. Ie: tier0/obj_tier0_linux32_srv + m_OutputFilename += "_srv"; + } + + if (pchPhase && pchPhase[0]) { + m_OutputFilename += "_"; + m_OutputFilename += pchPhase; + } + + if (pGameName && pGameName[0]) { + // game projects get decorated + m_OutputFilename += "_"; + m_OutputFilename += pGameName; + } + + if (m_bDecorateProject) { + char rgchDecorate[256] = ""; + DecorateProjectName(rgchDecorate); + m_OutputFilename += rgchDecorate; + } + + if (pchExtension && pchExtension[0]) { + m_OutputFilename += "."; + m_OutputFilename += pchExtension; + } +} + +bool CVPC::BuildTargetProject(IProjectIterator *pIterator, + projectIndex_t projectIndex, + script_t *pProjectScript, const char *pGameName) { + // evaluate the project's script conditional which determines game/platform + if (!EvaluateConditionalExpression(pProjectScript->m_condition.String())) { + // conditionals prevent this project from consideration + return false; + } + + // set once anything is expected to output + m_bAnyProjectQualified = true; + + if (!m_Projects.IsValidIndex(projectIndex)) { + // unexpected bad project index + Assert(0); + return false; + } + project_t *pProject = &m_Projects[projectIndex]; + + // track the internal project name, unaffected by user name mangling + m_ProjectName = pProject->name.String(); + m_LoadAddressName = pProject->name.String(); + + // win32 projects are the most prevalent, so by popular demand they have no + // decoration all other platforms use their platform name as a suffix + const char *pPlatformName = NULL; + if (!IsPlatformDefined("win32")) { + pPlatformName = GetTargetPlatformName(); + } + + // + // If we're doing multiple build phases, decorate the project + // names to indicate which phase they build in, and always include + // the platform. + // + const char *pPhaseName = NULL; + if (FindOrCreateConditional("phase2", false, CONDITIONAL_CUSTOM)) { + pPlatformName = GetTargetPlatformName(); + pPhaseName = "phase2"; + } else if (FindOrCreateConditional("phase1", false, CONDITIONAL_CUSTOM)) { + pPlatformName = GetTargetPlatformName(); + pPhaseName = "phase1"; + } + + // create a decorated project filename based on project/game/platform/etc + CreateOutputFilename( + pProject, pPlatformName, pPhaseName, pGameName, + g_pVPC->GetProjectGenerator()->GetProjectFileExtension()); + + // each vpc script is written with paths relative to their base + // force each script needs to start relative to their script location + // this allows vpc to be invoked anywhere, but the groups resolve their + // projects correctly + char szScriptPath[MAX_PATH]; + V_ComposeFileName(g_pVPC->GetStartDirectory(), pProjectScript->name.String(), + szScriptPath, sizeof(szScriptPath)); + V_StripFilename(szScriptPath); + m_ProjectPath = szScriptPath; + V_SetCurrentDirectory(szScriptPath); + + // build it + char szScriptName[MAX_PATH]; + Sys_StripPath(pProjectScript->name.String(), szScriptName); + return pIterator->VisitProject(projectIndex, szScriptName); +} + +//----------------------------------------------------------------------------- +// Iterate and build each of the projects. Game projects can themselves be +// auto-iterated to apply each of their mod variant. +//----------------------------------------------------------------------------- +void CVPC::IterateTargetProjects(CUtlVector &projectList, + IProjectIterator *pIterator) { + m_bGeneratedProject = false; + m_bAnyProjectQualified = false; + + if (!projectList.Count()) { + // nothing to do + return; + } + + for (int nProject = 0; nProject < projectList.Count(); nProject++) { + project_t *pProject = &m_Projects[projectList[nProject]]; + + // each project can have 1 or more scripts that are predicated by + // game/platform conditionals (i.e. client or server) + for (int nScript = 0; nScript < pProject->scripts.Count(); nScript++) { + script_t *pProjectScript = &pProject->scripts[nScript]; + + // occurrence of game condition(s) dictates iteration behavior + // client/server would have multiple game conditions + bool bHasGameCondition = g_pVPC->ConditionHasDefinedType( + pProjectScript->m_condition.String(), CONDITIONAL_GAME); + + if (!bHasGameCondition) { + // no game condition + BuildTargetProject(pIterator, projectList[nProject], pProjectScript, + NULL); + } else { + // auto iterate through all defined game conditionals, setting each in + // turn this provides for building say client for all mod(s) that it can + // support + for (int nTargetGame = 0; nTargetGame < m_Conditionals.Count(); + nTargetGame++) { + if (m_Conditionals[nTargetGame].type != CONDITIONAL_GAME || + !m_Conditionals[nTargetGame].m_bDefined) { + // the game conditions must be defined to be considered + // i.e. the user has specified to build /hl2 /tf2, but not /portal + continue; + } + + // only one game condition is active during project generation + for (int k = 0; k < m_Conditionals.Count(); k++) { + // unmark all game conditionals + if (m_Conditionals[k].type == CONDITIONAL_GAME) { + m_Conditionals[k].m_bGameConditionActive = false; + } + } + m_Conditionals[nTargetGame].m_bGameConditionActive = true; + + BuildTargetProject(pIterator, projectList[nProject], pProjectScript, + m_Conditionals[nTargetGame].name.String()); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Build all the projects in m_targetProjects. +//----------------------------------------------------------------------------- +bool CVPC::BuildTargetProjects() { + class CDefaultProjectIterator : public IProjectIterator { + public: + virtual bool VisitProject(projectIndex_t iProject, + const char *pScriptPath) { + Log_Msg(LOG_VPC, "\n"); + + // check project's crc signature + if (!g_pVPC->IsForceGenerate() && + g_pVPC->IsProjectCurrent(g_pVPC->GetOutputFilename(), true) && + !g_pVPC->IsForceIterate()) { + // valid, does not need to build + return false; + } + + return g_pVPC->ParseProjectScript(pScriptPath, 0, false, true); + } + }; + + if (!m_TargetProjects.Count()) { + VPCError( + "No recognized project(s) to build. Use /h or /projects or /groups to " + "spew more info."); + } + + /* At this point we might've built the project dependency graph, which + * increments the missing file count if *any* project in your VPC directory + * has a missing file, so we reset it here to get the accurate count. + */ + ResetMissingFilesCount(); + + CDefaultProjectIterator iterator; + IterateTargetProjects(m_TargetProjects, &iterator); + + if (GetMissingFilesCount() > 0) { + VPCError("%d files missing. VPC failed.\n", GetMissingFilesCount()); + } + + // Catch user attention to notify lack of any expected output + // Novice users would not be aware of expected conditionals + if (!m_bGeneratedProject && !m_bAnyProjectQualified) { + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), "\n"); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "----------------------------\n"); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "!!! No Project Generated !!!\n"); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "----------------------------\n"); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), "\n"); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "Possibly missing game, platform, or other conditional " + "expected by script.\n"); + Log_Warning(LOG_VPC, Color(255, 255, 0, 255), + "Use /h verify desired target build set.\n"); + + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Find the project that corresponds to the specified vcproj and setup +// to build that project. +//----------------------------------------------------------------------------- +void CVPC::FindProjectFromVCPROJ(const char *pScriptNameVCProj) { + // caller is specifying the output vcproj, i.e. via tool shortcut from within + // MSDEV to re-gen use the vpc standardized output vcproj name to determine + // re-gen parameters mod and platform will be separated by '_' after the + // project name resolve to correct project, best will be longest match, due to + // project names like foo_? and foo_bar_? + char szProject[MAX_PATH]; + szProject[0] = '\0'; + + size_t bestLen = 0; + for (int i = 0; i < m_Projects.Count(); i++) { + if (V_stristr(pScriptNameVCProj, m_Projects[i].name.String())) { + if (bestLen < strlen(m_Projects[i].name.String())) { + bestLen = strlen(m_Projects[i].name.String()); + strcpy(szProject, m_Projects[i].name.String()); + } + } + } + + if (bestLen == 0) { + VPCError("Could not resolve '%s' to any known projects", pScriptNameVCProj); + } + + // skip past known project + char szBuffer[MAX_PATH]; + V_StripExtension(pScriptNameVCProj + strlen(szProject), szBuffer, + sizeof(szBuffer)); + + // each token is separated by '_' + int numTokens = 0; + char *pToken = szBuffer; + char *pStart = pToken; + char szTokens[2][MAX_PATH]; + while (numTokens < 2) { + if (pStart[0] == '_') { + pStart++; + pToken = strchr(pStart, '_'); + if (!pToken) { + strcpy(szTokens[numTokens++], pStart); + break; + } else { + strncpy(szTokens[numTokens], pStart, pToken - pStart); + szTokens[numTokens][pToken - pStart] = '\0'; + numTokens++; + pStart = pToken; + } + } else { + break; + } + } + + // re-build a commandline + int localArgc = 0; + char *localArgv[16]; + char argBuffers[16][MAX_PATH]; + for (int i = 0; i < V_ARRAYSIZE(localArgv); i++) { + localArgv[i] = argBuffers[i]; + } + strcpy(localArgv[localArgc++], "vpc.exe"); + sprintf(localArgv[localArgc++], "+%s", szProject); + for (int i = 0; i < numTokens; i++) { + sprintf(localArgv[localArgc++], "/%s", szTokens[i]); + } + + ParseBuildOptions(localArgc, const_cast(localArgv)); +} + +//----------------------------------------------------------------------------- +// This sets up various defines that are funneled into the .vpc script and the +// #defines in the engine. +// +// VPC makes a distinction between defines and macros (defines are just binary +// on/off things that are used like this: +// $File "blah.cpp" [$somedefine] +// +// macros are used for substitutions like this: +// $File "blah.$SOMEMACRO" +//----------------------------------------------------------------------------- +void CVPC::SetMacrosAndConditionals() { + // Find the target platform. + conditional_t *pPlatformConditional = NULL; + for (int i = 0; i < m_Conditionals.Count(); i++) { + if (m_Conditionals[i].type == CONDITIONAL_PLATFORM && + m_Conditionals[i].m_bDefined) { + pPlatformConditional = &m_Conditionals[i]; + break; + } + } + + // Only one platform is allowed to be defined. + for (int i = 0; i < m_Conditionals.Count(); i++) { + if (&m_Conditionals[i] != pPlatformConditional && + m_Conditionals[i].type == CONDITIONAL_PLATFORM && + m_Conditionals[i].m_bDefined) { + // no no no, the user is not allowed to build multiple platforms + // simultaneously this prior feature really confused/crapped up the code, + // so absolutely not supporting that + VPCWarning("Detected multiple target platforms...Disabling '%s'", + m_Conditionals[i].name.String()); + m_Conditionals[i].m_bDefined = false; + } + } + + if (!pPlatformConditional) { + // no user specified platform defined, defaults to primary vpc.exe built + // platform +#if defined(WIN32) + pPlatformConditional = + FindOrCreateConditional("WIN32", false, CONDITIONAL_PLATFORM); +#elif defined(OSX) + pPlatformConditional = + FindOrCreateConditional("OSX32", false, CONDITIONAL_PLATFORM); +#elif defined(LINUX) + pPlatformConditional = + FindOrCreateConditional("LINUX32", false, CONDITIONAL_PLATFORM); +#elif defined(CYGWIN) + pPlatformConditional = + FindOrCreateConditional("CYGWIN", false, CONDITIONAL_PLATFORM); +#else +#error "Unsupported platform." +#endif + pPlatformConditional->m_bDefined = true; + } + + // Cache the platform name so that we can use it without dereferencing + // pPlatformConditional + CUtlString cVPCPlatform = pPlatformConditional->name; + // pPlatformConditional is a pointer into a container and it may be + // invalidated by future changes. NULLing the pointer is the only sure + // way to prevent programmers from using the invalid pointer. + pPlatformConditional = NULL; + VPCStatus(true, "Target Platform: %s", cVPCPlatform.String()); + + // src_main doesn't want this #define because it conflicts with Python's SDK. + // It really should be called something else that won't conflict with the rest + // of the world. + bool bIncludePlatformDefineInProjects = false; +#ifdef STEAM + bIncludePlatformDefineInProjects = true; +#endif + + SetMacro("PLATFORM", cVPCPlatform.String(), bIncludePlatformDefineInProjects); + + // DO NOT INTEGRATE OR TAKE THIS - THIS IS TEMP PORTING GLUE. + // This define will not exist in the Source2 branch. + SetMacro("SOURCE1", "1", true); + + // create reserved $QUOTE - used for embedding quotes, or use msdev's " + SetMacro("QUOTE", "\"", false); + + if (!V_stricmp(cVPCPlatform.String(), "WIN32") || + !V_stricmp(cVPCPlatform.String(), "WIN64") || + !V_stricmp(cVPCPlatform.String(), "X360")) { + // VS2010 is strictly win32/xbox360 + switch (m_eVSVersion) { + case k_EVSVersion_2022: + m_ExtraOptionsCRCString += "VS2022"; + SetConditional("VS2022", true); + + // temporarily allow VS2013 conditionals also as there are many. Will + // fix. + SetConditional("VS2015", true); + + // temporarily allow VS2013 conditionals also as there are many. Will + // fix. + SetConditional("VS2013", true); + + m_bUseVS2010FileFormat = true; + break; + + case k_EVSVersion_2015: + m_ExtraOptionsCRCString += "VS2015"; + SetConditional("VS2015", true); + + // temporarily allow VS2013 conditionals also as there are many. Will + // fix. + SetConditional("VS2013", true); + + m_bUseVS2010FileFormat = true; + break; + + case k_EVSVersion_2013: + m_ExtraOptionsCRCString += "VS2013"; + SetConditional("VS2013", true); + m_bUseVS2010FileFormat = true; + break; + + case k_EVSVersion_2012: + m_ExtraOptionsCRCString += "VS2012"; + SetConditional("VS2012", true); + m_bUseVS2010FileFormat = true; + break; + + case k_EVSVersion_2010: + m_ExtraOptionsCRCString += "VS2010"; + SetConditional("VS2010", true); + m_bUseVS2010FileFormat = true; + break; + + case k_EVSVersion_2008: + m_ExtraOptionsCRCString += "VS2008"; + SetConditional("VS2005", true); // use 2005 defines + m_bUseVS2010FileFormat = false; + break; + + default: + m_ExtraOptionsCRCString += "VS2005"; + SetConditional("VS2005", true); + m_bUseVS2010FileFormat = false; + break; + } + } + + // create and define various other platform related helper conditionals + // andmacros + if (V_stricmp(cVPCPlatform.String(), "WIN32") == 0 || + V_stricmp(cVPCPlatform.String(), "WIN64") == 0) { + if (V_stricmp(cVPCPlatform.String(), "WIN32") == 0) { + SetMacro("PLATSUBDIR", "\\win32", false); + } else { + SetMacro("PLATSUBDIR", "\\win64", false); + } + SetConditional("WINDOWS"); + + SetMacro("_DLL_EXT", ".dll", true); + SetMacro("_IMPLIB_EXT", ".lib", false); + + SetMacro("_IMPLIB_PREFIX", "", false); + SetMacro("_IMPLIB_DLL_PREFIX", "", false); + + SetMacro("_STATICLIB_PREFIX", "", false); + SetMacro("_STATICLIB_EXT", ".lib", false); + + SetMacro("_EXE_EXT", ".exe", false); + + SetMacro("_EXTERNAL_DLL_EXT", ".dll", true); + SetMacro("_EXTERNAL_IMPLIB_EXT", ".lib", false); + SetMacro("_EXTERNAL_STATICLIB_EXT", ".lib", false); + + } else if (V_stricmp(cVPCPlatform.String(), "X360") == 0) { + SetMacro("PLATSUBDIR", "\\x360", false); + + SetMacro("_DLL_EXT", "_360.dll", true); + SetMacro("_IMPLIB_EXT", "_360.lib", false); + + SetMacro("_IMPLIB_PREFIX", "", false); + + SetMacro("_IMPLIB_DLL_PREFIX", "", false); + + SetMacro("_STATICLIB_PREFIX", "", false); + SetMacro("_STATICLIB_EXT", "_360.lib", false); + + SetMacro("_EXE_EXT", ".exe", false); + } else if (V_stricmp(cVPCPlatform.String(), "PS3") == 0) { + SetMacro("PLATSUBDIR", "\\ps3", false); + + SetMacro("_DLL_EXT", "_ps3.sprx", true); + SetMacro("_IMPLIB_EXT", "_ps3.lib", false); + + SetMacro("_IMPLIB_PREFIX", "", false); + SetMacro("_IMPLIB_DLL_PREFIX", "", false); + + SetMacro("_STATICLIB_PREFIX", "", false); + SetMacro("_STATICLIB_EXT", "_ps3.lib", false); + + SetMacro("_EXE_EXT", ".self", false); + } else if (V_stricmp(cVPCPlatform.String(), "LINUX32") == 0 || + V_stricmp(cVPCPlatform.String(), "LINUX64") == 0) { + bool IsLinux32 = (V_stricmp(cVPCPlatform.String(), "LINUX32") == 0); + + SetMacro("PLATSUBDIR", IsLinux32 ? "\\linux32" : "\\linux64", false); + + SetConditional("LINUXALL"); + if (m_bDedicatedBuild) { + SetConditional("DEDICATED"); + } + SetConditional("POSIX"); + + SetMacro("LINUX", "1", true); + SetMacro("_LINUX", "1", true); + SetMacro("POSIX", "1", true); + SetMacro("_POSIX", "1", true); + + const char *str3264 = IsLinux32 ? "" : "64"; + const char *strSrv = m_bAppendSrvToDedicated ? "_srv" : ""; + CFmtStrN<128> strDso("%s%s.so", strSrv, str3264); + CFmtStrN<128> strLib("%s%s.a", strSrv, str3264); + + SetMacro("_DLL_EXT", strDso.Access(), true); + SetMacro("_IMPLIB_EXT", strDso.Access(), false); + SetMacro("_STATICLIB_EXT", strLib.Access(), false); + + // Extensions for external dependencies like libsteam_api.so (not + // libsteam_api_ds.so). VPC_Keyword_Folder in projectscript.cpp will check + // for ImpLibExternal or LibExternal and use these prefixes instead of + // _ds.so if they exist. + SetMacro("_EXTERNAL_DLL_EXT", ".so", true); + SetMacro("_EXTERNAL_IMPLIB_EXT", ".so", false); + SetMacro("_EXTERNAL_STATICLIB_EXT", ".a", false); + + // SetMacro( "_STATICLIB_PREFIX", "lib", false ); + SetMacro("_STATICLIB_PREFIX", "", false); + + SetMacro("_IMPLIB_PREFIX", "lib", false); + SetMacro("_IMPLIB_DLL_PREFIX", "lib", false); + SetMacro("_EXE_EXT", "", false); + SetMacro("_SYM_EXT", ".dbg", false); + + SetConditional("GL"); + } else if (V_stricmp(cVPCPlatform.String(), "OSX32") == 0 || + V_stricmp(cVPCPlatform.String(), "OSX64") == 0) { + if (V_stricmp(cVPCPlatform.String(), "OSX32") == 0) { + SetMacro("PLATSUBDIR", "\\osx32", false); + } else { + SetMacro("PLATSUBDIR", "\\osx64", false); + } + + SetConditional("OSXALL"); + if (m_bDedicatedBuild) { + SetConditional("DEDICATED"); + } + SetConditional("POSIX"); + SetMacro("_POSIX", "1", true); + + SetMacro("_DLL_EXT", ".dylib", true); + SetMacro("_IMPLIB_EXT", ".dylib", false); + + SetMacro("_IMPLIB_PREFIX", "lib", false); + SetMacro("_IMPLIB_DLL_PREFIX", "lib", false); + + // SetMacro( "_STATICLIB_PREFIX", "lib", false ); + SetMacro("_STATICLIB_PREFIX", "", false); + SetMacro("_STATICLIB_EXT", ".a", false); + + SetMacro("_EXE_EXT", "", false); + SetMacro("_SYM_EXT", ".dSYM", false); + + SetMacro("_EXTERNAL_DLL_EXT", ".dylib", true); + SetMacro("_EXTERNAL_IMPLIB_EXT", ".dylib", false); + SetMacro("_EXTERNAL_STATICLIB_EXT", ".a", false); + + // Mac defaults to GL on + SetConditional("GL"); + } else if (V_stricmp(cVPCPlatform.String(), "IOS") == 0) { + SetConditional("OSXALL"); + if (m_bDedicatedBuild) { + SetConditional("DEDICATED"); + } + SetConditional("POSIX"); + SetMacro("_POSIX", "1", true); + + SetConditional("IOS"); + SetMacro("_IOS", "1", true); + SetMacro("IOS", "1", true); + + SetMacro("_DLL_EXT", "_ios.dylib", true); + SetMacro("_IMPLIB_EXT", "_ios.dylib", false); + + SetMacro("_IMPLIB_PREFIX", "lib", false); + SetMacro("_IMPLIB_DLL_PREFIX", "lib", false); + + // SetMacro( "_STATICLIB_PREFIX", "lib", false ); + SetMacro("_STATICLIB_PREFIX", "", false); + SetMacro("_STATICLIB_EXT", "_ios.a", false); + + SetMacro("_EXE_EXT", "", false); + + SetMacro("_EXTERNAL_DLL_EXT", "_ios.dylib", true); + SetMacro("_EXTERNAL_IMPLIB_EXT", "_ios.dylib", false); + SetMacro("_EXTERNAL_STATICLIB_EXT", "_ios.a", false); + } else if (V_stricmp(cVPCPlatform.String(), "ANDROID") == 0) { + SetConditional("LINUXALL"); + if (m_bDedicatedBuild) { + SetConditional("DEDICATED"); + } + SetConditional("POSIX"); + SetConditional("ANDROID"); + + SetMacro("LINUX", "1", true); + SetMacro("_LINUX", "1", true); + SetMacro("POSIX", "1", true); + SetMacro("_POSIX", "1", true); + SetMacro("ANDROID", "1", true); + SetMacro("_ANDROID", "1", true); + + SetMacro("_DLL_EXT", "_an.so", true); + SetMacro("_IMPLIB_EXT", "_an.so", false); + + SetMacro("_IMPLIB_PREFIX", "lib", false); + SetMacro("_IMPLIB_DLL_PREFIX", "lib", false); + + SetMacro("_STATICLIB_PREFIX", "lib", false); + SetMacro("_STATICLIB_EXT", "_an.a", false); + + SetMacro("_EXE_EXT", "", false); + + SetMacro("_EXTERNAL_DLL_EXT", "_an.so", true); + SetMacro("_EXTERNAL_IMPLIB_EXT", "_an.so", false); + SetMacro("_EXTERNAL_STATICLIB_EXT", "_an.a", false); + + // and is a cross-compiled target + SetConditional("CROSS_COMPILED"); + SetMacro("CROSS_COMPILED", "1", true); + SetMacro("_CROSS_COMPILED", "1", true); + + SetConditional("GL"); + } else if (V_stricmp(cVPCPlatform.String(), "CYGWIN") == 0) { + SetMacro("PLATSUBDIR", "\\cygwin", false); + + SetConditional("CYGWIN"); + SetConditional("CYGWIN_WINDOWS_TARGET"); + SetConditional("DEDICATED"); + SetConditional("POSIX"); + + SetMacro("CYGWIN", "1", true); + SetMacro("_CYGWIN", "1", true); + SetMacro("CYGWIN_WINDOWS_TARGET", "1", true); + SetMacro("_CYGWIN_WINDOWS_TARGET", "1", true); + SetMacro("POSIX", "1", true); + SetMacro("_POSIX", "1", true); + + SetMacro("_DLL_EXT", ".dll", true); + SetMacro("_IMPLIB_EXT", ".dll.a", false); + + SetMacro("_IMPLIB_DLL_PREFIX", "", false); + SetMacro("_IMPLIB_PREFIX", "lib", false); + + // SetMacro( "_STATICLIB_PREFIX", "lib", false ); + SetMacro("_STATICLIB_PREFIX", "", false); + SetMacro("_STATICLIB_EXT", ".a", false); + + SetMacro("_EXE_EXT", ".exe", false); + } + + // DO NOT INTEGRATE OR TAKE THIS - THIS IS TEMP PORTING GLUE. + { + // CERT has been decided to be a platform permutation of RETAIL. + // The DOTA S1 scripts are not in a clean enough condition to place this + // logic there. The S2 scripts have it there along with similar common + // concepts. + conditional_t *pRetailConditional = + FindOrCreateConditional("RETAIL", false, CONDITIONAL_CUSTOM); + if (pRetailConditional && pRetailConditional->m_bDefined && + (!V_stricmp(cVPCPlatform.String(), "X360") || + !V_stricmp(cVPCPlatform.String(), "PS3"))) { + // CERT is a restricted console RETAIL concept, with publisher dictated + // rules, there is no CERT process for non-console platforms. + SetConditional("CERT"); + } + } + + // Set VPCGAME macro based on target game + if (m_bEnableVpcGameMacro) { + int nGameDefineIndex = -1; + for (int iOtherGameDefine = 0; iOtherGameDefine < m_Conditionals.Count(); + ++iOtherGameDefine) { + if (m_Conditionals[iOtherGameDefine].type == CONDITIONAL_GAME && + m_Conditionals[iOtherGameDefine].m_bDefined) { + if (nGameDefineIndex == -1) { + nGameDefineIndex = iOtherGameDefine; + } else { + // uh-oh, multiple games defined for target build + // can't set VPCGAME accurately + nGameDefineIndex = -2; + } + } + } + + SetMacro("VPCGAME", + (nGameDefineIndex >= 0) + ? m_Conditionals[nGameDefineIndex].name.Get() + : "valve", + true); + SetMacro("VPCGAMECAPS", + (nGameDefineIndex >= 0) + ? m_Conditionals[nGameDefineIndex].upperCaseName.Get() + : "VALVE", + true); + + // force this into additional CRC string + m_ExtraOptionsCRCString += CFmtStr("/vpcgame:%s", GetMacroValue("VPCGAME")); + } +} + +//----------------------------------------------------------------------------- +// Decorate project name +//----------------------------------------------------------------------------- +void CVPC::DecorateProjectName(char *pchProjectName) { + macro_t *pMacro = g_pVPC->FindOrCreateMacro("PLATFORM", false, NULL); + if (pMacro) { + char szPlatform[MAX_PATH]; + sprintf(szPlatform, " (%s)", pMacro->value.String()); + strcat(pchProjectName, szPlatform); + } + + const char *pchDecorate = g_pVPC->GetDecorateString(); + if (pchDecorate && V_strlen(pchDecorate) > 0) { + strcat(pchProjectName, pchDecorate); + } +} + +//----------------------------------------------------------------------------- +// Checks for command line /params ( +/- used for projects, so ICommandLine() +// not suitable) +//----------------------------------------------------------------------------- +bool CVPC::HasCommandLineParameter(const char *argv) { + for (int i = 1; i < m_nArgc; i++) { + if (V_stricmp(m_ppArgv[i], argv) == 0) return true; + } + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVPC::HasP4SLNCommand() { return HasCommandLineParameter("/p4sln"); } + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVPC::HandleP4SLN(IBaseSolutionGenerator *pSolutionGenerator) { +#if defined(WIN32) && !defined(NO_PERFORCE) + // If they want to generate a solution based on a Perforce changelist, adjust + // m_targetProjects and set it up like /mksln had been passed in. + if (m_iP4Changelists.Count() == 0) return false; + + if (!pSolutionGenerator) { + VPCError("No solution generator exists for this platform."); + } + + // Figure out where to put the solution file. + char szFullSolutionPath[MAX_PATH]; + if (V_IsAbsolutePath(m_P4SolutionFilename.Get())) { + V_strncpy(szFullSolutionPath, m_P4SolutionFilename.Get(), + sizeof(szFullSolutionPath)); + } else { + V_ComposeFileName(g_pVPC->GetStartDirectory(), m_P4SolutionFilename.Get(), + szFullSolutionPath, sizeof(szFullSolutionPath)); + } + + CProjectDependencyGraph dependencyGraph; + GenerateSolutionForPerforceChangelist(dependencyGraph, m_iP4Changelists, + pSolutionGenerator, szFullSolutionPath); + return true; +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::GetProjectDependencies( + CUtlVector &referencedProjects) { + bool bInSlnPass = m_bInMkSlnPass; + + m_bInMkSlnPass = true; + + // Find out what depends on what. + if (!m_dependencyGraph.HasGeneratedDependencies()) { + m_dependencyGraph.BuildProjectDependencies(0, m_pPhase1Projects); + } + + // GenerateBuildSet basically generates what we want, except it uses + // projectIndex_t's, meaning that we don't know what subset of games we should + // use until we've called VPC_IterateTargetProjects. + m_dependencyGraph.TranslateProjectIndicesToDependencyProjects( + m_TargetProjects, referencedProjects); + + m_bInMkSlnPass = bInSlnPass; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::HandleMKSLN(IBaseSolutionGenerator *solution_generator) { + if (m_MKSolutionFilename.IsEmpty()) return; + + m_bInMkSlnPass = true; + + if (!solution_generator) { + VPCError("No solution generator exists for this platform."); + } + + // Find out what depends on what. + if (!m_dependencyGraph.HasGeneratedDependencies()) { + m_dependencyGraph.BuildProjectDependencies(0); + } + + // GenerateBuildSet basically generates what we want, except it uses + // projectIndex_t's, meaning that we don't know what subset of games we should + // use until we've called VPC_IterateTargetProjects. + CUtlVector projects; + m_dependencyGraph.TranslateProjectIndicesToDependencyProjects( + m_TargetProjects, projects); + + // Generate a solution file. + char full_solution_path[MAX_PATH]; + if (V_IsAbsolutePath(m_MKSolutionFilename.Get())) { + V_strncpy(full_solution_path, m_MKSolutionFilename.Get(), + sizeof(full_solution_path)); + } else { + V_ComposeFileName(g_pVPC->GetStartDirectory(), m_MKSolutionFilename.Get(), + full_solution_path, sizeof(full_solution_path)); + } + + if (m_pPhase1Projects != nullptr) { + projects.AddVectorToTail(*m_pPhase1Projects); + } + + solution_generator->GenerateSolutionFile(full_solution_path, projects); + + m_bInMkSlnPass = false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVPC::SetupGenerators() { + extern IBaseSolutionGenerator *GetSolutionGenerator_Win32(); + extern IBaseProjectGenerator *GetWin32ProjectGenerator(); + extern IBaseProjectGenerator *GetWin32ProjectGenerator_2010(); + extern IBaseProjectGenerator *GetPS3ProjectGenerator(); + extern IBaseProjectGenerator *GetXbox360ProjectGenerator(); + extern IBaseProjectGenerator *GetXbox360ProjectGenerator_2010(); + extern IBaseProjectGenerator *GetMakefileProjectGenerator(); + extern IBaseSolutionGenerator *GetMakefileSolutionGenerator(); + extern IBaseProjectGenerator *GetXcodeProjectGenerator(); + extern IBaseSolutionGenerator *GetXcodeSolutionGenerator(); + + bool bIsLinux = IsPlatformDefined("LINUX32") || IsPlatformDefined("LINUX64"); + bool bIsOSX = IsPlatformDefined("OSX32") || IsPlatformDefined("OSX64"); + +#if defined(WIN32) + // Under Windows we have the ability to generate makefiles so if they + // specified a linux config, or if they're building the (non-SRCDS) dedicated + // server, then use the makefile generator + conditional_t *pConditional = + FindOrCreateConditional("DEDICATED", false, CONDITIONAL_CUSTOM); + + bool bUseMakefile = bIsLinux || (pConditional && pConditional->m_bDefined); + bool bUseXcode = bIsOSX; + + if (bUseMakefile) { + Log_Msg(LOG_VPC, + "\n** Detected Linux platform. Using Makefile generator.\n"); + } + + if (bUseMakefile) { + m_pProjectGenerator = GetMakefileProjectGenerator(); + m_pSolutionGenerator = GetMakefileSolutionGenerator(); + } else if (bUseXcode) { + m_pProjectGenerator = GetXcodeProjectGenerator(); + m_pSolutionGenerator = GetXcodeSolutionGenerator(); + m_bForceIterate = true; + } else { + if (IsPlatformDefined("PS3")) { + m_pProjectGenerator = GetPS3ProjectGenerator(); + m_pSolutionGenerator = GetSolutionGenerator_Win32(); + } else if (IsPlatformDefined("X360")) { + if (m_bUseVS2010FileFormat) { + Log_Msg(LOG_VPC, Color(0, 255, 255, 255), + "Generating for Visual Studio 2010.\n"); + m_pProjectGenerator = GetXbox360ProjectGenerator_2010(); + } else { + m_pProjectGenerator = GetXbox360ProjectGenerator(); + } + m_pSolutionGenerator = GetSolutionGenerator_Win32(); + } else { + // spew what we are generating + const char *pchLogLine = "Generating for Visual Studio 2005.\n"; + if (m_eVSVersion == k_EVSVersion_2022) + pchLogLine = "Generating for Visual Studio 2022.\n"; + else if (m_eVSVersion == k_EVSVersion_2015) + pchLogLine = "Generating for Visual Studio 2015.\n"; + else if (m_eVSVersion == k_EVSVersion_2013) + pchLogLine = "Generating for Visual Studio 2013.\n"; + else if (m_eVSVersion == k_EVSVersion_2012) + pchLogLine = "Generating for Visual Studio 2012.\n"; + else if (m_eVSVersion == k_EVSVersion_2010) + pchLogLine = "Generating for Visual Studio 2010.\n"; + + Log_Msg(LOG_VPC, Color(0, 255, 255, 255), pchLogLine); + + // pick a project generator + if (m_bUseVS2010FileFormat) + m_pProjectGenerator = GetWin32ProjectGenerator_2010(); + else + m_pProjectGenerator = GetWin32ProjectGenerator(); + + m_pSolutionGenerator = GetSolutionGenerator_Win32(); + } + } +#else + if (bIsLinux) { + // Linux always uses the makefile project generator. + m_pProjectGenerator = GetMakefileProjectGenerator(); + m_pSolutionGenerator = GetMakefileSolutionGenerator(); + } + if (bIsOSX) { + m_pProjectGenerator = GetXcodeProjectGenerator(); + m_pSolutionGenerator = GetXcodeSolutionGenerator(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Since Steam's VPC builds tier0 and vstdlib directly in, Steam uses vpc.exe as +// the CRC checker. Source uses vpccrccheck.exe to do this. +//----------------------------------------------------------------------------- +void CVPC::InProcessCRCCheck() { + for (int i{1}; i < m_nArgc; i++) { + if (!V_stricmp(m_ppArgv[i], "-crc") || !V_stricmp(m_ppArgv[i], "-crc2")) { + // caller wants the crc check only + const int rc{VPC_CommandLineCRCChecks(m_nArgc, m_ppArgv)}; + exit(rc); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CVPC::BuildTempGroupScript(const char *pVPCScriptName) { + char projectName[MAX_PATH]; + V_StripExtension(pVPCScriptName, projectName, sizeof(projectName)); + + char szCurrentDirectory[MAX_PATH]; + V_GetCurrentDirectory(szCurrentDirectory, sizeof(szCurrentDirectory)); + + // caller is specifying an explicit VPC, i.e. not a project from the default + // group create a temporary group file that mimics a VGC, that points to the + // current dir's VPC + + // Generate a really crappy temp filename + uint32 tmpHash = rand(); + for (const char *c = pVPCScriptName; *c; ++c) tmpHash = tmpHash * 257 + *c; + time_t tmpTime = time(nullptr); + + char tempGroupScriptFilename[MAX_PATH]; + V_ComposeFileName(szCurrentDirectory, + CFmtStr("%08x%08llxvgc.tmp", tmpHash, (long long)tmpTime), + tempGroupScriptFilename, sizeof(tempGroupScriptFilename)); + m_TempGroupScriptFilename = tempGroupScriptFilename; + + // build the temp group script + FILE *fp = fopen(m_TempGroupScriptFilename.Get(), "w+t"); + if (!fp) { + VPCError("Could not open temp file '%s'. Tell a Programmer.\n", + m_TempGroupScriptFilename.Get()); + } + + char vpcScriptFilename[MAX_PATH]; + V_ComposeFileName(szCurrentDirectory, pVPCScriptName, vpcScriptFilename, + sizeof(vpcScriptFilename)); + + // the actual vpc must be relative to the source path + const char *pVPCFilename = + StringAfterPrefix(vpcScriptFilename, m_SourcePath.Get()); + if (!pVPCFilename) { + VPCError("Script %s is not in source path %s\n", vpcScriptFilename, + m_SourcePath.Get()); + } + + if (pVPCFilename[0] == '\\') { + pVPCFilename++; + } + + fprintf(fp, "$Project \"%s\"\n", projectName); + fprintf(fp, "{\n"); + fprintf(fp, "\"%s\"\n", pVPCFilename); + fprintf(fp, "}\n"); + fclose(fp); + + // fake a build command + char buildCommand[MAX_PATH]; + V_snprintf(buildCommand, sizeof(buildCommand), "+%s", projectName); + intp index = m_BuildCommands.AddToTail(); + m_BuildCommands[index] = buildCommand; + + return m_TempGroupScriptFilename.Get(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CVPC::ProcessCommandLine() { + SetupDefaultConditionals(); + + DetermineSourcePath(); + + // possible extensions determine operation mode beyond expected normal user + // case + bool is_vgc = false, is_vpc = false, is_vcproj = false; + bool has_build_command = false; + + const char *script_name = nullptr, *script_name_vcproj = nullptr; + + for (int i = 1; i < m_nArgc; i++) { + const char *argv = m_ppArgv[i]; + if (V_stristr(argv, ".vgc")) { + // caller explicitly providing group + script_name = argv; + is_vgc = true; + has_build_command = true; + break; + } + + if (V_stristr(argv, ".vpc")) { + // caller is using a local vpc, i.e. one that is not hooked into the + // groups + script_name = argv; + is_vpc = true; + has_build_command = true; + break; + } + + if (V_stristr(argv, ".vcproj") || V_stristr(argv, ".vcxproj")) { + // caller wants to re-gen the vc{x}proj, this is commonly used by MSDEV to + // re-gen + script_name_vcproj = argv; + is_vcproj = true; + has_build_command = true; + break; + } + } + + for (int i{1}; i < m_nArgc; i++) { + const char symbol{m_ppArgv[i][0]}; + + if (symbol == '-' || symbol == '+' || symbol == '*' || symbol == '@') { + has_build_command = true; + break; + } + } + + if (is_vpc) { + script_name = BuildTempGroupScript(script_name); + + is_vpc = false; + is_vgc = true; + } + + if (!is_vgc) { + // no script, use default group + script_name = "vpc_scripts\\default.vgc"; + is_vgc = true; + } + + // set the current directory, it is to be expected src, i.e. .\vpc_scripts\.. + SetDefaultSourcePath(); + + char current_directory[MAX_PATH]; + V_GetCurrentDirectory(current_directory, sizeof(current_directory)); + m_StartDirectory = current_directory; + + // parse and build tables from group script that options will reference + VPC_ParseGroupScript(script_name); + + if (is_vcproj) { + // this is commonly used as an extern tool in MSDEV to re-vpc in place + // caller is msdev providing the vcproj name, solve to determine which + // project and generate + FindProjectFromVCPROJ(script_name_vcproj); + } else { + ParseBuildOptions(m_nArgc, m_ppArgv); + } + + // set macros and conditionals derived from command-line options + SetMacrosAndConditionals(); + + // generate a CRC string derived from command-line options + GenerateOptionsCRCString(); + + SetupGenerators(); + + // filter user's build commands + // generate list of build targets + CProjectDependencyGraph dependencyGraph; + GenerateBuildSet(dependencyGraph); + + if (!has_build_command && !HasP4SLNCommand()) { + // spew usage + m_bUsageOnly = true; + } + + if (m_bUsageOnly) { + // spew only + SpewUsage(); + return 0; + } + +#ifdef WIN32 + if (HandleP4SLN(m_pSolutionGenerator)) { + return 0; + } +#endif + + // iterate and build target projects + if (!BuildTargetProjects()) { + // build failure + return 0; + } + + // now that we have valid project files, can generate solution + HandleMKSLN(m_pSolutionGenerator); + + return 0; +} diff --git a/utils/vpc/vpc.h b/utils/vpc/vpc.h new file mode 100644 index 0000000..1c399a7 --- /dev/null +++ b/utils/vpc/vpc.h @@ -0,0 +1,518 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPC_VPC_H_ +#define VPC_VPC_H_ + +#include "tier1/utlstring.h" +#include "tier1/utlrbtree.h" +#include "tier1/utlvector.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlstack.h" +#include "tier1/utldict.h" +#include "tier1/utlsortvector.h" +#include "tier1/checksum_crc.h" +#include "tier1/checksum_md5.h" +#include "tier1/fmtstr.h" +#include "tier1/exprevaluator.h" +#include "tier1/interface.h" +#include "p4lib/ip4.h" +#include "scriptsource.h" +#include "logging.h" + +#ifdef STEAM +#include "vstdlib/strtools.h" +#else +#include "tier1/strtools.h" +#endif + +#include "sys_utils.h" +#include "keyvalues.h" +#include "generatordefinition.h" + +DECLARE_LOGGING_CHANNEL(LOG_VPC); + +#ifdef WIN32 +#include +#endif // WIN32 + +struct KeywordName_t { + const char *m_pName; + configKeyword_e m_Keyword; +}; + +typedef bool (*procptr_t)(const char *pPropertyName); +typedef bool (*GetSymbolProc_t)(const char *pKey); + +#define INVALID_INDEX -1 + +struct property_t { + const char *pName; + procptr_t handler; + int platformMask; +}; + +enum conditionalType_e { + CONDITIONAL_NULL, + CONDITIONAL_PLATFORM, + CONDITIONAL_GAME, + CONDITIONAL_CUSTOM +}; + +struct conditional_t { + conditional_t() { + type = CONDITIONAL_NULL; + m_bDefined = false; + m_bGameConditionActive = false; + } + + CUtlString name; + CUtlString upperCaseName; + conditionalType_e type; + + // a conditional can be present in the table but not defined + // e.g. default conditionals that get set by command line args + bool m_bDefined; + + // only used during multiple game iterations for game conditionals as each + // 'defined' game becomes active + bool m_bGameConditionActive; +}; + +struct macro_t { + macro_t() { + m_bSetupDefineInProjectFile = false; + m_bInternalCreatedMacro = false; + } + + CUtlString name; + CUtlString value; + + // If set to true, then VPC will add this as a -Dname=value parameter to the + // compiler's command line. + bool m_bSetupDefineInProjectFile; + + // VPC created this macro itself rather than the macro being created from a + // script file. + bool m_bInternalCreatedMacro; +}; + +typedef intp scriptIndex_t; +struct script_t { + CUtlString name; + CUtlString m_condition; +}; + +typedef intp projectIndex_t; +struct project_t { + CUtlString name; + CUtlVector scripts; +}; + +typedef intp groupIndex_t; +struct group_t { + CUtlVector projects; +}; + +typedef intp groupTagIndex_t; +struct groupTag_t { + groupTag_t() { bSameAsProject = false; } + + CUtlString name; + CUtlVector groups; + + // this tag is an implicit definition of the project + bool bSameAsProject; +}; + +struct scriptList_t { + scriptList_t() { m_crc = 0; } + + CUtlString m_scriptName; + CRC32_t m_crc; +}; + +class IProjectIterator { + public: + // iProject indexes g_projectList. + virtual bool VisitProject(projectIndex_t iProject, + const char *szScriptPath) = 0; +}; + +#include "ibasesolutiongenerator.h" +#include "ibaseprojectgenerator.h" +#if defined(WIN32) +#include "baseprojectdatacollector.h" +#include "projectgenerator_vcproj.h" +#include "projectgenerator_win32.h" +#include "projectgenerator_win32_2010.h" +#include "projectgenerator_xbox360.h" +#include "projectgenerator_xbox360_2010.h" +#include "projectgenerator_ps3.h" +#endif + +enum EVSVersion { + k_EVSVersion_Invalid, + k_EVSVersion_2005, + k_EVSVersion_2008, + k_EVSVersion_2010, + k_EVSVersion_2012, + k_EVSVersion_2013, + k_EVSVersion_2015, + k_EVSVersion_2022, +}; + +class CVPC { + public: + CVPC(); + // BUGBUG: There is probably some actual cleanup to be done here. + ~CVPC() noexcept = default; + + bool Init(int argc, const char **argv); + void Shutdown(bool bHasError = false); + + [[noreturn]] void VPCError(PRINTF_FORMAT_STRING const char *pFormat, ...); + void VPCWarning(PRINTF_FORMAT_STRING const char *pFormat, ...); + void VPCStatus(bool bAlwaysSpew, PRINTF_FORMAT_STRING const char *pFormat, + ...); + [[noreturn]] void VPCSyntaxError( + PRINTF_FORMAT_STRING const char *pFormat = NULL, ...); + + bool IsProjectCurrent(const char *pVCProjFilename, bool bSpewStatus); + + bool HasCommandLineParameter(const char *pParamName); + bool HasP4SLNCommand(); + + CScript &GetScript() { return m_Script; } + + bool IsVerbose() const { return m_bVerbose; } + bool IsQuiet() const { return m_bQuiet; } + bool IsShowDependencies() const { return m_bShowDeps; } + bool IsForceGenerate() const { return m_bForceGenerate; } + bool IsPosixPCHDisabled() const { return m_bNoPosixPCH; } + bool IsForceIterate() const { return m_bForceIterate; } + bool IsDecorateProject() const { return m_bDecorateProject; } + const char *GetDecorateString() { return m_strDecorate.String(); } + bool IsCheckFiles() const { return m_bCheckFiles; } + bool Is2008() const { return m_eVSVersion == k_EVSVersion_2008; } + bool Is2010() const { + return m_bUseVS2010FileFormat || m_eVSVersion == k_EVSVersion_2010; + } + bool Is2012() const { + return m_eVSVersion == k_EVSVersion_2012; + } // When this returns true so does Is2010() because of the file format + // similarities + bool Is2013() const { + return m_eVSVersion == k_EVSVersion_2013; + } // When this returns true so does Is2010() because of the file format + // similarities + bool Is2015() const { + return m_eVSVersion == k_EVSVersion_2015; + } // When this returns true so does Is2010() because of the file format + // similarities + bool Is2022() const { + return m_eVSVersion == k_EVSVersion_2022; + } // When this returns true so does Is2010() because of the file format + // similarities + bool IsDedicatedBuild() const { return m_bDedicatedBuild; } + bool IsUnity() const { return m_bUseUnity; } + bool IsShowCaseIssues() const { return m_bShowCaseIssues; } + bool UseValveBinDir() const { return m_bUseValveBinDir; } + bool IsVerboseMakefile() const { return m_bVerboseMakefile; } + bool BUseP4SCC() const { return m_bP4SCC; } + bool BUse32BitTools() const { return m_b32BitTools; } + + void DecorateProjectName(char *pchProjectName); + + int GetMissingFilesCount() const { return m_FilesMissing; } + void IncrementFileMissing() { ++m_FilesMissing; } + void ResetMissingFilesCount() { m_FilesMissing = 0; } + + bool IsIgnoreRedundancyWarning() const { return m_bIgnoreRedundancyWarning; } + void SetIgnoreRedundancyWarning(bool bSet) { + m_bIgnoreRedundancyWarning = bSet; + } + + const char *GetStartDirectory() { return m_StartDirectory.Get(); } + const char *GetSourcePath() { return m_SourcePath.Get(); } + const char *GetProjectPath() { return m_ProjectPath.Get(); } + const char *GetCRCString() { return m_SupplementalCRCString.Get(); } + const char *GetSolutionItemsFilename() { + return m_SolutionItemsFilename.Get(); + } + + const char *GetOutputFilename() { return m_OutputFilename.Get(); } + void SetOutputFilename(const char *pOutputFilename) { + m_OutputFilename = pOutputFilename; + } + + const char *GetProjectName() { return m_ProjectName.Get(); } + void SetProjectName(const char *pProjectName) { + m_ProjectName = pProjectName; + } + + const char *GetLoadAddressName() { return m_LoadAddressName.Get(); } + void SetLoadAddressName(const char *pLoadAddressName) { + m_LoadAddressName = pLoadAddressName; + } + + const char *GetOutputMirrorPath() { return m_OutputMirrorString.Get(); } + + int ProcessCommandLine(); + + // Returns the mask identifying what platforms whould be built + bool IsPlatformDefined(const char *pName); + const char *GetTargetPlatformName(); + + void GetProjectDependencies( + CUtlVector &referencedProjects); + void SetPhase1Projects(CUtlVector *pPhase1Projects) { + m_pPhase1Projects = pPhase1Projects; + } + + IBaseProjectGenerator *GetProjectGenerator() { return m_pProjectGenerator; } + void SetProjectGenerator(IBaseProjectGenerator *pGenerator) { + m_pProjectGenerator = pGenerator; + } + + IBaseSolutionGenerator *GetSolutionGenerator() { + return m_pSolutionGenerator; + } + + // Conditionals + conditional_t *FindOrCreateConditional(const char *pName, bool bCreate, + conditionalType_e type); + void ResolveMacrosInConditional(char const *pString, char *pOutBuff, + int outBuffSize); + bool ResolveConditionalSymbol(const char *pSymbol); + bool EvaluateConditionalExpression(const char *pExpression); + bool ConditionHasDefinedType(const char *pCondition, conditionalType_e type); + void SetConditional(const char *pName, bool bSet = true); + + // Macros + macro_t *FindOrCreateMacro(const char *pName, bool bCreate, + const char *pValue); + void ResolveMacrosInString(char const *pString, char *pOutBuff, + int outBuffSize); + intp GetMacrosMarkedForCompilerDefines(CUtlVector ¯oDefines); + void RemoveScriptCreatedMacros(); + const char *GetMacroValue(const char *pName); + void SetMacro(const char *pName, const char *pValue, + bool bSetupDefineInProjectFile); + + // Iterates all the projects in the specified list, checks their conditionals, + // and calls pIterator->VisitProject for each one that passes the conditional + // tests. + // + // If bForce is false, then it does a CRC check before visiting any project to + // see if the target project file is already up-to-date with its .vpc file. + void IterateTargetProjects(CUtlVector &projectList, + IProjectIterator *pIterator); + + bool ParseProjectScript(const char *pScriptName, int depth, bool bQuiet, + bool bWriteCRCCheckFile); + + void AddScriptToCRCCheck(const char *pScriptName, CRC32_t crc); + + const char *KeywordToName(configKeyword_e keyword); + configKeyword_e NameToKeyword(const char *pKeywordName); + + intp GetProjectsInGroup(CUtlVector &projectList, + const char *pGroupHame); + + private: + void SpewUsage(void); + + bool LoadPerforceInterface(); + void UnloadPerforceInterface(); + + void InProcessCRCCheck(); + void CheckForInstalledXDK(); + + void DetermineSourcePath(); + void SetDefaultSourcePath(); + + void SetupGenerators(); + void SetupDefaultConditionals(); + void SetMacrosAndConditionals(); + void ResolveMacrosInStringInternal(char const *pString, char *pOutBuff, + int outBuffSize, + bool bStringIsConditional); + + void HandleSingleCommandLineArg(const char *pArg); + void ParseBuildOptions(int argc, const char *argv[]); + + bool CheckBinPath(char *pOutBinPath, int outBinPathSize); + bool RestartFromCorrectLocation(bool *pIsChild); + + void GenerateOptionsCRCString(); + void CreateOutputFilename(project_t *pProject, const char *pchPlatform, + const char *pchPhase, const char *pGameName, + const char *pchExtension); + void FindProjectFromVCPROJ(const char *pScriptNameVCProj); + const char *BuildTempGroupScript(const char *pScriptName); + + bool HandleP4SLN(IBaseSolutionGenerator *pSolutionGenerator); + void HandleMKSLN(IBaseSolutionGenerator *pSolutionGenerator); + + void GenerateBuildSet(CProjectDependencyGraph &dependencyGraph); + bool BuildTargetProjects(); + bool BuildTargetProject(IProjectIterator *pIterator, + projectIndex_t projectIndex, script_t *pProjectScript, + const char *pGameName); + + bool m_bVerbose; + bool m_bQuiet; + bool m_bUsageOnly; + bool m_bHelp; + bool m_bSpewPlatforms; + bool m_bSpewGames; + bool m_bSpewGroups; + bool m_bSpewProjects; + bool m_bIgnoreRedundancyWarning; + bool m_bSpewProperties; + bool m_bTestMode; + bool m_bForceGenerate; + bool m_bNoPosixPCH; + bool m_bForceIterate; + bool m_bEnableVpcGameMacro; + bool m_bCheckFiles; + bool m_bDecorateProject; + bool m_bShowDeps; + bool m_bP4AutoAdd; + bool m_bP4SlnCheckEverything; + bool m_bDedicatedBuild; + bool m_bAppendSrvToDedicated; // concat "_srv" to dedicated server .so's. + bool m_bUseValveBinDir; // On Linux, use gcc toolchain from /valve/bin/ + bool m_bAnyProjectQualified; + EVSVersion m_eVSVersion; + bool m_bUseVS2010FileFormat; + bool m_bUseUnity; + bool m_bShowCaseIssues; + bool m_bVerboseMakefile; + bool m_bP4SCC; // VPC_SCC_INTEGRATION define, or "/srcctl" cmd line option, + // or env var VPC_SRCCTL=1 + bool m_b32BitTools; // Normally we prefer the 64-bit toolchain when building + // a 64-bit target. This turns that off. + + // How many of the files listed in the VPC files are missing? + int m_FilesMissing; + + int m_nArgc; + const char **m_ppArgv; + + CColorizedLoggingListener m_LoggingListener; + + CSysModule *m_pP4Module; + CSysModule *m_pFilesystemModule; + + CScript m_Script; + + // Path where vpc was started from + CUtlString m_StartDirectory; + + // Root path to the sources (i.e. the directory where the vpc_scripts + // directory can be found in). + CUtlString m_SourcePath; + + // path to the project being processed (i.e. the directory where this + // project's .vpc can be found). + CUtlString m_ProjectPath; + + // strings derived from command-line commands which is checked alongside + // project CRCs: + CUtlString m_SupplementalCRCString; + CUtlString m_ExtraOptionsCRCString; + + CUtlString m_MKSolutionFilename; + + CUtlString m_SolutionItemsFilename; // For /slnitems + + CUtlString m_P4SolutionFilename; // For /p4sln + CUtlVector m_iP4Changelists; + + CUtlString m_OutputFilename; + CUtlString m_ProjectName; + CUtlString m_LoadAddressName; + + CUtlString m_OutputMirrorString; + + CUtlString m_TempGroupScriptFilename; + + CUtlString m_strDecorate; + + // This abstracts the differences between different output methods. + IBaseProjectGenerator *m_pProjectGenerator; + IBaseSolutionGenerator *m_pSolutionGenerator; + + CUtlVector m_BuildCommands; + + CUtlVector *m_pPhase1Projects; + + public: + CUtlVector m_Conditionals; + CUtlVector m_Macros; + + CUtlVector m_ScriptList; + + CUtlVector m_Projects; + CUtlVector m_TargetProjects; + + CProjectDependencyGraph m_dependencyGraph; + + CUtlVector m_Groups; + CUtlVector m_GroupTags; + + CUtlVector m_P4GroupRestrictions; + + CUtlVector m_SchemaFiles; + + CUtlDict m_CustomBuildSteps; + + bool m_bGeneratedProject; + + CUtlDict m_UnityFilesSeen; + CUtlStack m_UnityStack; + CUtlString m_sUnityCurrent; + bool m_bInMkSlnPass; +}; + +extern CVPC *g_pVPC; + +extern const char *g_pOption_ImportLibrary; // "$ImportLibrary"; +extern const char *g_pOption_OutputFile; // "$OutputFile"; +extern const char *g_pOption_GameOutputFile; // "$GameOutputFile"; +extern const char + *g_pOption_AdditionalIncludeDirectories; // "$AdditionalIncludeDirectories" +extern const char * + g_pOption_AdditionalProjectDependencies; // "$AdditionalProjectDependencies" +extern const char *g_pOption_AdditionalOutputFiles; // "$AdditionalOutputFiles" +extern const char + *g_pOption_PreprocessorDefinitions; // "$PreprocessorDefinitions" +extern const char *g_IncludeSeparators[2]; + +extern void VPC_ParseGroupScript(const char *pScriptName); + +extern groupTagIndex_t VPC_Group_FindOrCreateGroupTag(const char *pName, + bool bCreate); + +extern void VPC_Keyword_Configuration(); +extern void VPC_Keyword_FileConfiguration(); + +struct folderConfig_t { + CUtlVector vecConfigurationNames; + CScriptSource scriptSource; + + bool BHasConfig() const { return vecConfigurationNames.Count() > 0; } + void Clear() { vecConfigurationNames.RemoveAll(); } +}; + +void VPC_Keyword_FolderConfiguration(folderConfig_t *pFolderConfig); +void VPC_ApplyFolderConfigurationToFile(const folderConfig_t &folderConfig); + +extern void VPC_Config_SpewProperties(configKeyword_e keyword); +extern bool VPC_Config_IgnoreOption(const char *pPropertyName); + +extern void VPC_FakeKeyword_SchemaFolder( + class CBaseProjectDataCollector *pDataCollector); + +#endif // VPC_VPC_H_ diff --git a/utils/vpc/vpc.sln b/utils/vpc/vpc.sln new file mode 100644 index 0000000..9e106f5 --- /dev/null +++ b/utils/vpc/vpc.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vpc", "vpc.vcxproj", "{36C5F545-588F-4091-B480-89E03EDBDA93}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {36C5F545-588F-4091-B480-89E03EDBDA93}.Debug|Win32.ActiveCfg = Debug|Win32 + {36C5F545-588F-4091-B480-89E03EDBDA93}.Debug|Win32.Build.0 = Debug|Win32 + {36C5F545-588F-4091-B480-89E03EDBDA93}.Release|Win32.ActiveCfg = Release|Win32 + {36C5F545-588F-4091-B480-89E03EDBDA93}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/utils/vpc/vpc.vcxproj b/utils/vpc/vpc.vcxproj new file mode 100644 index 0000000..be37d59 --- /dev/null +++ b/utils/vpc/vpc.vcxproj @@ -0,0 +1,237 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {36C5F545-588F-4091-B480-89E03EDBDA93} + vpc + 8.1 + + + + Application + true + NotSet + v140_xp + + + Application + false + true + NotSet + v140_xp + + + + + + + + + + + + + + + Level3 + Disabled + ..\..\public;..\..\public\tier0;..\..\public\tier1;..\..\public\vstdlib;..\..\common\;..\..\common\p4api + WIN32;_WIN32;PLATFORM_WINDOWS;COMPILER_MSVC;COMPILER_MSVC32;_USE_32BIT_TIME_T;STATIC_TIER0;NO_MALLOC_OVERRIDE;STATIC_VSTDLIB;STANDALONE_VPC;_MBCS;_CRT_NO_VA_START_VALIDATION;%(PreprocessorDefinitions) + false + CompileAsCpp + MultiThreadedDebug + Async + + + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;wsock32.lib;Ws2_32.lib;%(AdditionalDependencies) + libcmt.lib + Console + + + p4 edit ..\..\..\..\devtools\bin\vpc.exe && copy /y $(TargetPath) ..\..\..\..\devtools\bin\vpc.exe +p4 edit ..\..\..\..\devtools\bin\vpc.pdb && copy /y $(TargetDir)\vpc.pdb ..\..\..\..\devtools\bin\vpc.pdb + + + + + + Level3 + MaxSpeed + true + true + ..\..\public;..\..\public\tier0;..\..\public\tier1;..\..\public\vstdlib;..\..\common\;..\..\common\p4api + WIN32;_WIN32;PLATFORM_WINDOWS;COMPILER_MSVC;COMPILER_MSVC32;_USE_32BIT_TIME_T;STATIC_TIER0;NO_MALLOC_OVERRIDE;STATIC_VSTDLIB;STANDALONE_VPC;_MBCS;_CRT_NO_VA_START_VALIDATION;%(PreprocessorDefinitions) + false + CompileAsCpp + MultiThreaded + + + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;wsock32.lib;Ws2_32.lib;%(AdditionalDependencies) + Console + + + p4 edit ..\..\..\..\devtools\bin\vpc.exe && copy /y $(TargetPath) ..\..\..\..\devtools\bin\vpc.exe +p4 edit ..\..\..\..\devtools\bin\vpc.pdb && copy /y $(TargetDir)\vpc.pdb ..\..\..\..\devtools\bin\vpc.pdb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + + + \ No newline at end of file diff --git a/utils/vpc/vpc.vcxproj.filters b/utils/vpc/vpc.vcxproj.filters new file mode 100644 index 0000000..be39a00 --- /dev/null +++ b/utils/vpc/vpc.vcxproj.filters @@ -0,0 +1,393 @@ + + + + + {46bac1ee-3374-43a2-9761-f79ee2691007} + + + {38599379-561b-43c5-bdc9-e84eee3ab186} + + + {13ae58f1-21cb-4159-82ae-3d2dbd5238a7} + + + {e86543a4-b232-42c1-91e4-ddd9dd9c36a8} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {f5d738f6-132d-42e7-9f03-05388b11b408} + + + {90afaed2-cb09-4747-bb13-2186c417c0f2} + + + {7bc720f7-b058-4998-9fa5-03df7999beef} + + + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + Dependencies\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\Source Files + + + VPC\VPCCRCCheck + + + VPC\Source Files + + + VPC\Source Files + + + Dependencies\Source Files + + + + + Dependencies\Header Files + + + Dependencies\Header Files + + + Dependencies\Header Files + + + Dependencies\Header Files + + + Dependencies\Header Files + + + Dependencies\Header Files + + + Dependencies\Header Files + + + Dependencies\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\VPCCRCCheck + + + VPC\Header Files + + + + + + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\Header Files + + + VPC\definitions + + + VPC\definitions + + + VPC\definitions + + + VPC\definitions + + + VPC\definitions + + + + + P4 Libs + + + P4 Libs + + + P4 Libs + + + P4 Libs + + + P4 Libs + + + P4 Libs + + + P4 Libs + + + P4 Libs + + + \ No newline at end of file diff --git a/utils/vpc/vpc.xcodeproj/project.pbxproj b/utils/vpc/vpc.xcodeproj/project.pbxproj new file mode 100644 index 0000000..abce567 --- /dev/null +++ b/utils/vpc/vpc.xcodeproj/project.pbxproj @@ -0,0 +1,530 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 976DE7B71239412500E8D60A /* crccheck_shared.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 976DE7B51239412500E8D60A /* crccheck_shared.cpp */; }; + 976DE7BA1239423E00E8D60A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 976DE7B91239423E00E8D60A /* Foundation.framework */; }; + 976DE7BC1239424500E8D60A /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 976DE7BB1239424500E8D60A /* CoreServices.framework */; }; + 976DE7BE1239424E00E8D60A /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 976DE7BD1239424E00E8D60A /* libiconv.dylib */; }; + 977F706212393A2A008D8433 /* baseprojectdatacollector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705012393A2A008D8433 /* baseprojectdatacollector.cpp */; }; + 977F706312393A2A008D8433 /* configuration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705212393A2A008D8433 /* configuration.cpp */; }; + 977F706412393A2A008D8433 /* dependencies.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705312393A2A008D8433 /* dependencies.cpp */; }; + 977F706512393A2A008D8433 /* ExprSimplifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705512393A2A008D8433 /* ExprSimplifier.cpp */; }; + 977F706612393A2A008D8433 /* GroupScript.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705612393A2A008D8433 /* GroupScript.cpp */; }; + 977F706712393A2A008D8433 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705912393A2A008D8433 /* main.cpp */; }; + 977F706712393A2A008D8433 /* vpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705912393A2A008D8433 /* vpc.cpp */; }; + 977F706812393A2A008D8433 /* projectgenerator_makefile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705A12393A2A008D8433 /* projectgenerator_makefile.cpp */; }; + 977F706912393A2A008D8433 /* projectgenerator_xcode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705B12393A2A008D8433 /* projectgenerator_xcode.cpp */; }; + 977F706A12393A2A008D8433 /* ProjectScript.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705D12393A2A008D8433 /* ProjectScript.cpp */; }; + 977F706B12393A2A008D8433 /* solutiongenerator_makefile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705E12393A2A008D8433 /* solutiongenerator_makefile.cpp */; }; + 977F706C12393A2A008D8433 /* solutiongenerator_xcode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F705F12393A2A008D8433 /* solutiongenerator_xcode.cpp */; }; + 977F706D12393A2A008D8433 /* sys_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F706012393A2A008D8433 /* sys_utils.cpp */; }; + 977F708912393ADD008D8433 /* assert_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707212393ADD008D8433 /* assert_dialog.cpp */; }; + 977F708A12393ADD008D8433 /* cpu_posix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707312393ADD008D8433 /* cpu_posix.cpp */; }; + 977F708B12393ADD008D8433 /* cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707412393ADD008D8433 /* cpu.cpp */; }; + 977F708C12393ADD008D8433 /* dbg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707512393ADD008D8433 /* dbg.cpp */; }; + 977F708D12393ADD008D8433 /* fasttimer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707612393ADD008D8433 /* fasttimer.cpp */; }; + 977F708E12393ADD008D8433 /* mem_helpers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707712393ADD008D8433 /* mem_helpers.cpp */; }; + 977F708F12393ADD008D8433 /* memblockhdr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707812393ADD008D8433 /* memblockhdr.cpp */; }; + 977F709012393ADD008D8433 /* memdbg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707912393ADD008D8433 /* memdbg.cpp */; }; + 977F709112393ADD008D8433 /* memstd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707A12393ADD008D8433 /* memstd.cpp */; }; + 977F709212393ADD008D8433 /* memvalidate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707B12393ADD008D8433 /* memvalidate.cpp */; }; + 977F709312393ADD008D8433 /* minidump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707C12393ADD008D8433 /* minidump.cpp */; }; + 977F709412393ADD008D8433 /* pch_tier0.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707D12393ADD008D8433 /* pch_tier0.cpp */; }; + 977F709512393ADD008D8433 /* platform_posix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707E12393ADD008D8433 /* platform_posix.cpp */; }; + 977F709612393ADD008D8433 /* pme_posix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F707F12393ADD008D8433 /* pme_posix.cpp */; }; + 977F709712393ADD008D8433 /* pmelib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708012393ADD008D8433 /* pmelib.cpp */; }; + 977F709812393ADD008D8433 /* testthread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708112393ADD008D8433 /* testthread.cpp */; }; + 977F709912393ADD008D8433 /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708212393ADD008D8433 /* thread.cpp */; }; + 977F709A12393ADD008D8433 /* threadtools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708312393ADD008D8433 /* threadtools.cpp */; }; + 977F709B12393ADD008D8433 /* tier0.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708412393ADD008D8433 /* tier0.cpp */; }; + 977F709C12393ADD008D8433 /* validator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708512393ADD008D8433 /* validator.cpp */; }; + 977F709D12393ADD008D8433 /* valobject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708612393ADD008D8433 /* valobject.cpp */; }; + 977F709E12393ADD008D8433 /* vcrmode_posix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708712393ADD008D8433 /* vcrmode_posix.cpp */; }; + 977F709F12393ADD008D8433 /* vprof.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F708812393ADD008D8433 /* vprof.cpp */; }; + 977F70AB12393B10008D8433 /* checksum_crc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A012393B10008D8433 /* checksum_crc.cpp */; }; + 977F70AC12393B10008D8433 /* checksum_md5.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A112393B10008D8433 /* checksum_md5.cpp */; }; + 977F70AD12393B10008D8433 /* convar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A212393B10008D8433 /* convar.cpp */; }; + 977F70AE12393B10008D8433 /* generichash.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A312393B10008D8433 /* generichash.cpp */; }; + 977F70AF12393B10008D8433 /* interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A412393B10008D8433 /* interface.cpp */; }; + 977F70B012393B10008D8433 /* KeyValues.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A512393B10008D8433 /* KeyValues.cpp */; }; + 977F70B112393B10008D8433 /* mempool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A612393B10008D8433 /* mempool.cpp */; }; + 977F70B212393B10008D8433 /* memstack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A712393B10008D8433 /* memstack.cpp */; }; + 977F70B312393B10008D8433 /* stringpool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A812393B10008D8433 /* stringpool.cpp */; }; + 977F70B412393B10008D8433 /* utlbuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70A912393B10008D8433 /* utlbuffer.cpp */; }; + 977F70B512393B10008D8433 /* utlsymbol.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70AA12393B10008D8433 /* utlsymbol.cpp */; }; + 977F70BF12393B49008D8433 /* commandline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70B612393B49008D8433 /* commandline.cpp */; }; + 977F70C012393B49008D8433 /* cvar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70B712393B49008D8433 /* cvar.cpp */; }; + 977F70C112393B49008D8433 /* keyvaluessystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70B812393B49008D8433 /* keyvaluessystem.cpp */; }; + 977F70C212393B49008D8433 /* osversion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70B912393B49008D8433 /* osversion.cpp */; }; + 977F70C312393B49008D8433 /* qsort_s.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70BA12393B49008D8433 /* qsort_s.cpp */; }; + 977F70C412393B49008D8433 /* random.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70BB12393B49008D8433 /* random.cpp */; }; + 977F70C512393B49008D8433 /* splitstring.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70BC12393B49008D8433 /* splitstring.cpp */; }; + 977F70C612393B49008D8433 /* stringnormalize.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70BD12393B49008D8433 /* stringnormalize.cpp */; }; + 977F70C712393B49008D8433 /* strtools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 977F70BE12393B49008D8433 /* strtools.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 977F703E12393174008D8433 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 976DE7B51239412500E8D60A /* crccheck_shared.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = crccheck_shared.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpccrccheck/crccheck_shared.cpp; sourceTree = ""; }; + 976DE7B61239412500E8D60A /* crccheck_shared.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crccheck_shared.h; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpccrccheck/crccheck_shared.h; sourceTree = ""; }; + 976DE7B91239423E00E8D60A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Xcode4/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 976DE7BB1239424500E8D60A /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = Xcode4/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; + 976DE7BD1239424E00E8D60A /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = Xcode4/SDKs/MacOSX10.5.sdk/usr/lib/libiconv.dylib; sourceTree = SDKROOT; }; + 977F704012393174008D8433 /* vpc_osx */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = vpc_osx; sourceTree = BUILT_PRODUCTS_DIR; }; + 977F705012393A2A008D8433 /* baseprojectdatacollector.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = baseprojectdatacollector.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/baseprojectdatacollector.cpp; sourceTree = ""; }; + 977F705112393A2A008D8433 /* baseprojectdatacollector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = baseprojectdatacollector.h; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/baseprojectdatacollector.h; sourceTree = ""; }; + 977F705212393A2A008D8433 /* configuration.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = configuration.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/configuration.cpp; sourceTree = ""; }; + 977F705312393A2A008D8433 /* dependencies.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dependencies.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/dependencies.cpp; sourceTree = ""; }; + 977F705412393A2A008D8433 /* dependencies.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dependencies.h; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/dependencies.h; sourceTree = ""; }; + 977F705512393A2A008D8433 /* ExprSimplifier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ExprSimplifier.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/ExprSimplifier.cpp; sourceTree = ""; }; + 977F705612393A2A008D8433 /* GroupScript.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GroupScript.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/GroupScript.cpp; sourceTree = ""; }; + 977F705712393A2A008D8433 /* ibaseprojectgenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ibaseprojectgenerator.h; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/ibaseprojectgenerator.h; sourceTree = ""; }; + 977F705812393A2A008D8433 /* ibasesolutiongenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ibasesolutiongenerator.h; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/ibasesolutiongenerator.h; sourceTree = ""; }; + 977F705912393A2A008D8433 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = main.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/main.cpp; sourceTree = ""; }; + 977F705A12393A2A008D8433 /* projectgenerator_makefile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = projectgenerator_makefile.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/projectgenerator_makefile.cpp; sourceTree = ""; }; + 977F705B12393A2A008D8433 /* projectgenerator_xcode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = projectgenerator_xcode.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/projectgenerator_xcode.cpp; sourceTree = ""; }; + 977F705C12393A2A008D8433 /* projectgenerator_xcode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = projectgenerator_xcode.h; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/projectgenerator_xcode.h; sourceTree = ""; }; + 977F705D12393A2A008D8433 /* ProjectScript.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ProjectScript.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/ProjectScript.cpp; sourceTree = ""; }; + 977F705E12393A2A008D8433 /* solutiongenerator_makefile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = solutiongenerator_makefile.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/solutiongenerator_makefile.cpp; sourceTree = ""; }; + 977F705F12393A2A008D8433 /* solutiongenerator_xcode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = solutiongenerator_xcode.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/solutiongenerator_xcode.cpp; sourceTree = ""; }; + 977F706012393A2A008D8433 /* sys_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sys_utils.cpp; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/sys_utils.cpp; sourceTree = ""; }; + 977F706112393A2A008D8433 /* sys_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sys_utils.h; path = /Users/dberger/P4Clients/steam3_main/src/utils/vpc/sys_utils.h; sourceTree = ""; }; + 977F707212393ADD008D8433 /* assert_dialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = assert_dialog.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/assert_dialog.cpp; sourceTree = ""; }; + 977F707312393ADD008D8433 /* cpu_posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cpu_posix.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/cpu_posix.cpp; sourceTree = ""; }; + 977F707412393ADD008D8433 /* cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cpu.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/cpu.cpp; sourceTree = ""; }; + 977F707512393ADD008D8433 /* dbg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dbg.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/dbg.cpp; sourceTree = ""; }; + 977F707612393ADD008D8433 /* fasttimer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fasttimer.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/fasttimer.cpp; sourceTree = ""; }; + 977F707712393ADD008D8433 /* mem_helpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mem_helpers.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/mem_helpers.cpp; sourceTree = ""; }; + 977F707812393ADD008D8433 /* memblockhdr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memblockhdr.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/memblockhdr.cpp; sourceTree = ""; }; + 977F707912393ADD008D8433 /* memdbg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memdbg.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/memdbg.cpp; sourceTree = ""; }; + 977F707A12393ADD008D8433 /* memstd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memstd.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/memstd.cpp; sourceTree = ""; }; + 977F707B12393ADD008D8433 /* memvalidate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memvalidate.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/memvalidate.cpp; sourceTree = ""; }; + 977F707C12393ADD008D8433 /* minidump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/minidump.cpp; sourceTree = ""; }; + 977F707D12393ADD008D8433 /* pch_tier0.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pch_tier0.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/pch_tier0.cpp; sourceTree = ""; }; + 977F707E12393ADD008D8433 /* platform_posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = platform_posix.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/platform_posix.cpp; sourceTree = ""; }; + 977F707F12393ADD008D8433 /* pme_posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pme_posix.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/pme_posix.cpp; sourceTree = ""; }; + 977F708012393ADD008D8433 /* pmelib.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pmelib.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/pmelib.cpp; sourceTree = ""; }; + 977F708112393ADD008D8433 /* testthread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = testthread.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/testthread.cpp; sourceTree = ""; }; + 977F708212393ADD008D8433 /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = thread.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/thread.cpp; sourceTree = ""; }; + 977F708312393ADD008D8433 /* threadtools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = threadtools.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/threadtools.cpp; sourceTree = ""; }; + 977F708412393ADD008D8433 /* tier0.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tier0.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/tier0.cpp; sourceTree = ""; }; + 977F708512393ADD008D8433 /* validator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = validator.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/validator.cpp; sourceTree = ""; }; + 977F708612393ADD008D8433 /* valobject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = valobject.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/valobject.cpp; sourceTree = ""; }; + 977F708712393ADD008D8433 /* vcrmode_posix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vcrmode_posix.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/vcrmode_posix.cpp; sourceTree = ""; }; + 977F708812393ADD008D8433 /* vprof.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vprof.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier0/vprof.cpp; sourceTree = ""; }; + 977F70A012393B10008D8433 /* checksum_crc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = checksum_crc.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/checksum_crc.cpp; sourceTree = ""; }; + 977F70A112393B10008D8433 /* checksum_md5.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = checksum_md5.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/checksum_md5.cpp; sourceTree = ""; }; + 977F70A212393B10008D8433 /* convar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = convar.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/convar.cpp; sourceTree = ""; }; + 977F70A312393B10008D8433 /* generichash.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = generichash.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/generichash.cpp; sourceTree = ""; }; + 977F70A412393B10008D8433 /* interface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = interface.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/interface.cpp; sourceTree = ""; }; + 977F70A512393B10008D8433 /* KeyValues.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = KeyValues.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/KeyValues.cpp; sourceTree = ""; }; + 977F70A612393B10008D8433 /* mempool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mempool.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/mempool.cpp; sourceTree = ""; }; + 977F70A712393B10008D8433 /* memstack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memstack.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/memstack.cpp; sourceTree = ""; }; + 977F70A812393B10008D8433 /* stringpool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = stringpool.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/stringpool.cpp; sourceTree = ""; }; + 977F70A912393B10008D8433 /* utlbuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = utlbuffer.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/utlbuffer.cpp; sourceTree = ""; }; + 977F70AA12393B10008D8433 /* utlsymbol.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = utlsymbol.cpp; path = /Users/dberger/P4Clients/steam3_main/src/tier1/utlsymbol.cpp; sourceTree = ""; }; + 977F70B612393B49008D8433 /* commandline.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = commandline.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/commandline.cpp; sourceTree = ""; }; + 977F70B712393B49008D8433 /* cvar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cvar.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/cvar.cpp; sourceTree = ""; }; + 977F70B812393B49008D8433 /* keyvaluessystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = keyvaluessystem.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/keyvaluessystem.cpp; sourceTree = ""; }; + 977F70B912393B49008D8433 /* osversion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = osversion.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/osversion.cpp; sourceTree = ""; }; + 977F70BA12393B49008D8433 /* qsort_s.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = qsort_s.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/qsort_s.cpp; sourceTree = ""; }; + 977F70BB12393B49008D8433 /* random.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = random.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/random.cpp; sourceTree = ""; }; + 977F70BC12393B49008D8433 /* splitstring.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = splitstring.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/splitstring.cpp; sourceTree = ""; }; + 977F70BD12393B49008D8433 /* stringnormalize.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = stringnormalize.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/stringnormalize.cpp; sourceTree = ""; }; + 977F70BE12393B49008D8433 /* strtools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = strtools.cpp; path = /Users/dberger/P4Clients/steam3_main/src/vstdlib/strtools.cpp; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 977F703D12393174008D8433 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 976DE7BE1239424E00E8D60A /* libiconv.dylib in Frameworks */, + 976DE7BC1239424500E8D60A /* CoreServices.framework in Frameworks */, + 976DE7BA1239423E00E8D60A /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 976DE7B41239411300E8D60A /* crccheck_shared */ = { + isa = PBXGroup; + children = ( + 976DE7B51239412500E8D60A /* crccheck_shared.cpp */, + 976DE7B61239412500E8D60A /* crccheck_shared.h */, + ); + name = crccheck_shared; + sourceTree = ""; + }; + 976DE7BF1239425600E8D60A /* Libs */ = { + isa = PBXGroup; + children = ( + 976DE7BD1239424E00E8D60A /* libiconv.dylib */, + 976DE7BB1239424500E8D60A /* CoreServices.framework */, + 976DE7B91239423E00E8D60A /* Foundation.framework */, + ); + name = Libs; + sourceTree = ""; + }; + 977F703312393173008D8433 = { + isa = PBXGroup; + children = ( + 977F703A12393174008D8433 /* Source */, + 976DE7BF1239425600E8D60A /* Libs */, + 977F704112393174008D8433 /* Products */, + ); + sourceTree = ""; + }; + 977F703A12393174008D8433 /* Source */ = { + isa = PBXGroup; + children = ( + 977F706F12393A45008D8433 /* tier0 */, + 977F707012393A4D008D8433 /* tier1 */, + 977F707112393A56008D8433 /* vstdlib */, + 976DE7B41239411300E8D60A /* crccheck_shared */, + 977F706E12393A3D008D8433 /* vpc */, + ); + path = Source; + sourceTree = ""; + }; + 977F704112393174008D8433 /* Products */ = { + isa = PBXGroup; + children = ( + 977F704012393174008D8433 /* vpc_osx */, + ); + name = Products; + sourceTree = ""; + }; + 977F706E12393A3D008D8433 /* vpc */ = { + isa = PBXGroup; + children = ( + 977F705012393A2A008D8433 /* baseprojectdatacollector.cpp */, + 977F705112393A2A008D8433 /* baseprojectdatacollector.h */, + 977F705212393A2A008D8433 /* configuration.cpp */, + 977F705312393A2A008D8433 /* dependencies.cpp */, + 977F705412393A2A008D8433 /* dependencies.h */, + 977F705512393A2A008D8433 /* ExprSimplifier.cpp */, + 977F705612393A2A008D8433 /* GroupScript.cpp */, + 977F705712393A2A008D8433 /* ibaseprojectgenerator.h */, + 977F705812393A2A008D8433 /* ibasesolutiongenerator.h */, + 977F705912393A2A008D8433 /* main.cpp */, + 977F705A12393A2A008D8433 /* projectgenerator_makefile.cpp */, + 977F705B12393A2A008D8433 /* projectgenerator_xcode.cpp */, + 977F705C12393A2A008D8433 /* projectgenerator_xcode.h */, + 977F705D12393A2A008D8433 /* ProjectScript.cpp */, + 977F705E12393A2A008D8433 /* solutiongenerator_makefile.cpp */, + 977F705F12393A2A008D8433 /* solutiongenerator_xcode.cpp */, + 977F706012393A2A008D8433 /* sys_utils.cpp */, + 977F706112393A2A008D8433 /* sys_utils.h */, + ); + name = vpc; + sourceTree = ""; + }; + 977F706F12393A45008D8433 /* tier0 */ = { + isa = PBXGroup; + children = ( + 977F707212393ADD008D8433 /* assert_dialog.cpp */, + 977F707312393ADD008D8433 /* cpu_posix.cpp */, + 977F707412393ADD008D8433 /* cpu.cpp */, + 977F707512393ADD008D8433 /* dbg.cpp */, + 977F707612393ADD008D8433 /* fasttimer.cpp */, + 977F707712393ADD008D8433 /* mem_helpers.cpp */, + 977F707812393ADD008D8433 /* memblockhdr.cpp */, + 977F707912393ADD008D8433 /* memdbg.cpp */, + 977F707A12393ADD008D8433 /* memstd.cpp */, + 977F707B12393ADD008D8433 /* memvalidate.cpp */, + 977F707C12393ADD008D8433 /* minidump.cpp */, + 977F707D12393ADD008D8433 /* pch_tier0.cpp */, + 977F707E12393ADD008D8433 /* platform_posix.cpp */, + 977F707F12393ADD008D8433 /* pme_posix.cpp */, + 977F708012393ADD008D8433 /* pmelib.cpp */, + 977F708112393ADD008D8433 /* testthread.cpp */, + 977F708212393ADD008D8433 /* thread.cpp */, + 977F708312393ADD008D8433 /* threadtools.cpp */, + 977F708412393ADD008D8433 /* tier0.cpp */, + 977F708512393ADD008D8433 /* validator.cpp */, + 977F708612393ADD008D8433 /* valobject.cpp */, + 977F708712393ADD008D8433 /* vcrmode_posix.cpp */, + 977F708812393ADD008D8433 /* vprof.cpp */, + ); + name = tier0; + sourceTree = ""; + }; + 977F707012393A4D008D8433 /* tier1 */ = { + isa = PBXGroup; + children = ( + 977F70A012393B10008D8433 /* checksum_crc.cpp */, + 977F70A112393B10008D8433 /* checksum_md5.cpp */, + 977F70A212393B10008D8433 /* convar.cpp */, + 977F70A312393B10008D8433 /* generichash.cpp */, + 977F70A412393B10008D8433 /* interface.cpp */, + 977F70A512393B10008D8433 /* KeyValues.cpp */, + 977F70A612393B10008D8433 /* mempool.cpp */, + 977F70A712393B10008D8433 /* memstack.cpp */, + 977F70A812393B10008D8433 /* stringpool.cpp */, + 977F70A912393B10008D8433 /* utlbuffer.cpp */, + 977F70AA12393B10008D8433 /* utlsymbol.cpp */, + ); + name = tier1; + sourceTree = ""; + }; + 977F707112393A56008D8433 /* vstdlib */ = { + isa = PBXGroup; + children = ( + 977F70B612393B49008D8433 /* commandline.cpp */, + 977F70B712393B49008D8433 /* cvar.cpp */, + 977F70B812393B49008D8433 /* keyvaluessystem.cpp */, + 977F70B912393B49008D8433 /* osversion.cpp */, + 977F70BA12393B49008D8433 /* qsort_s.cpp */, + 977F70BB12393B49008D8433 /* random.cpp */, + 977F70BC12393B49008D8433 /* splitstring.cpp */, + 977F70BD12393B49008D8433 /* stringnormalize.cpp */, + 977F70BE12393B49008D8433 /* strtools.cpp */, + ); + name = vstdlib; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 977F703F12393174008D8433 /* vpc */ = { + isa = PBXNativeTarget; + buildConfigurationList = 977F704812393174008D8433 /* Build configuration list for PBXNativeTarget "vpc" */; + buildPhases = ( + 977F703C12393174008D8433 /* Sources */, + 977F703D12393174008D8433 /* Frameworks */, + 977F703E12393174008D8433 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = vpc; + productName = vpc; + productReference = 977F704012393174008D8433 /* vpc_osx */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 977F703512393173008D8433 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 977F703812393173008D8433 /* Build configuration list for PBXProject "vpc" */; + compatibilityVersion = "Xcode 3.2"; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 977F703312393173008D8433; + productRefGroup = 977F704112393174008D8433 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 977F703F12393174008D8433 /* vpc */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 977F703C12393174008D8433 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 977F706212393A2A008D8433 /* baseprojectdatacollector.cpp in Sources */, + 977F706312393A2A008D8433 /* configuration.cpp in Sources */, + 977F706412393A2A008D8433 /* dependencies.cpp in Sources */, + 977F706512393A2A008D8433 /* ExprSimplifier.cpp in Sources */, + 977F706612393A2A008D8433 /* GroupScript.cpp in Sources */, + 977F706712393A2A008D8433 /* main.cpp in Sources */, + 977F706812393A2A008D8433 /* projectgenerator_makefile.cpp in Sources */, + 977F706912393A2A008D8433 /* projectgenerator_xcode.cpp in Sources */, + 977F706A12393A2A008D8433 /* ProjectScript.cpp in Sources */, + 977F706B12393A2A008D8433 /* solutiongenerator_makefile.cpp in Sources */, + 977F706C12393A2A008D8433 /* solutiongenerator_xcode.cpp in Sources */, + 977F706D12393A2A008D8433 /* sys_utils.cpp in Sources */, + 977F708912393ADD008D8433 /* assert_dialog.cpp in Sources */, + 977F708A12393ADD008D8433 /* cpu_posix.cpp in Sources */, + 977F708B12393ADD008D8433 /* cpu.cpp in Sources */, + 977F708C12393ADD008D8433 /* dbg.cpp in Sources */, + 977F708D12393ADD008D8433 /* fasttimer.cpp in Sources */, + 977F708E12393ADD008D8433 /* mem_helpers.cpp in Sources */, + 977F708F12393ADD008D8433 /* memblockhdr.cpp in Sources */, + 977F709012393ADD008D8433 /* memdbg.cpp in Sources */, + 977F709112393ADD008D8433 /* memstd.cpp in Sources */, + 977F709212393ADD008D8433 /* memvalidate.cpp in Sources */, + 977F709312393ADD008D8433 /* minidump.cpp in Sources */, + 977F709412393ADD008D8433 /* pch_tier0.cpp in Sources */, + 977F709512393ADD008D8433 /* platform_posix.cpp in Sources */, + 977F709612393ADD008D8433 /* pme_posix.cpp in Sources */, + 977F709712393ADD008D8433 /* pmelib.cpp in Sources */, + 977F709812393ADD008D8433 /* testthread.cpp in Sources */, + 977F709912393ADD008D8433 /* thread.cpp in Sources */, + 977F709A12393ADD008D8433 /* threadtools.cpp in Sources */, + 977F709B12393ADD008D8433 /* tier0.cpp in Sources */, + 977F709C12393ADD008D8433 /* validator.cpp in Sources */, + 977F709D12393ADD008D8433 /* valobject.cpp in Sources */, + 977F709E12393ADD008D8433 /* vcrmode_posix.cpp in Sources */, + 977F709F12393ADD008D8433 /* vprof.cpp in Sources */, + 977F70AB12393B10008D8433 /* checksum_crc.cpp in Sources */, + 977F70AC12393B10008D8433 /* checksum_md5.cpp in Sources */, + 977F70AD12393B10008D8433 /* convar.cpp in Sources */, + 977F70AE12393B10008D8433 /* generichash.cpp in Sources */, + 977F70AF12393B10008D8433 /* interface.cpp in Sources */, + 977F70B012393B10008D8433 /* KeyValues.cpp in Sources */, + 977F70B112393B10008D8433 /* mempool.cpp in Sources */, + 977F70B212393B10008D8433 /* memstack.cpp in Sources */, + 977F70B312393B10008D8433 /* stringpool.cpp in Sources */, + 977F70B412393B10008D8433 /* utlbuffer.cpp in Sources */, + 977F70B512393B10008D8433 /* utlsymbol.cpp in Sources */, + 977F70BF12393B49008D8433 /* commandline.cpp in Sources */, + 977F70C012393B49008D8433 /* cvar.cpp in Sources */, + 977F70C112393B49008D8433 /* keyvaluessystem.cpp in Sources */, + 977F70C212393B49008D8433 /* osversion.cpp in Sources */, + 977F70C312393B49008D8433 /* qsort_s.cpp in Sources */, + 977F70C412393B49008D8433 /* random.cpp in Sources */, + 977F70C512393B49008D8433 /* splitstring.cpp in Sources */, + 977F70C612393B49008D8433 /* stringnormalize.cpp in Sources */, + 977F70C712393B49008D8433 /* strtools.cpp in Sources */, + 976DE7B71239412500E8D60A /* crccheck_shared.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 977F704612393174008D8433 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + GCC_VERSION = com.apple.compilers.llvmgcc42; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.5; + STRIP_INSTALLED_PRODUCT = NO; + }; + name = Debug; + }; + 977F704712393174008D8433 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_VERSION = com.apple.compilers.llvmgcc42; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.5; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.5; + STRIP_INSTALLED_PRODUCT = NO; + }; + name = Release; + }; + 977F704912393174008D8433 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = YES; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_MODEL_TUNING = G5; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( + _POSIX, + TIER0_DLL_EXPORT, + GNUC, + POSIX, + OSX, + _OSX, + COMPILER_GCC, + STEAM, + ); + GCC_VERSION = com.apple.compilers.llvmgcc42; + INSTALL_PATH = ../../devtools/bin/; + OTHER_CFLAGS = "-fpermissive"; + PRODUCT_NAME = vpc_osx; + SDKROOT = macosx10.5; + USER_HEADER_SEARCH_PATHS = "../../public ../../common ../../public/tier0 ../../public/tier1 ../../public/vstdlib"; + }; + name = Debug; + }; + 977F704A12393174008D8433 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_MODEL_TUNING = G5; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( + _POSIX, + TIER0_DLL_EXPORT, + GNUC, + POSIX, + OSX, + _OSX, + COMPILER_GCC, + STEAM, + ); + GCC_VERSION = com.apple.compilers.llvmgcc42; + INSTALL_PATH = ../../devtools/bin/; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-fpermissive"; + PRODUCT_NAME = vpc_osx; + SDKROOT = macosx10.5; + USER_HEADER_SEARCH_PATHS = "../../public ../../common ../../public/tier0 ../../public/tier1 ../../public/vstdlib"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 977F703812393173008D8433 /* Build configuration list for PBXProject "vpc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 977F704612393174008D8433 /* Debug */, + 977F704712393174008D8433 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 977F704812393174008D8433 /* Build configuration list for PBXNativeTarget "vpc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 977F704912393174008D8433 /* Debug */, + 977F704A12393174008D8433 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 977F703512393173008D8433 /* Project object */; +} diff --git a/utils/vpccrccheck/crccheck_shared.cpp b/utils/vpccrccheck/crccheck_shared.cpp new file mode 100644 index 0000000..fc1da2b --- /dev/null +++ b/utils/vpccrccheck/crccheck_shared.cpp @@ -0,0 +1,601 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "../vpc/vpc.h" +#include "crccheck_shared.h" +#include "tier1/checksum_crc.h" +#include "tier1/strtools.h" + +#include +#include +#include + +#include + +#ifdef _WIN32 +#include +#else +#include +#define stricmp strcasecmp +#endif + +#include "tier0/memdbgon.h" + +#define MAX_INCLUDE_STACK_DEPTH 10 + +extern const char *g_szArrPlatforms[]; + +static bool IsValidPathChar(char token) { + // does it look like a file? If this ends up too tight, can probably just + // check that it's not '[' or '{' cause conditional blocks are what we really + // want to avoid. + return isalpha(token) || isdigit(token) || (token == '.') || + (token == '\\') || (token == '/'); +} + +static void BuildReplacements(const char *token, char *replacements) { + // Now go pickup the any files that exist, but were non-matches + *replacements = '\0'; + + for (ptrdiff_t i = 0; g_szArrPlatforms[i] != nullptr; i++) { + char path[MAX_PATH]; + char path_expanded[MAX_PATH]; + + V_strncpy(path, token, sizeof(path)); + Sys_ReplaceString(path, "$os", g_szArrPlatforms[i], path_expanded, + sizeof(path_expanded)); + V_FixSlashes(path_expanded); + V_RemoveDotSlashes(path_expanded); + V_FixDoubleSlashes(path_expanded); + + // this fopen is probably using a relative path, but that's ok, as + // everything in the crc code is opening relative paths and assuming the cwd + // is set ok. + FILE *f{fopen(path_expanded, "rb")}; + if (f) { + fclose(f); + + // strcat - blech + // really just need to stick the existing platforms seen in + strcat(replacements, g_szArrPlatforms[i]); + strcat(replacements, ";"); + } + } +} + +static const char *GetToken(const char *ln, char *token) { + *token = '\0'; + + while (*ln && isspace(*ln)) ln++; + + if (!ln[0]) return NULL; + + if (ln[0] == '"') { // does vpc allow \" inside the filename string - + // shouldn't matter, but we're going to assume no. + ln++; + while (*ln) { + if (ln[0] == '"') break; + *token++ = *ln++; + } + *token = '\0'; + } else if (IsValidPathChar(*ln)) { + while (*ln) { + if (isspace(*ln)) break; + *token++ = *ln++; + } + *token = '\0'; + } else { + token[0] = ln[0]; + token[1] = '\0'; + } + + return ln; +} + +static void PerformFileSubstitions(char *line, size_t line_length) { + static bool is_searching_file{false}; + const char *ln{line}; + + if (!is_searching_file) { + ln = V_stristr(ln, "$file "); + + if (ln) is_searching_file = true; + } + + if (is_searching_file) { + char token[1024]; + if (strlen(ln) >= std::size(token)) { + Sys_Error( + "Unable to find file in line. Line %s is too large to get token " + "from"); + } + + ln = GetToken(ln, token); + if (!ln) return; // no more tokens on line, we should try the next line + + is_searching_file = false; + + if (V_stristr(token, "$os")) { + if (!IsValidPathChar(*token)) + fprintf(stderr, + "Warning: can't expand %s for crc calculation. Changes to " + "this file set won't trigger automatic rebuild\n", + token); + + char replacements[2048]; + char buffer[4096]; + + BuildReplacements(token, replacements); + Sys_ReplaceString(line, "$os", replacements, buffer, sizeof(buffer)); + V_strncpy(line, buffer, line_length); + } + } + + static bool is_searching_for_file_pattern{false}; + ln = line; + + if (!is_searching_for_file_pattern) { + ln = V_stristr(ln, "$filepattern"); + while (ln) { + ln += 13; + if (isspace(ln[-1])) { + is_searching_for_file_pattern = true; + break; + } + } + } + + if (is_searching_for_file_pattern) { + char token[1024]; + if (strlen(ln) >= std::size(token)) { + Sys_Error( + "Unable to find file pattern in line. Line %s is too large to get " + "token from"); + } + + ln = GetToken(ln, token); + if (!ln) return; // no more tokens on line, we should try the next line + + is_searching_for_file_pattern = false; + + char replacements[2048]; + replacements[0] = '\0'; + + char buffer[4096]; + CUtlVector results; + Sys_ExpandFilePattern(token, results); + + if (results.Count()) { + for (auto &&r : results) { + V_strncat(replacements, CFmtStr("%s;", r.String()).Access(), + V_ARRAYSIZE(replacements)); + } + + CRC32_t crc{ + CRC32_ProcessSingleBuffer(replacements, V_strlen(replacements))}; + + Sys_ReplaceString(line, token, CFmtStr("%s:%u", token, crc).Access(), + buffer, sizeof(buffer)); + V_strncpy(line, buffer, line_length); + } else { + if (!IsValidPathChar(*token)) + fprintf( + stderr, + "Warning: %s couldn't be expanded during crc calculation. Changes " + "to this file set won't trigger automatic project rebuild\n", + token); + } + } +} + +//----------------------------------------------------------------------------- +// Sys_Error +// +//----------------------------------------------------------------------------- +[[noreturn]] void Sys_Error(PRINTF_FORMAT_STRING const char *format, ...) { + va_list argptr; + + va_start(argptr, format); + vfprintf(stderr, format, argptr); + va_end(argptr); + + exit(1); +} + +static void SafeSnprintf(char *out, int out_length, + PRINTF_FORMAT_STRING const char *format, ...) { + va_list marker; + va_start(marker, format); + V_vsnprintf(out, out_length, format, marker); + va_end(marker); + + out[out_length - 1] = '\0'; +} + +// for linked lists of strings +struct StringNode_t { + StringNode_t *m_pNext; + char m_Text[1]; // the string data +}; + +static StringNode_t *MakeStrNode(char const *str) { + constexpr size_t kNodeSize{sizeof(StringNode_t)}; + const size_t node_size{kNodeSize + strlen(str)}; + + alignas(unsigned char *) auto *rc = + reinterpret_cast(new unsigned char[node_size]); + + strcpy(rc->m_Text, str); + rc->m_pNext = nullptr; + return rc; +} + +//----------------------------------------------------------------------------- +// Sys_LoadTextFileWithIncludes +//----------------------------------------------------------------------------- +size_t Sys_LoadTextFileWithIncludes(const char *file_name, char **buffer, + bool should_insert_file_macro_expansion) { + FILE *file_stack[MAX_INCLUDE_STACK_DEPTH]; + int file_stack_it{MAX_INCLUDE_STACK_DEPTH}; + + StringNode_t *file_lines{nullptr}; // tail ptr for fast adds + + size_t total_file_bytes{0}; + FILE *handle{fopen(file_name, "r")}; + if (!handle) return std::numeric_limits::max(); + + char line_buffer[4096]; + + file_stack[--file_stack_it] = handle; // push + while (file_stack_it < MAX_INCLUDE_STACK_DEPTH) { + // read lines + for (;;) { + char *ln{ + fgets(line_buffer, sizeof(line_buffer), file_stack[file_stack_it])}; + if (!ln) break; // out of text + + ln += strspn(ln, "\t "); // skip white space + + // Need to insert actual files to make sure crc changes if disk-matched + // files match + if (should_insert_file_macro_expansion) + PerformFileSubstitions(ln, sizeof(line_buffer) - (ln - line_buffer)); + + if (memcmp(ln, "#include", 8) == 0) { + // omg, an include + ln += 8; + ln += strspn(ln, " \t\"<"); // skip whitespace, ", and < + + size_t path_name_length{strcspn(ln, " \t\">\n")}; + if (!path_name_length) { + Sys_Error("bad include %s via %s\n", line_buffer, file_name); + } + ln[path_name_length] = 0; // kill everything after end of filename + + FILE *include_file{fopen(ln, "r")}; + if (!include_file) { + Sys_Error("can't open #include of %s\n", ln); + } + + if (!file_stack_it) { + Sys_Error("include nesting too deep via %s", file_name); + } + + file_stack[--file_stack_it] = include_file; + } else { + const size_t line_length{strlen(ln)}; + + total_file_bytes += line_length; + + StringNode_t *new_line{MakeStrNode(ln)}; + + new_line->m_pNext = file_lines; + file_lines = new_line; + } + } + + fclose(file_stack[file_stack_it]); + file_stack_it++; // pop stack + } + + // Reverse the pFileLines list so it goes the right way. + StringNode_t *prev{nullptr}; + StringNode_t *it; + + for (it = file_lines; it;) { + StringNode_t *next{it->m_pNext}; + + it->m_pNext = prev; + prev = it; + it = next; + } + file_lines = prev; + + // Now dump all the lines out into a single buffer. + char *result_buffer = new char[total_file_bytes + 1]; // and null + *buffer = result_buffer; // tell caller + + // copy all strings and null terminate + size_t line{0}; + StringNode_t *next; + for (it = file_lines; it; it = next) { + next = it->m_pNext; + + const size_t length{strlen(it->m_Text)}; + + memcpy(result_buffer, it->m_Text, length); + result_buffer += length; + line++; + + // Cleanup the line.. + // delete [] (unsigned char*)pCur; + } + + *(result_buffer++) = '\0'; // null + + return total_file_bytes; +} + +// Just like fgets() but it removes trailing newlines. +template +static char *ChompLineFromFile(char (&out)[out_bytes], FILE *file) { + char *line{fgets(out, out_bytes, file)}; + if (line) { + const size_t length{strlen(line)}; + if (length > 0 && line[length - 1] == '\n') { + line[length - 1] = '\0'; + + if (length > 1 && line[length - 2] == '\r') line[length - 2] = '\0'; + } + } + + return line; +} + +static bool CheckSupplementalString(const char *supplemental, + const char *reference) { + // The supplemental string is only checked while VPC is determining if a + // project file is stale or not. It's not used by the pre-build event's CRC + // check. The supplemental string contains various options that tell how the + // project was built. It's generated in VPC_GenerateCRCOptionString. + // + // If there's no reference supplemental string (which is the case if we're + // running vpccrccheck.exe), then we ignore it and continue. + if (!reference) return true; + + return (supplemental && stricmp(supplemental, reference) == 0); +} + +static bool CheckVPCExeCRC(char *vpc_crc_check, const char *file_name, + char *error, int error_length) { + if (vpc_crc_check == NULL) { + SafeSnprintf(error, error_length, "Unexpected end-of-file in %s", + file_name); + return false; + } + + char *space{strchr(vpc_crc_check, ' ')}; + if (!space) { + SafeSnprintf(error, error_length, "Invalid line ('%s') in %s", + vpc_crc_check, file_name); + return false; + } + + // Null-terminate it so we have the CRC by itself and the filename follows the + // space. + *space = '\0'; + const char *vpc_file_name{space + 1}; + + // Parse the CRC out. + unsigned int reference_crc; + if (sscanf(vpc_crc_check, "%x", &reference_crc) != 1) { + SafeSnprintf(error, error_length, + "Missed reference CRC for %s: %s is not CRC.", vpc_file_name, + vpc_crc_check); + return false; + } + + char *buffer; + const int vpc_exe_size{Sys_LoadFile(vpc_file_name, (void **)&buffer)}; + if (!buffer) { + SafeSnprintf(error, error_length, "Unable to load %s for comparison.", + vpc_file_name); + return false; + } + + if (vpc_exe_size < 0) { + SafeSnprintf(error, error_length, "Could not load file '%s' to check CRC", + vpc_file_name); + return false; + } + + // Calculate the CRC from the contents of the file. + const CRC32_t actual_crc{CRC32_ProcessSingleBuffer(buffer, vpc_exe_size)}; + // Allocated via malloc buffer. + free(buffer); + + // Compare them. + if (actual_crc != reference_crc) { + SafeSnprintf(error, error_length, + "VPC executable has changed since the project was generated."); + return false; + } + + return true; +} + +bool VPC_CheckProjectDependencyCRCs(const char *project_file_name, + const char *reference_supplemental, + char *error, int error_length) { + // Build the xxxxx.vcproj.vpc_crc filename + char file_name[512]; + SafeSnprintf(file_name, sizeof(file_name), "%s.%s", project_file_name, + VPCCRCCHECK_FILE_EXTENSION); + + // Open it up. + FILE *file{fopen(file_name, "rt")}; + if (!file) { + SafeSnprintf(error, error_length, "Unable to load %s to check CRC strings", + file_name); + return false; + } + + bool rc{false}; + char line_buffer[2048]; + + // Check the version of the CRC file. + const char *version{ChompLineFromFile(line_buffer, file)}; + if (version && stricmp(version, VPCCRCCHECK_FILE_VERSION_STRING) == 0) { + char *vpc_exe_crc{ChompLineFromFile(line_buffer, file)}; + if (CheckVPCExeCRC(vpc_exe_crc, file_name, error, error_length)) { + // Check the supplemental CRC string. + const char *supplemental{ChompLineFromFile(line_buffer, file)}; + if (CheckSupplementalString(supplemental, reference_supplemental)) { + // Now read each line. Each line has a CRC and a filename on it. + while (1) { + char *line{ChompLineFromFile(line_buffer, file)}; + if (!line) { + // We got all the way through the file without a CRC error, so all's + // well. + rc = true; + break; + } + + char *space = strchr(line, ' '); + if (!space) { + SafeSnprintf(error, error_length, "Invalid line ('%s') in %s", line, + file_name); + break; + } + + // Null-terminate it so we have the CRC by itself and the filename + // follows the space. + *space = '\0'; + const char *vpc_file_name = space + 1; + + // Parse the CRC out. + unsigned int reference_crc; + if (sscanf(line, "%x", &reference_crc) != 1) { + SafeSnprintf(error, error_length, + "Invalid reference CRC at line ('%s') in %s", line, + file_name); + break; + } + + // Calculate the CRC from the contents of the file. + char *buffer; + const size_t total_file_bytes{ + Sys_LoadTextFileWithIncludes(vpc_file_name, &buffer, true)}; + if (total_file_bytes == std::numeric_limits::max()) { + SafeSnprintf(error, error_length, + "Unable to load %s for CRC comparison.", + vpc_file_name); + break; + } + + const CRC32_t actual_crc{ + CRC32_ProcessSingleBuffer(buffer, total_file_bytes)}; + delete[] buffer; + + // Compare them. + if (actual_crc != reference_crc) { + SafeSnprintf(error, error_length, + "This VCPROJ is out of sync with its VPC scripts.\n " + "%s mismatches (0x%x vs 0x%x).\n Please use VPC to " + "re-generate!\n \n", + vpc_file_name, reference_crc, actual_crc); + break; + } + } + } else { + SafeSnprintf(error, error_length, "Supplemental string mismatch."); + } + } + } else { + SafeSnprintf(error, error_length, + "CRC file %s has an invalid version string ('%s')", file_name, + version ? version : "[null]"); + } + + fclose(file); + return rc; +} + +static int VPC_OldStyleCRCChecks(int argc, const char **argv) { + for (int i{1}; i + 2 < argc;) { + const char *arg{argv[i]}; + + if (stricmp(arg, "-crc") != 0) { + ++i; + continue; + } + + const char *vpc_file_name{argv[i + 1]}; + + // Get the CRC value on the command line. + const char *test_crc{argv[i + 2]}; + unsigned int crc_from_cmd; + + if (sscanf(test_crc, "%x", &crc_from_cmd) != 1) { + Sys_Error("Unable to parse CRC from command line: %s not a hex CRC.", + test_crc); + } + + // Calculate the CRC from the contents of the file. + char *buffer; + const size_t file_size{ + Sys_LoadTextFileWithIncludes(vpc_file_name, &buffer, true)}; + if (file_size == std::numeric_limits::max()) { + Sys_Error("Unable to load %s for CRC comparison.", vpc_file_name); + } + + CRC32_t actual_crc{CRC32_ProcessSingleBuffer(buffer, file_size)}; + delete[] buffer; + + // Compare them. + if (actual_crc != crc_from_cmd) { + Sys_Error( + " \n This VCPROJ is out of sync with its VPC scripts.\n %s " + "mismatches (0x%x vs 0x%x).\n Please use VPC to re-generate!\n \n", + vpc_file_name, crc_from_cmd, actual_crc); + } + + i += 2; + } + + return 0; +} + +int VPC_CommandLineCRCChecks(int argc, const char **argv) { + if (argc < 2) { + fprintf(stderr, + "Invalid arguments to " VPCCRCCHECK_EXE_FILENAME + ". Format: " VPCCRCCHECK_EXE_FILENAME " [project filename]\n"); + return EINVAL; + } + + const char *first_crc{argv[1]}; + + // If the first argument starts with -crc but is not -crc2, then this is an + // old CRC check command line with all the CRCs and filenames directly on the + // command line. The new format puts all that in a separate file. + if (first_crc[0] == '-' && first_crc[1] == 'c' && first_crc[2] == 'r' && + first_crc[3] == 'c' && first_crc[4] != '2') { + return VPC_OldStyleCRCChecks(argc, argv); + } + + if (stricmp(first_crc, "-crc2") != 0) { + fprintf(stderr, "Missing -crc2 parameter on vpc CRC check command line."); + return EINVAL; + } + + const char *project_file_name{argv[2]}; + + char error[1024]; + bool is_crc_valid{ + VPC_CheckProjectDependencyCRCs(project_file_name, nullptr, error)}; + + if (is_crc_valid) return 0; + + fprintf(stderr, "%s", error); + return EINVAL; +} diff --git a/utils/vpccrccheck/crccheck_shared.h b/utils/vpccrccheck/crccheck_shared.h new file mode 100644 index 0000000..f8ce871 --- /dev/null +++ b/utils/vpccrccheck/crccheck_shared.h @@ -0,0 +1,38 @@ +// Copyright Valve Corporation, All rights reserved. + +#ifndef VPCCRCHECK_CRCCHECK_SHARED_H_ +#define VPCCRCHECK_CRCCHECK_SHARED_H_ + +#ifdef STANDALONE_VPC +#define VPCCRCCHECK_EXE_FILENAME "vpc.exe" +#else +#define VPCCRCCHECK_EXE_FILENAME "vpccrccheck.exe" +#endif + +// The file extension for the file that contains the CRCs that a vcproj depends +// on. +#define VPCCRCCHECK_FILE_EXTENSION "vpc_crc" +#define VPCCRCCHECK_FILE_VERSION_STRING "[vpc crc file version 2]" + +[[noreturn]] void Sys_Error(PRINTF_FORMAT_STRING const char *format, ...); + +size_t Sys_LoadTextFileWithIncludes(const char *file_name, char **buffer, + bool should_insert_file_macro_expansion); + +bool VPC_CheckProjectDependencyCRCs(const char *project_file_name, + const char *reference_summplemental, + char *error, int error_length); + +template +bool VPC_CheckProjectDependencyCRCs(const char *project_file_name, + const char *reference_summplemental, + char (&error)[error_length]) { + return VPC_CheckProjectDependencyCRCs( + project_file_name, reference_summplemental, error, error_length); +} + +// Used by vpccrccheck.exe or by vpc.exe to do the CRC check that's initiated in +// the pre-build steps. +int VPC_CommandLineCRCChecks(int argc, const char **argv); + +#endif // VPCCRCHECK_CRCCHECK_SHARED_H_ diff --git a/utils/vpccrccheck/vpccrccheck.cpp b/utils/vpccrccheck/vpccrccheck.cpp new file mode 100644 index 0000000..28b5667 --- /dev/null +++ b/utils/vpccrccheck/vpccrccheck.cpp @@ -0,0 +1,16 @@ + +#include "tier1/checksum_crc.h" +#include "crccheck_shared.h" +#include +#include + + + +int main( int argc, char **argv ) +{ + return VPC_CommandLineCRCChecks( argc, argv ); +} + + + + diff --git a/utils/vpccrccheck/vpccrccheck.vpc b/utils/vpccrccheck/vpccrccheck.vpc new file mode 100644 index 0000000..6cdfced --- /dev/null +++ b/utils/vpccrccheck/vpccrccheck.vpc @@ -0,0 +1,33 @@ + //----------------------------------------------------------------------------- +// VPCCRCCHECK.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\devtools\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + } +} + +$Project "vpccrccheck" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + $File "vpccrccheck.cpp" + $File "crccheck_shared.cpp" + $File "$SRCDIR/tier1/checksum_crc.cpp" + } + + $Folder "Link Libraries" + { + -$Implib "$LIBPUBLIC\vstdlib" + } +} diff --git a/vpc.sln b/vpc.sln new file mode 100644 index 0000000..467c743 --- /dev/null +++ b/vpc.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31205.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vpc", "vpc.vcxproj", "{539CFC5A-5AAE-4446-8925-96EA63D96EBB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Debug|x64.ActiveCfg = Debug|x64 + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Debug|x64.Build.0 = Debug|x64 + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Debug|x86.ActiveCfg = Debug|Win32 + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Debug|x86.Build.0 = Debug|Win32 + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Release|x64.ActiveCfg = Release|x64 + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Release|x64.Build.0 = Release|x64 + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Release|x86.ActiveCfg = Release|Win32 + {539CFC5A-5AAE-4446-8925-96EA63D96EBB}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E65901F8-3D74-428D-81DB-74C31A9EC5B2} + EndGlobalSection +EndGlobal diff --git a/vpc.vcxproj b/vpc.vcxproj new file mode 100644 index 0000000..4df1e45 --- /dev/null +++ b/vpc.vcxproj @@ -0,0 +1,434 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + true + true + true + true + + + + + + + + + + + + + + + true + true + true + true + + + + + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16.0 + Win32Proj + {539cfc5a-5aae-4446-8925-96ea63d96ebb} + vpc + 10.0 + + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\..\..\..\..\devtools\bin\ + $(Platform)\$(Configuration)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\..\..\..\..\devtools\bin\ + $(Platform)\$(Configuration)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\..\..\..\..\devtools\bin\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\..\..\..\..\devtools\bin\ + + + + Level4 + true + NO_PERFORCE;STANDALONE_VPC;STATIC_TIER0;STATIC_VSTDLIB;COMPILER_MSVC;_CRT_SECURE_NO_WARNINGS;COMPILER_MSVC32;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + public;public\tier0;public\tier1;public\tier2;public\vstdlib + true + Async + MultiThreadedDebugDLL + 4100;4127;4201 + stdcpp17 + ProgramDatabase + true + true + + + Console + true + true + libc;libcd;libcmt;libcpmt;libcpmt1 + true + + + + + Level4 + true + true + true + NO_PERFORCE;STANDALONE_VPC;STATIC_TIER0;STATIC_VSTDLIB;COMPILER_MSVC;_CRT_SECURE_NO_WARNINGS;COMPILER_MSVC32;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + public;public\tier0;public\tier1;public\tier2;public\vstdlib + true + Async + MultiThreadedDLL + 4100;4127;4201 + stdcpp17 + true + AnySuitable + Speed + + + Console + true + true + true + true + libc;libcd;libcmtd;libcpmtd;libcpmtd0;libcpmtd1 + + + + + Level4 + true + NO_PERFORCE;STANDALONE_VPC;STATIC_TIER0;STATIC_VSTDLIB;COMPILER_MSVC;_CRT_SECURE_NO_WARNINGS;COMPILER_MSVC64;PLATFORM_64BITS;X64BITS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + public;public\tier0;public\tier1;public\tier2;public\vstdlib + true + Async + MultiThreadedDebugDLL + 4100;4127;4201 + stdcpp17 + true + true + Guard + ProgramDatabase + true + true + + + Console + true + true + libc;libcd;libcmt;libcpmt;libcpmt1 + true + + + + + Level4 + true + true + true + NO_PERFORCE;STANDALONE_VPC;STATIC_TIER0;STATIC_VSTDLIB;COMPILER_MSVC;_CRT_SECURE_NO_WARNINGS;COMPILER_MSVC64;PLATFORM_64BITS;X64BITS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + public;public\tier0;public\tier1;public\tier2;public\vstdlib + true + Async + MultiThreadedDLL + 4100;4127;4201 + stdcpp17 + true + true + Guard + true + AnySuitable + Speed + + + Console + true + true + true + true + libc;libcd;libcmtd;libcpmtd;libcpmtd0;libcpmtd1 + + + + + + \ No newline at end of file diff --git a/vpc.vcxproj.filters b/vpc.vcxproj.filters new file mode 100644 index 0000000..a640643 --- /dev/null +++ b/vpc.vcxproj.filters @@ -0,0 +1,702 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {73166dc7-80dc-4f2e-b91e-973de357f8b0} + + + {b65dfd4e-c07a-49fb-bb4b-7ca15f7d36a6} + + + {a5ec0753-2f7f-43d6-b7e2-8eeb4324ab69} + + + {a7596c75-7f1d-4cc8-89fb-9654eefa6fa7} + + + {163f31f4-c221-4241-989a-2a9ff257ff6e} + + + {470b9814-bd83-48c5-be3e-d8007cedbc7d} + + + {eb462a40-67b6-48f3-bb7d-d820974c656a} + + + {523180ed-20cc-4bc8-8113-20f82ea8895c} + + + {ab225241-6766-45e9-a546-8d5754989f3c} + + + {8b88a178-f794-43a4-b8df-73981749f4f8} + + + {0072bf51-fc73-463f-b091-7d2ec8412310} + + + {10500149-6e0f-4eaf-b371-01c3890648b0} + + + {f167cbd9-5e02-4550-900c-854b6c52159d} + + + {4c0f80d9-9271-4290-8ab4-ba70e6f7d873} + + + {b7a2944e-f357-422c-b2ef-6bf5db264c12} + + + {3016826e-558d-44cb-a22b-5aa69202e05d} + + + {e13c0bf9-a05d-4591-8869-4710fc908817} + + + {a13c238d-58aa-4095-86a0-76593657a56d} + + + {d8645fea-9ba3-427f-a9fb-ce15e7f5f538} + + + {4d1885ac-ec5a-4dae-af60-3ca7bb9b00e6} + + + {b27d99c2-4d2f-4770-873b-7c07af6e7b3e} + + + + + Source Files\interfaces + + + Source Files\dlmalloc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vpc + + + Source Files\vstdlib + + + Source Files\vstdlib + + + Source Files\vstdlib + + + Source Files\vstdlib + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier1 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\tier0 + + + Source Files\vpccrccheck + + + Source Files\vpc + + + Source Files\vpc + + + + + Header Files\public\appframework + + + Header Files\public\interfaces + + + Header Files\public\mathlib + + + Header Files\public\mathlib + + + Header Files\public\mathlib + + + Header Files\public\mathlib + + + Header Files\public\mathlib + + + Header Files\public\p4lib + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier0 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier1 + + + Header Files\public\tier2 + + + Header Files\public\unitlib + + + Header Files\public\vstdlib + + + Header Files\public\vstdlib + + + Header Files\public\vstdlib + + + Header Files\public\vstdlib + + + Header Files\public\vstdlib + + + Header Files\public\vstdlib + + + Header Files\public + + + Header Files\public + + + Header Files\public + + + Header Files\public + + + Header Files\public + + + Header Files\public + + + Header Files\public + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpc + + + Header Files\vpccrccheck + + + Header Files\vstdlib + + + Header Files\tier0 + + + Header Files\tier0 + + + Header Files\tier0 + + + Header Files\tier0 + + + Header Files\tier0 + + + Header Files\tier0 + + + Header Files\tier0 + + + Header Files\tier0 + + + Header Files\public + + + + + Header Files\public\tier0 + + + + \ No newline at end of file diff --git a/vstdlib/concommandhash.h b/vstdlib/concommandhash.h new file mode 100644 index 0000000..86afb04 --- /dev/null +++ b/vstdlib/concommandhash.h @@ -0,0 +1,204 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Special case hash table for console commands + +#ifndef VPC_VSTDLIB_CONCOMMANDHASH_H_ +#define VPC_VSTDLIB_CONCOMMANDHASH_H_ + +#include "tier1/utllinkedlist.h" +#include "tier1/generichash.h" + +// This is a hash table class very similar to the CUtlHashFast, but +// modified specifically so that we can look up ConCommandBases +// by string names without having to actually store those strings in +// the dictionary, and also iterate over all of them. +// It uses separate chaining: each key hashes to a bucket, each +// bucket is a linked list of hashed commands. We store the hash of +// the command's string name as well as its pointer, so we can do +// the linked list march part of the Find() operation more quickly. +class CConCommandHash { + public: + typedef intp CCommandHashHandle_t; + typedef unsigned int HashKey_t; + + // Constructor/Deconstructor. + CConCommandHash(); + ~CConCommandHash(); + + // Memory. + void Purge(bool bReinitialize); + + // Invalid handle. + static CCommandHashHandle_t InvalidHandle(void) { + return (CCommandHashHandle_t)~0; + } + inline bool IsValidHandle(CCommandHashHandle_t hHash) const; + + /// Initialize. + void Init(void); // bucket count is hardcoded in enum below. + + /// Get hash value for a concommand + static inline HashKey_t Hash(const ConCommandBase *cmd); + + // Size not available; count is meaningless for multilists. + // int Count( void ) const; + + // Insertion. + CCommandHashHandle_t Insert(ConCommandBase *cmd); + CCommandHashHandle_t FastInsert(ConCommandBase *cmd); + + // Removal. + void Remove(CCommandHashHandle_t hHash); + void RemoveAll(void); + + // Retrieval. + inline CCommandHashHandle_t Find(const char *name) const; + CCommandHashHandle_t Find(const ConCommandBase *cmd) const; + // A convenience version of Find that skips the handle part + // and returns a pointer to a concommand, or NULL if none was found. + inline ConCommandBase *FindPtr(const char *name) const; + + inline ConCommandBase *&operator[](CCommandHashHandle_t hHash); + inline ConCommandBase *const &operator[](CCommandHashHandle_t hHash) const; + +#ifdef _DEBUG + // Dump a report to MSG + void Report(void); +#endif + + // Iteration + struct CCommandHashIterator_t { + intp bucket; + CCommandHashHandle_t handle; + + CCommandHashIterator_t(intp _bucket, const CCommandHashHandle_t &_handle) + : bucket(_bucket), handle(_handle){}; + // inline operator UtlHashFastHandle_t() const { return handle; }; + }; + inline CCommandHashIterator_t First() const; + inline CCommandHashIterator_t Next(const CCommandHashIterator_t &hHash) const; + inline bool IsValidIterator(const CCommandHashIterator_t &iter) const; + inline ConCommandBase *&operator[](const CCommandHashIterator_t &iter) { + return (*this)[iter.handle]; + } + inline ConCommandBase *const &operator[]( + const CCommandHashIterator_t &iter) const { + return (*this)[iter.handle]; + } + + private: + // a find func where we've already computed the hash for the string. + // (hidden private in case we decide to invent a custom string hash func + // for this class) + CCommandHashHandle_t Find(const char *name, HashKey_t hash) const; + + protected: + enum { + kNUM_BUCKETS = 256, + kBUCKETMASK = kNUM_BUCKETS - 1, + }; + + struct HashEntry_t { + HashKey_t m_uiKey; + ConCommandBase *m_Data; + + HashEntry_t(unsigned int _hash, ConCommandBase *_cmd) + : m_uiKey(_hash), m_Data(_cmd){}; + + HashEntry_t() : m_uiKey(0), m_Data(nullptr) {}; + }; + + typedef CUtlFixedLinkedList datapool_t; + + CUtlVector m_aBuckets; + datapool_t m_aDataPool; +}; + +inline bool CConCommandHash::IsValidHandle(CCommandHashHandle_t hHash) const { + return m_aDataPool.IsValidIndex(hHash); +} + +inline CConCommandHash::CCommandHashHandle_t CConCommandHash::Find( + const char *name) const { + return Find(name, HashStringCaseless(name)); +} + +inline ConCommandBase *&CConCommandHash::operator[]( + CCommandHashHandle_t hHash) { + return (m_aDataPool[hHash].m_Data); +} + +inline ConCommandBase *const &CConCommandHash::operator[]( + CCommandHashHandle_t hHash) const { + return (m_aDataPool[hHash].m_Data); +} + +//----------------------------------------------------------------------------- +// Purpose: For iterating over the whole hash, return the index of the first +// element +//----------------------------------------------------------------------------- +CConCommandHash::CCommandHashIterator_t CConCommandHash::First() const { + // walk through the buckets to find the first one that has some data + intp bucketCount = m_aBuckets.Count(); + const CCommandHashHandle_t invalidIndex = m_aDataPool.InvalidIndex(); + for (intp bucket = 0; bucket < bucketCount; ++bucket) { + CCommandHashHandle_t iElement = + m_aBuckets[bucket]; // get the head of the bucket + if (iElement != invalidIndex) + return CCommandHashIterator_t(bucket, iElement); + } + + // if we are down here, the list is empty + return CCommandHashIterator_t(-1, invalidIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: For iterating over the whole hash, return the next element after +// the param one. Or an invalid iterator. +//----------------------------------------------------------------------------- +CConCommandHash::CCommandHashIterator_t CConCommandHash::Next( + const CConCommandHash::CCommandHashIterator_t &iter) const { + const CCommandHashHandle_t invalidIndex = m_aDataPool.InvalidIndex(); + { + // look for the next entry in the current bucket + CCommandHashHandle_t next = m_aDataPool.Next(iter.handle); + if (next != invalidIndex) { + // this bucket still has more elements in it + return CCommandHashIterator_t(iter.bucket, next); + } + } + + { + // otherwise look for the next bucket with data + intp bucketCount = m_aBuckets.Count(); + for (intp bucket = iter.bucket + 1; bucket < bucketCount; ++bucket) { + CCommandHashHandle_t next = + m_aBuckets[bucket]; // get the head of the bucket + if (next != invalidIndex) return CCommandHashIterator_t(bucket, next); + } + } + + // if we're here, there's no more data to be had + return CCommandHashIterator_t(-1, invalidIndex); +} + +bool CConCommandHash::IsValidIterator( + const CCommandHashIterator_t &iter) const { + return ((iter.bucket >= 0) && (m_aDataPool.IsValidIndex(iter.handle))); +} + +inline CConCommandHash::HashKey_t CConCommandHash::Hash( + const ConCommandBase *cmd) { + return HashStringCaseless(cmd->GetName()); +} + +inline ConCommandBase *CConCommandHash::FindPtr(const char *name) const { + CCommandHashHandle_t handle = Find(name); + if (handle == InvalidHandle()) { + return NULL; + } else { + return (*this)[handle]; + } +} + +#endif // VPC_VSTDLIB_CONCOMMANDHASH_H_ diff --git a/vstdlib/cvar.cpp b/vstdlib/cvar.cpp new file mode 100644 index 0000000..81a190e --- /dev/null +++ b/vstdlib/cvar.cpp @@ -0,0 +1,1159 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vstdlib/cvar.h" + +#include +#include + +#include "tier0/icommandline.h" +#include "tier1/utlrbtree.h" +#include "tier1/strtools.h" +#include "tier1/keyvalues.h" +#include "tier1/convar.h" +#include "tier0/vprof.h" +#include "tier1/tier1.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include "tier1/fmtstr.h" + +#ifdef _X360 +#include "xbox/xbox_console.h" +#elif defined(_PS3) +#include "ps3/ps3_console.h" +#endif + +#ifdef POSIX +#include +#include + +#define VPROJ_INCREMENT_COUNTER(a, b) /* */ +#define VPROJ(a) /* */ +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Default implementation of CvarQuery +//----------------------------------------------------------------------------- +class CDefaultCvarQuery : public CBaseAppSystem { + public: + virtual void *QueryInterface(const char *pInterfaceName) { + if (!V_stricmp(pInterfaceName, CVAR_QUERY_INTERFACE_VERSION)) + return (ICvarQuery *)this; + return NULL; + } + + virtual bool AreConVarsLinkable(const ConVar *child, const ConVar *parent) { + return true; + } +}; + +static CDefaultCvarQuery s_DefaultCvarQuery; +static ICvarQuery *s_pCVarQuery = NULL; + +#include "concommandhash.h" + +//----------------------------------------------------------------------------- +// Default implementation +//----------------------------------------------------------------------------- +class CCvar : public CBaseAppSystem { + public: + CCvar(); + + // Methods of IAppSystem + virtual bool Connect(CreateInterfaceFn factory); + virtual void Disconnect(); + virtual void *QueryInterface(const char *pInterfaceName); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Inherited from ICVar + virtual CVarDLLIdentifier_t AllocateDLLIdentifier(); + virtual void RegisterConCommand(ConCommandBase *pCommandBase); + virtual void UnregisterConCommand(ConCommandBase *pCommandBase); + virtual void UnregisterConCommands(CVarDLLIdentifier_t id); + virtual const char *GetCommandLineValue(const char *pVariableName); + virtual ConCommandBase *FindCommandBase(const char *name); + virtual const ConCommandBase *FindCommandBase(const char *name) const; + virtual ConVar *FindVar(const char *var_name); + virtual const ConVar *FindVar(const char *var_name) const; + virtual ConCommand *FindCommand(const char *name); + virtual const ConCommand *FindCommand(const char *name) const; + virtual void InstallGlobalChangeCallback(FnChangeCallback_t callback); + virtual void RemoveGlobalChangeCallback(FnChangeCallback_t callback); + virtual void CallGlobalChangeCallbacks(ConVar *var, const char *pOldString, + float flOldValue); + virtual void InstallConsoleDisplayFunc(IConsoleDisplayFunc *pDisplayFunc); + virtual void RemoveConsoleDisplayFunc(IConsoleDisplayFunc *pDisplayFunc); + virtual void ConsoleColorPrintf(const Color &clr, const char *pFormat, + ...) const; + virtual void ConsolePrintf(PRINTF_FORMAT_STRING const char *pFormat, + ...) const; + virtual void ConsoleDPrintf(PRINTF_FORMAT_STRING const char *pFormat, + ...) const; + virtual void RevertFlaggedConVars(int nFlag); + virtual void InstallCVarQuery(ICvarQuery *pQuery); + +#if defined(USE_VXCONSOLE) + virtual void PublishToVXConsole(); +#endif + + virtual void SetMaxSplitScreenSlots(int nSlots); + virtual int GetMaxSplitScreenSlots() const; + + virtual void AddSplitScreenConVars(); + virtual void RemoveSplitScreenConVars(CVarDLLIdentifier_t id); + + virtual intp GetConsoleDisplayFuncCount() const; + virtual void GetConsoleText(int nDisplayFuncIndex, char *pchText, + size_t bufSize) const; + virtual bool IsMaterialThreadSetAllowed() const; + virtual void QueueMaterialThreadSetValue(ConVar *pConVar, const char *pValue); + virtual void QueueMaterialThreadSetValue(ConVar *pConVar, int nValue); + virtual void QueueMaterialThreadSetValue(ConVar *pConVar, float flValue); + virtual bool HasQueuedMaterialThreadConVarSets() const; + virtual int ProcessQueuedMaterialThreadConVarSets(); + + private: + enum { + CONSOLE_COLOR_PRINT = 0, + CONSOLE_PRINT, + CONSOLE_DPRINT, + }; + + void DisplayQueuedMessages(); + + CUtlVector m_GlobalChangeCallbacks; + CUtlVector m_DisplayFuncs; + int m_nNextDLLIdentifier; + + ConCommandBase *m_pConCommandList; + CConCommandHash m_CommandHash; + + // temporary console area so we can store prints before console display funs + // are installed + mutable CUtlBuffer m_TempConsoleBuffer; + int m_nMaxSplitScreenSlots; + + protected: + // internals for ICVarIterator + class CCVarIteratorInternal : public ICVarIteratorInternal { + public: + CCVarIteratorInternal(CCvar *outer) + : m_pOuter(outer), + m_pHash(&outer->m_CommandHash), // remember my CCvar, + m_hashIter(-1, -1) // and invalid iterator + {} + virtual void SetFirst(void); + virtual void Next(void); + virtual bool IsValid(void); + virtual ConCommandBase *Get(void); + + protected: + CCvar *const m_pOuter; + CConCommandHash *const m_pHash; + CConCommandHash::CCommandHashIterator_t m_hashIter; + }; + + virtual ICVarIteratorInternal *FactoryInternalIterator(void); + friend class CCVarIteratorInternal; + + enum ConVarSetType_t { + CONVAR_SET_STRING = 0, + CONVAR_SET_INT, + CONVAR_SET_FLOAT, + }; + + struct QueuedConVarSet_t { + ConVar *m_pConVar; + ConVarSetType_t m_nType; + int m_nInt; + float m_flFloat; + CUtlString m_String; + }; + + struct SplitScreenConVar_t { + SplitScreenConVar_t() { Reset(); } + + void Reset() { + m_VarName = ""; + m_pVar = NULL; + } + + CUtlString m_VarName; + CSplitScreenAddedConVar *m_pVar; + }; + + struct SplitScreenAddedConVars_t { + SplitScreenAddedConVars_t() { + for (int i = 0; i < MAX_SPLITSCREEN_CLIENTS - 1; ++i) { + m_Vars[i].Reset(); + } + } + + // Var names need "static" buffers... + SplitScreenConVar_t m_Vars[MAX_SPLITSCREEN_CLIENTS - 1]; + }; + + CUtlMap m_SplitScreenAddedConVarsMap; + CUtlVector m_QueuedConVarSets; + bool m_bMaterialSystemThreadSetAllowed; + + private: + // Standard console commands -- DO NOT PLACE ANY HIGHER THAN HERE BECAUSE + // THESE MUST BE THE FIRST TO DESTRUCT + CON_COMMAND_MEMBER_F( + CCvar, "find", Find, + "Find concommands with the specified string in their name/help text.", 0) +#ifdef _DEBUG + CON_COMMAND_MEMBER_F(CCvar, "ccvar_hash_report", HashReport, + "report info on bucket distribution of internal hash.", + 0) +#endif +}; + +void CCvar::CCVarIteratorInternal::SetFirst(void) { + m_hashIter = m_pHash->First(); +} + +void CCvar::CCVarIteratorInternal::Next(void) { + m_hashIter = m_pHash->Next(m_hashIter); +} + +bool CCvar::CCVarIteratorInternal::IsValid(void) { + return m_pHash->IsValidIterator(m_hashIter); +} + +ConCommandBase *CCvar::CCVarIteratorInternal::Get(void) { + Assert(IsValid()); + return (*m_pHash)[m_hashIter]; +} + +ICvar::ICVarIteratorInternal *CCvar::FactoryInternalIterator(void) { + return new CCVarIteratorInternal(this); +} + +//----------------------------------------------------------------------------- +// Factor for CVars +//----------------------------------------------------------------------------- +static CCvar s_Cvar; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CCvar, ICvar, CVAR_INTERFACE_VERSION, s_Cvar); + +//----------------------------------------------------------------------------- +// Returns a CVar dictionary for tool usage +//----------------------------------------------------------------------------- +CreateInterfaceFn VStdLib_GetICVarFactory() { return Sys_GetFactoryThis(); } + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CCvar::CCvar() + : m_TempConsoleBuffer((intp)0, (intp)1024), + m_SplitScreenAddedConVarsMap(0, 0, DefLessFunc(ConVar *)) { + m_nNextDLLIdentifier = 0; + m_pConCommandList = NULL; + m_nMaxSplitScreenSlots = 1; + m_bMaterialSystemThreadSetAllowed = false; + m_CommandHash.Init(); +} + +//----------------------------------------------------------------------------- +// Methods of IAppSystem +//----------------------------------------------------------------------------- +bool CCvar::Connect(CreateInterfaceFn factory) { + ConnectTier1Libraries(&factory, 1); + + s_pCVarQuery = (ICvarQuery *)factory(CVAR_QUERY_INTERFACE_VERSION, NULL); + if (!s_pCVarQuery) { + s_pCVarQuery = &s_DefaultCvarQuery; + } + + ConVar_Register(); + return true; +} + +void CCvar::Disconnect() { + ConVar_Unregister(); + s_pCVarQuery = NULL; + DisconnectTier1Libraries(); + + Assert(m_SplitScreenAddedConVarsMap.Count() == 0); +} + +InitReturnVal_t CCvar::Init() { return INIT_OK; } + +void CCvar::Shutdown() {} + +void *CCvar::QueryInterface(const char *pInterfaceName) { + // We implement the ICvar interface + if (!V_strcmp(pInterfaceName, CVAR_INTERFACE_VERSION)) return (ICvar *)this; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Method allowing the engine ICvarQuery interface to take over +//----------------------------------------------------------------------------- +void CCvar::InstallCVarQuery(ICvarQuery *pQuery) { + Assert(s_pCVarQuery == &s_DefaultCvarQuery); + s_pCVarQuery = pQuery ? pQuery : &s_DefaultCvarQuery; +} + +//----------------------------------------------------------------------------- +// Used by DLLs to be able to unregister all their commands + convars +//----------------------------------------------------------------------------- +CVarDLLIdentifier_t CCvar::AllocateDLLIdentifier() { + return m_nNextDLLIdentifier++; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *variable - +//----------------------------------------------------------------------------- +void CCvar::RegisterConCommand(ConCommandBase *variable) { + // Already registered + if (variable->IsRegistered()) return; + + variable->m_bRegistered = true; + + const char *pName = variable->GetName(); + if (!pName || !pName[0]) { + variable->m_pNext = NULL; + return; + } + + // If the variable is already defined, then setup the new variable as a proxy + // to it. + const ConCommandBase *pOther = FindCommandBase(variable->GetName()); + if (pOther) { + if (variable->IsCommand() || pOther->IsCommand()) { + Warning( + "WARNING: unable to link %s and %s because one or more is a " + "ConCommand.\n", + variable->GetName(), pOther->GetName()); + } else { + // This cast is ok because we make sure they're ConVars above. + ConVar *pChildVar = + const_cast(static_cast(variable)); + ConVar *pParentVar = + const_cast(static_cast(pOther)); + + // See if it's a valid linkage + if (s_pCVarQuery->AreConVarsLinkable(pChildVar, pParentVar)) { + // Make sure the default values are the same (but only spew about this + // for FCVAR_REPLICATED) + if (pChildVar->m_pszDefaultValue && pParentVar->m_pszDefaultValue && + pChildVar->IsFlagSet(FCVAR_REPLICATED) && + pParentVar->IsFlagSet(FCVAR_REPLICATED)) { + if (V_stricmp(pChildVar->m_pszDefaultValue, + pParentVar->m_pszDefaultValue) != 0) { + Warning( + "Parent and child ConVars with different default values! %s " + "child: %s parent: %s (parent wins)\n", + variable->GetName(), pChildVar->m_pszDefaultValue, + pParentVar->m_pszDefaultValue); + } + } + + pChildVar->m_pParent = pParentVar->m_pParent; + + // Absorb material thread related convar flags + pParentVar->m_nFlags |= + pChildVar->m_nFlags & + (FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS); + + // Transfer children's callbacks to parent + if (pChildVar->m_fnChangeCallbacks.Count()) { + for (int i = 0; i < pChildVar->m_fnChangeCallbacks.Count(); ++i) { + pParentVar->m_fnChangeCallbacks.AddToTail( + pChildVar->m_fnChangeCallbacks[i]); + } + // Wipe child callbacks + pChildVar->m_fnChangeCallbacks.RemoveAll(); + } + + // make sure we don't have conflicting help strings. + if (pChildVar->m_pszHelpString && + V_strlen(pChildVar->m_pszHelpString) != 0) { + if (pParentVar->m_pszHelpString && + V_strlen(pParentVar->m_pszHelpString) != 0) { + if (V_stricmp(pParentVar->m_pszHelpString, + pChildVar->m_pszHelpString) != 0) { + Warning( + "Convar %s has multiple help strings:\n\tparent (wins): " + "\"%s\"\n\tchild: \"%s\"\n", + variable->GetName(), pParentVar->m_pszHelpString, + pChildVar->m_pszHelpString); + } + } else { + pParentVar->m_pszHelpString = pChildVar->m_pszHelpString; + } + } + + // make sure we don't have conflicting FCVAR_*** flags. + static int const nFlags[] = {FCVAR_CHEAT, FCVAR_REPLICATED, + FCVAR_DONTRECORD, FCVAR_ARCHIVE, + FCVAR_ARCHIVE_GAMECONSOLE}; + static char const *const szFlags[] = { + "FCVAR_CHEAT", "FCVAR_REPLICATED", "FCVAR_DONTRECORD", + "FCVAR_ARCHIVE", "FCVAR_ARCHIVE_GAMECONSOLE"}; + + COMPILE_TIME_ASSERT(std::size(nFlags) == std::size(szFlags)); + + for (int k = 0; k < V_ARRAYSIZE(nFlags); ++k) { + if ((pChildVar->m_nFlags & nFlags[k]) != + (pParentVar->m_nFlags & nFlags[k])) { + Warning( + "Convar %s has conflicting %s flags (child: %s%s, parent: " + "%s%s, parent wins)\n", + variable->GetName(), szFlags[k], + (pChildVar->m_nFlags & nFlags[k]) ? "has " : "no ", szFlags[k], + (pParentVar->m_nFlags & nFlags[k]) ? "has " : "no ", + szFlags[k]); + } + } + } + } + + variable->m_pNext = NULL; + return; + } + + // link the variable in + variable->m_pNext = m_pConCommandList; + m_pConCommandList = variable; + + AssertMsg1(FindCommandBase(variable->GetName()) == NULL, + "Console command %s added twice!", variable->GetName()); + m_CommandHash.Insert(variable); +} + +void CCvar::AddSplitScreenConVars() { + if (m_nMaxSplitScreenSlots == 1) return; + + for (ConCommandBase *pCommand = m_pConCommandList; pCommand; + pCommand = pCommand->m_pNext) { + if (pCommand->IsCommand()) continue; + + ConVar *pConVar = static_cast(pCommand); + + if (!pConVar->IsFlagSet(FCVAR_SS)) continue; + + // See if it's already mapped in + unsigned short idx = m_SplitScreenAddedConVarsMap.Find(pConVar); + if (idx == m_SplitScreenAddedConVarsMap.InvalidIndex()) { + idx = m_SplitScreenAddedConVarsMap.Insert(pConVar); + } + + SplitScreenAddedConVars_t &info = m_SplitScreenAddedConVarsMap[idx]; + for (int i = 1; i < m_nMaxSplitScreenSlots; ++i) { + // Already registered it + if (info.m_Vars[i - 1].m_pVar) continue; + + // start at name2, etc. + info.m_Vars[i - 1].m_VarName = CFmtStr("%s%d", pConVar->GetName(), i + 1); + + CSplitScreenAddedConVar *pVar = new CSplitScreenAddedConVar( + i, info.m_Vars[i - 1].m_VarName.Get(), pConVar); + info.m_Vars[i - 1].m_pVar = pVar; + pVar->SetSplitScreenPlayerSlot(i); + + RegisterConCommand(pVar); + } + } + + ConCommandBase::s_pConCommandBases = NULL; +} + +void CCvar::RemoveSplitScreenConVars(CVarDLLIdentifier_t id) + +{ + if (m_nMaxSplitScreenSlots == 1) { + Assert(m_SplitScreenAddedConVarsMap.Count() == 0); + return; + } + + CUtlVector deleted; + + FOR_EACH_MAP(m_SplitScreenAddedConVarsMap, j) { + ConVar *key = m_SplitScreenAddedConVarsMap.Key(j); + + if (key->GetDLLIdentifier() != id) { + continue; + } + + SplitScreenAddedConVars_t &info = m_SplitScreenAddedConVarsMap[j]; + + for (int i = 1; i < m_nMaxSplitScreenSlots; ++i) { + if (info.m_Vars[i - 1].m_pVar) { + UnregisterConCommand(info.m_Vars[i - 1].m_pVar); + delete info.m_Vars[i - 1].m_pVar; + info.m_Vars[i - 1].m_pVar = NULL; + } + } + deleted.AddToTail(key); + } + + for (int i = 0; i < deleted.Count(); ++i) { + m_SplitScreenAddedConVarsMap.Remove(deleted[i]); + } +} + +void CCvar::UnregisterConCommand(ConCommandBase *pCommandToRemove) { + // Not registered? Don't bother + if (!pCommandToRemove->IsRegistered()) return; + + pCommandToRemove->m_bRegistered = false; + + // FIXME: Should we make this a doubly-linked list? Would remove faster + ConCommandBase *pPrev = NULL; + for (ConCommandBase *pCommand = m_pConCommandList; pCommand; + pCommand = pCommand->m_pNext) { + if (pCommand != pCommandToRemove) { + pPrev = pCommand; + continue; + } + + if (pPrev == NULL) { + m_pConCommandList = pCommand->m_pNext; + } else { + pPrev->m_pNext = pCommand->m_pNext; + } + pCommand->m_pNext = NULL; + m_CommandHash.Remove(m_CommandHash.Find(pCommand)); + break; + } +} + +void CCvar::UnregisterConCommands(CVarDLLIdentifier_t id) { + ConCommandBase *pNewList; + ConCommandBase *pCommand, *pNext; + + pNewList = NULL; + + m_CommandHash.Purge(true); + pCommand = m_pConCommandList; + while (pCommand) { + pNext = pCommand->m_pNext; + if (pCommand->GetDLLIdentifier() != id) { + pCommand->m_pNext = pNewList; + pNewList = pCommand; + + m_CommandHash.Insert(pCommand); + } else { + // Unlink + pCommand->m_bRegistered = false; + pCommand->m_pNext = NULL; + } + + pCommand = pNext; + } + + m_pConCommandList = pNewList; +} + +//----------------------------------------------------------------------------- +// Finds base commands +//----------------------------------------------------------------------------- +const ConCommandBase *CCvar::FindCommandBase(const char *name) const { + VPROF_INCREMENT_COUNTER("CCvar::FindCommandBase", 1); + VPROF_BUDGET("CCvar::FindCommandBase", VPROF_BUDGETGROUP_CVAR_FIND); + + return m_CommandHash.FindPtr(name); +} + +ConCommandBase *CCvar::FindCommandBase(const char *name) { + VPROF_INCREMENT_COUNTER("CCvar::FindCommandBase", 1); + VPROF_BUDGET("CCvar::FindCommandBase", VPROF_BUDGETGROUP_CVAR_FIND); + + return m_CommandHash.FindPtr(name); +} + +//----------------------------------------------------------------------------- +// Purpose Finds ConVars +//----------------------------------------------------------------------------- +const ConVar *CCvar::FindVar(const char *var_name) const { + const ConCommandBase *var = FindCommandBase(var_name); + if (!var) { + return NULL; + } else { + if (var->IsCommand()) { + AssertMsg1(false, + "Tried to look up command %s as if it were a variable.\n", + var_name); + return NULL; + } + } + + return static_cast(var); +} + +ConVar *CCvar::FindVar(const char *var_name) { + ConCommandBase *var = FindCommandBase(var_name); + if (!var) { + return NULL; + } else { + if (var->IsCommand()) { + AssertMsg1(false, + "Tried to look up command %s as if it were a variable.\n", + var_name); + return NULL; + } + } + + return static_cast(var); +} + +//----------------------------------------------------------------------------- +// Purpose Finds ConCommands +//----------------------------------------------------------------------------- +const ConCommand *CCvar::FindCommand(const char *pCommandName) const { + const ConCommandBase *var = FindCommandBase(pCommandName); + if (!var || !var->IsCommand()) return NULL; + + return static_cast(var); +} + +ConCommand *CCvar::FindCommand(const char *pCommandName) { + ConCommandBase *var = FindCommandBase(pCommandName); + if (!var || !var->IsCommand()) return NULL; + + return static_cast(var); +} + +const char *CCvar::GetCommandLineValue(const char *pVariableName) { + intp nLen = V_strlen(pVariableName); + char *pSearch = (char *)stackalloc(nLen + 2); + pSearch[0] = '+'; + memcpy(&pSearch[1], pVariableName, nLen + 1); + return CommandLine()->ParmValue(pSearch); +} + +//----------------------------------------------------------------------------- +// Install, remove global callbacks +//----------------------------------------------------------------------------- +void CCvar::InstallGlobalChangeCallback(FnChangeCallback_t callback) { + Assert(callback && m_GlobalChangeCallbacks.Find(callback) < 0); + m_GlobalChangeCallbacks.AddToTail(callback); +} + +void CCvar::RemoveGlobalChangeCallback(FnChangeCallback_t callback) { + Assert(callback); + m_GlobalChangeCallbacks.FindAndRemove(callback); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCvar::CallGlobalChangeCallbacks(ConVar *var, const char *pOldString, + float flOldValue) { + intp nCallbackCount = m_GlobalChangeCallbacks.Count(); + for (intp i = 0; i < nCallbackCount; ++i) { + (*m_GlobalChangeCallbacks[i])(var, pOldString, flOldValue); + } +} + +//----------------------------------------------------------------------------- +// Sets convars containing the flags to their default value +//----------------------------------------------------------------------------- +void CCvar::RevertFlaggedConVars(int nFlag) { + for (CConCommandHash::CCommandHashIterator_t i = m_CommandHash.First(); + m_CommandHash.IsValidIterator(i); i = m_CommandHash.Next(i)) { + ConCommandBase *var = m_CommandHash[i]; + if (var->IsCommand()) continue; + + ConVar *cv = (ConVar *)var; + + if (!cv->IsFlagSet(nFlag)) continue; + + // It's == to the default value, don't count + if (!V_stricmp(cv->GetDefault(), cv->GetString())) continue; + + cv->Revert(); + // DevMsg( "%s = \"%s\" (reverted)\n", cvar->GetName(), cvar->GetString() ); + } +} + +//----------------------------------------------------------------------------- +// Deal with queued material system convars +//----------------------------------------------------------------------------- +bool CCvar::IsMaterialThreadSetAllowed() const { + Assert(ThreadInMainThread()); + return m_bMaterialSystemThreadSetAllowed; +} + +void CCvar::QueueMaterialThreadSetValue(ConVar *pConVar, const char *pValue) { + Assert(ThreadInMainThread()); + intp j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_STRING; + m_QueuedConVarSets[j].m_String = pValue; +} + +void CCvar::QueueMaterialThreadSetValue(ConVar *pConVar, int nValue) { + Assert(ThreadInMainThread()); + intp j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_INT; + m_QueuedConVarSets[j].m_nInt = nValue; +} + +void CCvar::QueueMaterialThreadSetValue(ConVar *pConVar, float flValue) { + Assert(ThreadInMainThread()); + intp j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_FLOAT; + m_QueuedConVarSets[j].m_flFloat = flValue; +} + +bool CCvar::HasQueuedMaterialThreadConVarSets() const { + Assert(ThreadInMainThread()); + return m_QueuedConVarSets.Count() > 0; +} + +int CCvar::ProcessQueuedMaterialThreadConVarSets() { + Assert(ThreadInMainThread()); + m_bMaterialSystemThreadSetAllowed = true; + + int nUpdateFlags = 0; + intp nCount = m_QueuedConVarSets.Count(); + for (intp i = 0; i < nCount; ++i) { + const QueuedConVarSet_t &set = m_QueuedConVarSets[i]; + switch (set.m_nType) { + case CONVAR_SET_FLOAT: + set.m_pConVar->SetValue(set.m_flFloat); + break; + case CONVAR_SET_INT: + set.m_pConVar->SetValue(set.m_nInt); + break; + case CONVAR_SET_STRING: + set.m_pConVar->SetValue(set.m_String); + break; + } + + nUpdateFlags |= set.m_pConVar->GetFlags() & FCVAR_MATERIAL_THREAD_MASK; + } + + m_QueuedConVarSets.RemoveAll(); + m_bMaterialSystemThreadSetAllowed = false; + return nUpdateFlags; +} + +//----------------------------------------------------------------------------- +// Display queued messages +//----------------------------------------------------------------------------- +void CCvar::DisplayQueuedMessages() { + // Display any queued up messages + if (m_TempConsoleBuffer.TellPut() == 0) return; + + Color clr; + intp nStringLength; + while (m_TempConsoleBuffer.IsValid()) { + int nType = m_TempConsoleBuffer.GetChar(); + if (nType == CONSOLE_COLOR_PRINT) { + clr.SetRawColor(m_TempConsoleBuffer.GetInt()); + } + nStringLength = m_TempConsoleBuffer.PeekStringLength(); + char *pTemp = (char *)stackalloc(nStringLength + 1); + m_TempConsoleBuffer.GetString(pTemp); + + switch (nType) { + case CONSOLE_COLOR_PRINT: + ConsoleColorPrintf(clr, pTemp); + break; + + case CONSOLE_PRINT: + ConsolePrintf(pTemp); + break; + + case CONSOLE_DPRINT: + ConsoleDPrintf(pTemp); + break; + } + } + + m_TempConsoleBuffer.Purge(); +} + +//----------------------------------------------------------------------------- +// Install a console printer +//----------------------------------------------------------------------------- +void CCvar::InstallConsoleDisplayFunc(IConsoleDisplayFunc *pDisplayFunc) { + Assert(m_DisplayFuncs.Find(pDisplayFunc) < 0); + m_DisplayFuncs.AddToTail(pDisplayFunc); + DisplayQueuedMessages(); +} + +void CCvar::RemoveConsoleDisplayFunc(IConsoleDisplayFunc *pDisplayFunc) { + m_DisplayFuncs.FindAndRemove(pDisplayFunc); +} + +intp CCvar::GetConsoleDisplayFuncCount() const { return m_DisplayFuncs.Count(); } + +void CCvar::GetConsoleText(int nDisplayFuncIndex, char *pchText, + size_t bufSize) const { + m_DisplayFuncs[nDisplayFuncIndex]->GetConsoleText(pchText, bufSize); +} + +void CCvar::ConsoleColorPrintf(const Color &clr, const char *pFormat, + ...) const { + char temp[8192]; + va_list argptr; + va_start(argptr, pFormat); + _vsnprintf(temp, sizeof(temp) - 1, pFormat, argptr); + va_end(argptr); + temp[sizeof(temp) - 1] = 0; + + intp c = m_DisplayFuncs.Count(); + if (c == 0) { + m_TempConsoleBuffer.PutChar(CONSOLE_COLOR_PRINT); + m_TempConsoleBuffer.PutInt(clr.GetRawColor()); + m_TempConsoleBuffer.PutString(temp); + return; + } + + for (intp i = 0; i < c; ++i) { + m_DisplayFuncs[i]->ColorPrint(clr, temp); + } +} + +void CCvar::ConsolePrintf(PRINTF_FORMAT_STRING const char *pFormat, ...) const { + char temp[8192]; + va_list argptr; + va_start(argptr, pFormat); + _vsnprintf(temp, sizeof(temp) - 1, pFormat, argptr); + va_end(argptr); + temp[sizeof(temp) - 1] = 0; + + intp c = m_DisplayFuncs.Count(); + if (c == 0) { + m_TempConsoleBuffer.PutChar(CONSOLE_PRINT); + m_TempConsoleBuffer.PutString(temp); + return; + } + + for (intp i = 0; i < c; ++i) { + m_DisplayFuncs[i]->Print(temp); + } +} + +void CCvar::ConsoleDPrintf(PRINTF_FORMAT_STRING const char *pFormat, + ...) const { + char temp[8192]; + va_list argptr; + va_start(argptr, pFormat); + _vsnprintf(temp, sizeof(temp) - 1, pFormat, argptr); + va_end(argptr); + temp[sizeof(temp) - 1] = 0; + + intp c = m_DisplayFuncs.Count(); + if (c == 0) { + m_TempConsoleBuffer.PutChar(CONSOLE_DPRINT); + m_TempConsoleBuffer.PutString(temp); + return; + } + + for (intp i = 0; i < c; ++i) { + m_DisplayFuncs[i]->DPrint(temp); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#if defined(USE_VXCONSOLE) +#ifdef _PS3 +/* +Here's a terrible hack. +In porting the part of the game that speaks to VXConsole, EA chose to +write it as a cluster of global functions, instead of a class interface like +Aaron did with IXboxConsole. Some of these globals need access to symbols +inside the engine, so they are defined there. However, CCvar is inside vstdlib. +In the EA build this didn't make a difference because everything was a huge +monolithic executable, and you could just access any symbol from anywhere. +In our build, with its PRXes, that doesn't fly. +So, the proper solution to this problem is to wrap all of the PS3 vxconsole +stuff in an interface, put it inside vstlib, create the dcim connection there, +and then export the interface pointer. The engine meanwhile would export the +symbols the vxlib needs, and then we give that interface class inside +vstlib a pointer to the engine once the engine is available. +Right now however I just want to get the thing working with as little +modification as possible so I can fix the vxconsole windows app itself and +hopefully get bidirectional TTY to our game. So, instead of the proper solution, +I'm just duct-taping everything together by simply passing a pointer to the +engine symbol this function needs whenever I call it. Blech. I'll fix it later. +-egr 4/29/10. (is it later than September 2010? go call egr and make fun of +him.) +*/ +void CCvar::PublishToVXConsole() +#else +void CCvar::PublishToVXConsole() +#endif +{ + const char *commands[6 * 1024]; + const char *helptext[6 * 1024]; + int numCommands = 0; + + // iterate and publish commands to the remote console + for (CConCommandHash::CCommandHashIterator_t i = m_CommandHash.First(); + m_CommandHash.IsValidIterator(i); i = m_CommandHash.Next(i)) { + ConCommandBase *pCur = m_CommandHash[i]; + // add unregistered commands to list + if (numCommands < sizeof(commands) / sizeof(commands[0])) { + commands[numCommands] = pCur->GetName(); + helptext[numCommands] = pCur->GetHelpText(); + numCommands++; + } + } + + if (numCommands) { +#ifdef _PS3 + g_pValvePS3Console->AddCommands(numCommands, commands, helptext); +#else + XBX_rAddCommands(numCommands, commands, helptext); +#endif + } +} + +#endif + +static bool ConVarSortFunc(ConCommandBase *const &lhs, + ConCommandBase *const &rhs) { + return CaselessStringLessThan(lhs->GetName(), rhs->GetName()); +} + +//----------------------------------------------------------------------------- +// Console commands +//----------------------------------------------------------------------------- +void CCvar::Find(const CCommand &args) { + const char *search; + + if (args.ArgC() != 2) { + ConMsg("Usage: find \n"); + return; + } + + // Get substring to find + search = args[1]; + + CUtlRBTree sorted(0, 0, ConVarSortFunc); + + // Loop through vars and print out findings + for (CConCommandHash::CCommandHashIterator_t i = m_CommandHash.First(); + m_CommandHash.IsValidIterator(i); i = m_CommandHash.Next(i)) { + ConCommandBase *var = m_CommandHash[i]; + if (var->IsFlagSet(FCVAR_DEVELOPMENTONLY) || var->IsFlagSet(FCVAR_HIDDEN)) + continue; + + if (!V_stristr(var->GetName(), search) && + !V_stristr(var->GetHelpText(), search)) + continue; + + sorted.Insert(var); + } + + for (int i = sorted.FirstInorder(); i != sorted.InvalidIndex(); + i = sorted.NextInorder(i)) { + ConVar_PrintDescription(sorted[i]); + } +} + +#ifdef _DEBUG +void CCvar::HashReport(const CCommand &args) { m_CommandHash.Report(); } +#endif + +void CCvar::SetMaxSplitScreenSlots(int nSlots) { + m_nMaxSplitScreenSlots = nSlots; + + AddSplitScreenConVars(); +} + +int CCvar::GetMaxSplitScreenSlots() const { return m_nMaxSplitScreenSlots; } + +//----------------------------------------------------------------------------- +// Console command hash data structure +//----------------------------------------------------------------------------- +CConCommandHash::CConCommandHash() { Purge(true); } + +CConCommandHash::~CConCommandHash() { Purge(false); } + +void CConCommandHash::Purge(bool bReinitialize) { + m_aBuckets.Purge(); + m_aDataPool.Purge(); + if (bReinitialize) { + Init(); + } +} + +// Initialize. +void CConCommandHash::Init(void) { + // kNUM_BUCKETS must be a power of two. + COMPILE_TIME_ASSERT((kNUM_BUCKETS & (kNUM_BUCKETS - 1)) == 0); + + // Set the bucket size. + m_aBuckets.SetSize(kNUM_BUCKETS); + for (int iBucket = 0; iBucket < kNUM_BUCKETS; ++iBucket) { + m_aBuckets[iBucket] = m_aDataPool.InvalidIndex(); + } + + // Calculate the grow size. + int nGrowSize = 4 * kNUM_BUCKETS; + m_aDataPool.SetGrowSize(nGrowSize); +} + +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), +// WITH a check to see if the element already exists within the hash. +//----------------------------------------------------------------------------- +CConCommandHash::CCommandHashHandle_t CConCommandHash::Insert( + ConCommandBase *cmd) { + // Check to see if that key already exists in the buckets (should be unique). + CCommandHashHandle_t hHash = Find(cmd); + if (hHash != InvalidHandle()) return hHash; + + return FastInsert(cmd); +} +//----------------------------------------------------------------------------- +// Purpose: Insert data into the hash table given its key (unsigned int), +// WITHOUT a check to see if the element already exists within the +// hash. +//----------------------------------------------------------------------------- +CConCommandHash::CCommandHashHandle_t CConCommandHash::FastInsert( + ConCommandBase *cmd) { + // Get a new element from the pool. + intp iHashData = m_aDataPool.Alloc(true); + HashEntry_t *pHashData = &m_aDataPool[iHashData]; + + HashKey_t key = Hash(cmd); + + // Add data to new element. + pHashData->m_uiKey = key; + pHashData->m_Data = cmd; + + // Link element. + int iBucket = key & kBUCKETMASK; // HashFuncs::Hash( uiKey, m_uiBucketMask ); + m_aDataPool.LinkBefore(m_aBuckets[iBucket], iHashData); + m_aBuckets[iBucket] = iHashData; + + return iHashData; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a given element from the hash. +//----------------------------------------------------------------------------- +void CConCommandHash::Remove(CCommandHashHandle_t hHash) { + HashEntry_t *entry = &m_aDataPool[hHash]; + HashKey_t iBucket = entry->m_uiKey & kBUCKETMASK; + if (m_aBuckets[iBucket] == hHash) { + // It is a bucket head. + m_aBuckets[iBucket] = m_aDataPool.Next(hHash); + } else { + // Not a bucket head. + m_aDataPool.Unlink(hHash); + } + + // Remove the element. + m_aDataPool.Remove(hHash); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all elements from the hash +//----------------------------------------------------------------------------- +void CConCommandHash::RemoveAll(void) { + m_aBuckets.RemoveAll(); + m_aDataPool.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Find hash entry corresponding to a string name +//----------------------------------------------------------------------------- +CConCommandHash::CCommandHashHandle_t CConCommandHash::Find( + const char *name, HashKey_t hashkey) const { + // hash the "key" - get the correct hash table "bucket" + int iBucket = hashkey & kBUCKETMASK; + + for (datapool_t::IndexLocalType_t iElement = m_aBuckets[iBucket]; + iElement != m_aDataPool.InvalidIndex(); + iElement = m_aDataPool.Next(iElement)) { + const HashEntry_t &element = m_aDataPool[iElement]; + if (element.m_uiKey == hashkey && // if hashes of strings match, + V_stricmp(name, element.m_Data->GetName()) == + 0) // then test the actual strings + { + return iElement; + } + } + + // found nuffink + return InvalidHandle(); +} + +//----------------------------------------------------------------------------- +// Find a command in the hash. +//----------------------------------------------------------------------------- +CConCommandHash::CCommandHashHandle_t CConCommandHash::Find( + const ConCommandBase *cmd) const { + // Set this #if to 1 if the assert at bottom starts whining -- + // that indicates that a console command is being double-registered, + // or something similarly nonfatally bad. With this #if 1, we'll search + // by name instead of by pointer, which is more robust in the face + // of double registered commands, but obviously slower. +#if 0 + return Find(cmd->GetName()); +#else + HashKey_t hashkey = Hash(cmd); + int iBucket = hashkey & kBUCKETMASK; + + // hunt through all entries in that bucket + for (datapool_t::IndexLocalType_t iElement = m_aBuckets[iBucket]; + iElement != m_aDataPool.InvalidIndex(); + iElement = m_aDataPool.Next(iElement)) { + const HashEntry_t &element = m_aDataPool[iElement]; + if (element.m_uiKey == hashkey && // if the hashes match... + element.m_Data == cmd) // and the pointers... + { + // in debug, test to make sure we don't have commands under the same name + // or something goofy like that + AssertMsg1(iElement == Find(cmd->GetName()), + "ConCommand %s had two entries in the hash!", cmd->GetName()); + + // return this element + return iElement; + } + } + + // found nothing. +#ifdef DBGFLAG_ASSERT // double check against search by name + CCommandHashHandle_t dbghand = Find(cmd->GetName()); + + AssertMsg1( + InvalidHandle() == dbghand, + "ConCommand %s couldn't be found by pointer, but was found by name!", + cmd->GetName()); +#endif + return InvalidHandle(); +#endif +} + +#ifdef _DEBUG +// Dump a report to MSG +void CConCommandHash::Report(void) { + Msg("Console command hash bucket load:\n"); + int total = 0; + for (int iBucket = 0; iBucket < kNUM_BUCKETS; ++iBucket) { + int count = 0; + CCommandHashHandle_t iElement = + m_aBuckets[iBucket]; // get the head of the bucket + while (iElement != m_aDataPool.InvalidIndex()) { + ++count; + iElement = m_aDataPool.Next(iElement); + } + + Msg("%d: %d\n", iBucket, count); + total += count; + } + + Msg("\tAverage: %.1f\n", total / ((float)(kNUM_BUCKETS))); +} +#endif diff --git a/vstdlib/keyvaluessystem.cpp b/vstdlib/keyvaluessystem.cpp new file mode 100644 index 0000000..058c82f --- /dev/null +++ b/vstdlib/keyvaluessystem.cpp @@ -0,0 +1,576 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "vstdlib/ikeyvaluessystem.h" + +#include "tier1/keyvalues.h" +#include "tier1/mempool.h" +#include "tier1/utlsymbol.h" +#include "tier1/utlmap.h" +#include "tier0/threadtools.h" +#include "tier1/memstack.h" +#include "tier1/convar.h" + +#ifdef _PS3 +#include "ps3/ps3_core.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef NO_SBH // no need to pool if using tier0 small block heap +#define KEYVALUES_USE_POOL 1 +#endif + +// +// Defines platform-endian-specific macros: +// MEM_4BYTES_AS_0_AND_3BYTES : present a 4 byte uint32 as a memory +// layout where first memory byte is zero +// and the other 3 bytes represent value +// MEM_4BYTES_FROM_0_AND_3BYTES: unpack from memory with first zero byte +// and 3 value bytes the original uint32 value +// +// used for efficiently reading/writing storing 3 byte values into memory +// region immediately following a null-byte-terminated string, essentially +// sharing the null-byte-terminator with the first memory byte +// +#if defined(PLAT_LITTLE_ENDIAN) +// Number in memory has lowest-byte in front, use shifts to make it zero +#define MEM_4BYTES_AS_0_AND_3BYTES(x4bytes) (((uint32)(x4bytes)) << 8) +#define MEM_4BYTES_FROM_0_AND_3BYTES(x03bytes) (((uint32)(x03bytes)) >> 8) +#endif +#if defined(PLAT_BIG_ENDIAN) +// Number in memory has highest-byte in front, use masking to make it zero +#define MEM_4BYTES_AS_0_AND_3BYTES(x4bytes) (((uint32)(x4bytes)) & 0x00FFFFFF) +#define MEM_4BYTES_FROM_0_AND_3BYTES(x03bytes) \ + (((uint32)(x03bytes)) & 0x00FFFFFF) +#endif + +//----------------------------------------------------------------------------- +// Purpose: Central storage point for KeyValues memory and symbols +//----------------------------------------------------------------------------- +class CKeyValuesSystem : public IKeyValuesSystem { + public: + CKeyValuesSystem(); + ~CKeyValuesSystem(); + + // registers the size of the KeyValues in the specified instance + // so it can build a properly sized memory pool for the KeyValues objects + // the sizes will usually never differ but this is for versioning safety + void RegisterSizeofKeyValues(size_t size); + + // allocates/frees a KeyValues object from the shared mempool + void *AllocKeyValuesMemory(size_t size); + void FreeKeyValuesMemory(void *pMem); + + // symbol table access (used for key names) + HKeySymbol GetSymbolForString(const char *name, bool bCreate); + const char *GetStringForSymbol(HKeySymbol symbol); + + // returns the wide version of ansi, also does the lookup on #'d strings + void GetLocalizedFromANSI(const char *ansi, wchar_t *outBuf, + int unicodeBufferSizeInBytes); + void GetANSIFromLocalized(const wchar_t *wchar, char *outBuf, + int ansiBufferSizeInBytes); + + // for debugging, adds KeyValues record into global list so we can track + // memory leaks + virtual void AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name); + virtual void RemoveKeyValuesFromMemoryLeakList(void *pMem); + + // set/get a value for keyvalues resolution symbol + // e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables + // [$LOWVIOLENCE] + virtual void SetKeyValuesExpressionSymbol(const char *name, bool bValue); + virtual bool GetKeyValuesExpressionSymbol(const char *name); + + // symbol table access from code with case-preserving requirements (used for + // key names) + virtual HKeySymbol GetSymbolForStringCaseSensitive( + HKeySymbol &hCaseInsensitiveSymbol, const char *name, + bool bCreate = true); + + private: +#ifdef KEYVALUES_USE_POOL + CUtlMemoryPool *m_pMemPool; +#endif + size_t m_iMaxKeyValuesSize; + + // string hash table + /* + Here's the way key values system data structures are laid out: + hash table with 2047 hash buckets: + [0] { hash_item_t } + [1] + [2] + ... + each hash_item_t's stringIndex is an offset in m_Strings memory + at that offset we store the actual null-terminated string followed + by another 3 bytes for an alternative capitalization. + These 3 trailing bytes are set to 0 if no alternative capitalization + variants are present in the dictionary. + These trailing 3 bytes are interpreted as stringIndex into m_Strings + memory for the next alternative capitalization + + Getting a string value by HKeySymbol : constant time access at the + string memory represented by stringIndex + + Getting a symbol for a string value: + 1) compute the hash + 2) start walking the hash-bucket using special version of stricmp + until a case insensitive match is found + 3a) for case-insensitive lookup return the found stringIndex + 3b) for case-sensitive lookup keep walking the list of alternative + capitalizations using strcmp until exact case match is found + */ + CMemoryStack m_Strings; + struct hash_item_t { + int stringIndex; + hash_item_t *next; + }; + CUtlMemoryPool m_HashItemMemPool; + CUtlVector m_HashTable; + int CaseInsensitiveHash(const char *string, intp iBounds); + + struct MemoryLeakTracker_t { + int nameIndex; + void *pMem; + }; + static bool MemoryLeakTrackerLessFunc(const MemoryLeakTracker_t &lhs, + const MemoryLeakTracker_t &rhs) { + return lhs.pMem < rhs.pMem; + } + CUtlRBTree m_KeyValuesTrackingList; + + CUtlMap m_KvConditionalSymbolTable; + + CThreadFastMutex m_mutex; +}; + +// EXPOSE_SINGLE_INTERFACE(CKeyValuesSystem, IKeyValuesSystem, +// KEYVALUES_INTERFACE_VERSION); + +//----------------------------------------------------------------------------- +// Instance singleton and expose interface to rest of code +//----------------------------------------------------------------------------- +static CKeyValuesSystem g_KeyValuesSystem; + +IKeyValuesSystem *KeyValuesSystem() { return &g_KeyValuesSystem; } + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::CKeyValuesSystem() + : m_HashItemMemPool(sizeof(hash_item_t), 64, CUtlMemoryPool::GROW_FAST, + "CKeyValuesSystem::m_HashItemMemPool"), + m_KeyValuesTrackingList(0, 0, MemoryLeakTrackerLessFunc), + m_KvConditionalSymbolTable(DefLessFunc(HKeySymbol)) { + MEM_ALLOC_CREDIT(); + // initialize hash table + m_HashTable.AddMultipleToTail(2047); + for (int i = 0; i < m_HashTable.Count(); i++) { + m_HashTable[i].stringIndex = 0; + m_HashTable[i].next = NULL; + } + + m_Strings.Init("CKeyValuesSystem::m_Strings", 4 * 1024 * 1024, 64 * 1024, 0, + 4); + // Make 0 stringIndex to never be returned, by allocating + // and wasting minimal number of alignment bytes now: + char *pszEmpty = ((char *)m_Strings.Alloc(1)); + *pszEmpty = 0; + +#ifdef KEYVALUES_USE_POOL + m_pMemPool = NULL; +#endif + m_iMaxKeyValuesSize = sizeof(KeyValues); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::~CKeyValuesSystem() { +#ifdef KEYVALUES_USE_POOL +#ifdef _DEBUG + // display any memory leaks + if (m_pMemPool && m_pMemPool->Count() > 0) { + DevMsg("Leaked KeyValues blocks: %d\n", m_pMemPool->Count()); + } + + // iterate all the existing keyvalues displaying their names + for (int i = 0; i < m_KeyValuesTrackingList.MaxElement(); i++) { + if (m_KeyValuesTrackingList.IsValidIndex(i)) { + DevMsg("\tleaked KeyValues(%s)\n", + &m_Strings[m_KeyValuesTrackingList[i].nameIndex]); + } + } +#endif + + delete m_pMemPool; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: registers the size of the KeyValues in the specified instance +// so it can build a properly sized memory pool for the +// KeyValues objects the sizes will usually never differ but +// this is for versioning safety +//----------------------------------------------------------------------------- +void CKeyValuesSystem::RegisterSizeofKeyValues(size_t size) { + if (size > m_iMaxKeyValuesSize) { + m_iMaxKeyValuesSize = size; + } +} + +#ifdef KEYVALUES_USE_POOL +static void KVLeak(PRINTF_FORMAT_STRING char const *fmt, ...) { + va_list argptr; + char data[1024]; + + va_start(argptr, fmt); + V_vsnprintf(data, sizeof(data), fmt, argptr); + va_end(argptr); + + Msg(data); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: allocates a KeyValues object from the shared mempool +//----------------------------------------------------------------------------- +void *CKeyValuesSystem::AllocKeyValuesMemory(size_t size) { +#ifdef KEYVALUES_USE_POOL + // allocate, if we don't have one yet + if (!m_pMemPool) { + m_pMemPool = + new CUtlMemoryPool(m_iMaxKeyValuesSize, 1024, CUtlMemoryPool::GROW_FAST, + "CKeyValuesSystem::m_pMemPool"); + m_pMemPool->SetErrorReportFunc(KVLeak); + } + + return m_pMemPool->Alloc(size); +#else + return malloc(size); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: frees a KeyValues object from the shared mempool +//----------------------------------------------------------------------------- +void CKeyValuesSystem::FreeKeyValuesMemory(void *pMem) { +#ifdef KEYVALUES_USE_POOL + m_pMemPool->Free(pMem); +#else + free(pMem); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: symbol table access (used for key names) +//----------------------------------------------------------------------------- +HKeySymbol CKeyValuesSystem::GetSymbolForString(const char *name, + bool bCreate) { + if (!name) return -1; + + AUTO_LOCK(m_mutex); + MEM_ALLOC_CREDIT(); + + int hash = CaseInsensitiveHash(name, m_HashTable.Count()); + int i = 0; + hash_item_t *item = &m_HashTable[hash]; + + while (true) { + if (!stricmp(name, (char *)m_Strings.GetBase() + item->stringIndex)) { + return (HKeySymbol)item->stringIndex; + } + + i++; + + if (item->next == NULL) { + if (!bCreate) { + // not found + return -1; + } + + // we're not in the table + if (item->stringIndex != 0) { + // first item is used, an new item + item->next = + (hash_item_t *)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); + item = item->next; + } + + // build up the new item + item->next = NULL; + intp numStringBytes = V_strlen(name); + char *pString = (char *)m_Strings.Alloc(numStringBytes + 1 + 3); + if (!pString) { + Error("Out of keyvalue string space"); + return -1; + } + + item->stringIndex = (int)(pString - (char *)m_Strings.GetBase()); + V_memcpy(pString, name, numStringBytes); + + // string null-terminator + 3 alternative spelling bytes + *reinterpret_cast(pString + numStringBytes) = 0; + return (HKeySymbol)item->stringIndex; + } + + item = item->next; + } +} + +extern int _V_stricmp_NegativeForUnequal(const char *s1, const char *s2); + +//----------------------------------------------------------------------------- +// Purpose: symbol table access (used for key names) +//----------------------------------------------------------------------------- +HKeySymbol CKeyValuesSystem::GetSymbolForStringCaseSensitive( + HKeySymbol &hCaseInsensitiveSymbol, const char *name, bool bCreate) { + if (!name) return -1; + + AUTO_LOCK(m_mutex); + MEM_ALLOC_CREDIT(); + + int hash = CaseInsensitiveHash(name, m_HashTable.Count()); + intp numNameStringBytes = -1; + int i = 0; + hash_item_t *item = &m_HashTable[hash]; + while (1) { + char *pCompareString = (char *)m_Strings.GetBase() + item->stringIndex; + int iResult = _V_stricmp_NegativeForUnequal(name, pCompareString); + if (iResult == 0) { + // strings are exactly equal matching every letter's case + hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; + return (HKeySymbol)item->stringIndex; + } + + if (iResult > 0) { + // strings are equal in a case-insensitive compare, but have different + // case for some letters Need to walk the case-resolving chain + numNameStringBytes = V_strlen(pCompareString); + uint32 *pnCaseResolveIndex = + reinterpret_cast(pCompareString + numNameStringBytes); + hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; + + while (int nAlternativeStringIndex = + MEM_4BYTES_FROM_0_AND_3BYTES(*pnCaseResolveIndex)) { + pCompareString = (char *)m_Strings.GetBase() + nAlternativeStringIndex; + int res = strcmp(name, pCompareString); + if (!res) { + // found an exact match + return (HKeySymbol)nAlternativeStringIndex; + } + // Keep traversing alternative case-resolving chain + pnCaseResolveIndex = + reinterpret_cast(pCompareString + numNameStringBytes); + } + + // Reached the end of alternative case-resolving chain, pnCaseResolveIndex + // is pointing at 0 bytes indicating no further alternative stringIndex + if (!bCreate) { + // If we aren't interested in creating the actual string index, + // then return symbol with default capitalization + // NOTE: this is not correct value, but it cannot be used to create a + // new value anyway, only for locating a pre-existing value and lookups + // are case-insensitive + return (HKeySymbol)item->stringIndex; + } + + { + char *pString = (char *)m_Strings.Alloc(numNameStringBytes + 1 + 3); + if (!pString) { + Error("Out of keyvalue string space"); + return -1; + } + intp nNewAlternativeStringIndex = pString - (char *)m_Strings.GetBase(); + V_memcpy(pString, name, numNameStringBytes); + *reinterpret_cast(pString + numNameStringBytes) = + 0; // string null-terminator + 3 alternative spelling bytes + *pnCaseResolveIndex = MEM_4BYTES_AS_0_AND_3BYTES( + nNewAlternativeStringIndex); // link previous spelling entry to the + // new entry + return (HKeySymbol)nNewAlternativeStringIndex; + } + } + + i++; + + if (item->next == NULL) { + // not found + if (!bCreate) return -1; + + // we're not in the table + if (item->stringIndex != 0) { + // first item is used, an new item + item->next = + (hash_item_t *)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); + item = item->next; + } + + // build up the new item + item->next = NULL; + intp numStringBytes = V_strlen(name); + char *pString = (char *)m_Strings.Alloc(numStringBytes + 1 + 3); + if (!pString) { + Error("Out of keyvalue string space"); + return -1; + } + + item->stringIndex = (int)(pString - (char *)m_Strings.GetBase()); + V_memcpy(pString, name, numStringBytes); + // string null-terminator + 3 alternative spelling bytes + *reinterpret_cast(pString + numStringBytes) = 0; + + hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; + return (HKeySymbol)item->stringIndex; + } + + item = item->next; + } +} + +//----------------------------------------------------------------------------- +// Purpose: symbol table access +//----------------------------------------------------------------------------- +const char *CKeyValuesSystem::GetStringForSymbol(HKeySymbol symbol) { + if (symbol == -1) { + return ""; + } + + return ((char *)m_Strings.GetBase() + (size_t)symbol); +} + +//----------------------------------------------------------------------------- +// Purpose: adds KeyValues record into global list so we can track memory leaks +//----------------------------------------------------------------------------- +void CKeyValuesSystem::AddKeyValuesToMemoryLeakList(void *pMem, + HKeySymbol name) { +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = {name, pMem}; + m_KeyValuesTrackingList.Insert(item); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: used to track memory leaks +//----------------------------------------------------------------------------- +void CKeyValuesSystem::RemoveKeyValuesFromMemoryLeakList(void *pMem) { +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = {0, pMem}; + int index = m_KeyValuesTrackingList.Find(item); + m_KeyValuesTrackingList.RemoveAt(index); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: generates a simple hash value for a string +//----------------------------------------------------------------------------- +int CKeyValuesSystem::CaseInsensitiveHash(const char *string, intp iBounds) { + unsigned int hash = 0; + + for (; *string != 0; string++) { + if (*string >= 'A' && *string <= 'Z') { + hash = (hash << 1) + (*string - 'A' + 'a'); + } else { + hash = (hash << 1) + *string; + } + } + + return hash % iBounds; +} + +//----------------------------------------------------------------------------- +// Purpose: set/get a value for keyvalues resolution symbol +// e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables +// [$LOWVIOLENCE] +//----------------------------------------------------------------------------- +void CKeyValuesSystem::SetKeyValuesExpressionSymbol(const char *name, + bool bValue) { + if (!name) return; + + if (name[0] == '$') ++name; + + HKeySymbol hSym = GetSymbolForString(name, true); // find or create symbol + + { + AUTO_LOCK(m_mutex); + m_KvConditionalSymbolTable.InsertOrReplace(hSym, bValue); + } +} + +bool CKeyValuesSystem::GetKeyValuesExpressionSymbol(const char *name) { + if (!name) return false; + + if (name[0] == '$') ++name; + + HKeySymbol hSym = GetSymbolForString(name, false); // find or create symbol + if (hSym != -1) { + AUTO_LOCK(m_mutex); + CUtlMap::IndexType_t idx = + m_KvConditionalSymbolTable.Find(hSym); + if (idx != m_KvConditionalSymbolTable.InvalidIndex()) { + // Found the symbol value in conditional symbol table + return m_KvConditionalSymbolTable.Element(idx); + } + } + + // + // Fallback conditionals + // + + if (!V_stricmp(name, "GAMECONSOLESPLITSCREEN")) { +#if defined(_GAMECONSOLE) + return (XBX_GetNumGameUsers() > 1); +#else + return false; +#endif + } + + if (!V_stricmp(name, "GAMECONSOLEGUEST")) { +#if defined(_GAMECONSOLE) + return (XBX_GetPrimaryUserIsGuest() != 0); +#else + return false; +#endif + } + + if (!V_stricmp(name, "ENGLISH") || !V_stricmp(name, "JAPANESE") || + !V_stricmp(name, "GERMAN") || !V_stricmp(name, "FRENCH") || + !V_stricmp(name, "SPANISH") || !V_stricmp(name, "ITALIAN") || + !V_stricmp(name, "KOREAN") || !V_stricmp(name, "TCHINESE") || + !V_stricmp(name, "PORTUGUESE") || !V_stricmp(name, "SCHINESE") || + !V_stricmp(name, "POLISH") || !V_stricmp(name, "RUSSIAN") || + !V_stricmp(name, "TURKISH")) { + // the language symbols are true if we are in that language + // english is assumed when no language is present + const char *pLanguageString; +#ifdef _GAMECONSOLE + pLanguageString = XBX_GetLanguageString(); +#else + static ConVarRef cl_language("cl_language"); + pLanguageString = cl_language.GetString(); +#endif + if (!pLanguageString || !pLanguageString[0]) { + pLanguageString = "english"; + } + if (!V_stricmp(name, pLanguageString)) { + return true; + } else { + return false; + } + } + + // very expensive, back door for DLC updates + if (!V_strnicmp(name, "CVAR_", 5)) { + ConVarRef cvRef(name + 5); + if (cvRef.IsValid()) return cvRef.GetBool(); + } + + // purposely warn on these to prevent syntax errors + // need to get these fixed asap, otherwise unintended false behavior + Warning("KV Conditional: Unknown symbol %s\n", name); + return false; +} diff --git a/vstdlib/random.cpp b/vstdlib/random.cpp new file mode 100644 index 0000000..efd21a1 --- /dev/null +++ b/vstdlib/random.cpp @@ -0,0 +1,223 @@ +// Copyright Valve Corporation, All rights reserved. +// +// Purpose: Random number generator + +#include "vstdlib/random.h" + +#include + +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define IA 16807 +#define IM 2147483647 +#define IQ 127773 +#define IR 2836 +#define NDIV (1 + (IM - 1) / NTAB) +#define MAX_RANDOM_RANGE 0x7FFFFFFFUL + +// fran1 -- return a random floating-point number on the interval [0,1) +// +#define AM (1.0F / IM) +#define EPS 1.2e-7F +#define RNMX (1.0F - EPS) + +//----------------------------------------------------------------------------- +// globals +//----------------------------------------------------------------------------- +static CUniformRandomStream s_UniformStream; +static CGaussianRandomStream s_GaussianStream; +static IUniformRandomStream *s_pUniformStream = &s_UniformStream; + +//----------------------------------------------------------------------------- +// Installs a global random number generator, which will affect the Random +// functions above +//----------------------------------------------------------------------------- +void InstallUniformRandomStream(IUniformRandomStream *pStream) { + s_pUniformStream = pStream ? pStream : &s_UniformStream; +} + +//----------------------------------------------------------------------------- +// A couple of convenience functions to access the library's global uniform +// stream +//----------------------------------------------------------------------------- +void RandomSeed(int iSeed) { s_pUniformStream->SetSeed(iSeed); } + +float RandomFloat(float flMinVal, float flMaxVal) { + return s_pUniformStream->RandomFloat(flMinVal, flMaxVal); +} + +float RandomFloatExp(float flMinVal, float flMaxVal, float flExponent) { + return s_pUniformStream->RandomFloatExp(flMinVal, flMaxVal, flExponent); +} + +int RandomInt(int iMinVal, int iMaxVal) { + return s_pUniformStream->RandomInt(iMinVal, iMaxVal); +} + +float RandomGaussianFloat(float flMean, float flStdDev) { + return s_GaussianStream.RandomFloat(flMean, flStdDev); +} + +//----------------------------------------------------------------------------- +// +// Implementation of the uniform random number stream +// +//----------------------------------------------------------------------------- +CUniformRandomStream::CUniformRandomStream() { SetSeed(0); } + +void CUniformRandomStream::SetSeed(int iSeed) { + AUTO_LOCK(m_mutex); + m_idum = ((iSeed < 0) ? iSeed : -iSeed); + m_iy = 0; +} + +int CUniformRandomStream::GenerateRandomNumber() { + AUTO_LOCK(m_mutex); + int j; + int k; + + if (m_idum <= 0 || !m_iy) { + if (-(m_idum) < 1) + m_idum = 1; + else + m_idum = -(m_idum); + + for (j = NTAB + 7; j >= 0; j--) { + k = (m_idum) / IQ; + m_idum = IA * (m_idum - k * IQ) - IR * k; + if (m_idum < 0) m_idum += IM; + if (j < NTAB) m_iv[j] = m_idum; + } + m_iy = m_iv[0]; + } + k = (m_idum) / IQ; + m_idum = IA * (m_idum - k * IQ) - IR * k; + if (m_idum < 0) m_idum += IM; + j = m_iy / NDIV; + + // We're seeing some strange memory corruption in the contents of + // s_pUniformStream. Perhaps it's being caused by something writing past the + // end of this array? Bounds-check in release to see if that's the case. + if (j >= NTAB || j < 0) { + DebuggerBreakIfDebugging(); + Warning( + "CUniformRandomStream had an array overrun: tried to write to element " + "%d of 0..31. Contact Tom or Elan.\n", + j); + j = (j % NTAB) & 0x7fffffff; + } + + m_iy = m_iv[j]; + m_iv[j] = m_idum; + + return m_iy; +} + +float CUniformRandomStream::RandomFloat(float flLow, float flHigh) { + // float in [0,1) + float fl = AM * GenerateRandomNumber(); + if (fl > RNMX) { + fl = RNMX; + } + return (fl * (flHigh - flLow)) + flLow; // float in [low,high) +} + +float CUniformRandomStream::RandomFloatExp(float flMinVal, float flMaxVal, + float flExponent) { + // float in [0,1) + float fl = AM * GenerateRandomNumber(); + if (fl > RNMX) { + fl = RNMX; + } + if (flExponent != 1.0f) { + fl = powf(fl, flExponent); + } + return (fl * (flMaxVal - flMinVal)) + flMinVal; // float in [low,high) +} + +int CUniformRandomStream::RandomInt(int iLow, int iHigh) { + // ASSERT(lLow <= lHigh); + unsigned int maxAcceptable; + unsigned int x = iHigh - iLow + 1; + unsigned int n; + if (x <= 1 || MAX_RANDOM_RANGE < x - 1) { + return iLow; + } + + // The following maps a uniform distribution on the interval + // [0,MAX_RANDOM_RANGE] to a smaller, client-specified range of [0,x-1] in a + // way that doesn't bias the uniform distribution unfavorably. Even for a + // worst case x, the loop is guaranteed to be taken no more than half the + // time, so for that worst case x, the average number of times through the + // loop is 2. For cases where x is much smaller than MAX_RANDOM_RANGE, the + // average number of times through the loop is very close to 1. + // + maxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE + 1) % x); + do { + n = GenerateRandomNumber(); + } while (n > maxAcceptable); + + return iLow + (n % x); +} + +//----------------------------------------------------------------------------- +// +// Implementation of the gaussian random number stream +// We're gonna use the Box-Muller method (which actually generates 2 +// gaussian-distributed numbers at once) +// +//----------------------------------------------------------------------------- +CGaussianRandomStream::CGaussianRandomStream( + IUniformRandomStream *pUniformStream) { + AttachToStream(pUniformStream); +} + +//----------------------------------------------------------------------------- +// Attaches to a random uniform stream +//----------------------------------------------------------------------------- +void CGaussianRandomStream::AttachToStream( + IUniformRandomStream *pUniformStream) { + AUTO_LOCK(m_mutex); + m_pUniformStream = pUniformStream; + m_bHaveValue = false; + m_flRandomValue = 0.0f; +} + +//----------------------------------------------------------------------------- +// Generates random numbers +//----------------------------------------------------------------------------- +float CGaussianRandomStream::RandomFloat(float flMean, float flStdDev) { + AUTO_LOCK(m_mutex); + IUniformRandomStream *pUniformStream = + m_pUniformStream ? m_pUniformStream : s_pUniformStream; + float fac, rsq, v1, v2; + + if (!m_bHaveValue) { + // Pick 2 random #s from -1 to 1 + // Make sure they lie inside the unit circle. If they don't, try again + do { + v1 = 2.0f * pUniformStream->RandomFloat() - 1.0f; + v2 = 2.0f * pUniformStream->RandomFloat() - 1.0f; + rsq = v1 * v1 + v2 * v2; + } while ((rsq > 1.0f) || (rsq == 0.0f)); + + // The box-muller transformation to get the two gaussian numbers + fac = sqrtf(-2.0f * log(rsq) / rsq); + + // Store off one value for later use + m_flRandomValue = v1 * fac; + m_bHaveValue = true; + + return flStdDev * (v2 * fac) + flMean; + } else { + m_bHaveValue = false; + return flStdDev * m_flRandomValue + flMean; + } +} + +//----------------------------------------------------------------------------- +// Creates a histogram (for testing) +//----------------------------------------------------------------------------- diff --git a/vstdlib/vstrtools.cpp b/vstdlib/vstrtools.cpp new file mode 100644 index 0000000..1eb56db --- /dev/null +++ b/vstdlib/vstrtools.cpp @@ -0,0 +1,307 @@ +// Copyright Valve Corporation, All rights reserved. + +#include "tier0/dbg.h" +#include "vstdlib/vstrtools.h" + +#if defined(_WIN32) && !defined(_X360) +#include "winlite.h" +#endif + +#if defined(POSIX) && !defined(_PS3) +#include +#endif + +#ifdef _PS3 +#include +#include + +class DummyInitL10N { + public: + DummyInitL10N() { + int ret = cellSysmoduleLoadModule(CELL_SYSMODULE_L10N); + if (ret != CELL_OK) { + Warning( + "Cannot initialize l10n, unicode services will not work. Error %d\n", + ret); + } + } + + ~DummyInitL10N() { cellSysmoduleUnloadModule(CELL_SYSMODULE_L10N); } +} s_dummyInitL10N; +#endif + +// Purpose: Converts a UTF8 string into a unicode string +int V_UTF8ToUnicode(const char *pUTF8, wchar_t *pwchDest, + int cubDestSizeInBytes) { + if (!pUTF8) return 0; + + AssertValidStringPtr(pUTF8); + AssertValidWritePtr(pwchDest); + + pwchDest[0] = L'\0'; + +#ifdef _WIN32 + int cchResult = MultiByteToWideChar(CP_UTF8, 0, pUTF8, -1, pwchDest, + cubDestSizeInBytes / sizeof(wchar_t)); +#elif defined(_PS3) + size_t cchResult = cubDestSizeInBytes / sizeof(uint16), + cchSrc = V_strlen(pUTF8) + 1; + L10nResult result = UTF8stoUCS2s((const uint8 *)pUTF8, &cchSrc, + (uint16 *)pwchDest, &cchResult); + Assert(result == ConversionOK); + cchResult *= sizeof(uint16); +#elif POSIX + iconv_t conv_t = iconv_open("UTF-32LE", "UTF-8"); + int cchResult = -1; + size_t nLenUnicde = cubDestSizeInBytes; + size_t nMaxUTF8 = strlen(pUTF8) + 1; + char *pIn = (char *)pUTF8; + char *pOut = (char *)pwchDest; + if (conv_t > 0) { + cchResult = 0; + cchResult = iconv(conv_t, &pIn, &nMaxUTF8, &pOut, &nLenUnicde); + iconv_close(conv_t); + if ((int)cchResult < 0) + cchResult = 0; + else + cchResult = nMaxUTF8; + } +#endif + + pwchDest[(cubDestSizeInBytes / sizeof(wchar_t)) - 1] = L'\0'; + return cchResult; +} + +// Purpose: Converts a unicode string into a UTF8 (standard) string +int V_UnicodeToUTF8(const wchar_t *pUnicode, char *pUTF8, + int cubDestSizeInBytes) { + AssertValidStringPtr(pUTF8, cubDestSizeInBytes); + AssertValidReadPtr(pUnicode); + + if (cubDestSizeInBytes > 0) pUTF8[0] = '\0'; + +#ifdef _WIN32 + int cchResult = WideCharToMultiByte(CP_UTF8, 0, pUnicode, -1, pUTF8, + cubDestSizeInBytes, NULL, NULL); +#elif defined(_PS3) + size_t cchResult = cubDestSizeInBytes, cchSrc = V_wcslen(pUnicode) + 1; + L10nResult result = UCS2stoUTF8s((const uint16 *)pUnicode, &cchSrc, + (uint8 *)pUTF8, &cchResult); + Assert(result == ConversionOK); +#elif POSIX + int cchResult = 0; + if (pUnicode && pUTF8) { + iconv_t conv_t = iconv_open("UTF-8", "UTF-32LE"); + int cchResult = -1; + size_t nLenUnicde = (wcslen(pUnicode) + 1) * + sizeof(wchar_t); // 4 bytes per wchar vs. 1 byte for + // utf8 for simple english + size_t nMaxUTF8 = cubDestSizeInBytes; + char *pIn = (char *)pUnicode; + char *pOut = (char *)pUTF8; + if (conv_t > 0) { + cchResult = 0; + cchResult = iconv(conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8); + iconv_close(conv_t); + if ((int)cchResult < 0) + cchResult = 0; + else + cchResult = nMaxUTF8; + } + } +#endif + + if (cubDestSizeInBytes > 0) pUTF8[cubDestSizeInBytes - 1] = '\0'; + + return cchResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts a ucs2 string to a unicode (wchar_t) one, no-op on win32 +//----------------------------------------------------------------------------- +intp V_UCS2ToUnicode(const ucs2 *pUCS2, wchar_t *pUnicode, + intp cubDestSizeInBytes) { + AssertValidWritePtr(pUnicode); + AssertValidReadPtr(pUCS2); + + pUnicode[0] = L'\0'; + +#if defined(_WIN32) || defined(_PS3) + intp lenUCS2 = V_wcslen(pUCS2); + intp cchResult = MIN((lenUCS2 + 1) * (intp)sizeof(ucs2), cubDestSizeInBytes); + V_wcsncpy((wchar_t *)pUCS2, pUnicode, cchResult); +#else + iconv_t conv_t = iconv_open("UCS-4LE", "UCS-2LE"); + int cchResult = -1; + size_t nLenUnicde = cubDestSizeInBytes; + size_t nMaxUTF8 = cubDestSizeInBytes; + char *pIn = (char *)pUCS2; + char *pOut = (char *)pUnicode; + if (conv_t > 0) { + cchResult = 0; + cchResult = iconv(conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8); + iconv_close(conv_t); + if ((int)cchResult < 0) + cchResult = 0; + else + cchResult = nMaxUTF8; + } +#endif + + pUnicode[(cubDestSizeInBytes / sizeof(wchar_t)) - 1] = L'\0'; + return cchResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts a wchar_t string into a UCS2 string -noop on windows +//----------------------------------------------------------------------------- +int V_UnicodeToUCS2(const wchar_t *pUnicode, int cubSrcInBytes, char *pUCS2, + int cubDestSizeInBytes) { + // TODO: MACMERGE: Figure out how to convert from 2-byte Win32 wchars to + // platform wchar_t type that can be 4 bytes +#if defined(_WIN32) || defined(_PS3) + int cchResult = MIN(cubSrcInBytes, cubDestSizeInBytes); + V_wcsncpy((wchar_t *)pUCS2, pUnicode, cchResult); +#elif defined(POSIX) + iconv_t conv_t = iconv_open("UCS-2LE", "UTF-32LE"); + size_t cchResult = -1; + size_t nLenUnicde = cubSrcInBytes; + size_t nMaxUCS2 = cubDestSizeInBytes; + char *pIn = (char *)pUnicode; + char *pOut = pUCS2; + if (conv_t > 0) { + cchResult = 0; + cchResult = iconv(conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUCS2); + iconv_close(conv_t); + if ((int)cchResult < 0) + cchResult = 0; + else + cchResult = cubSrcInBytes / sizeof(wchar_t); + } +#endif + + return cchResult; +} + +// UTF-8 encodes each character (code point) in 1 to 4 octets (8-bit bytes). +// The first 128 characters of the Unicode character set (which correspond +// directly to the ASCII) use a single octet with the same binary value as in +// ASCII. url: https://en.wikipedia.org/wiki/UTF-8 +#define MAX_UTF8_CHARACTER_BYTES 4 + +//----------------------------------------------------------------------------- +// Purpose: Converts a ucs-2 (windows wchar_t) string into a UTF8 (standard) +// string +//----------------------------------------------------------------------------- +VSTRTOOLS_INTERFACE int V_UCS2ToUTF8(const ucs2 *pUCS2, char *pUTF8, + int cubDestSizeInBytes) { + AssertValidStringPtr(pUTF8, cubDestSizeInBytes); + AssertValidReadPtr(pUCS2); + Assert(cubDestSizeInBytes >= + 1); // must have at least 1 byte to write the terminator character + + pUTF8[0] = '\0'; +#ifdef _WIN32 + // under win32 wchar_t == ucs2, sigh + int cchResult = WideCharToMultiByte(CP_UTF8, 0, pUCS2, -1, pUTF8, + cubDestSizeInBytes, NULL, NULL); +#elif defined(_PS3) + size_t cchResult = cubDestSizeInBytes, cchSrc = V_wcslen(pUCS2) + 1; + L10nResult result = + UCS2stoUTF8s((const uint16 *)pUCS2, &cchSrc, (uint8 *)pUTF8, &cchResult); + Assert(result == ConversionOK); +#elif defined(POSIX) + iconv_t conv_t = iconv_open("UTF-8", "UCS-2LE"); + size_t cchResult = -1; + size_t nLenUnicde = cubDestSizeInBytes; + size_t nMaxUTF8 = cubDestSizeInBytes; + char *pIn = (char *)pUCS2; + char *pOut = (char *)pUTF8; + if (conv_t > 0) { + cchResult = 0; + cchResult = iconv(conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8); + iconv_close(conv_t); + if ((int)cchResult < 0) + cchResult = 0; + else + cchResult = nMaxUTF8; + } +#endif + pUTF8[cubDestSizeInBytes - 1] = '\0'; + return cchResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts a UTF8 to ucs-2 (windows wchar_t) +//----------------------------------------------------------------------------- +VSTRTOOLS_INTERFACE int V_UTF8ToUCS2(const char *pUTF8, + [[maybe_unused]] int cubSrcInBytes, + ucs2 *pUCS2, int cubDestSizeInBytes) { + AssertValidStringPtr(pUTF8, cubDestSizeInBytes); + AssertValidReadPtr(pUCS2); + + pUCS2[0] = 0; +#ifdef _WIN32 + // under win32 wchar_t == ucs2, sigh + int cchResult = MultiByteToWideChar(CP_UTF8, 0, pUTF8, -1, pUCS2, + cubDestSizeInBytes / sizeof(wchar_t)); +#elif defined(_PS3) + size_t cchResult = cubDestSizeInBytes / sizeof(uint16), + cchSrc = cubSrcInBytes; + L10nResult result = + UTF8stoUCS2s((const uint8 *)pUTF8, &cchSrc, (uint16 *)pUCS2, &cchResult); + Assert(result == ConversionOK); + cchResult *= sizeof(uint16); +#elif defined(POSIX) + iconv_t conv_t = iconv_open("UCS-2LE", "UTF-8"); + size_t cchResult = -1; + size_t nLenUnicde = cubSrcInBytes; + size_t nMaxUTF8 = cubDestSizeInBytes; + char *pIn = (char *)pUTF8; + char *pOut = (char *)pUCS2; + if (conv_t > 0) { + cchResult = 0; + cchResult = iconv(conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8); + iconv_close(conv_t); + if ((int)cchResult < 0) + cchResult = 0; + else + cchResult = cubSrcInBytes; + } +#endif + pUCS2[(cubDestSizeInBytes / sizeof(ucs2)) - 1] = 0; + return cchResult; +} + +//----------------------------------------------------------------------------- +// Purpose: copies at most nMaxBytes of the UTF-8 input data into the +// destination, ensuring that a trailing multi-byte sequence isn't truncated. +//----------------------------------------------------------------------------- +VSTRTOOLS_INTERFACE void *V_UTF8_strncpy(char *pDest, const char *pSrc, + size_t nMaxBytes) { + strncpy(pDest, pSrc, nMaxBytes); + + // https://en.wikipedia.org/wiki/UTF-8 + ptrdiff_t end = nMaxBytes - 1; + pDest[end] = 0; + + int nBytesSeen = 0, nBytesExpected = 0; + // walk backwards, ignoring nulls + while (pDest[end] == 0) --end; + + // found a non-null - see if it's part of a multi-byte sequence + while ((pDest[end] & 0x80) && !(pDest[end] & 0x40)) { + nBytesSeen++; + --end; + } + + if ((pDest[end] & 0xC0) == 0xC0) { + for (int i = 6; i > 1; --i) { + if ((char)(pDest[end] >> i) & 0x1) ++nBytesExpected; + } + } + + if (nBytesExpected != nBytesSeen) pDest[end] = 0; + + return pDest; +} \ No newline at end of file