diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..6e789c574 --- /dev/null +++ b/.clang-format @@ -0,0 +1,122 @@ +BasedOnStyle: LLVM +Language: Cpp +Standard: c++20 +TabWidth: 4 +IndentWidth: 4 +UseTab: Never +ColumnLimit: 160 + +#LineEndingStyle: LF +#MaxEmptyLinesToKeep: 3 + +##-- Alignment +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +#AlignEscapedNewlinesLeft: false +AlignTrailingComments: true + +DerivePointerAlignment: false +PointerAlignment: Right + +##-- Single line statements +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false + +##-- Braces +BreakBeforeBraces: Custom #Allman +BraceWrapping: + AfterCaseLabel: true + AfterClass: false + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +Cpp11BracedListStyle: false + +##-- Line breaks +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BreakAfterJavaFieldAnnotations: false +BreakBeforeBinaryOperators: None +BreakConstructorInitializers: AfterColon +#BreakInheritanceList: AfterColon +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: true +BreakStringLiterals: false + +##-- Indentation +IndentCaseBlocks: false +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: AfterHash +LambdaBodyIndentation: Signature +NamespaceIndentation: Inner + +##-- Spaces in/between statements/etc +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +#SpaceAroundPointerQualifiers: Left +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +##-- Misc +AllowAllParametersOfDeclarationOnNextLine: true +AccessModifierOffset: -4 +BinPackArguments: false +BinPackParameters: false +BreakAfterAttributes: Always +ExperimentalAutoDetectBinPacking: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +DisableFormat: false +InsertNewlineAtEOF: true +KeepEmptyLinesAtTheStartOfBlocks: false +ReflowComments: false +SeparateDefinitionBlocks: Always + +#-- Include order +#IncludeIsMainRegex: '$?' +#IncludeBlocks: Regroup +IncludeCategories: +#TODO: main include! + # Headers in "" with .h extension. + - Regex: '"([A-Za-z0-9\/-_])+\.h"' + Priority: 1 + # Headers in "" with .hpp extension. + - Regex: '"([A-Za-z0-9\/-_])+\.hpp"' + Priority: 2 + + # Headers in <> with .h extension. + - Regex: '<([A-Za-z0-9\/-_])+\.h>' + Priority: 10 + # Headers in <> with .hpp extension. + - Regex: '<([A-Za-z0-9\/-_])+\.hpp>' + Priority: 20 + # Headers in <> without extension. + - Regex: '<([A-Za-z0-9\/-_])+>' + Priority: 30 +SortIncludes: false #CaseSensitive diff --git a/.cmake-format b/.cmake-format new file mode 100644 index 000000000..aaf46bb1d --- /dev/null +++ b/.cmake-format @@ -0,0 +1,16 @@ +# https://cmake-format.readthedocs.io/en/latest/ +#https://github.com/bouncmpe/cpp-project-template/blob/main/.cmake-format +format: + line_width: 80 + tab_size: 2 + max_subgroups_hwrap: 2 + max_pargs_hwrap: 6 + max_rows_cmdline: 2 + dangle_parens: true + dangle_align: 'prefix' + min_prefix_chars: 8 + max_prefix_chars: 8 + max_lines_hwrap: 2 + command_case: 'lower' +markup: + enable_markup: false diff --git a/.editorconfig b/.editorconfig index f39fbb348..dc3145a32 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,14 +4,30 @@ root = true # Unix-style newlines with a newline ending every file [*] charset = utf-8 -end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true -# Tab indentation (no size specified) +# Optional: git will commit as lf, this will only affect local files +end_of_line = lf + +[*.md] +trim_trailing_whitespace = false + [Makefile] indent_style = tab +[*.{py,yml,sh,cmake}] +indent_style = space +indent_size = 2 + +[*.{py,yml,sh,cmake}.in] +indent_style = space +indent_size = 2 + +[CMakeLists.txt] +indent_style = space +indent_size = 2 + [*.{c,h,cpp,cxx,hpp}] charset = utf-8 indent_size = 4 @@ -19,41 +35,44 @@ indent_style = space trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf -cpp_new_line_before_open_brace_namespace = new_line -cpp_indent_preserve_comments = true -cpp_indent_namespace_contents = false -cpp_indent_case_labels = true -cpp_indent_case_contents = false -cpp_indent_case_contents_when_block = false -cpp_allow_comment_after_lbrace = true -cpp_simple_block_style = do_not_change -cpp_space_before_function_open_parenthesis = remove -cpp_space_within_parameter_list_parentheses = false -cpp_space_between_empty_parameter_list_parentheses = false -cpp_space_after_keywords_in_control_flow_statements = true -cpp_space_within_control_flow_statement_parentheses = false -cpp_space_before_lambda_open_parenthesis = false -cpp_space_within_cast_parentheses = false -cpp_space_after_cast_close_parenthesis = false -cpp_space_within_expression_parentheses = false -cpp_space_before_initializer_list_open_brace = false -cpp_space_within_initializer_list_braces = true -cpp_space_before_open_square_bracket = false -cpp_space_within_square_brackets = false -cpp_space_before_empty_square_brackets = false -cpp_space_between_empty_square_brackets = false -cpp_space_group_square_brackets = true -cpp_space_within_lambda_brackets = false -cpp_space_between_empty_lambda_brackets = false -cpp_space_before_comma = false -cpp_space_after_comma = true -cpp_space_remove_around_member_operators = false -cpp_space_before_inheritance_colon = true -cpp_space_before_constructor_colon = true -cpp_space_remove_before_semicolon = true -cpp_space_after_semicolon = false -cpp_space_remove_around_unary_operator = false -cpp_space_around_binary_operator = ignore -cpp_space_around_assignment_operator = insert -cpp_space_pointer_reference_alignment = ignore -cpp_space_around_ternary_operator = insert \ No newline at end of file + +# Already handled by .cmake-format +#cpp_new_line_before_open_brace_namespace = new_line +#cpp_indent_preserve_comments = true +#cpp_indent_namespace_contents = false +#cpp_indent_case_labels = true +#cpp_indent_case_contents = false +#cpp_indent_case_contents_when_block = false +#cpp_allow_comment_after_lbrace = true +#cpp_simple_block_style = do_not_change +#cpp_space_before_function_open_parenthesis = remove +#cpp_space_within_parameter_list_parentheses = false +#cpp_space_between_empty_parameter_list_parentheses = false +#cpp_space_after_keywords_in_control_flow_statements = true +#cpp_space_within_control_flow_statement_parentheses = false +#cpp_space_before_lambda_open_parenthesis = false +#cpp_space_within_cast_parentheses = false +#cpp_space_after_cast_close_parenthesis = false +#cpp_space_within_expression_parentheses = false +#cpp_space_before_initializer_list_open_brace = false +#cpp_space_within_initializer_list_braces = true +#cpp_space_before_open_square_bracket = false +#cpp_space_within_square_brackets = false +#cpp_space_before_empty_square_brackets = false +#cpp_space_between_empty_square_brackets = false +#cpp_space_group_square_brackets = true +#cpp_space_within_lambda_brackets = false +#cpp_space_between_empty_lambda_brackets = false +#cpp_space_before_comma = false +#cpp_space_after_comma = true +#cpp_space_remove_around_member_operators = false +#cpp_space_before_inheritance_colon = true +#cpp_space_before_constructor_colon = true +#cpp_space_remove_before_semicolon = true +#cpp_space_after_semicolon = false +#cpp_space_remove_around_unary_operator = false +#cpp_space_around_binary_operator = ignore +#cpp_space_around_assignment_operator = insert +#cpp_space_pointer_reference_alignment = ignore +#cpp_space_around_ternary_operator = insert + diff --git a/.gitattributes b/.gitattributes index 925d91ecc..5842d70fa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,16 +2,20 @@ # Set the default behavior for all files. * text=auto - -# Normalize and convert to native line endings on checkout. -*.c text -*.cpp text -*.h text -*.hpp text - # Ignore all differences in line endings. * -crlf +# Normalize and convert to native line endings on checkout. +*.c text eol=lf +*.cpp text eol=lf +*.h text eol=lf +*.hpp text eol=lf +*.txt text eol=lf + # Convert to LF line endings on checkout. #* text eol=lf +# CMake template files +*.h.in linguist-language=C++ +#*.cpp.in linguist-language=C++ +#*.c.in linguist-language=C diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 6667901e0..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,331 +0,0 @@ -name: Build - -on: - - push - - pull_request - -jobs: - windows-x86_64: - runs-on: windows-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v2 - - name: CMake - run: cmake -G "Visual Studio 17 2022" .\ - - name: MSBuild - run: msbuild SphereServer.sln /verbosity:minimal /maxcpucount /p:Configuration=Nightly - - name: Create package - run: | - pwd - mkdir accounts, logs, save, scripts - 7z a SphereSvrX-win-x86_64-nightly.zip accounts\ logs\ save\ scripts\ .\bin-x86_64\Nightly\SphereSvrX64_nightly.exe .\src\sphere.ini .\src\sphereCrypt.ini .\lib\_bin\x86_64\mariadb\libmariadb.dll - - # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches - - name: Upload artifact - if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Build-win-x86_64 - path: SphereSvrX-win-x86_64-nightly.zip - overwrite: true - - - windows-x86: - runs-on: windows-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v2 - - name: CMake - run: cmake -G "Visual Studio 17 2022" -A Win32 .\ - - name: MSBuild - run: msbuild SphereServer.sln /verbosity:minimal /maxcpucount /p:Configuration=Nightly - - name: Create package - run: | - pwd - mkdir accounts, logs, save, scripts - 7z a SphereSvrX-win-x86-nightly.zip accounts\ logs\ save\ scripts\ .\bin-x86\Nightly\SphereSvrX32_nightly.exe .\src\sphere.ini .\src\sphereCrypt.ini .\lib\_bin\x86\mariadb\libmariadb.dll - - # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches - - name: Upload artifact - if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Build-win-x86 - path: SphereSvrX-win-x86-nightly.zip - overwrite: true - - - linux-x86_64: - runs-on: ubuntu-22.04 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install prerequisites - run: | - lsb_release -d - - sudo add-apt-repository universe && sudo sudo apt-get -qq update - sudo apt install -yq --no-install-recommends ninja-build gcc-12 g++-12 > /dev/null - sudo apt install -yq --no-install-recommends mariadb-client libmariadb-dev - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 90 --slave /usr/bin/g++ g++ /usr/bin/g++-12 - - name: Report building tools - run: | - echo "GCC:" && gcc -v - echo && echo "CMake:" && cmake --version - echo && echo "Ninja:" && ninja --version - - - name: CMake - run: | - mkdir -p build - cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/Linux-GNU-x86_64.cmake -S ./ -B ./build - - name: Ninja - run: cd build && ninja && cd .. - - name: Create package - run: | - mkdir accounts logs save scripts - tar -czf SphereSvrX-linux-x86_64-nightly.tar.gz accounts/ logs/ save/ scripts/ build/bin-x86_64/SphereSvrX64_nightly src/sphere.ini src/sphereCrypt.ini - - # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches - - name: Upload artifact - if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Build-linux-x86_64 - path: SphereSvrX-linux-x86_64-nightly.tar.gz - overwrite: true - - linux-x86: - runs-on: ubuntu-22.04 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install prerequisites - run: | - lsb_release -d - - echo && echo "** Setting up repos" && echo - sudo sudo dpkg --add-architecture i386 - sudo add-apt-repository universe && sudo apt-get -qq update > /dev/null - echo "Done" - - echo && echo "** Installing and setting up compiler and tools" && echo - sudo apt install -yqq --no-install-recommends ninja-build linux-libc-dev:i386 gcc-12 gcc-12-multilib g++-12 g++-12-multilib > /dev/null - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 90 --slave /usr/bin/g++ g++ /usr/bin/g++-12 - echo "Done" - - # MariaDB dependencies - echo && echo "Install MariaDB dependencies" - echo "Installing some deps with apt..." - sudo apt install -yqq --no-install-recommends libc6:i386 libgcc-s1:i386 zlib1g:i386 - echo "Installing libssl1.1 manually from Bionic repo..." - wget --quiet --output-document=dep1.deb http://launchpadlibrarian.net/367327733/libssl1.1_1.1.0g-2ubuntu4_i386.deb - sudo dpkg -r --force-all libssl1.1 2> /dev/null - sudo dpkg -i --force-all dep1.deb 2> /dev/null - #sudo apt install --fix-broken ./dep1.deb - echo "Done" - - # Workaround: there are no official packages built for i386 on this OS. Let's install from previous Ubuntu LTS versions repo. - - echo "Installing MariaDB i386 manually from Bionic repo..." - wget --quiet --output-document=temp1.deb http://launchpadlibrarian.net/355877539/libmariadb3_3.0.3-1build1_i386.deb - wget --quiet --output-document=temp2.deb http://launchpadlibrarian.net/355877538/libmariadb-dev_3.0.3-1build1_i386.deb - - #echo "Installing MariaDB i386 manually from Focal repo..." - #wget --quiet --output-document=temp1.deb http://launchpadlibrarian.net/448491536/libmariadb3_10.3.18-1_i386.deb - # No libmariadb-dev ? - #wget --quiet --output-document=temp2.deb http://launchpadlibrarian.net/355877538/libmariadb-dev_3.0.3-1build1_i386.deb - - # Third choice: directly download and install packages from Debian repos? - - sudo dpkg -i temp1.deb temp2.deb 2> /dev/null - echo "Done" - - - - name: Report building tools - run: | - echo "GCC:" && gcc -v - echo && echo "CMake:" && cmake --version - echo && echo "Ninja:" && ninja --version - - - name: CMake - run: | - mkdir -p build - cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/Linux-GNU-x86.cmake -S ./ -B ./build - - name: Ninja - run: cd build && ninja && cd .. - - name: Create package - run: | - mkdir accounts logs save scripts - tar -czf SphereSvrX-linux-x86-nightly.tar.gz accounts/ logs/ save/ scripts/ build/bin-x86/SphereSvrX32_nightly src/sphere.ini src/sphereCrypt.ini - - # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches - - name: Upload artifact - if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Build-linux-x86 - path: SphereSvrX-linux-x86-nightly.tar.gz - overwrite: true - - - macos-x86_64: - runs-on: macos-13 # intel - env: - CMAKE_GEN: Ninja - CMAKE_TCH: cmake/toolchains/OSX-AppleClang-x86_64.cmake - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install prerequisites - run: | - sw_vers - - echo && echo "** Setting up compiler" && echo - export HOMEBREW_NO_AUTO_UPDATE=1 - export HOMEBREW_NO_INSTALL_CLEANUP=1 - brew install ninja mariadb-connector-c - - name: Report building tools - run: | - echo "Apple Clang:" && clang --version - echo && echo "CMake:" && cmake --version - echo && echo "Ninja:" && ninja --version - - - name: CMake - run: | - mkdir -p build - cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE="$CMAKE_TCH" -S . -B ./build - - name: Ninja - run: cd build && ninja -v && cd .. - - name: Create package - run: | - pwd - mkdir accounts logs save scripts - zip -r SphereSvrX-osx-x86_64-nightly.zip accounts/ logs/ save/ scripts/ - zip SphereSvrX-osx-x86_64-nightly.zip src/sphere.ini src/sphereCrypt.ini build/bin-x86_64/* - - # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches - - name: Upload artifact - if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Build-osx-x86_64 - path: SphereSvrX-osx-x86_64-nightly.zip - overwrite: true - - - macos-arm64: - runs-on: macos-14 # apple silicon - env: - CMAKE_GEN: Ninja - CMAKE_TCH: cmake/toolchains/OSX-AppleClang-native.cmake - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install prerequisites - run: | - sw_vers - - echo && echo "** Setting up compiler" && echo - export HOMEBREW_NO_AUTO_UPDATE=1 - export HOMEBREW_NO_INSTALL_CLEANUP=1 - brew install ninja mariadb-connector-c - - - name: Report building tools - run: | - echo "Apple Clang:" && clang --version - echo && echo "CMake:" && cmake --version - echo && echo "Ninja:" && ninja --version - - - name: CMake - run: | - mkdir -p build - cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE="$CMAKE_TCH" -S . -B ./build - - name: Ninja - run: cd build && ninja -v && cd .. - - name: Create package - run: | - pwd - mkdir accounts logs save scripts - zip -r SphereSvrX-osx-arm64-nightly.zip accounts/ logs/ save/ scripts/ - zip SphereSvrX-osx-arm64-nightly.zip src/sphere.ini src/sphereCrypt.ini build/bin-native64/* - - # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches - - name: Upload artifact - if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Build-osx-arm64 - path: SphereSvrX-osx-arm64-nightly.zip - overwrite: true - - - upload: - needs: [windows-x86_64, windows-x86, linux-x86_64, linux-x86, macos-x86_64, macos-arm64] - # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches - if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} - - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Download builds - uses: actions/download-artifact@v4 - with: - pattern: Build-* - merge-multiple: true - - - name: Generate changelog - run: git log --pretty=format:"%ad %an %s" --date=short > Git-Changelog.txt - - - name: Create release - uses: softprops/action-gh-release@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: Nightly - tag_name: Nightly - prerelease: true - files: | - Git-Changelog.txt - SphereSvrX-win-x86_64-nightly.zip - SphereSvrX-win-x86-nightly.zip - SphereSvrX-linux-x86_64-nightly.tar.gz - SphereSvrX-linux-x86-nightly.tar.gz - SphereSvrX-osx-x86_64-nightly.zip - SphereSvrX-osx-arm64-nightly.zip diff --git a/.github/workflows/build_aux_files.yml b/.github/workflows/build_aux_files.yml new file mode 100644 index 000000000..b515c0565 --- /dev/null +++ b/.github/workflows/build_aux_files.yml @@ -0,0 +1,29 @@ +name: Create/upload auxiliary files to the release. + +on: + - push + +jobs: + upload_github_release: + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + if: contains(fromJson('["master", "main"]'), github.ref_name) + + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + run: git log --pretty=format:"%ad %an %s" --date=short > Git-Changelog.txt + + - name: Add auxiliary files to the release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Nightly + tag_name: Nightly + prerelease: true + files: Git-Changelog.txt diff --git a/.github/workflows/build_linux_x86.yml b/.github/workflows/build_linux_x86.yml new file mode 100644 index 000000000..db8fe8c76 --- /dev/null +++ b/.github/workflows/build_linux_x86.yml @@ -0,0 +1,138 @@ +name: Linux x86 + +on: + - push + - pull_request + +jobs: + linux-x86: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install prerequisites + run: | + lsb_release -d + + echo && echo "** Setting up repos" && echo + sudo sudo dpkg --add-architecture i386 + sudo add-apt-repository universe && sudo apt-get -qq update > /dev/null + echo "Done" + + echo && echo "** Installing and setting up compiler and tools" && echo + sudo apt install -yqq --no-install-recommends ninja-build linux-libc-dev:i386 gcc-12 gcc-12-multilib g++-12 g++-12-multilib > /dev/null + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 90 --slave /usr/bin/g++ g++ /usr/bin/g++-12 + echo "Done" + + # MariaDB dependencies + echo && echo "Install MariaDB dependencies" + echo "Installing some deps with apt..." + sudo apt install -yqq --no-install-recommends libc6:i386 libgcc-s1:i386 zlib1g:i386 + + echo "Installing libssl1.1 manually from Bionic repo..." + wget --quiet --output-document=dep1.deb http://launchpadlibrarian.net/367327733/libssl1.1_1.1.0g-2ubuntu4_i386.deb + sudo dpkg -r --force-all libssl1.1 + sudo dpkg -i --force-all dep1.deb + #sudo apt install --fix-broken ./dep1.deb + echo "Done" + + # Workaround: there are no official packages built for i386 on this OS. Let's install from previous Ubuntu LTS versions repo. + + echo "Downloading MariaDB i386 manually from Bionic repo..." + wget --quiet --output-document=temp1.deb http://launchpadlibrarian.net/355877539/libmariadb3_3.0.3-1build1_i386.deb + wget --quiet --output-document=temp2.deb http://launchpadlibrarian.net/355877538/libmariadb-dev_3.0.3-1build1_i386.deb + + #echo "Downloading MariaDB i386 manually from Focal repo..." + #wget --quiet --output-document=temp1.deb http://launchpadlibrarian.net/448491536/libmariadb3_10.3.18-1_i386.deb + # No libmariadb-dev ? + #wget --quiet --output-document=temp2.deb http://launchpadlibrarian.net/355877538/libmariadb-dev_3.0.3-1build1_i386.deb + + # Third choice: directly download and install packages from Debian repos? + #echo "Packages to be installed by libmariadb3:" + #dpkg-deb -c temp1.deb + echo "Installing via dpkg libmariadb3" + sudo dpkg -i temp1.deb + echo "Done" + + #echo "Packages to be installed by libmariadb-dev:" + #dpkg-deb -c temp2.deb + echo "Installing via dpkg libmariadb-dev" + sudo dpkg -i temp2.deb + + echo "List lib directory files where MariaDB is supposed to be installed:" + ls /usr/lib/i386-linux-gnu/libmaria* + ls /usr/lib/i386-linux-gnu/mariadb + echo "Done" + + + - name: Report building tools + run: | + echo "GCC:" && gcc -v + echo && echo "CMake:" && cmake --version + echo && echo "Ninja:" && ninja --version + + - name: CMake + run: | + mkdir -p build + cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/Linux-GNU-x86.cmake -S ./ -B ./build + - name: Ninja + run: cd build && ninja && cd .. + - name: Create package + run: | + mkdir accounts logs save scripts + tar -czf SphereSvrX-linux-x86-nightly.tar.gz accounts/ logs/ save/ scripts/ build/bin-x86/SphereSvrX32_nightly src/sphere.ini src/sphereCrypt.ini + + # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches + - name: Upload artifact + if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Build-linux-x86 + path: SphereSvrX-linux-x86-nightly.tar.gz + overwrite: true + + + upload_github_release: + needs: linux-x86 + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + if: contains(fromJson('["master", "main"]'), github.ref_name) + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-linux-x86 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Nightly + tag_name: Nightly + prerelease: true + files: SphereSvrX-linux-x86-nightly.tar.gz + + + upload_selfhost_release: + needs: linux-x86 + if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-linux-x86 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Push release + run: | + curl -sST "{SphereSvrX-linux-x86-nightly.zip}" -u ${{secrets.UP_USER}}:${{secrets.UP_PASS}} ${{secrets.UP_WHERE}} diff --git a/.github/workflows/build_linux_x86_64.yml b/.github/workflows/build_linux_x86_64.yml new file mode 100644 index 000000000..2eee6222f --- /dev/null +++ b/.github/workflows/build_linux_x86_64.yml @@ -0,0 +1,90 @@ +name: Linux x86_64 + +on: + - push + - pull_request + +jobs: + linux-x86_64: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install prerequisites + run: | + lsb_release -d + + sudo add-apt-repository universe && sudo sudo apt-get -qq update + sudo apt install -yq --no-install-recommends ninja-build gcc-12 g++-12 > /dev/null + sudo apt install -yq --no-install-recommends mariadb-client libmariadb-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 90 --slave /usr/bin/g++ g++ /usr/bin/g++-12 + - name: Report building tools + run: | + echo "GCC:" && gcc -v + echo && echo "CMake:" && cmake --version + echo && echo "Ninja:" && ninja --version + + - name: CMake + run: | + mkdir -p build + cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/Linux-GNU-x86_64.cmake -S ./ -B ./build + - name: Ninja + run: cd build && ninja && cd .. + - name: Create package + run: | + mkdir accounts logs save scripts + tar -czf SphereSvrX-linux-x86_64-nightly.tar.gz accounts/ logs/ save/ scripts/ build/bin-x86_64/SphereSvrX64_nightly src/sphere.ini src/sphereCrypt.ini + + # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches + - name: Upload artifact + if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Build-linux-x86_64 + path: SphereSvrX-linux-x86_64-nightly.tar.gz + overwrite: true + + + upload_github_release: + needs: linux-x86_64 + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + if: contains(fromJson('["master", "main"]'), github.ref_name) + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-linux-x86_64 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Nightly + tag_name: Nightly + prerelease: true + files: SphereSvrX-linux-x86_64-nightly.tar.gz + + upload_selfhost_release: + needs: linux-x86_64 + if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-linux-x86_64 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Push release + run: | + curl -sST "{SphereSvrX-linux-x86_64-nightly.zip}" -u ${{secrets.UP_USER}}:${{secrets.UP_PASS}} ${{secrets.UP_WHERE}} diff --git a/.github/workflows/build_osx_arm.yml b/.github/workflows/build_osx_arm.yml new file mode 100644 index 000000000..4ec9673ec --- /dev/null +++ b/.github/workflows/build_osx_arm.yml @@ -0,0 +1,97 @@ +name: MacOS ARM + +on: + - push + - pull_request + +jobs: + macos-arm64: + runs-on: macos-14 # apple silicon + env: + CMAKE_GEN: Ninja + CMAKE_TCH: cmake/toolchains/OSX-AppleClang-native.cmake + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install prerequisites + run: | + sw_vers + + echo && echo "** Setting up compiler" && echo + export HOMEBREW_NO_AUTO_UPDATE=1 + export HOMEBREW_NO_INSTALL_CLEANUP=1 + brew install ninja mariadb-connector-c + + - name: Report building tools + run: | + echo "Apple Clang:" && clang --version + echo && echo "CMake:" && cmake --version + echo && echo "Ninja:" && ninja --version + + - name: CMake + run: | + mkdir -p build + cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE="$CMAKE_TCH" -S . -B ./build + - name: Ninja + run: cd build && ninja -v && cd .. + - name: Create package + run: | + pwd + mkdir accounts logs save scripts + zip -r SphereSvrX-osx-arm64-nightly.zip accounts/ logs/ save/ scripts/ + zip SphereSvrX-osx-arm64-nightly.zip src/sphere.ini src/sphereCrypt.ini build/bin-native64/* + + # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches + - name: Upload artifact + if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Build-osx-arm64 + path: SphereSvrX-osx-arm64-nightly.zip + overwrite: true + + + upload_github_release: + needs: macos-arm64 + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + if: contains(fromJson('["master", "main"]'), github.ref_name) + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-osx-arm64 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Nightly + tag_name: Nightly + prerelease: true + files: SphereSvrX-osx-arm64-nightly.zip + + + upload_selfhost_release: + needs: macos-arm64 + if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-arm64 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Push release + run: | + curl -sST "{SphereSvrX-osx-arm64-nightly.zip}" -u ${{secrets.UP_USER}}:${{secrets.UP_PASS}} ${{secrets.UP_WHERE}} diff --git a/.github/workflows/build_osx_x86_64.yml b/.github/workflows/build_osx_x86_64.yml new file mode 100644 index 000000000..ded620c17 --- /dev/null +++ b/.github/workflows/build_osx_x86_64.yml @@ -0,0 +1,95 @@ +name: MacOS x86_64 + +on: + - push + - pull_request + +jobs: + macos-x86_64: + runs-on: macos-13 # intel + env: + CMAKE_GEN: Ninja + CMAKE_TCH: cmake/toolchains/OSX-AppleClang-x86_64.cmake + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install prerequisites + run: | + sw_vers + + echo && echo "** Setting up compiler" && echo + export HOMEBREW_NO_AUTO_UPDATE=1 + export HOMEBREW_NO_INSTALL_CLEANUP=1 + brew install ninja mariadb-connector-c + - name: Report building tools + run: | + echo "Apple Clang:" && clang --version + echo && echo "CMake:" && cmake --version + echo && echo "Ninja:" && ninja --version + + - name: CMake + run: | + mkdir -p build + cmake -G "Ninja" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE="$CMAKE_TCH" -S . -B ./build + - name: Ninja + run: cd build && ninja -v && cd .. + - name: Create package + run: | + pwd + mkdir accounts logs save scripts + zip -r SphereSvrX-osx-x86_64-nightly.zip accounts/ logs/ save/ scripts/ + zip SphereSvrX-osx-x86_64-nightly.zip src/sphere.ini src/sphereCrypt.ini build/bin-x86_64/* + + # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches + - name: Upload artifact + if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Build-osx-x86_64 + path: SphereSvrX-osx-x86_64-nightly.zip + overwrite: true + + upload_github_release: + needs: macos-x86_64 + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + if: contains(fromJson('["master", "main"]'), github.ref_name) + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-osx-x86_64 + merge-multiple: true + run-id: ${{ github.run_id }} + + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Nightly + tag_name: Nightly + prerelease: true + files: SphereSvrX-osx-x86_64-nightly.zip + + upload_selfhost_release: + needs: macos-x86_64 + if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-osx-x86_64 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Push release + run: | + curl -sST "{SphereSvrX-osx-x86_64-nightly.zip}" -u ${{secrets.UP_USER}}:${{secrets.UP_PASS}} ${{secrets.UP_WHERE}} diff --git a/.github/workflows/build_win_x86.yml b/.github/workflows/build_win_x86.yml new file mode 100644 index 000000000..476da38d1 --- /dev/null +++ b/.github/workflows/build_win_x86.yml @@ -0,0 +1,77 @@ +name: Windows x86 + +on: + - push + - pull_request + +jobs: + windows-x86: + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v2 + - name: CMake + run: cmake -G "Visual Studio 17 2022" -A Win32 .\ + - name: MSBuild + run: msbuild SphereServer.sln /verbosity:minimal /maxcpucount /p:Configuration=Nightly + - name: Create package + run: | + pwd + mkdir accounts, logs, save, scripts + 7z a SphereSvrX-win-x86-nightly.zip accounts\ logs\ save\ scripts\ .\bin-x86\Nightly\SphereSvrX32_nightly.exe .\src\sphere.ini .\src\sphereCrypt.ini .\lib\_bin\x86\mariadb\libmariadb.dll + + # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches + - name: Upload artifact + if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Build-win-x86 + path: SphereSvrX-win-x86-nightly.zip + overwrite: true + + upload_github_release: + needs: windows-x86 + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + if: contains(fromJson('["master", "main"]'), github.ref_name) + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-win-x86 + merge-multiple: true + run-id: ${{ github.run_id }} + + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Nightly + tag_name: Nightly + prerelease: true + files: SphereSvrX-win-x86-nightly.zip + + upload_selfhost_release: + needs: windows-x86 + if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-win-x86 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Push release + run: | + curl -sST "{SphereSvrX-win-x86-nightly.zip}" -u ${{secrets.UP_USER}}:${{secrets.UP_PASS}} ${{secrets.UP_WHERE}} diff --git a/.github/workflows/build_win_x86_64.yml b/.github/workflows/build_win_x86_64.yml new file mode 100644 index 000000000..7c464127d --- /dev/null +++ b/.github/workflows/build_win_x86_64.yml @@ -0,0 +1,77 @@ +name: Windows x86_64 + +on: + - push + - pull_request + +jobs: + windows-x86_64: + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v2 + - name: CMake + run: cmake -G "Visual Studio 17 2022" .\ + + - name: MSBuild + run: msbuild SphereServer.sln /verbosity:minimal /maxcpucount /p:Configuration=Nightly + - name: Create package + run: | + pwd + mkdir accounts, logs, save, scripts + 7z a SphereSvrX-win-x86_64-nightly.zip accounts\ logs\ save\ scripts\ .\bin-x86_64\Nightly\SphereSvrX64_nightly.exe .\src\sphere.ini .\src\sphereCrypt.ini .\lib\_bin\x86_64\mariadb\libmariadb.dll + + # Upload artifact linked to the workflow run - only if the run is for a pull request, or for selected branches + - name: Upload artifact + if: contains(fromJson('["master", "main", "dev"]'), github.ref_name) || ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Build-win-x86_64 + path: SphereSvrX-win-x86_64-nightly.zip + overwrite: true + + upload_github_release: + needs: windows-x86_64 + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + if: contains(fromJson('["master", "main"]'), github.ref_name) + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-win-x86_64 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: Nightly + tag_name: Nightly + prerelease: true + files: SphereSvrX-win-x86_64-nightly.zip + + upload_selfhost_release: + needs: windows-x86_64 + if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + + runs-on: ubuntu-latest + steps: + - name: Download builds + uses: actions/download-artifact@v4 + with: + name: Build-win-x86_64 + merge-multiple: true + run-id: ${{ github.run_id }} + - name: Push release + run: | + curl -sST "{SphereSvrX-win-x86_64-nightly.zip}" -u ${{secrets.UP_USER}}:${{secrets.UP_PASS}} ${{secrets.UP_WHERE}} diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml index 700ccce21..2d93dbb61 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity-scan.yml @@ -57,7 +57,9 @@ jobs: run: | cd build ../cov-analysis-linux64/bin/cov-build --dir ./cov-int make + echo "Printing last lines of cov-int/build-log.txt:" tail cov-int/build-log.txt + pwd #- name: Coverity scm log # run: | @@ -67,6 +69,7 @@ jobs: - name: Submit the result to Coverity Scan run: | + pwd tar czvf build-data.tgz cov-int curl \ --form project=SphereServer/Source-X \ diff --git a/.gitignore b/.gitignore index 8261b5e5a..e88674472 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -# Ignore folder and files +# Ignore folders /.git /.vscode *.vshistory* /.kdev* +/.ctagsd +/.codelite /CMakeFiles /spheresvr.dir diff --git a/CMakeLists.txt b/CMakeLists.txt index 7da5088d3..4bd1cac74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,15 +90,10 @@ SET (CMAKE_EXE_LINKER_FLAGS_DEBUG "") SET (CMAKE_EXE_LINKER_FLAGS_NIGHTLY "") SET (CMAKE_EXE_LINKER_FLAGS_RELEASE "") - -# Determine system arch -# TODO: change name for the variable to ARCH_IS_32BITS and ARCH_IS_64BITS -INCLUDE ("cmake/CMakeDetectArch.cmake") -determine_target_architecture(ARCH_DETECTED) -STRING (FIND "${ARCH_DETECTED}" "x86_64" ARCH_HAS_x86_64) -STRING (FIND "${ARCH_DETECTED}" "x86" ARCH_HAS_x86) -booleanize_str_find(ARCH_HAS_x86_64) -booleanize_str_find(ARCH_HAS_x86) +set (is_win32_app_linker) +IF (WIN32 AND NOT ${WIN32_SPAWN_CONSOLE}) + set (is_win32_app_linker WIN32) # if not set, it defaults to console subsystem +ENDIF () # If we have not specified a toolchain, let's detect which one we should use, using the detected arch. @@ -166,7 +161,7 @@ IF (SINGLE_TARGET) IF (("${CMAKE_BUILD_TYPE}" STREQUAL "") OR (${CMAKE_BUILD_TYPE} MATCHES "(R|r?)elease")) SET (TARGETS ${TARGETS} spheresvr_release) - ADD_EXECUTABLE (spheresvr_release + ADD_EXECUTABLE (spheresvr_release ${is_win32_app_linker} ${SPHERE_SOURCES} # ${docs_TEXT} ) @@ -174,7 +169,7 @@ IF (SINGLE_TARGET) ENDIF () IF (("${CMAKE_BUILD_TYPE}" STREQUAL "") OR (${CMAKE_BUILD_TYPE} MATCHES "(N|n?)ightly")) SET (TARGETS ${TARGETS} spheresvr_nightly) - ADD_EXECUTABLE (spheresvr_nightly + ADD_EXECUTABLE (spheresvr_nightly ${is_win32_app_linker} ${SPHERE_SOURCES} # ${docs_TEXT} ) @@ -182,7 +177,7 @@ IF (SINGLE_TARGET) ENDIF () IF (("${CMAKE_BUILD_TYPE}" STREQUAL "") OR (${CMAKE_BUILD_TYPE} MATCHES "(D|d?)ebug")) SET (TARGETS ${TARGETS} spheresvr_debug) - ADD_EXECUTABLE (spheresvr_debug + ADD_EXECUTABLE (spheresvr_debug ${is_win32_app_linker} ${SPHERE_SOURCES} # ${docs_TEXT} ) @@ -192,7 +187,7 @@ IF (SINGLE_TARGET) ELSE (SINGLE_TARGET) SET (TARGETS ${TARGETS} spheresvr) - ADD_EXECUTABLE (spheresvr + ADD_EXECUTABLE (spheresvr ${is_win32_app_linker} ${SPHERE_SOURCES} ${docs_TEXT} ) diff --git a/Changelog.txt b/Changelog.txt index 2187e1f3b..73987c6f5 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -60,8 +60,8 @@ Gump Enums, New Container Defines, Misc Switches from Prop Attributes to Flags, - Added: uofiles_enums.h - New Gump Enums for several new containers, cards, and other miscellaneous assets. - Added: CContainer.cpp - definitions for the new Container Gump assets. -- Updated: Tables - Removed Only as these are unused. - Split the NoDropTrade back into separate flags as they also have separated CliLocs. +- Updated: Tables - Removed Only as these are unused. + Split the NoDropTrade back into separate flags as they also have separated CliLocs. Moved over Balanced, BattleLust, and BloodDrinker as they are now being checked as Item Attributes. These all are now stored on Items only. cItemBase_props.tbl had AlterItem added to it. @@ -91,7 +91,7 @@ Note: More needs to be switched over and I am trying to get this and the Base Pa the second param will result in default timer according to your magery skill. 29-05-2016, Kaylala (Khaos) -- Updated: Tables - Removed Balanced, BloodDrinker, and BattleLust from BaseBaseDef tables +- Updated: Tables - Removed Balanced, BloodDrinker, and BattleLust from BaseBaseDef tables and CObjBase tables since they were moved. Moved Bane and SplinteringWeapon to CItem tables. - Added: Attr_SplinteringWeapon to CItem.h. - Added: SplinteringWeapon, Balanced, and Bane to Item Properties and removed them from Obj @@ -229,8 +229,8 @@ Note: More needs to be switched over and I am trying to get this and the Base Pa 23-10-2017, Drk84 - [Port from 0.56d, 14-02-2017, Coruja] Fixed: Combat Hit Chance formulas incorrectly assuming that defender is always using the same combat skill as the attacker. - -24-10-2017, Drk84 + +24-10-2017, Drk84 - [Port from 0.56d, 19-09-2017, Coruja] Fixed: Client 'Open Spellbook' macro making the client crash if it tries to open the requested spellbook gump when the spellbook item is not loaded yet. 24-10-2017, Nolok @@ -263,7 +263,7 @@ Note: More needs to be switched over and I am trying to get this and the Base Pa The stat bonus are BONUS_STR, BONUS_DEX, BONUS_INT and BONUS_STATS and they can be found in sphere_skills.scp Normally you can see the skill values adjusted by the stats bonues by checking the value of the skill in the Skill Menu when the "Show Real" option is turned OFF, The adjusted skill value is used by Sphere for calculating the chance of success for NON-combat skills (and also ResistingSpells), in the scripts there was no - "short-way" to display the correct chance of success unless using a lot of multiplication and division. + "short-way" to display the correct chance of success unless using a lot of multiplication and division. Usage: SkillAdjusted.skillnumber or SkillAdjusted.skillkey (Example: SkillAdjusted.1 or SkillAdjusted.Anatomy) Example calculating chance of success by scripts: ON=@SkillStart @@ -312,7 +312,7 @@ Note: More needs to be switched over and I am trying to get this and the Base Pa - [Port from 0.56d, 05-09-2016, Coruja] Fixed: TimerCall setting on sphere.ini using wrong time format (hours instead minutes) to call f_onserver_timer function 13-12-2017, Drk84 -- Possible fix for stat cap issue when the sum of the players stats are at StatCap -1. +- Possible fix for stat cap issue when the sum of the players stats are at StatCap -1. 23-12-2017, Nolok - Added: trigger @NPCActWander. It is called each step an NPC does while wandering. ARGN1 = 0 if it will continue to wander, 1 if randomly deciding to stop, 2 if deciding to stop because @@ -347,11 +347,11 @@ Note: More needs to be switched over and I am trying to get this and the Base Pa if set, will be used instead of obtaining it from the SOUND property. Set one of these overrides to -1 if you don't want that action-related sound to be played. 14-01-2018, Drk84 -- Fixed: The effect value of spells that equips a "memory item" on a character (Bless, Curse and so on) was calculated twice, also causing to ignore the +- Fixed: The effect value of spells that equips a "memory item" on a character (Bless, Curse and so on) was calculated twice, also causing to ignore the value of local.effect. - + 25-01-2018, Nolok & Drk84 -- Changed the way ACTARG1/ACTARG2/ACTARG3 are saved, they will be saved only if their value is different from 0 and the character's action is a valid skill or is one +- Changed the way ACTARG1/ACTARG2/ACTARG3 are saved, they will be saved only if their value is different from 0 and the character's action is a valid skill or is one of the action that uses ACTARG1/ACTARG2/ACTARG3 (NPCACT_FLEE, NPCACT_TALK, NPCACT_TALKFOLLOW, NPCACT_RIDDEN). - Reverted the following changes made in a precedent commit for avoiding conflict with Spellcasting or custom scripts. Removed the call to Skill_CleanUp method inside the trigger checks in Skill_UseQuick. @@ -466,7 +466,7 @@ Note: More needs to be switched over and I am trying to get this and the Base Pa Added: New item property PICKUPSOUND to override default pickup sound for items (it can be used together with DROPSOUND). - [Port from 0.56d, 02-01-2018, Coruja] Fixed: ENHANCEPOTIONS item property only working when FEATURE_AOS_UPDATE_B .ini feature is enabled. -- [Port from 0.56d, 19-02-2018, Coruja] +- [Port from 0.56d, 19-02-2018, Coruja] Fixed: Sphere console not closing correctly when it get closed before load startup files. Fixed: Multis (houses/boats) not being loaded at proper distance when client is walking. - [Port from 0.56d, 14-03-2018, Coruja] @@ -563,10 +563,10 @@ Note: More needs to be switched over and I am trying to get this and the Base Pa - Fixed: parrying chance didn't scale correctly with Dexterity. - [Port from 0.56d, 18-04-2018, Coruja] Changed: Small improvements on tooltip engine (containers weren't resending their tooltip after their weight changed, and some tooltips weren't updated for items with an expired timer). - + 30-04-2018, Drk84 - Added behaviour for the Focus skill, the skill is used automatically and only when FEATURE_AOS_UPDATE_B is enabled. - The Focus skill increase, passively, the amount of stamina gained by 1 for each 10% points of skill value and increase by 1 the amount of mana gained for + The Focus skill increase, passively, the amount of stamina gained by 1 for each 10% points of skill value and increase by 1 the amount of mana gained for each 20% points of skill value. 02-05-2018, XuN @@ -651,7 +651,7 @@ To modify their HP use HITS and MAXHITS on them -Added RegenItemHits to control the update period of those items' hp. 08-05-2018, Drk84 -- Fixed #79: Protection spell AR stacking when COMBAT_ELEMENTAL_ENGINE is disabled. +- Fixed #79: Protection spell AR stacking when COMBAT_ELEMENTAL_ENGINE is disabled. 08-05-2018, XuN House triggers: @@ -664,7 +664,7 @@ House triggers: return false will remove this item from the commit.* *if not return false a local.MaxZ will check the P.Z value of the item and update it if it's higher than the old one. -Changed @HouseDesignCommit: added local.MAXZ(Read Only) returning the value of the top placed item. - + Added character storage for it's own houses: - Added AddHouse uid(WO): Adds the given uid to the player's house. If the player current count of houses is greater than the limit he has, the house will be redeeded. @@ -688,7 +688,7 @@ Massive changes on multis (houses for now). - Added MaxHousesAccount = Max houses an account can have (default 1). - Added MaxHousesPlayer = Max houses one player can have (default 1). - Added AutoHouseKeys = 1 to generate keys for multis on creation, 0 to disable. - + Updated [sphere_def.scp]: [DEFNAME house_types] house_private=0 @@ -699,17 +699,17 @@ Updated [sphere_def.scp]: transfer_lockdowns = 01 // Transfer LockDowns transfer_addon = 02 // Transfer Addons transfer_all = 04 // Transfer LockDowns, Addons and every other item found inside a house that is not a component of it. - + Updated [sphere_defs_types_hardcoded.scp] [TYPEDEFS]: t_multi_addon=201 // AddOns, the deed must have attr_magic to bypass placement checks and allow the addon to be placed inside houses. - + New keys added to [MULTIDEF ] (and updated [multis_houses.scp] with them): -BaseStorage (RW): Base limit of item storage for this multi. -BaseVendors (RW): Base limit of vendor capacity for this multi. -LockdownsPercent (RW): Base LockdownsPercent value for this multi. (Read the House Storage support revisions for more info.) - + Added internal storage for Owner, Coowners, Friends, Bans, Locked down items (Lockdowns), Vendors and House Components. -House Permissions: @@ -749,8 +749,8 @@ Added internal storage for Owner, Coowners, Friends, Bans, Locked down items (Lo - Added Components(R): Returns the total count of Components. - Added GenerateBaseComponents(W): Regenerates the components the house has on the [MULTIDEF ] (this does NOT delete current components nor recode/remove current keys). - Added DeleteAllComponents(W): Removes all current components. - - + + * Passing as param -1 (or 0FFFFFFF) will clear those lists but nothing more, it won't remove the objects, clear; attrs, props, events, etc; applied, it must be done before clearing the list. - Added REF to OWNER, COOWNER.x, FRIEND.x, BAN.x, LOCKDOWN.x, VENDOR.x, COMPONENT.x, to access the objects inside and work on them, eg: OWNER.say I'm da Boss!, local.coowner=, etc @@ -780,12 +780,12 @@ Added internal storage for Owner, Coowners, Friends, Bans, Locked down items (Lo TRANSFER_LOCKDOWNS= 0x1 // Transfer LockDowns, if there's only need of transfering lockdowns use MoveLocksToCrate) TRANSFER_ADDON = 0x2 // Transfer Addons TRANSFER_ALL = 0x4 // Transfer LockDowns, Addons and every other item found inside a house that is not a component of it. - + AddOns support: -Added t_multi_addon: Works like current multis&addons but will not count towards the House's limit. It's just a type definition to alter how these items works inside 'real' multis, but the current items using it (ie: m_fountain_white) will work the same excepting that they won't create keys by default. - + - Redeeding -Houses with search for TAG.DEED_ID to generate the new deed, if not value is set i_deed will be used, i_deed will be set by default on new placed multis. -Added Redeed(WO) fShowMsg, fMoveToCrate: Redeeds the multi to the OWNER's backpack (if there is no owner, there is no redeed). @@ -833,7 +833,7 @@ Updated [sphere_defs.scp]: transfer_lockdowns = 01 // Transfer LockDowns transfer_addon = 02 // Transfer Addons transfer_all = 04 // Transfer LockDowns, Addons and every other item found inside a house that is not a component of it. - + Added to [MULTIDEF ]: -BaseStorage (RW): Homologue to the House property BaseStorage. -BaseVendors (RW): Homologue to the House property BaseVendors. @@ -842,22 +842,22 @@ Added to [MULTIDEF ]: -Changed: now t_multis will be saved too in spheremultis.scp along with t_multi_custom. 10-05-2018, Drk84 -- Added @EffectRemove trigger for characters, +- Added @EffectRemove trigger for characters, Argo: the spell item. Src: the caster. Default object: the target of the spell. Argn1: the spell number. Return 0: remove the spell memory item but don't execute the default spell behaviour when the spell item is removed. - - The trigger works similarly to @EffectAdd but is fired when a spell memory item is removed (and after the @Destroy trigger of the item) + + The trigger works similarly to @EffectAdd but is fired when a spell memory item is removed (and after the @Destroy trigger of the item) The trigger is needed if you plan to use return 0 in the @EffectAdd trigger, otherwise the default spell behaviour will be executed when the spell item get removed. Example - I cast a curse spell and I have a return 0 in my @EffectAdd trigger, no default curse effect are applied but when the spell expires the curse item + I cast a curse spell and I have a return 0 in my @EffectAdd trigger, no default curse effect are applied but when the spell expires the curse item will be removed and the spell target will get the additional stats from the expired curse! Using return 0 in @EffectRemove will prevent this Additional notes: Both @EffectAdd and @EffectRemove with return 0 can conflict with Poison and Poison Fields spells or scripts that call the poison function, that's because the poison status and effect is applied no matter what. - For Summoning spells, returning 0 in @EffectRemove will prevent the summon to disappear. + For Summoning spells, returning 0 in @EffectRemove will prevent the summon to disappear. 10-05-2018, Nolok - Fixed: false-positive speedhack check when trying to walk on a disabled map. @@ -1362,7 +1362,7 @@ Added CanSleep function (Read Only) for sectors to check wether a sector can Sle - [Port from 0.56d, 25-09-2018, Coruja] Added: New function CODEXOFWISDOM to open client Codex of Wisdom menu at given topic ID. Syntax: CODEXOFWISDOM TopicID (1~176), [ForceOpen (0/1)] - [Port from 0.56d, 01-11-2018, Coruja] Added: Support for new Whip weapon type. - + 17-12-2018, Nolok - [Port from 0.56d, 04-07-2018, Coruja] - Added: Support for 'EquipLastWeapon' client macro. @@ -1409,7 +1409,7 @@ Added CanSleep function (Read Only) for sectors to check wether a sector can Sle Examples: With an EFFECT value of 20 the maximum tracking distance is 20 tiles, no matter the skill score. With an EFFECT value of 10,100 the maximum tracking distance will vary from 10 to 100 tiles depending from the player Tracking skill. - If EFFECT is not defined and we have 90.0 in Tracking Skill the maximum tracking distance will be 100 tiles (900 / 10 + 10, this is the default behaviour) + If EFFECT is not defined and we have 90.0 in Tracking Skill the maximum tracking distance will be 100 tiles (900 / 10 + 10, this is the default behaviour) Notes When using the Tracking skill, the maximum distance is stored in ACTARG2, this means that the value can be changed in the @PreStart, @Start and @Stroke triggers, but because the maximum distance is calculated after @Select and before @PreStart changing the value in @PreStart and @Start will take @@ -1799,25 +1799,25 @@ WARNING: Important change! 24-07-2019, Drk84 - Added: Two optional parameters "linearcheck" and "forcecheck" to the SKILLUSEQUICK character command: SKILLUSEQUICK "skillId", "difficulty", "linearcheck", "forcecheck" - + Linearcheck: This optional parameter when set to 1 will make Sphere performs a linear check instead of using the default Bell Curve formula. The linear check is usually used for combat checks (hitting, parrying, resisting spells) while the Bell curve check is usually used by almost all of the other skills. - Take note that when using the linear check, the highest is the difficulty value the easier is to pass the test, while the opposite is true for a Bell curve check. - + Take note that when using the linear check, the highest is the difficulty value the easier is to pass the test, while the opposite is true for a Bell curve check. + Forcecheck: This optional paramter allows the execution of the Skill_UseQuick method by a skill with SKF_SCRIPTED flag. By default Sphere does not allow a skill with SKF_SCRIPTED flag to execute the Skill_UseQuick method, setting the value to 1 will allow the execution of the Skill_UseQuick method. - + Examples: skillusequick skill_appraise,75 //Make a quick check with item id skill with a difficulty of 75 and using the bell formula. skillusequick skill_appraise,/10,1 //Make a quick check with the item id skill with a difficulty equal to the character itemid value and using the linear check formula. skillusequick skill_parrying,50,0,1 //Assuming that the parrying skill has the SKF_SCRIPTED flag, make a quick check with parrying skill, using the Bell curve formula with a difficulty of 50 and forcing the execution of the Skill_UseQuick method. - + Special Cases Sphere internally uses a quick check when certain actions are performed: - + Camping: Camping skill uses a quick check for activating up campfires. Mining: Lingot smelting is handled by a quick check. Musicianship: Playing an instrument is handled by a quick check. @@ -1827,7 +1827,7 @@ WARNING: Important change! Tinkering: Copying a key is performed by a Tinkering quick check. Training Dummies: All the Melee, Ranged and Pickpocket dummies uses a quick check. Tracking: Tracking uses a quick check when STARTING to track (the tracking menu is displayed and the quarry category is selected). - + 25-07-2019, Nolok - Changed: The "too dumb to be able to use NPC Advanced Pathfinding" INT value from 70 to 30 (to ignore this check enable NPC_AI_ALWAYSINT in the ini). @@ -1837,7 +1837,7 @@ WARNING: Important change! 28-07-2019, Nolok - Fixed: When a derived ITEMDEF (like [ITEMDEF i_something], not [ITEMDEF 01234]) was created and neither a type and a valid id were set in the script, the item base was considered having random type, layer, weight, tiledata flags and CAN flags. -- Fixed: NotoSaves weren't always cleared right when they timed out. +- Fixed: NotoSaves weren't always cleared right when they timed out. - Fixed: Attacker elapsed time value wasn't increased exactly every second (but after a greater delay) (Issue #304). 30-07-2019, Nolok @@ -1948,7 +1948,7 @@ WARNING: Important change! - Changed: Veterinary now works with all tameable creatures: that means all NPCs with brain set to BRAIN_ANIMAL, BRAIN_DRAGON or BRAIN_MONSTER AND with a Taming skill above 0. Before Veterinary only worked with BRAIN_ANIMAL npcs. Remember that a NPC is tameable when it has a Taming Skill above 0 and an Animal Lore skill set to 0 and the NPCs ID it's not one of the playable character races. -- Changed: All NPCs with a Poisoning skill above 0 can now apply poison (if unarmed), before only creatures with BRAIN_MONSTER could apply a poison. +- Changed: All NPCs with a Poisoning skill above 0 can now apply poison (if unarmed), before only creatures with BRAIN_MONSTER could apply a poison. - Fixed: There was no timeout for the NPC action THROWING, resulting in a mass spam of throwing actions, the timeout is now 3 seconds. 16-11-2019, Nolok @@ -2047,7 +2047,7 @@ R/W Keywords available for the t_champion_spawn item: "SPAWNSMAX": Gets or sets the total maximum spawns. "WHITECANDLES": Gets thhe count of white candles (read only). -Verbs available for the t_champion_spawn item: +Verbs available for the t_champion_spawn item: "ADDOBJ": Inserts an already created npc into the champion spawn (be sure you add them when the champion is activated, or they will be removed when it start). "DELOBJ": Removes one character from the champion counting towards the progression of itself, this does not remove the character. "ADDREDCANDLE": Advances the champion by adding one red candle (red candles and levels explained below). @@ -2059,7 +2059,7 @@ Verbs available for the t_champion_spawn item: "MULTICREATE": Emplaces a champion by script, a champion requires a multi area to be renamed and have custom flags, forbidding recalls or whatever. "START": Activates the champion. "STOP": Deactivates the champion and removes any progress and npcs. - + Default levels explanation: Level 1: From start until 6th red candle is finished. Level 2: From 7th red candle to Level 10th red candle. @@ -2095,7 +2095,7 @@ Must have event, even if it's empty, since it's applied by the source to generat 23-01-2020, Drk84 - Added: New ini flag DisplayArmorAsPercent. - When enabled, display, in the tooltip, the Armor of the item as a percentage of its full armor value, the percentage is based upon the body parts the item covers. + When enabled, display, in the tooltip, the Armor of the item as a percentage of its full armor value, the percentage is based upon the body parts the item covers. Remember that the displayed value is just for "cosmetical" purpose and, by default, is not used when calculating combat damage, the full AR is the one used. In some cases, the Armor value displayed in the tooltip will not match with the sum of the AR of each piece of armor displayed in the status gump, this happens when an armor item cover location overlaps with another armor item cover location that as an higher value. @@ -2278,7 +2278,7 @@ Must have event, even if it's empty, since it's applied by the source to generat - Fixed: Adding the Blade Spirits spell to a spellbook added the Clumsy spell. (Issue #496) Warning: existing Spellbooks with the Blade Spirits spell added by scroll or addspell have the more1 value corrupted and so need they must be replaced. Only Magery Spellbooks are affected because other spellsbooks doesn't have more than 32 spells (Mastery Spells are not implemented). - + 29-08-2020, Drk84 - Fixed: Cant create areas in the new maps (ilshenar, ter mur, etc) (Issue #499). @@ -2299,7 +2299,7 @@ Must have event, even if it's empty, since it's applied by the source to generat 20-11-2020, Drk84 - Fixed: Dismounting from a NPC cleared the actarg1 value of the rider instead of the NPC mount. (Issue #527) - This means that when unmounting and the character is using a skill that makes use of the Actarg1 value, such as casting a spell, the skill will continue its execution normally. + This means that when unmounting and the character is using a skill that makes use of the Actarg1 value, such as casting a spell, the skill will continue its execution normally. - Fixed: Invalid spell ID didn't abort the skill execution when the magic skill succeeds (this is related to the issue above), causing a possible skill gain. 01-12-2020, Nolok @@ -2314,7 +2314,7 @@ Must have event, even if it's empty, since it's applied by the source to generat - Modified: Before this modification, because VALUE is READ only, the only way to change the price of an item was changing the PRICE. NOW, PRICE keyword only used to set the selling price of an item sold by a player vendor. PRICE of an item will no longer change the gold you will receive when selling it to a NPC. PRICE is now exclusive to player vendor. -- Added: tag.override.value can now be add to an item to change his value when trading with a NPC. In scripts, on vendor template +- Added: tag.override.value can now be add to an item to change his value when trading with a NPC. In scripts, on vendor template (BUY= or SELL=). For changing the value of the itemdef, you must use tag.override.value=xxx instead of old syntax price=xxx. 05-12-2020, Drk84 @@ -2440,7 +2440,7 @@ Must have event, even if it's empty, since it's applied by the source to generat - Changed: ISTIMERF to also accept STRMATCH-style arguments. 31-01-2021, Jhobean -- Fixed: when hits, stam or mana were set to negative value, overflow occurred and the stat was set at near maximum values. Now setting a stat to a negative value will set it to 0. (Issue #406) +- Fixed: when hits, stam or mana were set to negative value, overflow occurred and the stat was set at near maximum values. Now setting a stat to a negative value will set it to 0. (Issue #406) - Fixed: Using stacked deeds consumed the whole pile. 30-01-2021, Ben @@ -2461,7 +2461,7 @@ Must have event, even if it's empty, since it's applied by the source to generat - Fixed: REGEN values not saved (issue #595). RegenHits, RegenMana, RegenStam and RegenFood will now be saved only if their value is equal or above to 1 and different from the default value in the ini setting. Default values in the ini are: 40 seconds for RegenHits, 20 seconds for RegenMana, 10 seconds for RegenStam and 86400 seconds (one day) for RegenFood. - + 03-03-2021, Drk84 - Updated SphereCrypt.ini - Fixed: Reagents consumed on magical skill abortion (issue #605). @@ -2479,7 +2479,7 @@ Must have event, even if it's empty, since it's applied by the source to generat local.damagetype (R/W) = The damage type of the spell, if you are making a custom spell you must set a value otherwise the spell will not cause damage. return 1: Destroy the spell memory and block the spell execution. return 0: If the spell has the flag SPELLFLAG_SCRIPTED blocks the spell execution - + These triggers fires on the following spells and any custom spells with the SPELLFLAG_TICK: Poison: You can now change the damage and damage type by changing the local.effect and local.damage value in these triggers. Strangle and Pain Spike: You can now change the damage and damage type in these triggers. @@ -2489,7 +2489,7 @@ Must have event, even if it's empty, since it's applied by the source to generat Custom Spells: Simply add SPELLFLAG_TICK to the spell spellflags. By default every spell memory starts with one charge. Spells with SPELLFLAG_HARM and local.damagetype set will cause damage every local.delay seconds. Spells with SPELLFLAG_HEAL will heal hitpoints every local.delay seconds. - Remember that is not mandatory to use SPELLFLAG_SCRIPTED to make a custom spell, you can just use a spell id higher than 1055, usually i choose 2000. + Remember that is not mandatory to use SPELLFLAG_SCRIPTED to make a custom spell, you can just use a spell id higher than 1055, usually i choose 2000. It's RECOMMENDED to UPDATE the spells scripts file and sphere defs file, alternatively just add the following line, in the spellflag section in defs.scp/sphere_defs.scp: spellflag_tick 080000000 // A spell is going to tick and causing an effect. @@ -2556,20 +2556,20 @@ Must have event, even if it's empty, since it's applied by the source to generat https://www.uoguide.com/Lesser_Cure_Potion https://www.uoguide.com/Cure_Potion https://www.uoguide.com/Greater_Cure_Potion - + If MAGICF_OSIFORMULAS is disabled the chance to cure is based upon sphere classic bell formula: Example if the poison level is 60.0 and the healing skill is 50.0, the chance of curing the poison is around 25%. - + In addition, you can override the cure poison chance by using the TAG.OVERRIDE.CUREPOISONCHANCE in the poison item that will be placed upon the character. In this case a simple percent check is made, You can add this tag by using @SpellEffectAdd/@EffectAdd (see also below), example: ON=@EffectAdd argo.tag.override.curepoisonchance = 25 //a 25% chance of curing the poison. - + - Changed: For consistency renamed characters triggers @EffectAdd and @EffectRemove to @SpellEffectAdd and SpellEffectRemove. Added spell triggers @EffectAdd and @EffectRemove (spell triggers must be defined in the spell block). Quick summary for @SpellEffectAdd/@EffectAdd and @SpellEffectRemove/@EffectRemove: - + @SpellEffectAdd/@EffectAdd Argo: the spell item. Src: the caster. @@ -2577,21 +2577,21 @@ Must have event, even if it's empty, since it's applied by the source to generat Argn1: the spell number. return 0: Let default checks to happen. return 1: Blocks any action and stops everything, also deletes the memory (Blocking any changes, this will cause also the effect to do not have duration) - + @SpellEffectRemove/@EffectRemove Argo: the spell item. Src: the caster. Default object: the target of the spell. Argn1: the spell number. Return 0: remove the spell memory item but don't execute the default spell behaviour when the spell item is removed. - + 20-04-2021, Drk84 - Fixed: Trading layer item was added to the character weight when starting a trade (Issue #660). - Fixed: MonsterFear in Sphere.ini does not seem to affect monsters (Issue #603). I also changed a little the motivation formula, now the remaining health of the NPC counts more towards the fleeing chance. Take in consideration that if the NPC has a big STR value it will be unlikely to flee. Remember that you can modify ARGN2 in @NpcActFight trigger to set the motivation value (ARGN2 <= 0 the NPC will flee). - + 22-04-2021, Drk84 - Fixed: The NEW object after executing the NEWDUPE command always returned the last created item instead of the newly duped character (Issue #661). @@ -2606,76 +2606,76 @@ Must have event, even if it's empty, since it's applied by the source to generat - Added: New item/char triggers @Smelt/@ItemSmelt and @CarveCorpse/@ItemCarveCorpse (Issue #616). @Smelt(Item Trigger)/@ItemSmelt (Character Trigger) These triggers are fired when a character attempts to smelt ores and items on a forge or by the SMELT command but also: - + Src: The character smelting the item. Default Object: The item being smelted in the @Smelt trigger. The character smelting the item in the @ItemSmelt trigger. - - Argn1: R/W, the adjusted mining skill of the character. + + Argn1: R/W, the adjusted mining skill of the character. Argn2: R, the total amount of different resources the item being smelted has. For example a mace is made of iron ingots and logs, so the number of different resources is 2. For ores the total amount of different resources is always 1. - + local.resource.n.ID: R/W, The ID of the itemdef that will be created if the smelting check is succesfull, n starts at 0. local.resource.n.amount: R/W, The amount value that will set on that particular resource if the smelting check is succesfull, n starts at 0. - + Return 1: Fails the smelting attempt and block default smelting behaviour. Return 0: Succeeds the smel attempt and block default smelting behaviour. - + Notes: If you are using the ScriptPack X you need to COMMENT the following lines in the types_forge.scp file for enabling the smelting of items/ores by forge: TARGETF f_craft_blacksmith_smelt_targ RETURN 1 - + Reading local.resource.n.ID: The itemdef of the resource being created is in numerical format so for outputting it in readable format you need to use: serv.itemdef..iD>> - + For assigning the value just use the plain itemdef instead: local.resource.n.ID = i_ingot_gold local.resource.n.amount = 30 - + Only ingots and gems will "survive" the smelting process, the rest of resources is not created. For every ingots that can be created Sphere makes two additional check: - First, the character need to have the minimum skill to smelt the ores + First, the character need to have the minimum skill to smelt the ores Second, the character must pass a skillquick check, if fail up to half of the item amount value is consumed (so non-ores items are usually destroyed). - + Example: Given an item with the following resource property RESOURCES=6 i_ingot_iron,1 i_log,1 i_gem_diamond,3 i_ingot_bronze ON=@ItemSmelt // FOR 0 - 1> serv.log resource obtained .ID>.baseid> - Amount: .amount> - endfor + endfor Argn2 is 4 (i_ingot_iron, i_log, i_gem_diamond and i_ingot_bronze) The output will be: i_ingot_iron - 6, i_log 1 - ,i_gem_diamond -3, i_ingot_bronze - 3 - - + + @CarveCorpse (Item Trigger)/@ItemCarveCorpse(Character Trigger) These triggers are fired when a character attempts to carve a corpse or a corpse is being carved: - + Src: The character carving the corpse. Default Object: The corpse being carved in the @CarveCorpse trigger. The character carving the corpse in the @ItemCarveCorpse trigger. Argo: The item used for carving the corpse. - + Argn1: R The total amount of different resources the corpse being carved has. For example the ogre resources property contains only the i_ribs_raw item so the number of different resources is just 1. - + local.resource.n.ID: R/W, The ID of the itemdef that will be created when carving the corpse, n starts at 0. local.resource.n.amount: R/W, The amount of value that will be set on that particular resource, n starts at 0. - + Return 1: Block the default carving behaviour. - + Notes local.resource.n.ID stores the itemdef of the resource being created in numerical format so for outputting it in readable format you need to use: serv.itemdef..iD>> - + For assigning the value just use the plain itemdef instead: local.resource.n.ID = i_gem_ruby local.resource.n.amount = 5 - + Example: [TYPEDEF t_corpse] ON=@CarveCorpse @@ -2684,8 +2684,8 @@ Must have event, even if it's empty, since it's applied by the source to generat if ( >= 900) && ( == i_dagger) local.resource..amount += 5 endif - endfor - If we are carving an ogre, ARGN1 will be 1 and the output will be + endfor + If we are carving an ogre, ARGN1 will be 1 and the output will be Raw Ribs- 2 If player forensics skill is >= 90.0% and we are using a dagger for carving we get an additional 5 i_ribs_raw @@ -2701,11 +2701,11 @@ Must have event, even if it's empty, since it's applied by the source to generat - Fixed: Crash when accessing a non-existant SRC (the caster) in the @EffectRemove and @EffectTick triggers. Remember to use the ISVALID command by assigning SRC to a REF to check if the caster is still valid - Fixed: Necromancy spells Blood Oath and Evil Omen have their default behaviour executed even if their spell definition has the SPELLFLAG_SCRIPTED set. -- Fixed: Poison memory item was not re-created after a character was poisoned again. +- Fixed: Poison memory item was not re-created after a character was poisoned again. - Changed: At great request set cap to 100 for LOWERMANACOST. 06-06-2021, Jhobean -- Fixed: Fix illimited arrow with Tdata3=0 for bow (Issue #698). +- Fixed: Fix illimited arrow with Tdata3=0 for bow (Issue #698). 07-06-2021, Drk84 - Fixed: Dragon breath action and Throwing Action (not the Throwing skill) spam.(Issue #689). @@ -2725,12 +2725,12 @@ Must have event, even if it's empty, since it's applied by the source to generat Argo: The weapon/shield used for parry, if any. Local.ParryChance: The chance to parry the blow. The chance will be passed to the SkillUseQuick/UseQuick triggers. Local.ParrySkillID: The skill used for parrying, this can be changed by setting the appropriate skill ID. - Example by setting local.ParrySkillID = the parrying check will be made by using the weapon skill and thus the UseQuick trigger of the weapon + Example by setting local.ParrySkillID = the parrying check will be made by using the weapon skill and thus the UseQuick trigger of the weapon skill will be called. Local.ItemParryDamageChance: The chance that the parrying item will be damaged (default 100%). Local.Damage: The amount of damage (raw) before the parry reduction. Return 1: Completely blocks the damage as if the parry damage reduction was set to 100. - + 10-06-2021, Drk84 - Fixed: Damage was always reduced by parry even if the character failed the parry action. @@ -2767,7 +2767,7 @@ Must have event, even if it's empty, since it's applied by the source to generat - Fixed: Pets are now able again to damage their own owner, this wasn't noticeable unless the COMBAT_NOPETDESERT flag was enabled (Issue #724). 01-08-2021, jkachhad -- Added: New dialog section "prebutton" - Provides input processing before button triggers. This section can be utilized to check if the input is valid (obj in range, line of sight, etc..). This should reduce amount of code in each button trigger. +- Added: New dialog section "prebutton" - Provides input processing before button triggers. This section can be utilized to check if the input is valid (obj in range, line of sight, etc..). This should reduce amount of code in each button trigger. Example: [dialog d_test] ... @@ -2816,15 +2816,15 @@ It will check if distance is > 10, so you don't need to write the distance check // 0 --> Ignore this, stop hardcoded behavior but generate a log message // 1 --> Ignore this, stop hardcoded behavior and do not generate a log message // 2 --> Disconnect client and generate log message (default behavior) - - + + 13-09-2021, Drk84 -Fixed: Failing the smelting action consumed the whole ore/item stack instead of a random amount (a value between 1 and half the stack size). -Added: ARGN3 in the @ItemSmelt/@Smelt trigger, setting it to 1 will skip the minimum requirement for smelting ore to ingots. By default the minimum requirement for smelting is stored in the TDATA1 property of the ingot itemdef. 05-10-2021, Jhobean -- Added: Ini setting ManaLossAbort / ReagentLossAbort, to make the char lose part of the mana/reag required to cast the spell if the spell is abort. +- Added: Ini setting ManaLossAbort / ReagentLossAbort, to make the char lose part of the mana/reag required to cast the spell if the spell is abort. ManaLossFail / ReagentLossFail are now use only when the spell fizzle because of lacking skill. (Issue #769) - Fixed: ManaLossAbort/ManaLossFail consume the exact amount of mana required for the spell. ManaLossPercent will now control the % of mana player lost. - Fixed: ARGN1 value from @Hit trigger was sometime negative even if negative damage was impossible @@ -2832,23 +2832,23 @@ It will check if distance is > 10, so you don't need to write the distance check 02-11-2021, Drk84 - Changed: In old clients (starting from 2.0.0 ) the "You are frozen and can not move." will appear again when a player is paralyzed and attempting to move, thanks for a1exp for the solution.(Issue #789) - Recent clients (i think from 6.0.0+) seems to block the movement request when the statf_frozen flag is applied, while clients 5.0.0+ will get the "You are frozen and can not move." - message if attempting to use the pathfinding. + Recent clients (i think from 6.0.0+) seems to block the movement request when the statf_frozen flag is applied, while clients 5.0.0+ will get the "You are frozen and can not move." + message if attempting to use the pathfinding. - Changed: Mana Drain behaviour slightly changed: (Issue #781) 1)It will use its own layer (LAYER_SPELL_MANA_DRAIN, layer number 79) instead of LAYER_SPELL_STATS, in this way casting Mana Drain will not remove stat spells on the affected characters. 2)The Mana Drain effect value depends if you have enabled MAGICF_OSIFORMULAS or not. - If enabled the effect value is calculated from this formula: (400 + Caster Eval Int - Target Magic Resistance ) / 10. + If enabled the effect value is calculated from this formula: (400 + Caster Eval Int - Target Magic Resistance ) / 10. If disabled the effect value is by default 10,30, only in this case you can overwrite the effect of Mana Drain by changing the value in the SPELLDEF or by changing local.effect in the @SpellEffect/@Effect triggers, same for duration (default duration is 5 seconds). 3)In both cases the mana lost is temporary! If you want to get a permanent drain effect just use the following script under the Mana Drain SPELLDEF - + ON=@EffectRemove return 0 //Return 0 will prevent the recover mana behaviour of the Mana Spell but the Mana Drain spell item will still be destroyed. - Remember to update your scripts by changing - In spells_magery.scp file: - LAYER = LAYER_SPELL_STATS to LAYER = LAYER_SPELL_MANA_DRAIN + Remember to update your scripts by changing + In spells_magery.scp file: + LAYER = LAYER_SPELL_STATS to LAYER = LAYER_SPELL_MANA_DRAIN In your defs.scp file: LAYER_SPELL_Mana_Drain 79 - + 03-11-2021, Drk84 - Fixed: MT_NONMOVER flag makes the NPCs spam attacks. (Issue #767) - Updated SphereCrypt.ini. @@ -2932,7 +2932,7 @@ Also excluded GMs from most checks inside this code. 12-02-2022, Soulless - Changed: 'StaminaLossOverweightMultiplier' to 'StaminaLossOverweight', and changed the value for additional increase of stamina loss from every - 25 stones to every 5 stones. + 25 stones to every 5 stones. 13-02-2022, XuN - Fixed players being able to cast spells on items inside their bankbox when they are not supossed to reach it. @@ -2957,7 +2957,7 @@ Note: The only way the server has to know if the bankself is closed is to store 09-06-2022, Drk84 - Fixed: NPCs canceling other npcs from hearing player speech (Issue #859). -- Fixed: Undefined keyword 'shipturnleft' (Issue #860). +- Fixed: Undefined keyword 'shipturnleft' (Issue #860). 25-06-2022, Drk84 - Fixed: Screensize.Y returning wrong value with clients > 5.00.00 (Issue #630). @@ -2966,7 +2966,7 @@ Note: The only way the server has to know if the bankself is closed is to store 30-06-2022, Drk84 - Fixed: Summoned ridden creatures did not disappear when the Summon Spell timer expired until they were dismounted (Issue #582). -- Fixed: When using Elemental Resistance setting, Protection spell with the SPELLFLAG_SCRIPT still called the default Protection behaviour when resisting a spell. +- Fixed: When using Elemental Resistance setting, Protection spell with the SPELLFLAG_SCRIPT still called the default Protection behaviour when resisting a spell. 08-07-2022, Drk84 - Fixed: Ugly workaround for the ONAME property bug (Issue #825). @@ -2986,7 +2986,7 @@ Note: The only way the server has to know if the bankself is closed is to store Notes: Only positive numbers are accepted, if ACTIONEFFECT is set to a value less than 0 Sphere will use the default value of the skill. ACTIONEFFECT has, by default, no effect in @UseQuick/@SkillUseQuick triggers - Added: ARGN2 in trigger @Start/@SkillStart, this allow to override/read the DELAY value from the skill definition (how much time it takes for the skill to be completed). - + 26-07-2022, Drk84 - Fixed: Wrong calculation in Magic Resistance check. @@ -2997,10 +2997,10 @@ Note: The only way the server has to know if the bankself is closed is to store t_armor_chain 302 t_armor_ring 303 t_talisman 304 -- Added: SPELLFLAG_FREEZEONCAST, when a player casts this spell it gets frozen, opposite to SPELLFLAG_NOFREEZEONCAST +- Added: SPELLFLAG_FREEZEONCAST, when a player casts this spell it gets frozen, opposite to SPELLFLAG_NOFREEZEONCAST Example use: freeze while opening a gate or recall). MAGICF_FREEZEONCAST must be disabled. *IMPORTANTE* You need to add the following definition under [DEFNAME spell_flags] in your scripts: - spellflag_freezeoncast 00001000 + spellflag_freezeoncast 00001000 - Added: Chars TAG, TAG.SkillMod%d being %d the SKILL ID. This allows you to increase the bonus for certaing skill instead of modifying the raw base value (just like stats affect skill bonus). Example: TAG.SkillMod10 = 100 will bonus you 10.0% in Camping (SKILL 10). If you click on "show real" in the skill menu, you won't see this bonus but the real value. Note that most of the internal calculations are used taking in account stats + tag bonus. @@ -3038,11 +3038,11 @@ Note: The only way the server has to know if the bankself is closed is to store - Changed: Skills with SKF_GATHER, @Start/@SkillStart, now ACT is the Worldgem Bit we're gathering from. 09-09-2022, Jhobean -- Added: @SendPaperdoll There 3 ways (Dclick a player, use client macro or use the context menu) to open a paperdoll and requesting the paperdoll packet (0x88). - All 3 method will initiate the trigger on the player doing the action. This trigger could be use to modify some aspect of paperdoll on custom client or +- Added: @SendPaperdoll There 3 ways (Dclick a player, use client macro or use the context menu) to open a paperdoll and requesting the paperdoll packet (0x88). + All 3 method will initiate the trigger on the player doing the action. This trigger could be use to modify some aspect of paperdoll on custom client or to detect someone looking your paperdoll. SRC = The player asking to open the paperdoll - i = The player concerned in the information packet + i = The player concerned in the information packet 19-09-2022, Tolokio -Added: Wake() is now avaible to be used by script using command: "wake". Wake function require to wake a sleeping NPC (death corpse and invisible body) @@ -3076,7 +3076,7 @@ Notes: The command syntaxis: PARTY.CREATE msg,uid(s). Where msg is 0/1 sending default sysmessages or not, the following args (up to 9 more) is/are the players to add to this party. Note that by using PARTY.CREATE the player will be forced to enter the party, so you need to add your own "acceptance system". - + 24-10-2022, Raylde/Jhobean [sphere.ini]: Added TimerCallUnit to be able use seconds instead of minutes with TimerCall @@ -3095,12 +3095,12 @@ Notes: - Changed: AR (the old armour value) property can be displayed up to 65k. (Issue #968) - Changed: The DAM property (for both characters and weapons) can be displayed up to 65k. (Issue #967) - Added: t_armor_bone typedef (value 305). If you want to use it you need to add it in the core/defs_types_hardcoded.scp file under the typedef section. (Issue #890) - + 03-12-2022, Drk84 - Fix: Deleting a player while it's in a guild cause the server to crash. (Issue #971) 06-12-2022, Drk84 -- Added [Sphere.ini] : New Sphere.ini settings "DisplayElementalResistance". (Issue #742) +- Added [Sphere.ini] : New Sphere.ini settings "DisplayElementalResistance". (Issue #742) This setting allows to display the elemental resistances (Fire, Cold, Energy, Poison but not Physical) on status bar and tooltips, even if combat flag ELEMENTAL_ENGINE is disabled. Remember that the old AC(and MODAR) system will be used if ELEMENTAL_ENGINE flag is disabled. @@ -3109,7 +3109,7 @@ Notes: - Fixed: Possible fix for disconnected characters on a movable multi being re-connected when the multi starts to move. (Issue #948) - Fix: Removed no longer needed / duplicated code. - Fix: At @redeed, return 1 no longer avoid house deletion. (Redeed can be prevented by script at house dialog, so there is no need of this feature. Also wiki says return 1 just prevent deed creation, not house deletion.) -- Fixed: Possible fix for disconnected characters on a movable multi being re-connected when the multi starts to move. (Issue #948) +- Fixed: Possible fix for disconnected characters on a movable multi being re-connected when the multi starts to move. (Issue #948) 13-12-2022, Drk84 Fixed: @Ship_Turn not firing, this trigger will fire on a T_SHIP item and for every item on the deck. (Issue #981) @@ -3126,22 +3126,22 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss Example script; on=@Ship_Stop (UID/I = Ship) TAG0.SHIPMOVE= - + Accordingly, the ship is still under multidef. on=@Ship_Move (UID/I = Ship, ARGN1 = Move Direction) TAG0.SHIPMOVE=1 // or - + You can check whether the ship is in motion or not. In addition, in @Ship_Move trigger, the direction of the ship is sent to the trigger as . @Ship_Move trigger is only triggered on successful ship movements. It will not trigger if the ship is stuck somewhere. - + 31-12-2022, Drk84 - Added: New command DIRMOVE for ships, read only, returns the direction (0..8) where the ship is going. (Issue #993) -- Added: New command DAMADJUSTED, read only, return the damage range including the various modifier (like CombatBonusStat, tactics bonus if enabled etc). +- Added: New command DAMADJUSTED, read only, return the damage range including the various modifier (like CombatBonusStat, tactics bonus if enabled etc). It pratically returns the damage range shown in the character status panel. Example: ON=@Hit argn1 = > //This recalculates the full damage including the various bonus if any. - + 01-12-2023, Drk84 - Added: New trigger @NpcActCast, this trigger is fired when an NPC is going to select which spell is going to cast. This triggers can fires multiple time if the NPC cannot cast the selected spell (for example because the spell has the SPELLFLAG_PLAYERONLY flag). @@ -3157,7 +3157,7 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss ARGN2: It's 1 if the spell is being cast by a wand, 0 otherwise, read only. Return 1: Interrupt spell selection. - Changed: If the NPC is wielding a wand, it has a 50% to use the wand instead of the a spell from the spellbook11. Remember that the wand must have the ATTR_MAGIC set. -- Fixed: Bonus from CombatBonusStat, when the stat was above 100, was not properly applied when COMBAT_DAMAGEERA was set to 2 (AOS) +- Fixed: Bonus from CombatBonusStat, when the stat was above 100, was not properly applied when COMBAT_DAMAGEERA was set to 2 (AOS) 02-01-2023, Drk84 - Added [Sphere.ini]: New Sphere.ini settings "NPCHealThreshold", this setting determines the amount of target hitpoints (in %) at which a NPC will try to heal by spell. @@ -3169,10 +3169,10 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss - Replaced: HEALTHPERCENT command is replaced by STATPERCENT.stat_key command where stat_key is STR for hits, dex for stamina and int for mana. Example: if we have 25 mana and 50 int and we do .show statpercent.int we will get 50%. - Added: CURE command, for curing poison and hallucination effect. - Example: + Example: CURE remove the poison memory item and clear the poison flag. - CURE 1 remove the poison memory item, the hallucination memory item, clear the poison and hallucination flags. - + CURE 1 remove the poison memory item, the hallucination memory item, clear the poison and hallucination flags. + 10-01-2023, Jhobean/Drk84/Luxion - Fixed: Sectors clients going negative and disconnected clients being displayed on ships. @@ -3183,7 +3183,7 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss Added and keys to catch hash/fame changes under trigger. also moved under functions to trigger with .fame 1 / .karma 5 commands. 16-01-2023, tolokio -- Added: @charShove trigger. +- Added: @charShove trigger. Fires when a char is going to step another char. src->The char being stepped Default Object->the one who step on the other. @@ -3203,7 +3203,7 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss - Changed: Added more checks to avoid TIMER being set on objects which shouldn't tick/have a timer. 07-02-2023, Jhobean -- Changed: Spawn now continues to generate Items/NPC even on sleeping sectors. If the sector is sleeping when the spawn point reaches the max amount of objects, it will finally go in the sleeping state. +- Changed: Spawn now continues to generate Items/NPC even on sleeping sectors. If the sector is sleeping when the spawn point reaches the max amount of objects, it will finally go in the sleeping state. This change will avoid the scenario in which a player enters a dungeon/region which partially spawned, even if a sufficient amount of time elapsed, because the sector went sleeping too soon. - Fixed: Dragging an item linked to a spawn point now unlinks them. This will avoid to have item dissapearing on player packpack when the spawn point is resetted and sets the spawn to continue spawning new items. - Added: 2 news triggers for Spawn objects: @@ -3224,14 +3224,14 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss -Fixed: When using ISNEARBYTPE command the last argument "CheckZ" on FindItemTypeNearby method was never used. 20-02-2023, Drk84 --(Cheap) Fixed: Spawn Keyword AT.n.xx not working properly. (Issue #1033) +-(Cheap) Fixed: Spawn Keyword AT.n.xx not working properly. (Issue #1033) 27-02-2023, Drk84 - Fixed: Wrong duration for Gate Travel Spell. (Issue #1043) - Fixed: More accurate messages when a container/hold is locked and you have/don't have the key. (Issue #1044) -- Added: New local in the @GetHit trigger: - LOCAL.SPELL: Read Only, display the spell number that damaged the character. - +- Added: New local in the @GetHit trigger: + LOCAL.SPELL: Read Only, display the spell number that damaged the character. + 02-03-2023, Drk84 - Fixed: Animation was played while out of range when COMBAT_SWING_NORANGE flag is enabled. (Issue #819) - Fixed: Spell duration was calculated two times when a spell was cast. @@ -3243,7 +3243,7 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss These locals contains the amount in % of the elemental damage inflicted, and thus they are available only with Elemental Engine enabled. 22-03-2023, Drk84 -- Fixed: Undefined item ID (it's actually Item ID 00) when adding ON=@Cancel trigger in SKILLMENU. (Issue #1056) +- Fixed: Undefined item ID (it's actually Item ID 00) when adding ON=@Cancel trigger in SKILLMENU. (Issue #1056) 07-04-2023, Drk84 - Fixed: ModMaxWeight not working on NPCs. (Issue #1061) @@ -3298,7 +3298,7 @@ Fixed: Multi type items weren't calling the @Destroy triggers when removed. (Iss - Fixed: Invalid MULTICOMPONENT and MULTILOCKDOWN tag on items throwing an exception. 13-05-2023, Drk84 -- Fixed: Timer not set when an ammunition item was placed on the ground because the strike missed. (Issue #1072) +- Fixed: Timer not set when an ammunition item was placed on the ground because the strike missed. (Issue #1072) - Fixed: Casting the SUMMON CREATURE spell while a fighting skill was active caused the spell creature ID to be resetted. (Issue #1070) 14-05-2023, Nolok @@ -3336,16 +3336,16 @@ Additionally, the problem of zig-zag issue following in the South direction has 13-09-2023, Drk84 - Fixed: Sphere.ini setting ManaLossPercent not consuming mana unless set to 100. (Issue #1096) Remember that the mana lost depends from the spell Mana base cost and eventually the LowerManaCost property if set. - + 14-09-2023, Drk84 - Fixed: Using ATTACKER.TARGET on a fighting NPC will return always -1. (Issue #1098) - + 15-09-2023, Drk84 - Fixed: Ship plank not remaining open when turning the ship around. ( Issue #1089) The ship plank will now autoclose after 5 seconds. -- Fixed: Client Linger Timer is 3600 seconds on stoned players. (Issue #1081) +- Fixed: Client Linger Timer is 3600 seconds on stoned players. (Issue #1081) -23-09-2023 / 1-10-2023 (2 commits), Jhobean +23-09-2023 / 1-10-2023 (2 commits), Jhobean - Fixed: When deleting account, f_onchar_delete was not call on char and char's item was not remove causing warning on next server boot. ( Issue #1029) - Changed: Optimisation how f_onchar_delete is launch and avoid dual launch when delete char on login screen. @@ -3395,7 +3395,7 @@ Additionally, the problem of zig-zag issue following in the South direction has - Fixed: Setting GuardsOnMurderers to 0 causes vendors to run away players and call guards. (Issue: #720) 16-10-2023, Nolok -- Fixed: Memory leaks due to the old CSPtrTypeArray and CSObjArray containers, replaced with new ones using smart pointers as wrappers. +- Fixed: Memory leaks due to the old CSPtrTypeArray and CSObjArray containers, replaced with new ones using smart pointers as wrappers. - Fixed: Possible random errors caused by reading some internal data before it was initialized during server startup (CCrypto::client_keys, holding SphereCrypt data, and ThreadHolder, holding threads data). - Fixed: CSString instances becoming invalid after calling the Clear method. @@ -3518,7 +3518,7 @@ Additionally, the problem of zig-zag issue following in the South direction has This LOCALs changes default behaviour of poisoned weapons while removing poison. LOCAL.ItemPoisonReductionChance = Is the chance that weapon reduce poison morez. (Default 100%) LOCAL.ItemPoisonReductionAmount = Is the amount of poison that weapon reduce. (Default: CurrentPoisonAmount/ 2) - + - Added: You can now use TDATA2 for setting default cooldown (delay) for drinks. (Default: 15secs for potions, 1500 secs for ales) - Changed: Reactive armor range increased from 1 to 2 in default. @@ -3779,3 +3779,26 @@ Added: 'H' shortcut for variables to get the value as hexadecimal. // ITEM_CANTIMER_IN_CONTAINER 1 // Items can hold a timer even if equipped. - Added: item CAN flag (Issue #1180): CAN_I_TIMER_CONTAINED 0x40000000 // This item (if not "sleeping") can have a timer even if inside a container (overrides global default behavior). + +21-06-2024, Nolok +- Improvement: optimized various C++ core subsystems, resulting in a significantly reduced CPU usage and (slightly) reduced startup time. +- Fixed: RAM usage always increasing during uptime. Caused by a couple of memory leaks, the greatest happening because the cached map blocks were not properly released. +- Fixed: adopted a more conservative memory allocation policy for strings, resulting in reduced RAM usage. +- Changed: I_MEMORY (ITEMID_MEMORY) items won't have anymore the ComponentProps Faction, ItemChar, Item, reducing their memory footprint. + +25-06-2024, Nolok +- Fixed: LASTUSED, LASTDISCONNECTED, TIMESTAMP being interpreted as a number with a different base (seconds or tenths of seconds or milliseconds) in different contexts. Now they expect in scripts only values in seconds. +- Fixed: crash when moving a char in an area full of teleporters, or whichever items made the char move multiple times in the same tick. + +26-06-2024, Jhobean +- Changed: @NPCRESTOCK References modification. Now SRC:Server I:NPC (Before it was always server) + +30-06-2024 (merged on 20-07-2024), DavideRei +- Fixed: hallucination spell duration (before it was fixed at 1000 seconds) +- Fixed: wrong layer checked for potion delay to set the memory name +- Modified: blocked spelleffect on ridden chars for spells with flag SPELLFLAG_FIELD or SPELLFLAG_AREA +- Fixed: NPC_Act_Follow use combat target even if the npc is not in combat +- Fixed: wrong highest threat when there are no attackers + +20-07-2024, Nolok +- Changed: When trying to set/modify a STAT to reach a value < 0, print an error and default that stat to 0. Nevertheless, remember to check for underflows or overflows in your scripts before passing the value to the core! This update is a safety net but you can expect bugs anyways if you don't check the math in the scripts. diff --git a/README.md b/README.md index 94e88a2bb..358cbc58d 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,66 @@ # SphereServer X -Ultima Online game server developed in C++ -
-[![GitHub license](https://img.shields.io/github/license/Sphereserver/Source-X?color=blue)](https://github.com/Sphereserver/Source-X/blob/master/LICENSE) -[![GitHub repo size](https://img.shields.io/github/repo-size/Sphereserver/Source-X.svg)](https://github.com/Sphereserver/Source-X/) -[![GitHub stars](https://img.shields.io/github/stars/Sphereserver/Source-X?logo=github)](https://github.com/Sphereserver/Source-X/stargazers) -
-[![GitHub issues](https://img.shields.io/github/issues/Sphereserver/Source-X.svg)](https://github.com/Sphereserver/Source-X/issues) -[![GitHub last commit](https://img.shields.io/github/last-commit/Sphereserver/Source-X.svg)](https://github.com/Sphereserver/Source-X/) +Ultima Online game server, developed in C++. +

+[![GitHub License](https://img.shields.io/github/license/Sphereserver/Source-X?color=blue)](https://github.com/Sphereserver/Source-X/blob/master/LICENSE) +    [![GitHub Repo size](https://img.shields.io/github/repo-size/Sphereserver/Source-X.svg)](https://github.com/Sphereserver/Source-X/) +    [![GitHub Stars](https://img.shields.io/github/stars/Sphereserver/Source-X?logo=github)](https://github.com/Sphereserver/Source-X/stargazers)
-[![Build](https://github.com/Sphereserver/Source-X/actions/workflows/build.yml/badge.svg)](https://github.com/Sphereserver/Source-X/actions/workflows/build.yml) -[![Build status](https://ci.appveyor.com/api/projects/status/ab152o83mipjojin?svg=true)](https://ci.appveyor.com/project/cbnolok/sphereserver-x) [![Coverity Scan Build Status](https://scan.coverity.com/projects/20225/badge.svg)](https://scan.coverity.com/projects/sphereserver-source-x) -

-Join SphereServer Discord channel! -[![Discord Shield](https://discordapp.com/api/guilds/354358315373035542/widget.png?style=shield)](https://discord.gg/ZrMTXrs) +    [![GitHub Issues](https://img.shields.io/github/issues/Sphereserver/Source-X.svg)](https://github.com/Sphereserver/Source-X/issues) +| Join the SphereServer Discord channel! | +| :---: | +| [![Discord Shield](https://discordapp.com/api/guilds/354358315373035542/widget.png?style=shield)](https://discord.gg/ZrMTXrs) | -### Download automated SphereServer builds and ScriptPack releases +## Releases -**SphereServer:** +### **Core** -+ Github Release +| Branch: Master
(most stable pre-releases) | Branch: Dev
(most recent, potentially unstable) | +| :--- | :--- | +| [![GitHub last commit on Master branch](https://img.shields.io/github/last-commit/Sphereserver/Source-X/master.svg)](https://github.com/Sphereserver/Source-X/)   Changelog | [![GitHub last commit on Dev branch](https://img.shields.io/github/last-commit/Sphereserver/Source-X/dev.svg)](https://github.com/Sphereserver/Source-X/tree/dev)   Changelog | +| **Nightly builds:** | **Nightly builds:** | +| [![Build status: Windows x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml) | [![Build status: Windows x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml) | +| [![Build status: Windows x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml) | [![Build status: Windows x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml) | +| [![Build status: Linux x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml) | [![Build status: Linux x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml) | +| [![Build status: Linux x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml) | [![Build status: Linux x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml) | +| [![Build status: MacOS x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml) | [![Build status: MacOS x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml) | +| [![Build status: MacOS ARM](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml) | [![Build status: MacOS ARM](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml) | + +**Click the badges or follow the links:** + ++ Github Nightly builds + SphereServer Website -
-**ScriptPack:** +### **ScriptPack** + The official script pack is fully compatible with X new syntax, has all the new X features and preserves legacy/classic systems, which can be activated back in place of the new ones. -It is currently being revamped to add original OSI features. -Beware, it's still not 100% complete! +
It is currently being revamped to add original OSI features. +
**Beware, it's still not 100% complete!** -+ Up to date -+ Official releases ++ Current (most updated) ++ Milestone releases -### Resources +## Resources + Scripting guide -+ Official website ++ ScriptShare ++ SphereCommunity website -#### Coming from a different SphereServer version? +### Coming from a different SphereServer version? + From 0.56d? Here a list of major scripting changes! + From an older 0.55 or 0.56 version? This might help resuming major changes until 0.56d. -## Why a fork? +## Why the fork? This branch started in 2016 from a slow and radical rework of SphereServer 0.56d, while trying to preserve script compatibility with the starting branch. Though, something has changed script-wise, so we suggest to take a look here. Most notable changes (right now) are: + Bug fixes and heavy changing of some internal behaviours, with the aim to achieve truly better **speed** and **stability**; -+ Support for x86_64 (64 bits) architecture, Mac OSX and MinGW compiler for Windows; ++ Support for x86_64 (64 bits) and ARM architectures, Mac OSX builds, Clang and GCC Linux builds, MinGW compiler for Windows; + CMake is now the standard way to generate updated build and project files; + Switched from MySQL 5.x to MariaDB client; + Added (and still adding) comments to the code to make it more understandable; @@ -62,10 +72,12 @@ Most notable changes (right now) are: ### Required libraries (Windows) + `libmariadb.dll` (MariaDB Client v10.*package), found in lib/bin/*cpu_architecture*/mariadb/libmariadb.dll +
### Required libraries (Linux) + MariaDB Client library. Get it from the following sources. +
#### From MariaDB website @@ -79,6 +91,7 @@ Install MariaDB client: `sudo apt-get install mariadb-client` or `sudo apt-get i #### CentOS - Red Hat Enterprise Linux - Fedora repositories Then install MariaDB client via yum (CentOS or RH) or dnf (Fedora): `mariadb-connector-c` +
### Required libraries (MacOS) @@ -88,7 +101,7 @@ Then install MariaDB client via yum (CentOS or RH) or dnf (Fedora): `mariadb-con ### Generating the project files -The compilation of the code is possible only using recent compilers, since C++20 features are used: newer is better. Oldest compiler versions supporting C++20: Visual Studio 2019 version 16.11, GCC 8, MinGW distributions using GCC 8, Clang version 10.
+The compilation of the code is possible only using recent compilers, since C++20 features are used: the newer the compiler, the better. Oldest compiler versions supporting C++20: Visual Studio 2019 version 16.11, GCC 8, MinGW distributions using GCC 8, Clang version 10.
You need to build Makefiles or Ninja files (and project files if you wish) with CMake for both Linux (GCC) and Windows (MSVC and MinGW).
Both 32 and 64 bits compilation are supported.
No pre-built project files included.
@@ -98,12 +111,12 @@ Does CMake give you an error? Ensure that you have Git installed, and if you are #### Toolchains and custom CMake variables -When generating project files, if you don't specify a toolchain, the CMake script will pick the 32 bits one as default.
+When generating project files, if you don't specify a toolchain, the CMake script will pick the native one as default.
How to set a toolchain: + Via CMake GUI: when configuring for the first time the project, choose "Specify toolchain file for cross-compiling", then on the next step you'll be allowed to select the toolchain file + Via CMake CLI (command line interface): pass the parameter `-DCMAKE_TOOLCHAIN_FILE="..."` - When using Unix Makefiles, you can specify a build type by setting (also this via GUI or CLI) `CMAKE_BUILD_TYPE="build"`, where build is Nightly, Debug or Release. If the build type + When using Makefiles or Ninja, you can specify a build type by setting (also this via GUI or CLI) `CMAKE_BUILD_TYPE="build"`, where build is **Nightly**, **Debug** or **Release**. If the build type was not set, by default the makefiles for all of the three build types are generated.

@@ -133,8 +146,10 @@ Building will require more packages than the ones needed to run Sphere. ##### Ubuntu and Debian Install these additional packages: -++ `sudo apt-get install git cmake`
-++ MariaDB client: `sudo apt-get install libmariadb-dev` and `libmariadb3` or `mariadb-client` (depends on the OS version) + ++ `sudo apt-get install git cmake` ++ MariaDB client: `sudo apt-get install libmariadb-dev` and `libmariadb3` or `mariadb-client` (depends on the OS version) + If you are on a 64 bits architecture but you want to compile (or execute) a 32 bits binary, you will need to install MariaDB packages adding the postfix `:i386` to each package name. @@ -223,8 +238,9 @@ if (fTrue)
## Licensing -Copyright 2023 SphereServer development team.
+Copyright 2024 SphereServer development team.
Licensed under the Apache License, Version 2.0 (the "License").
You may not use any file of this project except in compliance with the License.
You may obtain a copy of the License at + diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 88f616119..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,217 +0,0 @@ -# AppVeyor workflows for SphereServer-X. -# Used to build and upload on the website only nightlies on a new commit. -# We use Github workflows to build both nightlies and pull requests. - - -# Do not build on tags (GitHub only) -skip_tags: true - -# Do not increment the current build number after each automatic build -#pull_requests: -# do_not_increment_build_number: true - -# Branches to build -branches: - only: - - master - -# Version format -version: 1.1.0.{build} - -# Set up two building environments (two first class elements in the build matrix) -image: - - Visual Studio 2022 - - Ubuntu2004 - - macos-monterey - -# Environmental variables -environment: - # Common to all the builds of the matrix - BUILD_DIR_32: build-32 - BUILD_DIR_64: build-64 - FTP_USER: - secure: zkxiTiVr5wfmALN6TjuhfQ== - FTP_PASSWORD: - secure: C21uMb7NoO6R6ILqDeVqrY3W+6Jap5PFqLgUt65BEzU= - FTP_SERVER: - secure: ypUVoKvq2e+1urlrXABB5r5XsWuxKXWmvlJpQEe4iNw= - - -# Build scripts -for: - - - # Windows build - matrix: - only: - - image: Visual Studio 2022 - - environment: - MSBUILD_FLAGS: /verbosity:minimal /maxcpucount - CMAKE_GEN: Visual Studio 17 2022 - - install: - # Report our building tools - - msbuild /version - - cmake --version - - # Use our custom script instead of automatic MSBuild - build_script: - - mkdir %BUILD_DIR_32% %BUILD_DIR_64% - - cd "%BUILD_DIR_64%" - - - IF DEFINED APPVEYOR_PULL_REQUEST_NUMBER ( - ECHO. && ECHO ** Starting to build 64 bits Nightly version to test the Pull Request && ECHO. - ) ELSE ( - ECHO. $$ECHO ** Starting to build 64 bits Nightly version && ECHO. - ) - - cmake -G "%CMAKE_GEN%" -S ..\ -B . - - msbuild SphereServer.sln %MSBUILD_FLAGS% /p:Configuration=Nightly - - ECHO. && ECHO ** End of the compilation of the 64 bits Nightly version && ECHO. - - # if we are testing a pull request, building only a version will be sufficient, so stop this script - - IF DEFINED APPVEYOR_PULL_REQUEST_NUMBER (appveyor exit) - - - cd "..\%BUILD_DIR_32%" - - - ECHO. && ECHO ** Starting to build 32 bits Nightly version && ECHO. - - cmake -G "%CMAKE_GEN_%" -A Win32 -S ..\ -B . - - msbuild SphereServer.sln %MSBUILD_FLAGS% /p:Configuration=Nightly - - ECHO. && ECHO ** End of the compilation of the 32 bits Nightly version && ECHO. - - - cd .. - - after_build: - # if we are testing a pull request, we don't want to upload the build to the host - - IF DEFINED APPVEYOR_PULL_REQUEST_NUMBER (appveyor exit) - - - ECHO ** Compilation done. Packing the files and uploading to SphereCommunity - - mkdir accounts logs save scripts - - 7z a SphereSvrX-win64-nightly.zip accounts\ logs\ save\ scripts\ "%APPVEYOR_BUILD_FOLDER%\%BUILD_DIR_64%\bin-x86_64\Nightly\SphereSvrX64_nightly.exe" - "%APPVEYOR_BUILD_FOLDER%\src\sphere.ini" "%APPVEYOR_BUILD_FOLDER%\src\sphereCrypt.ini" "%APPVEYOR_BUILD_FOLDER%\lib\bin\x86_64\mariadb\libmariadb.dll" - - 7z a SphereSvrX-win32-nightly.zip accounts\ logs\ save\ scripts\ "%APPVEYOR_BUILD_FOLDER%\%BUILD_DIR_32%\bin-x86\Nightly\SphereSvrX32_nightly.exe" - "%APPVEYOR_BUILD_FOLDER%\src\sphere.ini" "%APPVEYOR_BUILD_FOLDER%\src\sphereCrypt.ini" "%APPVEYOR_BUILD_FOLDER%\lib\bin\x86\mariadb\libmariadb.dll" - - curl -sST "{SphereSvrX-win64-nightly.zip,SphereSvrX-win32-nightly.zip}" -u %FTP_USER%:%FTP_PASSWORD% %FTP_SERVER% - - artifacts: - - path: SphereSvrX-win64-nightly.zip - - path: SphereSvrX-win32-nightly.zip - - - - # Linux build - matrix: - only: - - image: Ubuntu - - environment: - CMAKE_GEN: Ninja - CMAKE_TCH_32: cmake/toolchains/Linux-GNU-x86.cmake - CMAKE_TCH_64: cmake/toolchains/Linux-GNU-x86_64.cmake - - install: - - lsb_release -d - - - echo && echo "** Setting up GCC compiler" && echo - - sudo add-apt-repository universe && sudo apt-get -qq update - - sudo apt install -yq --no-install-recommends curl linux-libc-dev:i386 gcc-12 gcc-12-multilib g++-12 g++-12-multilib > /dev/null - #- export CC="gcc-9" && export CXX="g++-9" - #- sudo apt install -yqq --no-install-recommends ninja-build linux-libc-dev:i386 gcc-12 gcc-12-multilib g++-12 g++-12-multilib > /dev/null - #- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 90 --slave /usr/bin/g++ g++ /usr/bin/g++-12 - - # Report our building tools - - gcc -v && cmake --version && ninja --version - - # Use our custom script - build_script: - - echo && echo "** Setting up MariaDB packages for x86_64 arch (64 bits)" && echo - - sudo apt install -yq --no-install-recommends libmariadb3 libmariadb-dev > /dev/null - - - mkdir "$BUILD_DIR_32" "$BUILD_DIR_64" - - cd "$BUILD_DIR_64" - - - |- - if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER}" ]; then - echo && echo "** Starting to build 64 bits Nightly version to test the Pull Request" && echo - else - echo && echo "** Starting to build 64 bits Nightly version" && echo - fi - - cmake -G "$CMAKE_GEN" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE="$CMAKE_TCH_64" -S ../ -B . - - ninja || exit 1 # if the build fails, stop here - - echo && echo "** End of the compilation of the 64 bits Nightly version" && echo - - # if we are testing a pull request, building only a version will be sufficient, so stop this script - - |- - if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER}" ]; then - exit 0 - fi - - cd .. - - - echo && echo "** Setting up MariaDB packages for x86 arch (32 bits)" && echo - #- sudo apt remove -yq libmariadb3 > /dev/null - - sudo apt install -yq --no-install-recommends libmariadb3:i386 libmariadb-dev:i386 > /dev/null - - - cd "$BUILD_DIR_32" - - - echo && echo "** Starting to build 32 bits Nightly version" && echo - - cmake -G "$CMAKE_GEN" -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE="$CMAKE_TCH_32" -S ../ -B . - - ninja || exit 1 # if the build fails, stop here - - echo && echo "** End of the compilation of the 32 bits Nightly version" && echo - - - cd .. - - after_build: - # if we are testing a pull request, we don't want to upload the build to the host - - |- - if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER}" ]; then - exit 0 - fi - - echo && echo "** Compilation done. Packing the files and uploading to SphereCommunity" && echo - - mkdir accounts logs save scripts - - tar -czf SphereSvrX-linux64-nightly.tar.gz accounts/ logs/ save/ scripts/ "${BUILD_DIR_64}/bin-x86_64/SphereSvrX64_nightly" -C src/ sphere.ini sphereCrypt.ini - - tar -czf SphereSvrX-linux32-nightly.tar.gz accounts/ logs/ save/ scripts/ "${BUILD_DIR_32}/bin-x86/SphereSvrX32_nightly" -C src/ sphere.ini sphereCrypt.ini - - curl -sST "{SphereSvrX-linux64-nightly.tar.gz,SphereSvrX-linux32-nightly.tar.gz}" -u ${FTP_USER}:${FTP_PASSWORD} ${FTP_SERVER} - - artifacts: - - path: SphereSvrX-linux64-nightly.tar.gz - - path: SphereSvrX-linux32-nightly.tar.gz - - - - # OSX build - matrix: - only: - - image: macos-monterey - - environment: - CMAKE_GEN: Ninja - CMAKE_TCH_64: cmake/toolchains/OSX-AppleClang-x86_64.cmake - - install: - - sw_vers - - echo && echo "** Setting up compiler" && echo - - export HOMEBREW_NO_AUTO_UPDATE=1 - - export HOMEBREW_NO_INSTALL_CLEANUP=1 - - brew install ninja mariadb-connector-c - - gcc -v && cmake --version && ninja --version - - # Use our custom script - build_script: - - echo && echo "** Starting to build 64 bits Nightly version" && echo - - mkdir -p "$BUILD_DIR_64" - - cd "$BUILD_DIR_64" - - cmake -G $CMAKE_GEN -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE="$CMAKE_TCH_64" -S ../ -B . - - ninja || exit 1 # if the build fails, stop here - - cd .. - - after_build: - # if we are testing a pull request, we don't want to upload the build to the host - - |- - if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER}" ]; then - exit 0 - fi - - echo && echo "** Compilation done. Packing the files and uploading to SphereCommunity" && echo - - mkdir accounts logs save scripts - - zip -r SphereSvrX-osx-x86_64-nightly.zip accounts/ logs/ save/ scripts/ - - zip -r SphereSvrX-osx-x86_64-nightly.zip src/sphere.ini src/sphereCrypt.ini "${BUILD_DIR_64}/bin-x86_64/*" - - curl -sST "{SphereSvrX-osx-x86_64-nightly.zip}" -u ${FTP_USER}:${FTP_PASSWORD} ${FTP_SERVER} - - artifacts: - - path: SphereSvrX-osx-x86_64-nightly.zip diff --git a/cmake/CMakeDetectArch.cmake b/cmake/CMakeDetectArch.cmake index 06fe284c5..2f0f0eec0 100644 --- a/cmake/CMakeDetectArch.cmake +++ b/cmake/CMakeDetectArch.cmake @@ -1,49 +1,153 @@ -# https://github.com/civetweb/civetweb/blob/master/cmake/DetermineTargetArchitecture.cmake - -# - Determines the target architecture of the compilation -# -# This function checks the architecture that will be built by the compiler -# and sets a variable to the architecture -# -# determine_target_architecture() -# -# - Example -# -# include(DetermineTargetArchitecture) -# determine_target_architecture(PROJECT_NAME_ARCHITECTURE) - -if(__determine_target_architecture) - return() +## +## Author: Hank Anderson +## Description: Ported from the OpenBLAS/c_check perl script. +## This is triggered by prebuild.cmake and runs before any of the code is built. +## Creates config.h and Makefile.conf. + +# Convert CMake vars into the format that OpenBLAS expects (modified for SphereServer) + +# If i'm not overriding it, use mine (host machine). +if (NOT CMAKE_SYSTEM_PROCESSOR) + set (CMAKE_SYSTEM_PROCESSOR "${CMAKE_HOST_SYSTEM_PROCESSOR}" CACHE INTERNAL "" FORCE) +endif () + +string(TOUPPER "${CMAKE_SYSTEM_NAME}" HOST_OS) +if ("${HOST_OS}" STREQUAL "WINDOWS") + set(HOST_OS WINNT CACHE INTERNAL "") +endif () + +if ("${HOST_OS}" STREQUAL "LINUX") +# check if we're building natively on Android (TERMUX) + EXECUTE_PROCESS( COMMAND uname -o COMMAND tr -d '\n' OUTPUT_VARIABLE OPERATING_SYSTEM) + if("${OPERATING_SYSTEM}" MATCHES "Android") + set(HOST_OS ANDROID CACHE INTERNAL "") + endif() endif() -set(__determine_target_architecture INCLUDED) - -function(determine_target_architecture FLAG) - if (MSVC) - if("${MSVC_C_ARCHITECTURE_ID}" STREQUAL "X86") - set(ARCH "i686") - elseif("${MSVC_C_ARCHITECTURE_ID}" STREQUAL "x64") - set(ARCH "x86_64") - elseif("${MSVC_C_ARCHITECTURE_ID}" STREQUAL "ARM") - set(ARCH "arm") - else() - message(FATAL_ERROR "Failed to determine the MSVC target architecture: ${MSVC_C_ARCHITECTURE_ID}") + +message (STATUS "Detecting target arch from SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") +message (STATUS "Host machine: ${HOST_OS}") + +if(MINGW) + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpmachine + OUTPUT_VARIABLE MINGW_TARGET_MACHINE + OUTPUT_STRIP_TRAILING_WHITESPACE) + if("${MINGW_TARGET_MACHINE}" MATCHES "amd64|x86_64|AMD64") + set(MINGW64 1) endif() +endif() + + +if (ARCH_BITS) + if ( NOT (ARCH_BITS EQUAL 64 OR ARCH_BITS EQUAL 32) ) + message(ERROR "Invalid ARCH_BITS?") + endif () +else () + if (${CMAKE_SIZEOF_VOID_P} EQUAL 8) + set (ARCH_BITS 64 CACHE INTERNAL "") + elseif (${CMAKE_SIZEOF_VOID_P} EQUAL 4) + set (ARCH_BITS 32 CACHE INTERNAL "") + else () + #set (ARCH_BITS -1) + message(ERROR "Unknown arch?") + endif () +endif () + +# Pretty thorough determination of arch. Add more if needed +if(CMAKE_CL_64 OR MINGW64) + if ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "^(aarch64.*|AARCH64.*|arm64.*|ARM64.*)") + set(ARM64 1) else() - execute_process( - COMMAND ${CMAKE_C_COMPILER} -dumpmachine - RESULT_VARIABLE RESULT - OUTPUT_VARIABLE ARCH - ERROR_QUIET - ) - if (RESULT) - message(FATAL_ERROR "Failed to determine target architecture triplet: ${RESULT}") - endif() - string(REGEX MATCH "([^-]+).*" ARCH_MATCH ${ARCH}) - if (NOT CMAKE_MATCH_1 OR NOT ARCH_MATCH) - message(FATAL_ERROR "Failed to match the target architecture triplet: ${ARCH}") - endif() - set(ARCH ${CMAKE_MATCH_1}) + set(X86_64 1) + endif() +elseif(MINGW OR (MSVC AND NOT CMAKE_CROSSCOMPILING)) + set(X86 1) +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "ppc.*|power.*|Power.*" OR ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin" AND "${CMAKE_OSX_ARCHITECTURES}" MATCHES "ppc.*")) + set(POWER 1) +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "mips64.*") + set(MIPS64 1) +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "loongarch64.*") + set(LOONGARCH64 1) +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "riscv64.*") + set(RISCV64 1) +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "amd64.*|x86_64.*|AMD64.*" OR ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin" AND "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "i686.*|i386.*|x86.*")) + if (${ARCH_BITS} EQUAL 64) + set(X86_64 1) + else () + set(X86 1) endif() - message(STATUS "Detected target architecture: ${ARCH}") - set(${FLAG} ${ARCH} PARENT_SCOPE) -endfunction() +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "i686.*|i386.*|x86.*|x86_64.*|amd64.*|AMD64.*") + if (${ARCH_BITS} EQUAL 64) + set(X86_64 1) + else () + set(X86 1) + endif() +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "^(aarch64.*|AARCH64.*|arm64.*|ARM64.*|armv8.*)") + if (${ARCH_BITS} EQUAL 64) + set(ARM64 1) + else() + set(ARM 1) + endif() +elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "^(arm.*|ARM.*)") + set(ARM 1) +elseif (CMAKE_CROSSCOMPILING) + if ("${TARGET}" STREQUAL "CORE2") + if (NOT ARCH_BITS) + set(X86 1) + elseif (${ARCH_BITS} EQUAL 64) + set(X86_64 1) + else () + set(X86 1) + endif() + elseif ("${TARGET}" STREQUAL "P5600" OR "${TARGET}" MATCHES "MIPS.*") + set(MIPS32 1) + elseif ("${TARGET}" STREQUAL "ARMV7") + set(ARM 1) + else() + set(ARM64 1) + endif () +else () + message(WARNING "Target ARCH could not be determined, got \"${CMAKE_SYSTEM_PROCESSOR}\"") +endif() + +if (NOT ARCH) + if (X86_64) + set(ARCH "x86_64" CACHE INTERNAL "") + set(ARCH_BASE "x86" CACHE INTERNAL "") + elseif(X86) + set(ARCH "x86" CACHE INTERNAL "") + set(ARCH_BASE "x86" CACHE INTERNAL "") + elseif(POWER) + set(ARCH "power" CACHE INTERNAL "") + set(ARCH_BASE "power" CACHE INTERNAL "") + elseif(MIPS32) + set(ARCH "mips" CACHE INTERNAL "") + set(ARCH_BASE "mips" CACHE INTERNAL "") + elseif(MIPS64) + set(ARCH "mips64" CACHE INTERNAL "") + set(ARCH_BASE "mips" CACHE INTERNAL "") + elseif(ARM) + set(ARCH "arm" CACHE INTERNAL "") + set(ARCH_BASE "arm" CACHE INTERNAL "") + elseif(ARM64) + set(ARCH "arm64" CACHE INTERNAL "") + set(ARCH_BASE "arm" CACHE INTERNAL "") + else() + set(ARCH ${CMAKE_SYSTEM_PROCESSOR} CACHE INTERNAL "Target Architecture") + endif () +endif () + +if (NOT ARCH_BITS) + if (X86_64 OR ARM64 OR MIPS64 OR LOONGARCH64 OR RISCV64 OR (POWER AND NOT (CMAKE_OSX_ARCHITECTURES STREQUAL "ppc"))) + set(ARCH_BITS 64 CACHE INTERNAL "") + else () + set(ARCH_BITS 32 CACHE INTERNAL "") + endif () +endif() + +if(ARCH_BITS EQUAL 64) + set(ARCH_BITS64 1 CACHE INTERNAL "") +else() + set(ARCH_BITS32 1 CACHE INTERNAL "") +endif() + +MESSAGE (STATUS "Target Arch: ${ARCH}") diff --git a/cmake/toolchains/Linux-Clang-native.cmake b/cmake/toolchains/Linux-Clang-native.cmake index 63d328044..1dff5b9af 100644 --- a/cmake/toolchains/Linux-Clang-native.cmake +++ b/cmake/toolchains/Linux-Clang-native.cmake @@ -3,15 +3,17 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Linux-Clang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Linux-Clang-native.cmake.") - SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) + + toolchain_after_project_common() IF (CMAKE_SIZEOF_VOID_P EQUAL 8) - MESSAGE (STATUS "Detected 64 bits architecture") - SET(ARCH_BITS 64 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 64 bits architecture") + #SET(ARCH_BITS 64 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native64" PARENT_SCOPE) ELSE () - MESSAGE (STATUS "Detected 32 bits architecture") - SET(ARCH_BITS 32 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 32 bits architecture") + #SET(ARCH_BITS 32 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native32" PARENT_SCOPE) ENDIF () diff --git a/cmake/toolchains/Linux-Clang-x86.cmake b/cmake/toolchains/Linux-Clang-x86.cmake index cf4de3c0c..c7c69b4e5 100644 --- a/cmake/toolchains/Linux-Clang-x86.cmake +++ b/cmake/toolchains/Linux-Clang-x86.cmake @@ -2,8 +2,14 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Linux-Clang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Linux-Clang-x86.cmake.") - SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) - SET(ARCH_BITS 32 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 32 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) + + toolchain_after_project_common() + SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-x86" PARENT_SCOPE) SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=i686 -m32" PARENT_SCOPE) diff --git a/cmake/toolchains/Linux-Clang-x86_64.cmake b/cmake/toolchains/Linux-Clang-x86_64.cmake index 6a9f30ba3..a7636ccb8 100644 --- a/cmake/toolchains/Linux-Clang-x86_64.cmake +++ b/cmake/toolchains/Linux-Clang-x86_64.cmake @@ -2,10 +2,16 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Linux-Clang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Linux-Clang-x86_64.cmake.") - SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) - SET(ARCH_BITS 64 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 64 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86_64" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) + + toolchain_after_project_common() + SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-x86_64" PARENT_SCOPE) - + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64 -m64" PARENT_SCOPE) SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64 -m64" PARENT_SCOPE) endfunction() diff --git a/cmake/toolchains/Linux-GNU-native.cmake b/cmake/toolchains/Linux-GNU-native.cmake index 05bde7c92..40a288449 100644 --- a/cmake/toolchains/Linux-GNU-native.cmake +++ b/cmake/toolchains/Linux-GNU-native.cmake @@ -3,24 +3,26 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Linux-GNU_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Linux-GNU-native.cmake.") - SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) IF (CMAKE_SIZEOF_VOID_P EQUAL 8) - MESSAGE (STATUS "Detected 64 bits architecture") - SET(ARCH_BITS 64 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 64 bits architecture") + #SET(ARCH_BITS 64 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native64" PARENT_SCOPE) ELSE () - MESSAGE (STATUS "Detected 32 bits architecture") - SET(ARCH_BITS 32 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 32 bits architecture") + #SET(ARCH_BITS 32 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native32" PARENT_SCOPE) ENDIF () + toolchain_after_project_common() + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native" PARENT_SCOPE) SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native" PARENT_SCOPE) endfunction() -function (toolchain_exe_stuff) +function (toolchain_exe_stuff) toolchain_exe_stuff_common() # Propagate global variables set in toolchain_exe_stuff_common to the upper scope diff --git a/cmake/toolchains/Linux-GNU-x86.cmake b/cmake/toolchains/Linux-GNU-x86.cmake index 4ab034f73..6b32b9504 100644 --- a/cmake/toolchains/Linux-GNU-x86.cmake +++ b/cmake/toolchains/Linux-GNU-x86.cmake @@ -2,8 +2,13 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Linux-GNU_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Linux-GNU-x86.cmake.") - SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) - SET(ARCH_BITS 32 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 32 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) + + toolchain_after_project_common() SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-x86" PARENT_SCOPE) diff --git a/cmake/toolchains/Linux-GNU-x86_64.cmake b/cmake/toolchains/Linux-GNU-x86_64.cmake index 43c0348ee..1d6afcaa7 100644 --- a/cmake/toolchains/Linux-GNU-x86_64.cmake +++ b/cmake/toolchains/Linux-GNU-x86_64.cmake @@ -2,8 +2,13 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Linux-GNU_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Linux-GNU-x86_64.cmake.") - SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) - SET(ARCH_BITS 64 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Linux" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 64 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86_64" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) + + toolchain_after_project_common() SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-x86_64" PARENT_SCOPE) diff --git a/cmake/toolchains/OSX-AppleClang-native.cmake b/cmake/toolchains/OSX-AppleClang-native.cmake index b7c20a2d1..2960b5ba5 100644 --- a/cmake/toolchains/OSX-AppleClang-native.cmake +++ b/cmake/toolchains/OSX-AppleClang-native.cmake @@ -2,15 +2,17 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/OSX-AppleClang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: OSX-AppleClang-native.cmake.") - SET(CMAKE_SYSTEM_NAME "OSX" PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "OSX" PARENT_SCOPE) # Darwin + + toolchain_after_project_common() IF (CMAKE_SIZEOF_VOID_P EQUAL 8) - MESSAGE (STATUS "Detected 64 bits architecture") - SET(ARCH_BITS 64 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 64 bits architecture") + #SET(ARCH_BITS 64 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native64" PARENT_SCOPE) ELSE (CMAKE_SIZEOF_VOID_P EQUAL 8) - MESSAGE (STATUS "Detected 32 bits architecture") - SET(ARCH_BITS 32 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 32 bits architecture") + #SET(ARCH_BITS 32 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native32" PARENT_SCOPE) ENDIF (CMAKE_SIZEOF_VOID_P EQUAL 8) diff --git a/cmake/toolchains/OSX-AppleClang-x86_64.cmake b/cmake/toolchains/OSX-AppleClang-x86_64.cmake index ee2d8515b..a21f849ef 100644 --- a/cmake/toolchains/OSX-AppleClang-x86_64.cmake +++ b/cmake/toolchains/OSX-AppleClang-x86_64.cmake @@ -2,8 +2,13 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/OSX-AppleClang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: OSX-AppleClang-x86_64.cmake.") - SET(CMAKE_SYSTEM_NAME "OSX" PARENT_SCOPE) - SET(ARCH_BITS 64 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "OSX" PARENT_SCOPE) # Darwin + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 64 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86_64" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) + + toolchain_after_project_common() SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-x86_64" PARENT_SCOPE) @@ -14,7 +19,7 @@ endfunction() function (toolchain_exe_stuff) toolchain_exe_stuff_common() - + # Propagate global variables set in toolchain_exe_stuff_common to the upper scope #SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_ARCH_OPTS}" PARENT_SCOPE) #SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_ARCH_OPTS}" PARENT_SCOPE) diff --git a/cmake/toolchains/Windows-Clang-native.cmake b/cmake/toolchains/Windows-Clang-native.cmake index cb0c80d45..298fd8449 100644 --- a/cmake/toolchains/Windows-Clang-native.cmake +++ b/cmake/toolchains/Windows-Clang-native.cmake @@ -3,19 +3,19 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Windows-Clang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Windows-Clang-native.cmake.") - SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) toolchain_after_project_common() # To enable RC language, to compile Windows Resource files IF (CMAKE_SIZEOF_VOID_P EQUAL 8) - MESSAGE (STATUS "Detected 64 bits architecture") - SET(ARCH_BITS 64 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 64 bits architecture") + #SET(ARCH_BITS 64 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native64" PARENT_SCOPE) LINK_DIRECTORIES ("lib/_bin/x86_64/mariadb/") ELSE (CMAKE_SIZEOF_VOID_P EQUAL 8) MESSAGE (STATUS "Detected 32 bits architecture") - SET(ARCH_BITS 32 PARENT_SCOPE) - SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native32" PARENT_SCOPE) + #SET(ARCH_BITS 32 CACHE INTERNAL) # override + #SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native32" PARENT_SCOPE) LINK_DIRECTORIES ("lib/_bin/x86/mariadb/") ENDIF (CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -31,9 +31,9 @@ function (toolchain_after_project) endfunction() -function (toolchain_exe_stuff) +function (toolchain_exe_stuff) toolchain_exe_stuff_common() - + # Propagate global variables set in toolchain_exe_stuff_common to the upper scope #SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_ARCH_OPTS}" PARENT_SCOPE) #SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_ARCH_OPTS}" PARENT_SCOPE) diff --git a/cmake/toolchains/Windows-Clang-x86.cmake b/cmake/toolchains/Windows-Clang-x86.cmake index 670065b64..3bf957ce5 100644 --- a/cmake/toolchains/Windows-Clang-x86.cmake +++ b/cmake/toolchains/Windows-Clang-x86.cmake @@ -2,8 +2,11 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Windows-Clang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Windows-Clang-x86.cmake.") - SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) - SET(ARCH_BITS 32 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 32 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) toolchain_after_project_common() # To enable RC language, to compile Windows Resource files @@ -30,5 +33,5 @@ function (toolchain_exe_stuff) SET_TARGET_PROPERTIES(spheresvr PROPERTIES RUNTIME_OUTPUT_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release" ) SET_TARGET_PROPERTIES(spheresvr PROPERTIES RUNTIME_OUTPUT_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug" ) SET_TARGET_PROPERTIES(spheresvr PROPERTIES RUNTIME_OUTPUT_NIGHTLY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Nightly" ) - + endfunction() diff --git a/cmake/toolchains/Windows-Clang-x86_64.cmake b/cmake/toolchains/Windows-Clang-x86_64.cmake index 59e4a2928..7d360d96d 100644 --- a/cmake/toolchains/Windows-Clang-x86_64.cmake +++ b/cmake/toolchains/Windows-Clang-x86_64.cmake @@ -2,8 +2,11 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Windows-Clang_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Windows-Clang-x86_64.cmake.") - SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) - SET(ARCH_BITS 64 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 64 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86_64" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) toolchain_after_project_common() # To enable RC language, to compile Windows Resource files diff --git a/cmake/toolchains/Windows-GNU-native.cmake b/cmake/toolchains/Windows-GNU-native.cmake index f88343155..84eb79e36 100644 --- a/cmake/toolchains/Windows-GNU-native.cmake +++ b/cmake/toolchains/Windows-GNU-native.cmake @@ -3,20 +3,20 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Windows-GNU_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Windows-GNU-native.cmake.") - SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) IF (CMAKE_SIZEOF_VOID_P EQUAL 8) - MESSAGE (STATUS "Detected 64 bits architecture") - SET(ARCH_BITS 64 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 64 bits architecture") + #SET(ARCH_BITS 64 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native64" PARENT_SCOPE) LINK_DIRECTORIES ("lib/_bin/x86_64/mariadb/") ELSE (CMAKE_SIZEOF_VOID_P EQUAL 8) - MESSAGE (STATUS "Detected 32 bits architecture") - SET(ARCH_BITS 32 PARENT_SCOPE) + #MESSAGE (STATUS "Detected 32 bits architecture") + #SET(ARCH_BITS 32 CACHE INTERNAL) # override SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin-native32" PARENT_SCOPE) LINK_DIRECTORIES ("lib/_bin/x86/mariadb/") ENDIF (CMAKE_SIZEOF_VOID_P EQUAL 8) - + toolchain_after_project_common() # To enable RC language, to compile Windows Resource files SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native" PARENT_SCOPE) @@ -24,9 +24,9 @@ function (toolchain_after_project) endfunction() -function (toolchain_exe_stuff) +function (toolchain_exe_stuff) toolchain_exe_stuff_common() - + # Propagate global variables set in toolchain_exe_stuff_common to the upper scope #SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_ARCH_OPTS}" PARENT_SCOPE) #SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_ARCH_OPTS}" PARENT_SCOPE) diff --git a/cmake/toolchains/Windows-GNU-x86.cmake b/cmake/toolchains/Windows-GNU-x86.cmake index 2cca08d4f..28f3a1aa7 100644 --- a/cmake/toolchains/Windows-GNU-x86.cmake +++ b/cmake/toolchains/Windows-GNU-x86.cmake @@ -2,10 +2,13 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Windows-GNU_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Windows-GNU-x86.cmake.") - SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) - SET(ARCH_BITS 32 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 32 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) - toolchain_after_project_common() # To enable RC language, to compile Windows Resource files + toolchain_after_project_common() # Also to enable RC language, to compile Windows Resource files LINK_DIRECTORIES ("lib/_bin/x86/mariadb/") SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin_x86" PARENT_SCOPE) @@ -16,7 +19,7 @@ function (toolchain_after_project) endfunction() -function (toolchain_exe_stuff) +function (toolchain_exe_stuff) toolchain_exe_stuff_common() # Propagate global variables set in toolchain_exe_stuff_common to the upper scope @@ -24,5 +27,5 @@ function (toolchain_exe_stuff) #SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_ARCH_OPTS}" PARENT_SCOPE) #SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE) #SET (CMAKE_RC_FLAGS "${RC_FLAGS}" PARENT_SCOPE) - + endfunction() diff --git a/cmake/toolchains/Windows-GNU-x86_64.cmake b/cmake/toolchains/Windows-GNU-x86_64.cmake index 845df5368..2c1ce7b94 100644 --- a/cmake/toolchains/Windows-GNU-x86_64.cmake +++ b/cmake/toolchains/Windows-GNU-x86_64.cmake @@ -2,8 +2,11 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/include/Windows-GNU_common.inc.cmake") function (toolchain_after_project) MESSAGE (STATUS "Toolchain: Windows-GNU-x86_64.cmake.") - SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) - SET(ARCH_BITS 64 PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) + SET(ARCH_BASE "x86" CACHE INTERNAL "" FORCE) # override + SET(ARCH_BITS 64 CACHE INTERNAL "" FORCE) # override + SET(ARCH "x86_64" CACHE INTERNAL "" FORCE) # override + SET(CMAKE_SYSTEM_PROCESSOR "${ARCH}" CACHE INTERNAL "" FORCE) toolchain_after_project_common() # To enable RC language, to compile Windows Resource files diff --git a/cmake/toolchains/Windows-MSVC.cmake b/cmake/toolchains/Windows-MSVC.cmake index 356e04f85..29b032cb1 100644 --- a/cmake/toolchains/Windows-MSVC.cmake +++ b/cmake/toolchains/Windows-MSVC.cmake @@ -6,20 +6,32 @@ function (toolchain_force_compiler) #SET (CMAKE_CXX_COMPILER "...cl.exe" CACHE STRING "C++ compiler" FORCE) MESSAGE (STATUS "Toolchain: Windows-MSVC.cmake.") - SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) + #SET(CMAKE_SYSTEM_NAME "Windows" PARENT_SCOPE) endfunction () function (toolchain_after_project) - +#[[ IF (CMAKE_SIZEOF_VOID_P EQUAL 8) MESSAGE (STATUS "Detected 64 bits architecture") - SET(ARCH_BITS 64 PARENT_SCOPE) + SET(ARCH_BITS 64 CACHE INTERNAL "" FORCE) # override ELSE () MESSAGE (STATUS "Detected 32 bits architecture") - SET(ARCH_BITS 32 PARENT_SCOPE) + SET(ARCH_BITS 32 CACHE INTERNAL "" FORCE) # override ENDIF () - +]] + if (NOT CMAKE_VS_PLATFORM_NAME) + set (CMAKE_VS_PLATFORM_NAME "${CMAKE_VS_PLATFORM_NAME_DEFAULT}") + endif () + if (${CMAKE_VS_PLATFORM_NAME} STREQUAL "Win32") + set (CMAKE_SYSTEM_PROCESSOR "x86" CACHE INTERNAL "" FORCE) + endif () + set (CMAKE_SYSTEM_PROCESSOR "${CMAKE_VS_PLATFORM_NAME}" CACHE INTERNAL "" FORCE) + + include ("${CMAKE_SOURCE_DIR}/cmake/CMakeDetectArch.cmake") + + MESSAGE (STATUS "Target Arch: ${ARCH}") + MESSAGE (STATUS "Generating for MSVC platform ${CMAKE_VS_PLATFORM_NAME}.") endfunction() @@ -34,14 +46,13 @@ function (toolchain_exe_stuff) ENDIF () - #-- Configure the Windows application type. + #-- Configure the Windows application type and add global linker flags. - SET (EXE_LINKER_EXTRA "") IF (${WIN32_SPAWN_CONSOLE}) - SET (EXE_LINKER_EXTRA ${EXE_LINKER_EXTRA} /SUBSYSTEM:CONSOLE /ENTRY:WinMainCRTStartup) + add_link_options ("LINKER:/ENTRY:WinMainCRTStartup") # Handled by is_win32_app_linker -> "LINKER:/SUBSYSTEM:CONSOLE" SET (PREPROCESSOR_DEFS_EXTRA _WINDOWS_CONSOLE) - ELSE () - SET (EXE_LINKER_EXTRA ${EXE_LINKER_EXTRA} /SUBSYSTEM:WINDOWS) + #ELSE () + # add_link_options ("LINKER:/ENTRY:WinMainCRTStartup") # Handled by is_win32_app_linker -> "LINKER: /SUBSYSTEM:WINDOWS" ENDIF () @@ -105,12 +116,15 @@ function (toolchain_exe_stuff) target_compile_options(spheresvr PRIVATE ${cxx_compiler_flags_common} - $<$: $,/MT,/MD> /EHsc /GL /GA /Gw /Gy /GF /GR- $,/O1 /Zi,/O2>> - $<$: $,/MT,/MD> /EHa /GL /GA /Gw /Gy /GF $,/O1 /Zi,/O2>> - $<$: $,/MTd,/MDd> /EHsc /Oy- /MDd /ob1 /Od $,/Zi,/ZI>> #/Gs + $<$: $,/MT,/MD> /EHsc /Oy /GL /GA /Gw /Gy /GF /GR- $,/O1 /Zi,/O2>> + $<$: $,/MT,/MD> /EHa /Oy /GL /GA /Gw /Gy /GF $,/O1 /Zi,/O2>> + $<$: $,/MTd,/MDd> /EHsc /Oy- /MDd /ob1 /Od $,/Zi,/ZI>> #/Gs # ASan (and compilation for ARM arch) doesn't support edit and continue option (ZI) ) + if ("${ARCH}" STREQUAL "x86_64") + target_compile_options(spheresvr PRIVATE /arch:SSE2) + endif () #-- Apply linker flags. @@ -129,7 +143,7 @@ function (toolchain_exe_stuff) #-- Windows libraries to link against. TARGET_LINK_LIBRARIES ( spheresvr PRIVATE ws2_32 libmariadb ) - + #-- Set define macros. diff --git a/cmake/toolchains/include/Linux-Clang_common.inc.cmake b/cmake/toolchains/include/Linux-Clang_common.inc.cmake index b53091c92..0cf3452a2 100644 --- a/cmake/toolchains/include/Linux-Clang_common.inc.cmake +++ b/cmake/toolchains/include/Linux-Clang_common.inc.cmake @@ -5,6 +5,9 @@ function (toolchain_force_compiler) SET (CMAKE_CXX_COMPILER "clang++" CACHE STRING "C++ compiler" FORCE) endfunction () +function (toolchain_after_project_common) + include ("${CMAKE_SOURCE_DIR}/cmake/CMakeDetectArch.cmake") +endfunction () function (toolchain_exe_stuff_common) @@ -51,29 +54,45 @@ function (toolchain_exe_stuff_common) IF (${USE_ASAN}) - SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=address -fno-sanitize-recover=address -fsanitize-address-use-after-scope) - set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=address>) + SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} # -fsanitize=safe-stack # Can't be used with asan! + -fsanitize=address -fno-sanitize-recover=address #-fsanitize-cfi # cfi: control flow integrity + -fsanitize-address-use-after-scope -fsanitize=pointer-compare -fsanitize=pointer-subtract + # Flags for additional instrumentation not strictly needing asan to be enabled + -fcf-protection=full -fstack-check -fstack-protector-all -fstack-clash-protection + # Not supported by Clang, but supported by GCC + #-fvtable-verify=preinit -fharden-control-flow-redundancy -fhardcfr-check-exceptions + # Other + #-fsanitize-trap=all + ) + set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=address) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} ADDRESS_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_MSAN}) MESSAGE (WARNING "You have enabled MSAN. Make sure you do know what you are doing. It doesn't work out of the box. \ See comments in the toolchain and: https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo") SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=memory -fsanitize-memory-track-origins -fPIE) - set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=memory>) + set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=memory) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} MEMORY_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_LSAN}) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=leak) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=leak>) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} LEAK_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_UBSAN}) SET (UBSAN_FLAGS - -fsanitize=undefined,shift,integer-divide-by-zero,vla-bound,null,signed-integer-overflow,bounds - -fsanitize=float-divide-by-zero,float-cast-overflow,pointer-overflow,unreachable,nonnull-attribute,returns-nonnull-attribute - -fno-sanitize=enum) + -fsanitize=undefined,float-divide-by-zero,local-bounds + -fno-sanitize=enum + # Supported? + -fsanitize=unsigned-integer-overflow #Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. + -fsanitize=implicit-conversion + ) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} ${UBSAN_FLAGS} -fsanitize=return,vptr) - set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=undefined>) + set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=undefined) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} UNDEFINED_BEHAVIOR_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () @@ -88,9 +107,11 @@ See comments in the toolchain and: https://github.com/google/sanitizers/wiki/Mem set (cxx_local_opts_warnings -Wall -Wextra -Wno-unknown-pragmas -Wno-switch -Wno-implicit-fallthrough -Wno-parentheses -Wno-misleading-indentation -Wno-conversion-null -Wno-unused-result - # clang-specific: - -Wno-format-security - ) + -Wno-format-security # TODO: disable that when we'll have time to fix every printf format issue + + # clang -specific: + # -fforce-emit-vtables + ) set (cxx_local_opts -std=c++20 -pthread -fexceptions -fnon-call-exceptions -pipe -ffast-math @@ -119,17 +140,17 @@ See comments in the toolchain and: https://github.com/google/sanitizers/wiki/Mem SET (COMPILE_OPTIONS_EXTRA -fno-omit-frame-pointer -fno-inline) ENDIF () IF (TARGET spheresvr_release) - TARGET_COMPILE_OPTIONS ( spheresvr_release PUBLIC -s -O3 ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_release PUBLIC -O3 -flto=full -fvirtual-function-elimination ${COMPILE_OPTIONS_EXTRA}) ENDIF () IF (TARGET spheresvr_nightly) IF (ENABLED_SANITIZER) TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC -ggdb3 -O1 ${COMPILE_OPTIONS_EXTRA}) ELSE () - TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC -O3 ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC -O3 -flto=full -fvirtual-function-elimination ${COMPILE_OPTIONS_EXTRA}) ENDIF () ENDIF () IF (TARGET spheresvr_debug) - TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC -ggdb3 -Og ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC -ggdb3 -O1 ${COMPILE_OPTIONS_EXTRA}) ENDIF () diff --git a/cmake/toolchains/include/Linux-GNU_common.inc.cmake b/cmake/toolchains/include/Linux-GNU_common.inc.cmake index 753e416d6..bdc27ced5 100644 --- a/cmake/toolchains/include/Linux-GNU_common.inc.cmake +++ b/cmake/toolchains/include/Linux-GNU_common.inc.cmake @@ -5,6 +5,9 @@ function (toolchain_force_compiler) SET (CMAKE_CXX_COMPILER "g++" CACHE STRING "C++ compiler" FORCE) endfunction () +function (toolchain_after_project_common) + include ("${CMAKE_SOURCE_DIR}/cmake/CMakeDetectArch.cmake") +endfunction () function (toolchain_exe_stuff_common) @@ -48,8 +51,20 @@ function (toolchain_exe_stuff_common) SET (ENABLED_SANITIZER false) IF (${USE_ASAN}) - SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=address -fno-sanitize-recover=address -fsanitize-address-use-after-scope) + SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} + -fsanitize=address -fno-sanitize-recover=address #-fsanitize-cfi # cfi: control flow integrity, not supported by GCC + -fsanitize-address-use-after-scope -fsanitize=pointer-compare -fsanitize=pointer-subtract + # Flags for additional instrumentation not strictly needing asan to be enabled + #-fvtable-verify=preinit # This causes a GCC internal error! Tested with 13.2.0 + -fstack-check -fstack-protector-all + -fcf-protection=full + # GCC 14? + #-fharden-control-flow-redundancy -fhardcfr-check-exceptions + # Other + #-fsanitize-trap=all + ) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=address $<$:-static-libasan>) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} ADDRESS_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_MSAN}) @@ -57,21 +72,26 @@ function (toolchain_exe_stuff_common) SET (USE_MSAN false) #SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=memory -fsanitize-memory-track-origins=2 -fPIE) #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=memory )#$<$:-static-libmsan>) + #SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} MEMORY_SANITIZER) #SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_LSAN}) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=leak) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=leak $<$:-static-liblsan>) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} LEAK_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_UBSAN}) SET (UBSAN_FLAGS - -fsanitize=undefined,shift,integer-divide-by-zero,vla-bound,null,signed-integer-overflow,bounds-strict - -fsanitize=float-divide-by-zero,float-cast-overflow,pointer-overflow,unreachable,nonnull-attribute,returns-nonnull-attribute + -fsanitize=undefined,float-divide-by-zero -fno-sanitize=enum + # Unsupported (yet?) by GCC 13 + #-fsanitize=unsigned-integer-overflow #Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. + #-fsanitize=implicit-conversion, local-bounds ) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} ${UBSAN_FLAGS} -fsanitize=return,vptr) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=undefined $<$:-static-libubsan>) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} UNDEFINED_BEHAVIOR_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () @@ -84,7 +104,8 @@ function (toolchain_exe_stuff_common) set (cxx_local_opts_warnings -Wall -Wextra -Wno-nonnull-compare -Wno-unknown-pragmas -Wno-switch -Wno-implicit-fallthrough - -Wno-parentheses -Wno-format-security -Wno-misleading-indentation -Wno-conversion-null -Wno-unused-result + -Wno-parentheses -Wno-misleading-indentation -Wno-conversion-null -Wno-unused-result + -Wno-format-security # TODO: disable that when we'll have time to fix every printf format issue ) set (cxx_local_opts -std=c++20 -pthread -fexceptions -fnon-call-exceptions @@ -111,7 +132,7 @@ function (toolchain_exe_stuff_common) ENDIF () ENDIF () IF (TARGET spheresvr_debug) - TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC -ggdb3 -Og ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC -ggdb3 -O1 ${COMPILE_OPTIONS_EXTRA}) ENDIF () diff --git a/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake b/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake index 121373e02..d37042fc8 100644 --- a/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake +++ b/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake @@ -8,6 +8,9 @@ function (toolchain_force_compiler) set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON") endfunction () +function (toolchain_after_project_common) + include ("${CMAKE_SOURCE_DIR}/cmake/CMakeDetectArch.cmake") +endfunction () function (toolchain_exe_stuff_common) @@ -37,30 +40,46 @@ function (toolchain_exe_stuff_common) # -static-libsan Statically link the sanitizer runtime (Not supported for ASan, TSan or UBSan on darwin) IF (${USE_ASAN}) - SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=address -fno-sanitize-recover=address -fsanitize-address-use-after-scope) + SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} # -fsanitize=safe-stack # Can't be used with asan! + -fsanitize=address -fno-sanitize-recover=address #-fsanitize-cfi # cfi: control flow integrity + -fsanitize-address-use-after-scope -fsanitize=pointer-compare -fsanitize=pointer-subtract + # Flags for additional instrumentation not strictly needing asan to be enabled + -fcf-protection=full -fstack-check -fstack-protector-all -fstack-clash-protection + # Not supported by Clang, but supported by GCC + #-fvtable-verify=preinit -fharden-control-flow-redundancy -fhardcfr-check-exceptions + # Other + #-fsanitize-trap=all + ) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=address ) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} ADDRESS_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_MSAN}) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=memory -fsanitize-memory-track-origins=2 -fPIE) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=memory ) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} MEMORY_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_LSAN}) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=leak) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=leak ) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} LEAK_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_UBSAN}) - SET (UBSAN_FLAGS - -fsanitize=undefined,shift,integer-divide-by-zero,vla-bound,null,signed-integer-overflow,bounds - -fsanitize=float-divide-by-zero,float-cast-overflow,pointer-overflow,unreachable,nonnull-attribute,returns-nonnull-attribute - -fno-sanitize=enum - ) + SET (UBSAN_FLAGS + -fsanitize=undefined,float-divide-by-zero,local-bounds + -fno-sanitize=enum + # Supported? + -fsanitize=unsigned-integer-overflow #Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. + -fsanitize=implicit-conversion + ) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} ${UBSAN_FLAGS} -fsanitize=return,vptr) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=undefined) + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} UNDEFINED_BEHAVIOR_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () + IF (${ENABLED_SANITIZER}) SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} _SANITIZERS) set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} $<$:-static-libsan>) @@ -72,8 +91,8 @@ function (toolchain_exe_stuff_common) set (cxx_local_opts_warnings -Wall -Wextra -Wno-unknown-pragmas -Wno-switch -Wno-implicit-fallthrough -Wno-parentheses -Wno-misleading-indentation -Wno-conversion-null -Wno-unused-result + -Wno-format-security # TODO: disable that when we'll have time to fix every printf format issue # clang-specific: - -Wno-format-security -Wno-deprecated-declarations # spams so much warnings for the use of sprintf ) set (cxx_local_opts diff --git a/cmake/toolchains/include/Windows-Clang_common.inc.cmake b/cmake/toolchains/include/Windows-Clang_common.inc.cmake index d426173ab..bdb48a40b 100644 --- a/cmake/toolchains/include/Windows-Clang_common.inc.cmake +++ b/cmake/toolchains/include/Windows-Clang_common.inc.cmake @@ -18,26 +18,23 @@ endfunction () function (toolchain_after_project_common) ENABLE_LANGUAGE(RC) + include ("${CMAKE_SOURCE_DIR}/cmake/CMakeDetectArch.cmake") endfunction () function (toolchain_exe_stuff_common) - #-- Configure the linker and the Windows application type. + #-- Configure the Windows application type and add global linker flags. - SET (EXE_LINKER_EXTRA "") - - IF (CLANG_USE_GCC_LINKER) - SET (CLANG_SUBSYSTEM_PREFIX --entry=WinMainCRTStartup -m) # --entry might not work - ELSE () - SET (CLANG_SUBSYSTEM_PREFIX -Xlinker /ENTRY:WinMainCRTStartup -Xlinker /subsystem:) - ENDIF() IF (${WIN32_SPAWN_CONSOLE}) - SET (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} ${CLANG_SUBSYSTEM_PREFIX}console) SET (PREPROCESSOR_DEFS_EXTRA _WINDOWS_CONSOLE) - ELSE () - SET (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} ${CLANG_SUBSYSTEM_PREFIX}windows) - ENDIF () + IF (${CLANG_USE_GCC_LINKER}) + # --entry might not work + add_link_options ("LINKER:--entry=WinMainCRTStartup") # Handled by is_win32_app_linker -> "LINKER:SHELL:-m$,CONSOLE,WINDOWS>" + ELSE () + add_link_options ("LINKER:/ENTRY:WinMainCRTStartup") # Handled by is_win32_app_linker -> "LINKER:SHELL:/SUBSYSTEM:$,CONSOLE,WINDOWS>" + ENDIF() + ENDIF() #-- Validate sanitizers options and store them between the common compiler flags. @@ -47,55 +44,88 @@ function (toolchain_exe_stuff_common) # -static-libsan Statically link the sanitizer runtime (Not supported for ASan, TSan or UBSan on darwin) IF (${USE_ASAN}) - SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=address -fno-sanitize-recover=address -fsanitize-address-use-after-scope) - set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=address) + SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} # -fsanitize=safe-stack # Can't be used with asan! + -fsanitize=address -fno-sanitize-recover=address #-fsanitize-cfi # cfi: control flow integrity + -fsanitize-address-use-after-scope -fsanitize=pointer-compare -fsanitize=pointer-subtract + # Flags for additional instrumentation not strictly needing asan to be enabled + -fcf-protection=full -fstack-check -fstack-protector-all -fstack-clash-protection + # Not supported by Clang, but supported by GCC + #-fvtable-verify=preinit -fharden-control-flow-redundancy -fhardcfr-check-exceptions + # Other + #-fsanitize-trap=all + ) + IF (${CLANG_USE_GCC_LINKER}) + set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=address) + ENDIF () + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} ADDRESS_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_MSAN}) MESSAGE (FATAL_ERROR "Windows Clang doesn't yet support MSAN") SET (USE_MSAN false) #SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=memory -fsanitize-memory-track-origins=2 -fPIE) - #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=memory) + #IF (${CLANG_USE_GCC_LINKER}) + #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=memory) + #ENDIF + #SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} MEMORY_SANITIZER) #SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_LSAN}) MESSAGE (FATAL_ERROR "Windows Clang doesn't yet support LSAN") SET (USE_LSAN false) #SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=leak) - #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=leak) + #IF (${CLANG_USE_GCC_LINKER}) + #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=leak) + #ENDIF + #SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} LEAK_SANITIZER) #SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_UBSAN}) - SET (UBSAN_FLAGS - -fsanitize=undefined,shift,integer-divide-by-zero,vla-bound,null,signed-integer-overflow,bounds - -fsanitize=float-divide-by-zero,float-cast-overflow,pointer-overflow,unreachable,nonnull-attribute,returns-nonnull-attribute - -fno-sanitize=enum - ) + SET (UBSAN_FLAGS + -fsanitize=undefined,float-divide-by-zero,local-bounds + -fno-sanitize=enum + # Supported? + -fsanitize=unsigned-integer-overflow #Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. + -fsanitize=implicit-conversion + ) SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} ${UBSAN_FLAGS} -fsanitize=return) - set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=undefined) + #IF (${CLANG_USE_GCC_LINKER}) + #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=undefined) + #ENDIF + SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} UNDEFINED_BEHAVIOR_SANITIZER) SET (ENABLED_SANITIZER true) ENDIF () + IF (${ENABLED_SANITIZER}) SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} _SANITIZERS) - set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} $<$:-static-libsan>) + if (${RUNTIME_STATIC_LINK}) + set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -static-libsan) + else () + set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -shared-libsan) + endif () ENDIF () #-- Store compiler flags common to all builds. SET (cxx_local_opts_warnings - -Wall -Wextra -Wno-unknown-pragmas -Wno-switch -Wno-parentheses -Wno-conversion-null - -Wno-misleading-indentation -Wno-implicit-fallthrough - ) + -Wall -Wextra -Wno-unknown-pragmas -Wno-switch -Wno-implicit-fallthrough + -Wno-parentheses -Wno-misleading-indentation -Wno-conversion-null -Wno-unused-result + -Wno-format-security # TODO: disable that when we'll have time to fix every printf format issue + + # clang -specific: + # -fforce-emit-vtables + ) SET (cxx_local_opts -std=c++20 -fexceptions -fnon-call-exceptions -pipe -ffast-math -mno-ms-bitfields # -mno-ms-bitfields is needed to fix structure packing; # -pthread unused here? we only need to specify that to the linker? + -Wno-format-security # TODO: disable that when we'll have time to fix every printf format issue # clang-specific: - -Wno-format-security + # # Flags supported by GCC but not by Clang: -fno-expensive-optimizations, -Wno-error=maybe-uninitialized ) @@ -110,17 +140,44 @@ function (toolchain_exe_stuff_common) SET (COMPILE_OPTIONS_EXTRA -fno-omit-frame-pointer -fno-inline) ENDIF () IF (TARGET spheresvr_release) - TARGET_COMPILE_OPTIONS ( spheresvr_release PUBLIC -s -O3 ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_release PUBLIC -O3 -flto=full -fvirtual-function-elimination ${COMPILE_OPTIONS_EXTRA}) + #[[ + IF (NOT ${CLANG_USE_GCC_LINKER}) + if (${RUNTIME_STATIC_LINK}) + TARGET_COMPILE_OPTIONS ( spheresvr_release PUBLIC /MT ) + else () + TARGET_COMPILE_OPTIONS ( spheresvr_release PUBLIC /MD ) + endif () + endif () + ]] ENDIF () IF (TARGET spheresvr_nightly) IF (ENABLED_SANITIZER) - TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC -ggdb3 -O1 ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC -ggdb3 -O1 ${COMPILE_OPTIONS_EXTRA} ) ELSE () - TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC -O3 ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC -O3 -flto=full -fvirtual-function-elimination ${COMPILE_OPTIONS_EXTRA} ) ENDIF () + #[[ + IF (NOT ${CLANG_USE_GCC_LINKER}) + if (${RUNTIME_STATIC_LINK}) + TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC /MT ) + else () + TARGET_COMPILE_OPTIONS ( spheresvr_nightly PUBLIC /MD ) + endif () + endif () + ]] ENDIF () IF (TARGET spheresvr_debug) - TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC -ggdb3 -Og ${COMPILE_OPTIONS_EXTRA}) + TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC -ggdb3 -Og ) + #[[ + IF (NOT ${CLANG_USE_GCC_LINKER}) + if (${RUNTIME_STATIC_LINK}) + TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC /MTd ) + else () + TARGET_COMPILE_OPTIONS ( spheresvr_debug PUBLIC /MDd ) + endif () + endif () + ]] ENDIF () @@ -130,17 +187,26 @@ function (toolchain_exe_stuff_common) SET (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -pie) ENDIF() - set (cxx_linker_options_common ${CMAKE_EXE_LINKER_FLAGS_EXTRA} - $<$:-pthread -dynamic - $<$: -static-libstdc++ -static-libgcc>> # no way to statically link against libc? maybe we can on windows? - ) - + set (cxx_linker_options_common ${CMAKE_EXE_LINKER_FLAGS_EXTRA}) + if (${CLANG_USE_GCC_LINKER}) + set (cxx_linker_options_common ${cxx_linker_options_common} -pthread -dynamic) + if (${RUNTIME_STATIC_LINK}) + set (cxx_linker_options_common ${cxx_linker_options_common} -static-libstdc++ -static-libgcc) # no way to statically link against libc? maybe we can on windows? + endif () +#[[ + else () + if (${RUNTIME_STATIC_LINK}) + set (cxx_linker_options_common ${cxx_linker_options_common} /MTd ) + else () + set (cxx_linker_options_common ${cxx_linker_options_common} /MDd ) + endif () +]] + endif () #-- Store common define macros. set(cxx_compiler_definitions_common ${PREPROCESSOR_DEFS_EXTRA} - $<$>:_GITVERSION> _EXCEPTIONS_DEBUG # _EXCEPTIONS_DEBUG: Enable advanced exceptions catching. Consumes some more resources, but is very useful for debug # on a running environment. Also it makes sphere more stable since exceptions are local. @@ -149,33 +215,79 @@ function (toolchain_exe_stuff_common) _WINSOCK_DEPRECATED_NO_WARNINGS # _WINSOCK_DEPRECATED_NO_WARNINGS: Removing warnings until the code gets updated or reviewed. ) + if (NOT ${CMAKE_NO_GIT_REVISION}) + set(cxx_compiler_definitions_common ${cxx_compiler_definitions_common} _GITVERSION) + endif () + #-- Apply define macros, only the ones specific per build type. IF (TARGET spheresvr_release) - TARGET_COMPILE_DEFINITIONS ( spheresvr_release PUBLIC NDEBUG THREAD_TRACK_CALLSTACK ) - ENDIF (TARGET spheresvr_release) + target_compile_definitions ( spheresvr_release PUBLIC NDEBUG THREAD_TRACK_CALLSTACK ) + ENDIF () IF (TARGET spheresvr_nightly) - TARGET_COMPILE_DEFINITIONS ( spheresvr_nightly PUBLIC NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD ) - ENDIF (TARGET spheresvr_nightly) + target_compile_definitions ( spheresvr_nightly PUBLIC NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD ) + ENDIF () IF (TARGET spheresvr_debug) - TARGET_COMPILE_DEFINITIONS ( spheresvr_debug PUBLIC _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP ) + target_compile_definitions ( spheresvr_debug PUBLIC _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP ) IF (USE_ASAN AND NOT CLANG_USE_GCC_LINKER) - # Even with this, it appears that they are overridden to 1... - TARGET_COMPILE_DEFINITIONS ( spheresvr_debug PUBLIC _HAS_ITERATOR_DEBUGGING=0 _ITERATOR_DEBUG_LEVEL=0 ) + target_compile_definitions ( spheresvr_debug PUBLIC _HAS_ITERATOR_DEBUGGING=0 _ITERATOR_DEBUG_LEVEL=0 ) ENDIF() - ENDIF (TARGET spheresvr_debug) + ENDIF () + + + #-- Apply linker options, only the ones specific per build type. + + #if (NOT ${CLANG_USE_GCC_LINKER}) + # IF (TARGET spheresvr_release) + # target_link_options ( spheresvr_release PUBLIC "LINKER:SHELL:" ) + # ENDIF () + # IF (TARGET spheresvr_nightly) + # target_link_options ( spheresvr_nightly PUBLIC "LINKER:SHELL:" ) + # ENDIF () + # IF (TARGET spheresvr_debug) + # target_link_options ( spheresvr_debug PUBLIC "LINKER:/DEBUG" ) + # ENDIF () + #endif () #-- Now add back the common compiler options, preprocessor macros, linker targets and options. - set (libs_prefix $<$>:lib>) - set (libs_to_link_against ws2_32 ${LIBS_PREFIX}mariadb) + if (NOT ${CLANG_USE_GCC_LINKER}) + set (libs_prefix lib) +#[[ + if (ENABLED_SANITIZER) + # one should add to the linker path its LLVM lib folder... + if (ARCH_BITS EQUAL 64) + if (TARGET spheresvr_release) + target_link_libraries(spheresvr_release PUBLIC clang_rt.asan_dll_thunk-x86_64) #clang_rt.asan_dynamic-x86_64) + endif () + if (TARGET spheresvr_nightly) + target_link_libraries(spheresvr_nightly PUBLIC clang_rt.asan_dll_thunk-x86_64) #clang_rt.asan_dynamic-x86_64) + endif () + if (TARGET spheresvr_debug) + target_link_libraries(spheresvr_debug PUBLIC clang_rt.asan_dll_thunk-x86_64) #clang_rt.asan_dbg_dynamic-x86_64) + endif () + else () + if (TARGET spheresvr_release) + target_link_libraries(spheresvr_release PUBLIC clang_rt.asan_dll_thunk-i386) #clang_rt.asan_dynamic-x86) + endif () + if (TARGET spheresvr_nightly) + target_link_libraries(spheresvr_nightly PUBLIC clang_rt.asan_dll_thunk-i386) #clang_rt.asan_dynamic-x86) + endif () + if (TARGET spheresvr_debug) + target_link_libraries(spheresvr_debug PUBLIC clang_rt.asan_dll_thunk-i386) #clang_rt.asan_dbg_dynamic-x86) + endif () + endif() + endif () +]] + endif() + set (libs_to_link_against ${libs_to_link_against} ws2_32 ${libs_prefix}mariadb) foreach(tgt ${TARGETS}) target_compile_options (${tgt} PRIVATE ${cxx_compiler_options_common}) target_compile_definitions (${tgt} PRIVATE ${cxx_compiler_definitions_common}) - target_link_options (${tgt} PRIVATE ${cxx_linker_options_common}) + target_link_options (${tgt} PRIVATE LINKER:${cxx_linker_options_common}) target_link_libraries (${tgt} PRIVATE ${libs_to_link_against}) endforeach() diff --git a/cmake/toolchains/include/Windows-GNU_common.inc.cmake b/cmake/toolchains/include/Windows-GNU_common.inc.cmake index 5f3412f30..dad1e3a26 100644 --- a/cmake/toolchains/include/Windows-GNU_common.inc.cmake +++ b/cmake/toolchains/include/Windows-GNU_common.inc.cmake @@ -8,6 +8,7 @@ endfunction () function (toolchain_after_project_common) ENABLE_LANGUAGE(RC) + include ("${CMAKE_SOURCE_DIR}/cmake/CMakeDetectArch.cmake") endfunction () @@ -15,12 +16,13 @@ function (toolchain_exe_stuff_common) #-- Configure the Windows application type. - IF (${WIN32_SPAWN_CONSOLE}) - SET (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -mconsole) - SET (PREPROCESSOR_DEFS_EXTRA _WINDOWS_CONSOLE) - #ELSE () - # SET (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -mwindows) - ENDIF () + # Subsystem is already managed by is_win32_app_linker. GCC doesn't need us to specify the entry point. + #IF (${WIN32_SPAWN_CONSOLE}) + # add_link_options ("LINKER:SHELL:-mconsole") + # SET (PREPROCESSOR_DEFS_EXTRA _WINDOWS_CONSOLE) + ##ELSE () + ## add_link_options ("LINKER:SHELL:-mwindows") + #ENDIF () #-- Validate sanitizers options and store them between the common compiler flags. @@ -29,8 +31,22 @@ function (toolchain_exe_stuff_common) IF (${USE_ASAN}) MESSAGE (FATAL_ERROR "MinGW-GCC doesn't yet support ASAN") SET (USE_ASAN false) - #SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=address -fsanitize-address-use-after-scope) + #[[ + SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} + -fsanitize=address -fno-sanitize-recover=address #-fsanitize-cfi # cfi: control flow integrity, not currently supported by GCC (even on Linux) + -fsanitize-address-use-after-scope -fsanitize=pointer-compare -fsanitize=pointer-subtract + # Flags for additional instrumentation not strictly needing asan to be enabled + #-fvtable-verify=preinit # This causes a GCC internal error! Tested with 13.2.0 + -fstack-check -fstack-protector-all + -fcf-protection=full + # GCC 14? + #-fharden-control-flow-redundancy -fhardcfr-check-exceptions + # Other + #-fsanitize-trap=all + ) + ]] #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=address $<$:-static-libasan>) + #SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} ADDRESS_SANITIZER) #SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_MSAN}) @@ -38,6 +54,7 @@ function (toolchain_exe_stuff_common) SET (USE_MSAN false) #SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=memory -fsanitize-memory-track-origins=2 -fPIE) #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=memory )#$<$:-static-libmsan>) + #SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} MEMORY_SANITIZER) #SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_LSAN}) @@ -45,19 +62,27 @@ function (toolchain_exe_stuff_common) SET (USE_LSAN false) #SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} -fsanitize=leak) #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=leak #$<$:-static-liblsan>) + #SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} LEAK_SANITIZER) #SET (ENABLED_SANITIZER true) ENDIF () IF (${USE_UBSAN}) MESSAGE (FATAL_ERROR "MinGW-GCC doesn't yet support UBSAN") SET (USE_UBSAN false) -# SET (UBSAN_FLAGS -# -fsanitize=undefined,#shift,integer-divide-by-zero,vla-bound,null,signed-integer-overflow,bounds -# -fsanitize=float-divide-by-zero,float-cast-overflow,pointer-overflow,unreachable,nonnull-attribute,returns-nonnull-attribute -# -fno-sanitize=enum) + #[[ + SET (UBSAN_FLAGS + -fsanitize=undefined,float-divide-by-zero + -fno-sanitize=enum + # Unsupported (yet?) by GCC 13 + #-fsanitize=unsigned-integer-overflow #Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. + #-fsanitize=implicit-conversion, local-bounds + ) + ]] #SET (CXX_FLAGS_EXTRA ${CXX_FLAGS_EXTRA} ${UBSAN_FLAGS} -fsanitize=return,vptr) #set (CMAKE_EXE_LINKER_FLAGS_EXTRA ${CMAKE_EXE_LINKER_FLAGS_EXTRA} -fsanitize=undefined #$<$:-static-libubsan>) + #SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} UNDEFINED_BEHAVIOR_SANITIZER) #SET (ENABLED_SANITIZER true) ENDIF () + #IF (${ENABLED_SANITIZER}) # SET (PREPROCESSOR_DEFS_EXTRA ${PREPROCESSOR_DEFS_EXTRA} _SANITIZERS) #ENDIF () @@ -67,7 +92,8 @@ function (toolchain_exe_stuff_common) set (cxx_local_opts_warnings -Wall -Wextra -Wno-nonnull-compare -Wno-unknown-pragmas -Wno-switch -Wno-implicit-fallthrough - -Wno-parentheses -Wno-format-security -Wno-misleading-indentation -Wno-conversion-null -Wno-unused-result + -Wno-parentheses -Wno-misleading-indentation -Wno-conversion-null -Wno-unused-result + -Wno-format-security # TODO: disable that when we'll have time to fix every printf format issue ) set (cxx_local_opts -std=c++20 -pthread -fexceptions -fnon-call-exceptions -mno-ms-bitfields diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 463ccd9e8..ae2f6f9d5 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,11 +1,13 @@ add_subdirectory(bcrypt) +add_subdirectory(cdrc) add_subdirectory(flat_containers) add_subdirectory(mariadb_connector_c) add_subdirectory(object_ptr) add_subdirectory(parallel_hashmap) add_subdirectory(rand) add_subdirectory(regex) +#add_subdirectory(simde) add_subdirectory(twofish) if (${LIB_LIBEV_BUILD}) @@ -43,8 +45,11 @@ foreach (tgt ${TARGETS}) PRIVATE $ ) + # import the compile_definitions defined by the INTERFACE targets + target_compile_definitions(${tgt} PUBLIC ${INTERFACE_COMPILE_DEFINITIONS}) + # Link to (and import include directories) those clasic (.c/.h) libraries. - target_link_libraries(${tgt} PRIVATE bcrypt twofish) + target_link_libraries(${tgt} PRIVATE bcrypt twofish) # cdrc (not needed for now) if (${LIB_LIBEV_BUILD}) target_link_libraries(${tgt} PRIVATE libev) endif() diff --git a/lib/bcrypt/CMakeLists.txt b/lib/bcrypt/CMakeLists.txt index f4af674bd..18ce7745a 100644 --- a/lib/bcrypt/CMakeLists.txt +++ b/lib/bcrypt/CMakeLists.txt @@ -19,5 +19,6 @@ source_group (lib\\bcrypt FILES ${lib_SOURCES}) # set compiler flags include("../lib_build_flags_common_c.cmake") -target_compile_options (bcrypt PRIVATE ${c_compiler_options_common}) -target_link_options (bcrypt PRIVATE ${c_linker_options_common}) \ No newline at end of file +target_compile_options (bcrypt PRIVATE ${c_compiler_options_common}) +target_compile_definitions (bcrypt PRIVATE ${c_compiler_definitions_common}) +target_link_options (bcrypt PRIVATE ${c_linker_options_common}) diff --git a/lib/cdrc/CMakeLists.txt b/lib/cdrc/CMakeLists.txt new file mode 100644 index 000000000..9105cee9c --- /dev/null +++ b/lib/cdrc/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required (VERSION 3.19) +project(cdrc CXX) + +set (lib_SOURCES + cdrc/atomic_rc_ptr.h + cdrc/atomic_weak_ptr.h + cdrc/marked_arc_ptr.h + cdrc/rc_ptr.h + cdrc/snapshot_ptr.h + cdrc/weak_ptr.h + cdrc/weak_snapshot_ptr.h +) +set (lib_custom_SOURCES + cdrc/custom/rand.cpp + cdrc/custom/rand.h + cdrc/custom/threadid.cpp + cdrc/custom/threadid.h +) + +# add to the proper directories in the solution explorer (eg. Visual Studio) +source_group (lib\\cdrc FILES ${lib_SOURCES}) +source_group (lib\\cdrc\\custom FILES ${lib_custom_SOURCES}) + +add_library(cdrc STATIC ${lib_SOURCES} ${lib_custom_SOURCES}) + +# set compiler flags +if (MSVC) + set(my_comp_options + /wd4244 + /wd4267 + /wd4324 + ) +endif () + +include("../lib_build_flags_common_c.cmake") +target_compile_options (cdrc PRIVATE ${c_compiler_options_common} ${my_comp_options}) +target_link_options (cdrc PRIVATE ${c_linker_options_common}) + +# make the headers available in the include path (accessible with < >). +target_include_directories (cdrc PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/lib/cdrc/cdrc/LICENSE b/lib/cdrc/cdrc/LICENSE new file mode 100644 index 000000000..98c2b71fe --- /dev/null +++ b/lib/cdrc/cdrc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-present Daniel Anderson and Yuanhao Wei + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/cdrc/cdrc/README.md b/lib/cdrc/cdrc/README.md new file mode 100644 index 000000000..0f462ad16 --- /dev/null +++ b/lib/cdrc/cdrc/README.md @@ -0,0 +1,160 @@ +# Concurrent Deferred Reference Counting + +[![Build Status](https://github.com/cmuparlay/concurrent_deferred_rc/actions/workflows/build.yml/badge.svg)](https://github.com/cmuparlay/concurrent_deferred_rc/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +A library for high-performance reference-counted pointers for automatic memory management in C++. + +This library is part of the following research project. If you use it for scientific purposes, please cite the corresponding papers: + +> **Turning Manual Concurrent Memory Reclamation +into Automatic Reference Counting**
+> Daniel Anderson, Guy E. Blelloch, Yuanhao Wei
+> The 43rd ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI 2022), 2022 + +> **Concurrent Deferred Reference Counting with Constant-Time Overhead**
+> Daniel Anderson, Guy E. Blelloch, Yuanhao Wei
+> The 42nd ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI 2021), 2021 + +## Getting started + +To use the library on its own, you can +simply include the `include` directory. The library is header only and has no other dependencies. Our types are provided inside the namespace `cdrc`. You'll need a compiler that supports C++20. The library should work on Linux, MacOS, and Windows. The benchmarks however only work on Linux. + +There may be some additional dependencies to run the benchmarks. To work with the tests and benchmarks, you will need a recent version of CMake. Compiling the benchmarks will also require the [Boost](https://www.boost.org/) library, the [hwloc](https://www.open-mpi.org/projects/hwloc/) library, and the [jemalloc](https://github.com/jemalloc/jemalloc) library, but these are not required to just use our library itself. To produce the benchmark figures, you will additionally need a recent version of Python with Matplotlib. + +### Using the library + +Our library provides three types that work similarly to C++'s standard [`shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr) and [`atomic`](https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2). These types, and some of their important supported methods are: + +* **atomic_rc_ptr**: `atomic_rc_ptr` is closely modelled after C++’s `atomic>`. It provides support for all of the standard operations, such as atomic load, store, exchange and CAS. + * `load()`. Atomically creates an `rc_ptr` to the currently managed object, returning the rc_ptr + * `get_snapshot()`. Atomically creates a `snapshot_ptr` to the currently managed object, returning the `snapshot_ptr`. + * `store(desired)`. Atomically replaces the currently managed pointer with desired, which may be either an `rc_ptr` or a `snapshot_ptr`. + * `compare_and_swap(expected, desired)`. Atomically compares the managed pointer with expected, and if they are equal, replaces the managed pointer with desired. The types of expected and desired may be either `rc_ptr` or `snapshot_ptr`, and need not be the same. If `desired` is passed in as an r-value reference, then it is only moved from if the compare_and_swap succeeds. + * `compare_exchange_weak(expected, desired)`. Same as `compare_and_swap` but, if the managed pointer is not equal to expected, loads the currently managed pointer into expected. This operation may spuriously return false, i.e. it is possible that the value of expected does not change +* **rc_ptr**: The `rc_ptr` type is closely modelled after C++’s standard library `shared_ptr`. It supports all +pointer-like operations, such as dereferencing, i.e. obtaining +a reference to the underlying managed object, and assignment of another `rc_ptr` to replace the current one. It is safe to read/copy an `rc_ptr` concurrently from many threads, as long as there is never a race between one thread updating the `rc_ptr` and another reading it. Such a situation should be handled by an `atomic_rc_ptr`. +* **snapshot_ptr**: The `snapshot_ptr` type supports all of the same operations as `rc_ptr`, except that `snapshot_ptr` can only be moved and not copied. Additionally, while `rc_ptr` can safely be shared between threads, `snapshot_ptr` should only be used locally by the thread that created it. The use of `snapshot_ptr` should result in better performance than `rc_ptr` provided that each thread does not hold too many `snapshot_ptr` at once. Therefore, `snapshot_ptr` is ideal for reading short-lived local references, for example, reading nodes in a data structure while traversing it. + +These types are provided in the correspondingly named header files: ``, ``, and ``. + +To allocate a new reference-counted object mananged by an `rc_ptr`, use the static method `rc_ptr::make_shared(args...)` in the same way as the standard C++ `std::make_shared`. Alternatively, the convenient aliases `cdrc::make_rc(args...)` and `cdrc::make_shared(args...)` can be used. + +As a simple example, the following code implements a concurrent stack using our library. You can read a complete implementation in [stack.h](./examples/stack.h) in the [examples](./examples) directory. + +``` +struct Node { T t; rc_ptr next; } +atomic_rc_ptr head; + +void push_front(T t) { + rc_ptr p = make_rc(t, head.load()); + while (!head.compare_exchange_weak(p->next, p)) {} +} + +std::optional pop_front() { + snapshot_ptr p = head.get_snapshot(); + while (p != nullptr && !head.compare_exchange_weak(p, p->next)) { } + if (p != nullptr) return {p->t}; + else return {}; +} +``` + +The head node of the +stack is stored in an `atomic_rc_ptr` because it may be modified and read concurrently by multiple threads. Each node +of the stack stores its next pointer as a non-atomic `rc_ptr`. +This is safe, because although multiple threads may read the +same pointer concurrently, the internal nodes of the stack +are never modified, only the head is. Lastly, we can use a +`snapshot_ptr` while performing `pop_front`, since reading the +head is a short-lived local reference that will never be shared +with another thread. + +### Weak pointers + +In addition to the three main pointer types, three additional types are provided for writing more complicated data structures with cyclic references. To enable cyclic references to be collected, **weak pointers** are a kind of smart pointer that hold a reference to a shared object, but do not contribute to its reference count. Since they do not contribute to the reference count, they can not be read directly, but must instead be updgraded to an `rc_ptr` before they can be dereferrenced. We also provide a `weak_snapshot_ptr` type, which is analagous to `snapshot_ptr`, which enables reading from an `atomic_weak_ptr` without incrementing the reference count. The types, in summary, are: + +* **atomic_weak_ptr**: `atomic_weak_ptr` is analagous to `atomic_rc_ptr` and is modelled after C++'s `atomic>`. It supports the same operations as `atomic_rc_ptr` for storing instances of `weak_ptr`, including `load()`, `get_snapshot()`, `store(desired)`, `compare_and_swap(expected, desired)`, and `compare_exchange_weak(expected, desired)`. Note that `get_snapshot` returns a `weak_snapshot_ptr` rather than a `snapshot_ptr`. +* **weak_ptr**: `weak_ptr` is modelled after C++'s `weak_ptr` and supports the same interface. Note that `weak_ptr` can not be dereferrenced or read directly. To read a `weak_ptr`, it must be converted into an `rc_ptr` by calling its `lock()` method. `weak_ptr` can conversely be constructed from an instance of `rc_ptr`. +* **weak_snapshot_ptr**: `weak_snapshot_ptr` is analagous to `snapshot_ptr` for `atomic_weak_ptr`. Note that it is possible for the reference count of an object to reach zero while holding a `weak_snapshot_ptr` that refers to it. However, it is guaranteed that the object will not be destroyed and hence is safe to read as long as the snapshot is held. + +These types are provided in the corresponding headers ``, ``, ``. + +### Marked pointers + +To support writing advanced data structures, we also support *marked* pointers. Marked pointers allow you to utilize some of the redundant bits of the pointer representation to store flags, avoiding the need to store flags adjacent to the pointer and having to use double-word instructions to read/write the pointer/flag pair atomically. To use marked pointers, the types `marked_arc_ptr`, `marked_rc_ptr`, and `marked_snapshot_ptr` can be used as substitutes for `atomic_rc_ptr`, `rc_ptr`, and `snapshot_ptr` respectively. There are also marked versions of the three weak pointer types, `marked_aw_ptr`, `marked_weak_ptr`, and `marked_ws_ptr`, for `atomic_weak_ptr`, `weak_ptr`, and `weak_snapshot_ptr` respectively. You can include them all from the header `` They support all of the same methods, but additionally support: +* `get_mark()`. Gets the current mark on the pointer. +* `set_mark(mark)`. Sets the current mark on the pointer. The mark must be an unsigned integer at most 3, i.e., use only the bottom two bits. + +Like regular `rc_ptr`, to create a new reference-counted object managed by a `marked_rc_ptr`, use `marked_rc_ptr::make_shared(args...)`. + +As expected, the `load` method of `marked_arc_ptr` returns a `marked_rc_ptr`, and the `get_snapshot` method returns a `marked_snapshot_ptr`. The `get` method of `marked_rc_ptr` and `marked_snapshot_ptr` returns returns a raw pointer **without** the mark, and hence can be safely dereferenced. + +Lastly, the `marked_arc_ptr` and `marked_aw_ptr` types also support these additional operations: +* `compare_and_set_mark(expected, desired_mark)`. Atomically compares the current marked pointer with expected (which should be a `marked_rc_ptr` or a `marked_snapshot_ptr`), and, if they are equal, sets the mark to `desired_mark` and returns true. Otherwise returns false. +* `set_mark_bit(bit)`. Atomically sets the value of the bit at the given position (must be 1 or 2) to 1 +* `get_mark_bit(bit)`. Returns the value of the mark bit at the given position (must be 1 or 2) + +An example of how to use these marked pointers can be found in [linked_list.h](./examples/linked_list.h) in the [examples](./examples) directory. + +## Using different memory management backends + +CDRC can be configured to use different memory management algorithms under the hood, which can result in different performance profiles. By default, it uses the hazard-pointer backend, which has good performance and bounded garbage accumulation. There are four backends available to choose from, summarized in the following table. + +| Scheme | Throughput | Memory usage | +|----------------------------------| -----------| ------------ | +| Hazard-pointers (default) | Moderate | Low | +| Epoch-based reclamation (EBR) | High | High | +| Interval-based reclamation (IBR) | Moderate-high | Moderate-high | +| Hyaline | High | Moderate-high | + +### Guard types + +For every backend other than the default (hazard-pointers), an additional tool is required to safely use the smart pointer types. Before performing any potentially concurrent read or write to an atomic pointer type, the user must first acquire a **guard** object. For EBR and IBR, the guard object is of type ``cdrc::epoch_guard``. For Hyaline, the guard object is of type ``cdrc::hyaline_guard``. For example, using EBR, the `pop_front` method of our example stack becomes + +```c++ +std::optional pop_front() { + epoch_guard g; // This is important!! + snapshot_ptr_ebr p = head.get_snapshot(); + while (p != nullptr && !head.compare_exchange_weak(p, p->next)) { } + if (p != nullptr) return {p->t}; + else return {}; +} +``` + +The guard is released automatically at the end of the enclosing scope. Note that snapshot pointers cannot outlive the guard that they were created during. It is safe to hold multiple nested guards inside nested scopes. Guards should not be held for long periods of time, as they may delay memory reclamation and lead to the accumulation of more garbage. Ideally, the lifetime of a guard should denote the span of a single operation on the data structure. + + +### Selecting an alternate backend + +There are two ways to select an alternate memory management algorithm. One is to provide an additional template argument to the pointer types. For example, to declare an `atomic_rc_ptr` that uses EBR as the backend, we can write + +```c++ +cdrc::atomic_r_ptr> p; +``` + +Note that this extra template argument applies to all six of the pointer types, and pointers with different memory management backends are not compatible (e.g., you can not store an `rc_ptr>` inside an `atomic_rc_ptr>`). + +Alternatively, a set of predefined alias templates for each of the pointer types with each backend are available. Each alias corresponds to the original pointer type with a suffix indicating the backend, e.g., `atomic_rc_ptr_ebr` is an alias template for `atomic_rc_ptr>`. The full list of backend types and suffixes is as follows + +| Backend | Type | Suffix | Guard type | +| ------- |----------------------------| ------ | ---------- | +| Hazard-pointers | `cdrc::hp_backend` | `_hp` | None | +| EBR | `cdrc::ebr_backend` | `_ebr` | `cdrc::epoch_guard` | +| IBR | `cdrc::ibr_backend` | `_ibr` | `cdrc::epoch_guard` | +| Hyaline | `cdrc::hyaline_backend` | `_hyaline` | `cdrc::hyaline_guard` | + +Note that the marked pointer alias templates also support both the additional template argument to select a backend, and the suffixed template alises, e.g., `marked_aw_ptr>` and `marked_aw_ptr_ebr` are valid and equivalent. + +## Configuring the CMake project for testing and benchmarking + +To configure the project for testing and benchmarking, create a build directory and run CMake. This is as easy as + +``` +mkdir build +cd build +cmake .. +make +``` + +This will build both the benchmarks and the tests. By default, the CMake project will build in Release mode. You probably want to do a Debug build for testing, by adding `-DCMAKE_BUILD_TYPE=Debug` to the CMake configuration command. The tests can then be run by writing `make test` from the build directory. These will validate that the code is functioning sensibly. See [benchmarks](benchmarks/README.md) for information on running the provided benchmarks. diff --git a/lib/cdrc/cdrc/atomic_rc_ptr.h b/lib/cdrc/cdrc/atomic_rc_ptr.h new file mode 100644 index 000000000..2510644bc --- /dev/null +++ b/lib/cdrc/cdrc/atomic_rc_ptr.h @@ -0,0 +1,227 @@ + +#ifndef CDRC_ATOMIC_RC_PTR_H +#define CDRC_ATOMIC_RC_PTR_H + +#include + +#include + +#include "internal/counted_object.h" +#include "internal/fwd_decl.h" +#include "internal/utils.h" + +#include "rc_ptr.h" +#include "snapshot_ptr.h" + +namespace cdrc { + +template +class atomic_rc_ptr : public pointer_policy::template arc_ptr_policy { + + // The pointer_policy template argument introduces a customization point + // that allows atomic_rc_ptr to be used with various kinds of marked + // pointers. pointer_policy should defined a typename pointer_type, which + // is implicitly convertible to T* and support all of the usual pointer-like + // operations. It should also define three member types arc_ptr_policy, + // rc_ptr_policy, and snapshot_ptr_policy, which can add additional methods + // to the corresponding pointer types. See marked_arc_ptr.h for an example. + + private: + using counted_object_t = internal::counted_object; + using counted_ptr_t = typename pointer_policy::template pointer_type; + + using rc_ptr_t = rc_ptr; + using snapshot_ptr_t = snapshot_ptr; + using weak_ptr_t = weak_ptr; + using atomic_weak_ptr_t = atomic_weak_ptr; + using weak_snapshot_ptr_t = weak_snapshot_ptr; + + friend rc_ptr_t; + friend snapshot_ptr_t; + friend weak_ptr_t; + friend atomic_weak_ptr_t; + friend weak_snapshot_ptr_t; + + friend typename pointer_policy::template arc_ptr_policy; + + public: + atomic_rc_ptr() : atomic_ptr(nullptr) {} + + /* implicit */ atomic_rc_ptr(std::nullptr_t) : atomic_ptr(nullptr) {} + + /* implicit */ atomic_rc_ptr(rc_ptr_t desired) : atomic_ptr(desired.release()) {} + + ~atomic_rc_ptr() { + auto ptr = atomic_ptr.load(); + if (ptr != nullptr) mm.delayed_decrement_ref_cnt(ptr); + } + + atomic_rc_ptr(const atomic_rc_ptr &) = delete; + + atomic_rc_ptr &operator=(const atomic_rc_ptr &) = delete; + + atomic_rc_ptr(atomic_rc_ptr &&) = delete; + + atomic_rc_ptr &operator=(atomic_rc_ptr &&) = delete; + + [[nodiscard]] bool is_lock_free() const noexcept { return true; } + + static constexpr bool is_always_lock_free = true; + + void store(std::nullptr_t) noexcept { + auto old_ptr = atomic_ptr.exchange(nullptr, std::memory_order_seq_cst); + if (old_ptr != nullptr) mm.delayed_decrement_ref_cnt(old_ptr); + } + + void store(rc_ptr_t desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + auto new_ptr = desired.release(); + auto old_ptr = atomic_ptr.exchange(new_ptr, order); + if (old_ptr != nullptr) mm.delayed_decrement_ref_cnt(old_ptr); + } + + void store_non_racy(rc_ptr_t desired) noexcept { + auto new_ptr = desired.release(); + auto old_ptr = atomic_ptr.load(); + atomic_ptr.store(new_ptr, std::memory_order_release); + if (old_ptr != nullptr) mm.delayed_decrement_ref_cnt(old_ptr); + } + + void store(const snapshot_ptr_t &desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + auto new_ptr = desired.get_counted(); + + // If desired is protected, a small optimization opportunity is to not + // increment/decrement the reference count of the new/old value if they + // turn out to be the same. If desired isn't protected, we must proactively + // increment, though, otherwise it could be decremented after we exchange + // but before we perform the increment. + if (desired.is_protected()) { + auto old_ptr = atomic_ptr.exchange(new_ptr, order); + if (old_ptr != new_ptr) { + if (new_ptr != nullptr) mm.increment_ref_cnt(new_ptr); + if (old_ptr != nullptr) mm.delayed_decrement_ref_cnt(old_ptr); + } + } + else { + if (new_ptr != nullptr) mm.increment_ref_cnt(new_ptr); + auto old_ptr = atomic_ptr.exchange(new_ptr, order); + if (old_ptr != nullptr) mm.delayed_decrement_ref_cnt(old_ptr); + } + } + + rc_ptr_t load() const noexcept { + auto acquired_ptr = mm.acquire(&atomic_ptr); + rc_ptr_t result(acquired_ptr.get(), rc_ptr_t::AddRef::yes); + return result; + } + + snapshot_ptr_t get_snapshot() const noexcept { + return snapshot_ptr_t(mm.protect_snapshot(&atomic_ptr)); + } + + bool compare_exchange_weak(rc_ptr_t &expected, const rc_ptr_t &desired) noexcept { + if (!compare_and_swap(expected, desired)) { + expected = load(); + return false; + } else + return true; + } + + bool compare_exchange_weak(snapshot_ptr_t &expected, const rc_ptr_t &desired) noexcept { + if (!compare_and_swap(expected, desired)) { + expected = get_snapshot(); + return false; + } else + return true; + } + + // Atomically compares the underlying rc_ptr with expected, and if they refer to + // the same managed object, replaces the current rc_ptr with a copy of desired + // (incrementing its reference count) and returns true. Otherwise, returns false. + template + bool compare_and_swap(const P1& expected, const P2& desired) noexcept { + + // We need to make a reservation if the desired snapshot pointer no longer has + // an announcement slot. Otherwise, desired is protected, assuming that another + // thread can not clear the announcement slot (this might change one day!) + [[maybe_unused]] auto reservation = !desired.is_protected() ? mm.reserve(desired.get_counted()) : + mm.template reserve_nothing(); + + if (compare_and_swap_impl(expected.get_counted(), desired.get_counted())) { + auto desired_ptr = desired.get_counted(); + if (desired_ptr != nullptr) mm.increment_ref_cnt(desired_ptr); + return true; + } else { + return false; + } + } + + // Atomically compares the underlying rc_ptr with expected, and if they are equal, + // replaces the current rc_ptr with desired by move assignment, hence leaving its + // reference count unchanged. Otherwise returns false and leaves desired unmodified. + template + auto compare_and_swap(const P1& expected, P2&& desired) noexcept + -> std::enable_if_t, bool> { + if (compare_and_swap_impl(expected.get_counted(), desired.get_counted())) { + desired.release(); + return true; + } else { + return false; + } + } + + // Swaps the currently stored shared pointer with the given + // shared pointer. This operation does not affect the reference + // counts of either shared pointer. + // + // Note that it is not safe to concurrently access desired + // while this operation is taking place, since desired is a + // non-atomic shared pointer! + void swap(rc_ptr_t &desired) noexcept { + auto desired_ptr = desired.release(); + auto current_ptr = atomic_ptr.load(); + desired = rc_ptr_t(current_ptr, rc_ptr_t::AddRef::no); + while (!atomic_ptr.compare_exchange_weak(desired.ptr, desired_ptr)) { } + } + + rc_ptr_t exchange(rc_ptr_t desired) noexcept { + auto new_ptr = desired.release(); + auto old_ptr = atomic_ptr.exchange(new_ptr, std::memory_order_seq_cst); + return rc_ptr_t(old_ptr, rc_ptr_t::AddRef::no); + } + + atomic_rc_ptr& operator=(rc_ptr_t desired) noexcept { + store(std::move(desired)); + return *this; + } + + /* implicit */ operator rc_ptr_t() const noexcept { return load(); } + + bool friend operator==(const atomic_rc_ptr& p, std::nullptr_t) noexcept { + return p.atomic_ptr.load() == nullptr; + } + + static size_t currently_allocated() { + return mm.currently_allocated(); + } + + protected: + + bool compare_and_swap_impl(counted_ptr_t expected_ptr, counted_ptr_t desired_ptr) noexcept { + if (atomic_ptr.compare_exchange_strong(expected_ptr, desired_ptr, std::memory_order_seq_cst)) { + if (expected_ptr != nullptr) { + mm.delayed_decrement_ref_cnt(expected_ptr); + } + return true; + } else { + return false; + } + } + + static inline memory_manager& mm = memory_manager::instance(); + + std::atomic atomic_ptr; +}; + +} // namespace cdrc + +#endif // CDRC_ATOMIC_RC_PTR_H diff --git a/lib/cdrc/cdrc/atomic_weak_ptr.h b/lib/cdrc/cdrc/atomic_weak_ptr.h new file mode 100644 index 000000000..2214317d4 --- /dev/null +++ b/lib/cdrc/cdrc/atomic_weak_ptr.h @@ -0,0 +1,208 @@ + +#ifndef CDRC_ATOMIC_WEAK_PTR_H +#define CDRC_ATOMIC_WEAK_PTR_H + +#include + +#include +#include + +#include "internal/counted_object.h" +#include "internal/fwd_decl.h" +#include "internal/utils.h" + +#include "snapshot_ptr.h" +#include "weak_ptr.h" +#include "weak_snapshot_ptr.h" + +namespace cdrc { + +template +class atomic_weak_ptr : public pointer_policy::template arc_ptr_policy { + + // The pointer_policy template argument introduces a customization point + // that allows atomic_rc_ptr to be used with various kinds of marked + // pointers. pointer_policy should defined a typename pointer_type, which + // is implicitly convertible to T* and support all of the usual pointer-like + // operations. It should also define three member types arc_ptr_policy, + // rc_ptr_policy, and snapshot_ptr_policy, which can add additional methods + // to the corresponding pointer types. See marked_arc_ptr.h for an example. + + private: + using counted_object_t = internal::counted_object; + using counted_ptr_t = typename pointer_policy::template pointer_type; + + using atomic_ptr_t = atomic_rc_ptr; + using rc_ptr_t = rc_ptr; + using weak_ptr_t = weak_ptr; + using snapshot_ptr_t = snapshot_ptr; + using weak_snapshot_ptr_t = weak_snapshot_ptr; + + friend rc_ptr_t; + friend weak_ptr_t; + friend snapshot_ptr_t; + + friend typename pointer_policy::template arc_ptr_policy; + + public: + atomic_weak_ptr() : atomic_ptr(nullptr) {} + + /* implicit */ atomic_weak_ptr(std::nullptr_t) : atomic_ptr(nullptr) {} + + /* implicit */ atomic_weak_ptr(weak_ptr_t desired) : atomic_ptr(desired.release()) {} + + ~atomic_weak_ptr() { + auto ptr = atomic_ptr.load(); + if (ptr != nullptr) mm.delayed_decrement_weak_cnt(ptr); + } + + atomic_weak_ptr(const atomic_weak_ptr &) = delete; + + atomic_weak_ptr &operator=(const atomic_weak_ptr &) = delete; + + atomic_weak_ptr(atomic_weak_ptr &&) = delete; + + atomic_weak_ptr &operator=(atomic_weak_ptr &&) = delete; + + [[nodiscard]] bool is_lock_free() const noexcept { return true; } + + static constexpr bool is_always_lock_free = true; + + void store(std::nullptr_t) noexcept { + auto old_ptr = atomic_ptr.exchange(nullptr, std::memory_order_seq_cst); + if (old_ptr != nullptr) mm.delayed_decrement_weak_cnt(old_ptr); + } + + void store(weak_ptr_t desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + auto new_ptr = desired.release(); + auto old_ptr = atomic_ptr.exchange(new_ptr, order); + if (old_ptr != nullptr) mm.delayed_decrement_weak_cnt(old_ptr); + } + + void store_non_racy(weak_ptr_t desired) noexcept { + auto new_ptr = desired.release(); + auto old_ptr = atomic_ptr.load(); + atomic_ptr.store(new_ptr, std::memory_order_release); + if (old_ptr != nullptr) mm.delayed_decrement_weak_cnt(old_ptr); + } + + void store(const weak_snapshot_ptr_t &desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + auto new_ptr = desired.get_counted(); + if (new_ptr != nullptr) mm.increment_weak_cnt(new_ptr); + auto old_ptr = atomic_ptr.exchange(new_ptr, order); + if (old_ptr != nullptr) mm.delayed_decrement_weak_cnt(old_ptr); + } + + void store(const snapshot_ptr_t &desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + auto new_ptr = desired.get_counted(); + if (new_ptr != nullptr) mm.increment_weak_cnt(new_ptr); + auto old_ptr = atomic_ptr.exchange(new_ptr, order); + if (old_ptr != nullptr) mm.delayed_decrement_weak_cnt(old_ptr); + } + + weak_ptr_t load() const noexcept { + auto acquired_ptr = mm.acquire(&atomic_ptr); + weak_ptr_t result(acquired_ptr.get(), weak_ptr_t::AddRef::yes); + return result; + } + + weak_snapshot_ptr_t get_snapshot() const noexcept { + while (true) { + auto p = mm.protect_snapshot(&atomic_ptr); + auto ptr = p.get(); + if (ptr && ptr->get_use_count() > 0) return weak_snapshot_ptr_t(std::move(p)); + else if (ptr == nullptr || atomic_ptr.load() == ptr) { + return weak_snapshot_ptr_t(nullptr); + } + } + } + + bool compare_exchange_weak(weak_ptr_t &expected, const weak_ptr_t &desired) noexcept { + if (!compare_and_swap(expected, desired)) { + expected = load(); + return false; + } else + return true; + } + + bool compare_exchange_weak(weak_snapshot_ptr_t &expected, const weak_ptr_t &desired) noexcept { + if (!compare_and_swap(expected, desired)) { + expected = get_snapshot(); + return false; + } else + return true; + } + + // Atomically compares the underlying weak_ptr with expected, and if they refer to + // the same managed object, replaces the current weak_ptr with a copy of desired + // (incrementing its reference count) and returns true. Otherwise, returns false. + template + bool compare_and_swap(const P1& expected, const P2& desired) noexcept { + + // We need to make a reservation if the desired snapshot pointer no longer has + // an announcement slot. Otherwise, desired is protected, assuming that another + // thread can not clear the announcement slot (this might change one day!) + [[maybe_unused]] auto reservation = !desired.is_protected() ? mm.reserve(desired.get_counted()) : + mm.template reserve_nothing(); + + if (compare_and_swap_impl(expected.get_counted(), desired.get_counted())) { + auto desired_ptr = desired.get_counted(); + if (desired_ptr != nullptr) mm.increment_weak_cnt(desired_ptr); + return true; + } else { + return false; + } + } + + // Atomically compares the underlying weak_ptr with expected, and if they are equal, + // replaces the current weak_ptr with desired by move assignment, hence leaving its + // reference count unchanged. Otherwise returns false and leaves desired unmodified. + template + auto compare_and_swap(const P1& expected, P2&& desired) noexcept + -> std::enable_if_t, bool> { + if (compare_and_swap_impl(expected.get_counted(), desired.get_counted())) { + desired.release(); + return true; + } else { + return false; + } + } + + weak_ptr_t exchange(weak_ptr_t desired) noexcept { + auto new_ptr = desired.release(); + auto old_ptr = atomic_ptr.exchange(new_ptr, std::memory_order_seq_cst); + return weak_ptr_t(old_ptr, weak_ptr_t::AddRef::no); + } + + atomic_weak_ptr& operator=(weak_ptr_t desired) noexcept { + store(std::move(desired)); + return *this; + } + + /* implicit */ operator weak_ptr_t() const noexcept { return load(); } + + static size_t currently_allocated() { + return mm.currently_allocated(); + } + + protected: + + bool compare_and_swap_impl(counted_ptr_t expected_ptr, counted_ptr_t desired_ptr) noexcept { + if (atomic_ptr.compare_exchange_strong(expected_ptr, desired_ptr, std::memory_order_seq_cst)) { + if (expected_ptr != nullptr) { + mm.delayed_decrement_weak_cnt(expected_ptr); + } + return true; + } else { + return false; + } + } + + static inline memory_manager& mm = memory_manager::instance(); + + std::atomic atomic_ptr; +}; + +} // namespace cdrc + +#endif // CDRC_ATOMIC_WEAK_PTR_H diff --git a/lib/cdrc/cdrc/custom/rand.cpp b/lib/cdrc/cdrc/custom/rand.cpp new file mode 100644 index 000000000..f3fc922e8 --- /dev/null +++ b/lib/cdrc/cdrc/custom/rand.cpp @@ -0,0 +1,23 @@ +namespace cdrc::utils::rand { + + thread_local static unsigned long x=123456789, y=362436069, z=521288629; + + void init(int seed) { + x += seed; + } + + unsigned long get_rand() { //period 2^96-1 + unsigned long t; + x ^= x << 16; + x ^= x >> 5; + x ^= x << 1; + + t = x; + x = y; + y = z; + z = t ^ x ^ y; + + return z; + } + +} // namespace diff --git a/lib/cdrc/cdrc/custom/rand.h b/lib/cdrc/cdrc/custom/rand.h new file mode 100644 index 000000000..b65411a65 --- /dev/null +++ b/lib/cdrc/cdrc/custom/rand.h @@ -0,0 +1,18 @@ +#ifndef _INC_CDRC_RAND_H +#define _INC_CDRC_RAND_H + +namespace cdrc +{ +namespace utils +{ +namespace rand +{ + +void init(int seed); +unsigned long get_rand(); + +} // namespace +} +} + +#endif // _INC_CDRC_RAND_H diff --git a/lib/cdrc/cdrc/custom/threadid.cpp b/lib/cdrc/cdrc/custom/threadid.cpp new file mode 100644 index 000000000..98ccd395c --- /dev/null +++ b/lib/cdrc/cdrc/custom/threadid.cpp @@ -0,0 +1,39 @@ +#include "threadid.h" +#include "../internal/utils.h" +#include + +namespace cdrc +{ +namespace utils +{ + +size_t num_threads() { + static size_t n_threads = []() -> size_t { + //if (const auto env_p = std::getenv("NUM_THREADS")) { + // return std::stoi(env_p) + 1; + //} + //else { + return std::thread::hardware_concurrency() + 1; + //} + }(); + return n_threads; +} + +ThreadID::ThreadID() { + for (size_t i = 0; i < num_threads(); i++) { + bool expected = false; + if (!in_use[i] && in_use[i].compare_exchange_strong(expected, true)) { + tid = i; + return; + } + } + //std::cerr << "Error: more than " << num_threads() << " threads created" << std::endl; + //std::exit(1); + assert(false); +} + +thread_local ThreadID threadID; +std::vector> ThreadID::in_use(num_threads()); + +} +} // namespace diff --git a/lib/cdrc/cdrc/custom/threadid.h b/lib/cdrc/cdrc/custom/threadid.h new file mode 100644 index 000000000..7cddc57e7 --- /dev/null +++ b/lib/cdrc/cdrc/custom/threadid.h @@ -0,0 +1,33 @@ +#ifndef _INC_CDRC_THREADID_H +#define _INC_CDRC_THREADID_H + +#include +#include +#include +#include + +namespace cdrc +{ +namespace utils +{ + +size_t num_threads(); + +struct ThreadID { + static std::vector> in_use; // initialize to false + int tid; + + ThreadID(); + ~ThreadID() { + in_use[tid] = false; + } + + int getTID() const { return tid; } +}; + +extern thread_local ThreadID threadID; + +} +} // namespace + +#endif // _INC_THREADID_H diff --git a/lib/cdrc/cdrc/internal/counted_object.h b/lib/cdrc/cdrc/internal/counted_object.h new file mode 100644 index 000000000..8affb591b --- /dev/null +++ b/lib/cdrc/cdrc/internal/counted_object.h @@ -0,0 +1,114 @@ + +#ifndef CDRC_INTERNAL_COUNTED_OBJECT_H +#define CDRC_INTERNAL_COUNTED_OBJECT_H + + +#include +#include + +#include +#include +#include +#include + +#include "utils.h" + +namespace cdrc { +namespace internal { + +// An instance of an object of type T with an atomic reference count. +template +struct counted_object { + alignas(alignof(T)) unsigned char storage[sizeof(T)]; + utils::StickyCounter ref_cnt; + utils::StickyCounter weak_cnt; + +// In debug mode only, keep track of whether the object has been +// destroyed yet, to ensure that it is correctly destroyed +#ifndef NDEBUG + std::atomic disposed; +#endif + + template + explicit counted_object(Args &&... args) : ref_cnt(1), weak_cnt(1) { + new (&storage) T(std::forward(args)...); + } + + counted_object(const counted_object &) = delete; + counted_object(counted_object &&) = delete; + +#ifndef NDEBUG + ~counted_object() { + assert(disposed.load() == true); + } +#else + ~counted_object() = default; +#endif + + T *get() { return std::launder(reinterpret_cast(&storage)); } + const T *get() const { return std::launder(reinterpret_cast(&storage)); } + + // Destroy the managed object, but keep the control data intact + void dispose() { + get()->~T(); +#ifndef NDEBUG + disposed.store(true); +#endif + } + + auto get_use_count() const { return ref_cnt.load(); } + auto get_weak_count() const { return weak_cnt.load(); } + + bool add_refs(uint64_t count) { return ref_cnt.increment(count, std::memory_order_relaxed); } + + enum class EjectAction { + nothing, + delay, + destroy + }; + + // Release strong references to the object. If the strong reference count reaches zero, + // the managed object will be destroyed, and the weak reference count will be decremented + // by one. If this causes the weak reference count to hit zero, returns true, indicating + // that the caller should delete this object. + EjectAction release_refs(uint64_t count) { + + // A decrement-release + an acquire fence is recommended by Boost's documentation: + // https://www.boost.org/doc/libs/1_57_0/doc/html/atomic/usage_examples.html + // Alternatively, an acquire-release decrement would work, but might be less efficient since the + // acquire is only relevant if the decrement zeros the counter. + if (ref_cnt.decrement(count, std::memory_order_release)) { + std::atomic_thread_fence(std::memory_order_acquire); + // If there are no live weak pointers, we can immediately destroy + // everything. Otherwise, we have to defer the disposal of the + // managed object since an atomic_weak_ptr might be about to + // take a snapshot... + if (weak_cnt.load(std::memory_order_relaxed) == 1) { + // Immediately destroy the managed object and + // collect the control data, since no more + // live (strong or weak) references exist + dispose(); + return EjectAction::destroy; + } + else { + // At least one weak reference exists, so we have to + // delay the destruction of the managed object + return EjectAction::delay; + } + } + return EjectAction::nothing; + } + + bool add_weak_refs(uint64_t count) { return weak_cnt.increment(count, std::memory_order_relaxed); } + + // Release weak references to the object. If this causes the weak reference count + // to hit zero, returns true, indicating that the caller should delete this object. + bool release_weak_refs(uint64_t count) { + return weak_cnt.decrement(count, std::memory_order_release); + } +}; + +} +} + +#endif // CDRC_INTERNAL_COUNTED_OBJECT_H diff --git a/lib/cdrc/cdrc/internal/epoch_tracker.h b/lib/cdrc/cdrc/internal/epoch_tracker.h new file mode 100644 index 000000000..b269ca77c --- /dev/null +++ b/lib/cdrc/cdrc/internal/epoch_tracker.h @@ -0,0 +1,138 @@ + +#ifndef CDRC_INTERNAL_EPOCH_TRACKER_H +#define CDRC_INTERNAL_EPOCH_TRACKER_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +namespace cdrc { +namespace internal { + +struct epoch_tracker { + + using epoch_type = uint64_t; + constexpr static epoch_type no_epoch = std::numeric_limits::max(); + + struct alignas(128) Epoch : public std::atomic { + Epoch() : std::atomic(no_epoch) {} + + explicit Epoch(epoch_type x) : std::atomic(x) {} + + Epoch(const Epoch &other) : std::atomic(other.load()) {} + + ~Epoch() = default; + }; + + static epoch_tracker& instance() { + static epoch_tracker tracker; + return tracker; + } + + // Return the value of the current global epoch + epoch_type get_current_epoch() { + return global_epoch.load(); + } + + // Increment the global epoch + auto advance_global_epoch() { + return global_epoch.fetch_add(1); + } + + // Return the value of the earliest announced epoch. Returns + // numeric_limits::max() if no threads has an + // active announcement. + epoch_type get_min_announced_epoch() { + epoch_type answer = std::numeric_limits::max(); + auto nt = utils::num_threads(); + for (size_t i = 0; i < nt; i++) { + answer = std::min(answer, local_epoch[i].load(std::memory_order_acquire)); + } + return answer; + } + + // Apply the function f(i, e) to every currently announced epoch e at index i + template + void scan_announced_epochs(F &&f) { + std::atomic_thread_fence(std::memory_order_seq_cst); // TODO: is this necessary? + auto nt = utils::num_threads(); + for (size_t i = 0; i < nt; i++) { + epoch_type e = local_epoch[i].load(std::memory_order_acquire); + if(e != no_epoch) f(i, e); + } + } + + bool begin_critical_section() { + auto id = utils::threadID.getTID(); + if (critical_section[id]) { + return false; + } else { + auto current = global_epoch.load(std::memory_order_acquire); + local_epoch[id].exchange(current); + critical_section[id] = true; + return true; + } + } + + void end_critical_section() { + auto id = utils::threadID.getTID(); + assert(critical_section[id]); + assert(local_epoch[id].load() != no_epoch); + local_epoch[id].store(no_epoch, std::memory_order_release); + critical_section[id] = false; + } + + bool in_critical_section() { + auto id = utils::threadID.getTID(); + return critical_section[id]; + } + +private: + epoch_tracker() : global_epoch(0), local_epoch(utils::num_threads()), critical_section(utils::num_threads()) {} + + Epoch global_epoch; + std::vector local_epoch; + std::vector > critical_section; +}; + +} // namespace internal + +struct epoch_guard { + epoch_guard() : engaged(internal::epoch_tracker::instance().begin_critical_section()) {} + + ~epoch_guard() { + if (engaged) { internal::epoch_tracker::instance().end_critical_section(); } + } + + epoch_guard(const epoch_guard &) = delete; + + epoch_guard(epoch_guard &&) = delete; + + epoch_guard &operator=(const epoch_guard &) = delete; + + epoch_guard &operator=(epoch_guard &&) = delete; + +private: + bool engaged; +}; + +template +std::invoke_result_t with_epoch_guard(F &&f) { + epoch_guard g; + return std::invoke(std::forward(f)); +} + +} // namespace cdrc + +#endif // CDRC_INTERNAL_EPOCH_TRACKER_H diff --git a/lib/cdrc/cdrc/internal/fwd_decl.h b/lib/cdrc/cdrc/internal/fwd_decl.h new file mode 100644 index 000000000..39328473d --- /dev/null +++ b/lib/cdrc/cdrc/internal/fwd_decl.h @@ -0,0 +1,156 @@ + +#ifndef CDRC_INTERNAL_FWD_DECL_H +#define CDRC_INTERNAL_FWD_DECL_H + +#include + +#include "smr/acquire_retire.h" +#include "smr/acquire_retire_ebr.h" +#include "smr/acquire_retire_ibr.h" +#include "smr/acquire_retire_hyaline.h" + +namespace cdrc { + +namespace internal { + +// Blank policy that adds nothing +class default_pointer_policy { + public: + template + using pointer_type = std::add_pointer_t; + + template + class arc_ptr_policy { }; + + template + class rc_ptr_policy { }; + + template + class snapshot_ptr_policy { }; +}; + +template +using default_memory_manager = internal::acquire_retire; + +} // namespace internal + +// Definition of each pointer type with default memory manager backend + +template, typename pointer_policy = internal::default_pointer_policy> +class atomic_rc_ptr; + +template, typename pointer_policy = internal::default_pointer_policy> +class rc_ptr; + +template, typename pointer_policy = internal::default_pointer_policy> +class snapshot_ptr; + +template, typename pointer_policy = internal::default_pointer_policy> +class atomic_weak_ptr; + +template, typename pointer_policy = internal::default_pointer_policy> +class weak_ptr; + +template, typename pointer_policy = internal::default_pointer_policy> +class weak_snapshot_ptr; + +// Explicit hazard-pointer version of each type + +template +using atomic_rc_ptr_hp = atomic_rc_ptr>; + +template +using rc_ptr_hp = rc_ptr>; + +template +using snapshot_ptr_hp = snapshot_ptr>; + +template +using atomic_weak_ptr_hp = atomic_weak_ptr>; + +template +using weak_ptr_hp = weak_ptr>; + +template +using weak_snapshot_ptr_hp = weak_snapshot_ptr>; + +// Explicit EBR version of each type + +template +using atomic_rc_ptr_ebr = atomic_rc_ptr>; + +template +using rc_ptr_ebr = rc_ptr>; + +template +using snapshot_ptr_ebr = snapshot_ptr>; + +template +using atomic_weak_ptr_ebr = atomic_weak_ptr>; + +template +using weak_ptr_ebr = weak_ptr>; + +template +using weak_snapshot_ptr_ebr = weak_snapshot_ptr>; + + +// Explicit IBR version of each type + +template +using atomic_rc_ptr_ibr = atomic_rc_ptr>; + +template +using rc_ptr_ibr = rc_ptr>; + +template +using snapshot_ptr_ibr = snapshot_ptr>; + +template +using atomic_weak_ptr_ibr = atomic_weak_ptr>; + +template +using weak_ptr_ibr = weak_ptr>; + +template +using weak_snapshot_ptr_ibr = weak_snapshot_ptr>; + + +// Explicit Hyaline version of each type + +template +using atomic_rc_ptr_hyaline = atomic_rc_ptr>; + +template +using rc_ptr_hyaline = rc_ptr>; + +template +using snapshot_ptr_hyaline = snapshot_ptr>; + +template +using atomic_weak_ptr_hyaline = atomic_weak_ptr>; + +template +using weak_ptr_hyaline = weak_ptr>; + +template +using weak_snapshot_ptr_hyaline = weak_snapshot_ptr>; + + +// Memory management backend aliases + +template +using hp_backend = internal::acquire_retire; + +template +using ebr_backend = internal::acquire_retire_ebr; + +template +using ibr_backend = internal::acquire_retire_ibr; + +template +using hyaline_backend = internal::acquire_retire_hyaline; + +} // namespace cdrc + +#endif //CDRC_INTERNAL_FWD_DECL_H diff --git a/lib/cdrc/cdrc/internal/memory_manager_base.h b/lib/cdrc/cdrc/internal/memory_manager_base.h new file mode 100644 index 000000000..387ea593b --- /dev/null +++ b/lib/cdrc/cdrc/internal/memory_manager_base.h @@ -0,0 +1,174 @@ + +#ifndef CDRC_INTERNAL_MEMORY_MANAGER_BASE_H +#define CDRC_INTERNAL_MEMORY_MANAGER_BASE_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "counted_object.h" +#include "utils.h" + +namespace cdrc { +namespace internal { + +constexpr static size_t num_retire_types = 3; + +enum class RetireType { + decrement_strong_count, + decrement_weak_count, + dispose +}; + +template +struct basic_acquired_pointer { + public: + + basic_acquired_pointer() : value(nullptr) {} + + /* implicit */ basic_acquired_pointer(U value_) : value(value_) {} + + basic_acquired_pointer(basic_acquired_pointer&& other) noexcept : value(other.value) { + other.value = nullptr; + } + + basic_acquired_pointer &operator=(basic_acquired_pointer&& other) noexcept { + value = other.value; + other.value = nullptr; + } + + void swap(basic_acquired_pointer &other) { + std::swap(value, other.value); + } + + U& get() { return value; } + + U get() const { return value; } + + [[nodiscard]] bool is_protected() const { return true; } + + void clear_protection() {} + + void clear() { value = nullptr; } + + private: + U value; +}; + +template +struct memory_manager_base { + + using counted_object_t = counted_object; + using counted_ptr_t = std::add_pointer_t; + + explicit memory_manager_base(size_t num_threads) : num_allocated(num_threads) {} + + void dispose(counted_ptr_t ptr) { + assert(ptr->get_use_count() == 0); + ptr->dispose(); + if (ptr->release_weak_refs(1)) destroy(ptr); + } + + void destroy(counted_ptr_t ptr) { + assert(ptr->get_use_count() == 0); + assert(ptr->disposed.load() == true); + static_cast(this)->delete_object(ptr); + } + + void retire(counted_ptr_t ptr, RetireType type) { + static_cast(this)->retire(ptr, type); + } + + // Perform an eject action. This can correspond to any action that + // should be delayed until the ptr is no longer protected + void eject(counted_ptr_t ptr, RetireType type) { + assert(ptr != nullptr); + + if (type == RetireType::decrement_strong_count) { + decrement_ref_cnt(ptr); + } + else if (type == RetireType::decrement_weak_count) { + decrement_weak_cnt(ptr); + } + else { + assert(type == RetireType::dispose); + dispose(ptr); + } + } + + bool increment_ref_cnt(counted_ptr_t ptr) { + assert(ptr != nullptr); + return ptr->add_refs(1); + } + + bool increment_weak_cnt(counted_ptr_t ptr) { + assert(ptr != nullptr); + return ptr->add_weak_refs(1); + } + + void decrement_ref_cnt(counted_ptr_t ptr) { + assert(ptr != nullptr); + assert(ptr->get_use_count() >= 1); + auto result = ptr->release_refs(1); + if (result == counted_object_t::EjectAction::destroy) { + destroy(ptr); + } else if (result == counted_object_t::EjectAction::delay) { + retire(ptr, RetireType::dispose); + } + } + + void decrement_weak_cnt(counted_ptr_t ptr) { + assert(ptr != nullptr); + assert(ptr->get_weak_count() >= 1); + if (ptr->release_weak_refs(1)) { + destroy(ptr); + } + } + + void delayed_decrement_ref_cnt(counted_ptr_t ptr) { + assert(ptr->get_use_count() >= 1); + retire(ptr, RetireType::decrement_strong_count); + } + + void delayed_decrement_weak_cnt(counted_ptr_t ptr) { + assert(ptr->get_weak_count() >= 1); + retire(ptr, RetireType::decrement_weak_count); + } + + void decrement_allocations() { + int tid = utils::threadID.getTID(); + num_allocated[tid].store(num_allocated[tid].load(std::memory_order_seq_cst) - 1, std::memory_order_seq_cst); + } + + void increment_allocations() { + int tid = utils::threadID.getTID(); + num_allocated[tid].store(num_allocated[tid].load(std::memory_order_seq_cst) + 1, std::memory_order_seq_cst); + } + + size_t currently_allocated() { + size_t total = 0; + for (size_t t = 0; t < utils::num_threads(); t++) { + total += num_allocated[t].load(std::memory_order_acquire); + } + return total; + } + + std::vector>> num_allocated; +}; + +} // namespace internal +} // namespace cdrc + +// Specialize hash for the RetireType enum so it can be used in a hashtable +template<> +struct std::hash { + std::size_t operator()(cdrc::internal::RetireType t) const { return static_cast(t); } +}; + + +#endif // CDRC_INTERNAL_MEMORY_MANAGER_BASE_H diff --git a/lib/cdrc/cdrc/internal/smr/acquire_retire.h b/lib/cdrc/cdrc/internal/smr/acquire_retire.h new file mode 100644 index 000000000..5d9a14516 --- /dev/null +++ b/lib/cdrc/cdrc/internal/smr/acquire_retire.h @@ -0,0 +1,339 @@ + +#ifndef CDRC_SMR_ACQUIRE_RETIRE_H +#define CDRC_SMR_ACQUIRE_RETIRE_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../counted_object.h" +#include "../memory_manager_base.h" +#include "../utils.h" + +namespace cdrc { + +namespace internal { + +// An interface for safe memory reclamation that protects reference-counted +// resources by deferring their reference count decrements until no thread +// is still reading them. +// +// Unlike hazard pointers, acquire-retire allows multiple concurrent retires +// of the same handle, which is what makes it suitable for managing reference +// counted pointers, since multiple copies of the same reference counted pointer +// may need to be destructed (i.e., have their counter decremented) concurrently. +// +// T = The underlying type of the object being protected +// snapshot_slots = The number of additional announcement slots available for +// snapshot pointers. More allows more snapshots to be alive +// at a time, but makes reclamation slower +// eject_delay = The maximum number of deferred ejects that will be held by +// any one worker thread is at most eject_delay * #threads. +// +template +struct acquire_retire : public memory_manager_base> { + + using base = memory_manager_base>; + + using base::increment_allocations; + using base::decrement_allocations; + using base::increment_ref_cnt; + using base::decrement_weak_cnt; + using base::eject; + + private: + + using counted_object_t = counted_object; + using counted_ptr_t = std::add_pointer_t; + + // Align to cache line boundary to avoid false sharing + struct alignas(128) LocalSlot { + std::atomic announcement; + std::array, snapshot_slots> snapshot_announcements{}; + alignas(128) size_t last_free{0}; + + LocalSlot() : announcement(nullptr) { + for (auto &a : snapshot_announcements) { + std::atomic_init(&a, nullptr); + } + } + }; + + public: + + static acquire_retire& instance() { + static acquire_retire ar{utils::num_threads()}; + return ar; + } + + template + counted_ptr_t create_object(Args &&... args) { + increment_allocations(); + return new counted_object_t(std::forward(args)...); + } + + void delete_object(counted_ptr_t p) { + delete p; + decrement_allocations(); + } + + // An RAII wrapper around an acquired handle. Automatically + // releases the handle when the wrapper goes out of scope. + template + struct acquired_pointer { + public: + friend struct acquire_retire; + + acquired_pointer() : value(nullptr), slot(nullptr) {} + + acquired_pointer(U value_, std::atomic* slot_) : value(value_), slot(slot_) {} + + acquired_pointer(acquired_pointer&& other) noexcept : value(other.value), slot(other.slot) { + other.value = nullptr; + other.slot = nullptr; + } + + ~acquired_pointer() { clear_protection(); } + + acquired_pointer& operator=(acquired_pointer&& other) noexcept { + value = other.value; + slot = other.slot; + other.value = nullptr; + other.slot = nullptr; + } + + void swap(acquired_pointer &other) { + std::swap(value, other.value); + std::swap(slot, other.slot); + } + + U& get() { + return value; + } + + U get() const { + return value; + } + + bool is_protected() const { + return slot != nullptr && value != nullptr; + } + + void clear_protection() { + if (value != nullptr && slot != nullptr) { + slot->store(nullptr, std::memory_order_release); + } + } + + void clear() { + clear_protection(); + value = nullptr; + slot = nullptr; + } + + private: + U value; + std::atomic *slot; + }; + + explicit acquire_retire(size_t num_threads) : + base(num_threads), + announcement_slots(num_threads), + in_progress(num_threads), + deferred_destructs(num_threads), + amortized_work(num_threads) {} + + template + [[nodiscard]] acquired_pointer acquire(const std::atomic *p) { + auto id = utils::threadID.getTID(); + U result; + do { + result = p->load(std::memory_order_seq_cst); + announcement_slots[id].announcement.store(static_cast(result), std::memory_order_seq_cst); + } while (p->load(std::memory_order_seq_cst) != result); + return acquired_pointer(result, &announcement_slots[id].announcement); + } + + // Like acquire, but assuming that the caller already has a + // copy of the handle and knows that it is protected + template + [[nodiscard]] acquired_pointer reserve(U p) { + auto id = utils::threadID.getTID(); + announcement_slots[id].announcement.store(static_cast(p), + std::memory_order_seq_cst); // TODO: memory_order_release could be sufficient here + return acquired_pointer(p, &announcement_slots[id].announcement); + } + + // Dummy function for when we need to conditionally reserve + // something, but might need to reserve nothing + template + [[nodiscard]] acquired_pointer reserve_nothing() const { + return {}; + } + + template + [[nodiscard]] acquired_pointer protect_snapshot(const std::atomic *p) { + auto *slot = get_free_slot(); + + // If no snapshot slot is available, just increment the reference count + if (slot == nullptr) { + while (true) { + auto a = acquire(p); + if (a.get() && increment_ref_cnt(a.get())) return acquired_pointer(a.get(), nullptr); + else if (a.get() == nullptr || p->load() == a.get()) return acquired_pointer(nullptr, nullptr); + } + } + + U result; + do { + result = p->load(std::memory_order_seq_cst); + PARLAY_PREFETCH(result, 0, 0); + if (result == nullptr) { + slot->store(nullptr, std::memory_order_release); + return acquired_pointer(result, nullptr); + } + slot->store(static_cast(result), std::memory_order_seq_cst); + } while (p->load(std::memory_order_seq_cst) != result); + return acquired_pointer(result, slot); + } + + [[nodiscard]] std::atomic *get_free_slot() { + assert(snapshot_slots != 0); + auto id = utils::threadID.getTID(); + for (size_t i = 0; i < snapshot_slots; i++) { + if (announcement_slots[id].snapshot_announcements[i].load(std::memory_order_acquire) == nullptr) { + return std::addressof(announcement_slots[id].snapshot_announcements[i]); + } + } + return nullptr; + } + + void release() { + auto id = utils::threadID.getTID(); + auto &slot = announcement_slots[id].announcement; + slot.store(nullptr, std::memory_order_release); + } + + void retire(counted_ptr_t p, RetireType type) { + auto id = utils::threadID.getTID(); + deferred_destructs[id].emplace_back(p, type); + work_toward_deferred_decrements(1); + } + + // Perform any remaining deferred destruction. Need to be very careful + // about additional objects being queued for deferred destruction by + // an object that was just destructed. + ~acquire_retire() { + in_progress.assign(in_progress.size(), true); + + // Loop because the destruction of one object could trigger the deferred + // destruction of another object (possibly even in another thread), and + // so on recursively. + while (std::any_of(deferred_destructs.begin(), deferred_destructs.end(), + [](const auto &v) { return !v.empty(); })) { + + // Move all of the contents from the deferred destruction lists + // into a single local list. We don't want to just iterate the + // deferred lists because a destruction may trigger another + // deferred destruction to be added to one of the lists, which + // would invalidate its iterators + std::vector> destructs; + for (auto &v : deferred_destructs) { + destructs.insert(destructs.end(), v.begin(), v.end()); + v.clear(); + } + + // Perform all of the pending deferred destructions + for (const auto& x : destructs) { + eject(x.first, x.second); + } + } + } + + private: + // Apply the function f to every currently announced handle + template + void scan_slots(F &&f) { + std::atomic_thread_fence(std::memory_order_seq_cst); + for (const auto &announcement_slot : announcement_slots) { + auto x = announcement_slot.announcement.load(std::memory_order_seq_cst); + if (x != nullptr) f(x); + for (const auto &free_slot : announcement_slot.snapshot_announcements) { + auto y = free_slot.load(std::memory_order_seq_cst); + if (y != nullptr) f(y); + } + } + } + + void work_toward_deferred_decrements(size_t work = 1) { + auto id = utils::threadID.getTID(); + amortized_work[id] = amortized_work[id] + work; + auto threshold = std::max(30, eject_delay * amortized_work.size()); // Always attempt at least 30 ejects + while (!in_progress[id] && amortized_work[id] >= threshold) { + amortized_work[id] = 0; + if (deferred_destructs[id].size() == 0) break; // nothing to collect + in_progress[id] = true; + auto deferred = AlignedVector>(std::move(deferred_destructs[id])); + + // We need a custom hash because the standard doesn't know how to hash enum types... + struct Hash { + std::size_t operator()(std::pair t) const { + return std::hash{}(t.first) ^ std::hash{}(static_cast(t.second)); + } + }; + + // Since there can be multiple kinds of deferred actions (delayed ejects), each announcement + // needs to be able to protect each kind of action, since announcements do not specify which + // actions they wish to protect against. + std::unordered_map, unsigned int, Hash> announced; + scan_slots([&](auto reserved) { + for (size_t i = 0; i < num_retire_types; i++) { + // The first announcement needs to protect up to two actions + auto& cnt = announced[std::make_pair(reserved, static_cast(i))]; + if (cnt) cnt++; + else cnt = 2; + } + }); + + // For a given deferred decrement, we first check if it is announced, and, if so, + // we defer it again. If it is not announced, it can be safely applied. If an + // object is deferred / announced multiple times, each announcement only protects + // against one of the deferred decrements, so for each object, the amount of + // decrements applied in total will be #deferred - #announced + auto f = [this, &announced](const auto& x) { + auto it = announced.find(x); + if (it == announced.end()) { + eject(x.first, x.second); + return true; + } else { + if (--(it->second) == 0) announced.erase(it); + return false; + } + }; + + // Remove the deferred decrements that are successfully applied + deferred.erase(remove_if(deferred.begin(), deferred.end(), f), deferred.end()); + deferred_destructs[id].insert(deferred_destructs[id].end(), deferred.begin(), deferred.end()); + in_progress[id] = false; + } + } + + std::vector announcement_slots; // Announcement array slots + std::vector in_progress; // Local flags to prevent reentrancy while destructing + std::vector>> deferred_destructs; // Thread-local lists of pending deferred destructs + std::vector amortized_work; // Amortized work to pay for ejecting deferred destructs +}; + +} // namespace internal + +} // namespace cdrc + +#endif // CDRC_SMR_ACQUIRE_RETIRE_H diff --git a/lib/cdrc/cdrc/internal/smr/acquire_retire_ebr.h b/lib/cdrc/cdrc/internal/smr/acquire_retire_ebr.h new file mode 100644 index 000000000..bd79a34cb --- /dev/null +++ b/lib/cdrc/cdrc/internal/smr/acquire_retire_ebr.h @@ -0,0 +1,209 @@ + +#ifndef CDRC_SMR_ACQUIRE_RETIRE_EBR_H +#define CDRC_SMR_ACQUIRE_RETIRE_EBR_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../counted_object.h" +#include "../epoch_tracker.h" +#include "../memory_manager_base.h" +#include "../utils.h" + +namespace cdrc { + +namespace internal { + +// An interface for safe memory reclamation that protects reference-counted +// resources by deferring their reference count decrements until no thread +// is still reading them. +// +// This implementation uses epoch-based reclamation, in which the user is +// responsible for acquiring a guard before performing any reads or writes +// to the shared pointer. The guard is acquired by holding a local instance +// of an "epoch_guard", like so +// +// { +// cdrc::epoch_guard g; +// // critical code +// } +// +// T = The underlying type of the object being protected +// epoch_frequency = How often to update the global epoch. More often (lower value) +// will reduce performance but decrease memory usage. +// eject_delay = The maximum number of deferred ejects that will be held by +// any one worker thread is at most eject_delay * #threads. +// +template +struct acquire_retire_ebr : public memory_manager_base> { + + using base = memory_manager_base>; + + using base::increment_allocations; + using base::decrement_allocations; + using base::increment_ref_cnt; + using base::eject; + using base::decrement_weak_cnt; + +private: + using counted_object_t = counted_object; + using counted_ptr_t = std::add_pointer_t; + +public: + + static acquire_retire_ebr& instance() { + static acquire_retire_ebr ar{utils::num_threads()}; + return ar; + } + + template + counted_ptr_t create_object(Args &&... args) { + increment_allocations(); + work_toward_advancing_epoch(1); + return new counted_object_t(std::forward(args)...); + } + + void delete_object(counted_ptr_t p) { + delete p; + decrement_allocations(); + } + + struct RetiredObj { + counted_ptr_t obj; uint64_t retireTS; RetireType type; + RetiredObj(counted_ptr_t obj, uint64_t ts, RetireType type_) : obj(obj), retireTS(ts), type(type_) {} + }; + + template + using acquired_pointer = basic_acquired_pointer; + + explicit acquire_retire_ebr(size_t num_threads_) : + base(num_threads_), + num_threads(num_threads_), + in_progress(num_threads), + deferred_destructs(num_threads), + eject_work(num_threads), + epoch_work(num_threads) {} + + template + [[nodiscard]] acquired_pointer acquire(const std::atomic *p) { + return {p->load(std::memory_order_acquire)}; + } + + // Like acquire, but assuming that the caller already has a + // copy of the handle and knows that it is protected + template + [[nodiscard]] acquired_pointer reserve(U p) { + return {p}; + } + + // Dummy function for when we need to conditionally reserve + // something, but might need to reserve nothing + template + [[nodiscard]] acquired_pointer reserve_nothing() const { + return {}; + } + + template + [[nodiscard]] acquired_pointer protect_snapshot(const std::atomic *p) { + auto ptr = p->load(std::memory_order_acquire); + if (ptr != nullptr && ptr->get_use_count() == 0) ptr = nullptr; + return {ptr}; + } + + void release() { } + + void retire(counted_ptr_t p, RetireType type) { + auto id = utils::threadID.getTID(); + deferred_destructs[id].emplace_back(p, epoch_tracker::instance().get_current_epoch(), type); + work_toward_ejects(1); + } + + // Perform any remaining deferred destruction. Need to be very careful + // about additional objects being queued for deferred destruction by + // an object that was just destructed. + ~acquire_retire_ebr() { + in_progress.assign(in_progress.size(), true); + + // Loop because the destruction of one object could trigger the deferred + // destruction of another object (possibly even in another thread), and + // so on recursively. + while (std::any_of(deferred_destructs.begin(), deferred_destructs.end(), + [](const auto &v) { return !v.empty(); })) { + + // Move all of the contents from the deferred destruction lists + // into a single local list. We don't want to just iterate the + // deferred lists because a destruction may trigger another + // deferred destruction to be added to one of the lists, which + // would invalidate its iterators + std::vector> destructs; + for (auto &v : deferred_destructs) { + for (const auto& x : v) { + destructs.emplace_back(x.obj, x.type); + } + v.clear(); + } + + // Perform all of the pending deferred ejects + for (auto [x,type] : destructs) { + eject(x,type); + } + } + } + +private: + + void work_toward_advancing_epoch(size_t work = 1) { + auto id = utils::threadID.getTID(); + epoch_work[id] = epoch_work[id] + work; + if(epoch_work[id] >= epoch_frequency * num_threads) { + epoch_work[id] = 0; + epoch_tracker::instance().advance_global_epoch(); + } + } + + void work_toward_ejects(size_t work = 1) { + auto id = utils::threadID.getTID(); + eject_work[id] = eject_work[id] + work; + auto threshold = std::max(30, eject_delay * num_threads); // Always attempt at least 30 ejects + while (!in_progress[id] && eject_work[id] >= threshold) { + eject_work[id] = 0; + if (deferred_destructs[id].size() == 0) break; // nothing to collect + in_progress[id] = true; + auto deferred = std::vector(std::move(deferred_destructs[id])); + auto min_epoch = epoch_tracker::instance().get_min_announced_epoch(); + + auto f = [this, min_epoch](const auto& x) { + if (x.retireTS < min_epoch) { + eject(x.obj, x.type); + return true; + } + return false; + }; + + // Remove the deferred decrements that are successfully applied + deferred.erase(remove_if(deferred.begin(), deferred.end(), f), deferred.end()); + deferred_destructs[id].insert(deferred_destructs[id].end(), deferred.begin(), deferred.end()); + in_progress[id] = false; + } + } + + size_t num_threads; + std::vector in_progress; // Local flags to prevent reentrancy while destructing + std::vector> deferred_destructs; // Thread-local lists of pending deferred destructs + std::vector eject_work; // Amortized work to pay for ejecting deferred destructs + std::vector epoch_work; // Amortized work to pay for incrementing the epoch +}; + + +} // namespace internal + +} // namespace cdrc + +#endif // CDRC_SMR_ACQUIRE_RETIRE_EBR_H diff --git a/lib/cdrc/cdrc/internal/smr/acquire_retire_hyaline.h b/lib/cdrc/cdrc/internal/smr/acquire_retire_hyaline.h new file mode 100644 index 000000000..f5c6318f6 --- /dev/null +++ b/lib/cdrc/cdrc/internal/smr/acquire_retire_hyaline.h @@ -0,0 +1,347 @@ + +#ifndef CDRC_SMR_ACQUIRE_RETIRE_HYALINE_H +#define CDRC_SMR_ACQUIRE_RETIRE_HYALINE_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../counted_object.h" +#include "../memory_manager_base.h" +#include "../utils.h" + +namespace cdrc { + +namespace internal { + +struct hyaline_tracker { + + struct alignas(128) Reservation; + + struct Node { // TODO: unclear if this should be aligned + void* obj; + union { // Each node takes 3 memory words + std::atomic refc; // REFS: Refer. counter + Node* bnext; // SLOT: Next node + }; + union { + Node* next; // SLOT: After retiring + std::function* decrement; + }; + Node* blink; // REFS: First SLOT node, + + Node(void* obj) : obj(obj), bnext(nullptr), next(nullptr), blink(nullptr) {} + }; + + inline static Node* invptr = reinterpret_cast(0x1); + inline static const int64_t REFC_PROTECT = (1ull << 62); + + struct alignas(128) Batch { + Node* first; + Node* refs; + size_t counter; + Batch() : first(nullptr), refs(nullptr), counter(0) {} + }; + + struct alignas(128) Reservation { + std::atomic list; + Reservation() : list(invptr) {} + }; + + static hyaline_tracker &instance() { + static hyaline_tracker tracker; + return tracker; + } + + bool begin_critical_section() { + auto id = utils::threadID.getTID(); + if (critical_section[id]) { + return false; + } else { + rsrv[id].list.exchange(nullptr); + critical_section[id] = true; + return true; + } + } + + void end_critical_section() { + auto id = utils::threadID.getTID(); + assert(critical_section[id]); + Node* p = rsrv[id].list.exchange(invptr); + assert(p != invptr); + traverse(p); + critical_section[id] = false; + } + + bool in_critical_section() { + auto id = utils::threadID.getTID(); + return critical_section[id]; + } + + void add_batch(const Batch& batch) { + batch.refs->blink = batch.first; + Node* curr = batch.first; + int64_t cnt = -REFC_PROTECT; + for(size_t i = 0; i < utils::num_threads(); i++) { + while(true) { + Node* prev = rsrv[i].list.load(); + if(prev == invptr) break; + curr->next = prev; + if(rsrv[i].list.compare_exchange_strong(prev, curr)) { + cnt++; + break; + } + } + curr = curr->bnext; + } + if(batch.refs->refc.fetch_add(cnt) == -cnt) // Finish + free_batch(batch.refs); // retiring: change refc // TODO: fix recursion problem + } + +private: + void traverse(Node* next) { + while(next != nullptr) { + Node* curr = next; + assert(curr != invptr); + next = curr->next; + Node* refs = curr->blink; + if(refs->refc.fetch_add(-1) == 1) free_batch(refs); + } + } + + void free_batch(Node* refs) { + std::function& decrement = *(refs->decrement); + Node* n = refs->blink; + do { + Node* node = n; + // refc and bnext overlap and are 0 + // (nullptr) for the last REFS node + assert(n != invptr); + n = n->bnext; + decrement(node->obj); + delete node; + } while(n != nullptr); + } + +public: + hyaline_tracker() : critical_section(utils::num_threads()), + rsrv(utils::num_threads()) {} + + std::vector> critical_section; + std::vector rsrv; +}; + +} // namespace internal + +struct hyaline_guard { + hyaline_guard() : engaged(internal::hyaline_tracker::instance().begin_critical_section()) {} + + ~hyaline_guard() { + if (engaged) { internal::hyaline_tracker::instance().end_critical_section(); } + } + + hyaline_guard(const hyaline_guard &) = delete; + + hyaline_guard(hyaline_guard &&) = delete; + + hyaline_guard &operator=(const hyaline_guard &) = delete; + + hyaline_guard &operator=(hyaline_guard &&) = delete; + +private: + bool engaged; +}; + +template +std::invoke_result_t with_hyaline_guard(F&& f) { + hyaline_guard g; + return std::invoke(std::forward(f)); +} + +namespace internal { + +// An interface for safe memory reclamation that protects reference-counted +// resources by deferring their reference count decrements until no thread +// is still reading them. +// +// This implementation uses Hyaline, in which the user is +// responsible for acquiring a guard before performing any reads or writes +// to the shared pointer. The guard is acquired by holding a local instance +// of a "hyaline_guard", like so +// +// { +// cdrc::hyaline_guard g; +// // critical code +// } +// +// T = The underlying type of the object being protected +// batch_size accumulate (batch_size*num_threads)+1 nodes before announcing batch +// +// NOTE: handling recursion was tricky +// Note: Hyaline doesn't suffer as much from the recursive destruct problem. No maybe it +// still does because batching. But it might be easier to fix. +template +struct acquire_retire_hyaline : public memory_manager_base> { + + using base = memory_manager_base>; + + using base::increment_allocations; + using base::decrement_allocations; + + using Node = hyaline_tracker::Node; + using Batch = hyaline_tracker::Batch; + +private: + using counted_object_t = counted_object; + using counted_ptr_t = std::add_pointer_t; + +public: + + static acquire_retire_hyaline& instance() { + static acquire_retire_hyaline ar{utils::num_threads()}; + return ar; + } + + template + counted_ptr_t create_object(Args &&... args) { + increment_allocations(); + return new counted_object_t(std::forward(args)...); + } + + void delete_object(counted_ptr_t p) { + delete p; + decrement_allocations(); + } + + template + using acquired_pointer = basic_acquired_pointer; + + explicit acquire_retire_hyaline(size_t num_threads_) : + base(num_threads_), + num_threads(num_threads_), + local_batch(num_threads), + in_progress(num_threads, false) + { + hyaline_tracker::instance(); // touch the tracker to force it to initialize before this object, + // otherwise, destruction order may be wrong since acquire_retire_hyaline + // refers to hyaline_tracker in its destructor, and hence hyaline tracker + // MUST be destructed after (and hence constructed before!) + + strong_eject = [this](void* obj) { + this->eject(reinterpret_cast(obj), RetireType::decrement_strong_count); + }; + weak_eject = [this](void* obj) { + this->eject(reinterpret_cast(obj), RetireType::decrement_weak_count); + }; + dispose_eject = [this](void* obj) { + this->eject(reinterpret_cast(obj), RetireType::dispose); + }; + } + + template + [[nodiscard]] acquired_pointer acquire(const std::atomic *p) { + return acquired_pointer(p->load(std::memory_order_acquire)); + } + + // Like acquire, but assuming that the caller already has a + // copy of the handle and knows that it is protected + template + [[nodiscard]] acquired_pointer reserve(U p) { + return acquired_pointer(p); + } + + // Dummy function for when we need to conditionally reserve + // something, but might need to reserve nothing + template + [[nodiscard]] acquired_pointer reserve_nothing() const { + return acquired_pointer(); + } + + template + [[nodiscard]] acquired_pointer protect_snapshot(const std::atomic *p) { + auto ptr = p->load(std::memory_order_acquire); + if (ptr != nullptr && ptr->get_use_count() == 0) ptr = nullptr; + return {ptr}; + } + + void release() {} + + void retire(counted_ptr_t p, RetireType type) { + auto id = utils::threadID.getTID(); + if(p == nullptr) {return;} + + Batch& batch = local_batch[id]; + Node* node = new Node(reinterpret_cast(p)); + if(!batch.first) { // the REFS node + batch.refs = node; + node->refc.store(hyaline_tracker::REFC_PROTECT, std::memory_order_release); + if (type == RetireType::decrement_strong_count) node->decrement = &strong_eject; + else if (type == RetireType::decrement_weak_count) node->decrement = &weak_eject; + else node->decrement = &dispose_eject; + } else { // SLOT nodes + node->blink = batch.refs; // points to REFS + node->bnext = batch.first; + } + batch.first = node; + batch.counter++; + // Must have MAX_THREADS+1 nodes to insert to + // MAX_THREADS lists, exit if do not have enough + while(!in_progress[id] && batch.counter > batch_size*num_threads) { + const Batch batch_copy = batch; + batch.first = nullptr; + batch.counter = 0; + // if(in_progress) std::cout << "recursive call to retire" << std::endl; + in_progress[id] = true; + hyaline_tracker::instance().add_batch(batch_copy); + in_progress[id] = false; + } + } + + // Perform any remaining deferred destruction. Need to be very careful + // about additional objects being queued for deferred destruction by + // an object that was just destructed. + ~acquire_retire_hyaline() { + auto id = utils::threadID.getTID(); + do { + for (size_t i = 0; i < num_threads; i++) { + assert(hyaline_tracker::instance().rsrv[i].list.load() == hyaline_tracker::invptr); + while(local_batch[i].first != nullptr) { + Batch& batch = local_batch[i]; + const Batch batch_copy = batch; + batch.first = nullptr; + batch.counter = 0; + Node* node = batch_copy.first; + while(node != nullptr) { + Node* next = node->bnext; + void* obj = node->obj; + in_progress[id] = true; + (*batch_copy.refs->decrement)(obj); + in_progress[id] = false; + delete node; + if(node == batch_copy.refs) break; + node = next; + } + } + } + } while(local_batch[id].first != nullptr); + } + +private: + alignas(128) size_t num_threads; + alignas(128) std::vector local_batch; + alignas(128) std::function strong_eject, weak_eject, dispose_eject; + std::vector in_progress; // Local flags to prevent reentrancy while destructing +}; + + +} // namespace internal + +} // namespace cdrc + +#endif // CDRC_SMR_ACQUIRE_RETIRE_HYALINE_H diff --git a/lib/cdrc/cdrc/internal/smr/acquire_retire_ibr.h b/lib/cdrc/cdrc/internal/smr/acquire_retire_ibr.h new file mode 100644 index 000000000..10ded6af0 --- /dev/null +++ b/lib/cdrc/cdrc/internal/smr/acquire_retire_ibr.h @@ -0,0 +1,264 @@ +#ifndef CDRC_SMR_ACQUIRE_RETIRE_IBR_H +#define CDRC_SMR_ACQUIRE_RETIRE_IBR_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../counted_object.h" +#include "../epoch_tracker.h" +#include "../memory_manager_base.h" +#include "../utils.h" + +namespace cdrc { + +namespace internal { + +// An interface for safe memory reclamation that protects reference-counted +// resources by deferring their reference count decrements until no thread +// is still reading them. +// +// This implementation uses interval-based reclamation, in which the user is +// responsible for acquiring a guard before performing any reads or writes +// to the shared pointer. The guard is acquired by holding a local instance +// of an "epoch_guard", like so +// +// { +// cdrc::epoch_guard g; +// // critical code +// } +// +// T = The underlying type of the object being protected +// epoch_frequency = How often to update the global epoch. More often (lower value) +// will reduce performance but decrease memory usage. +// eject_delay = The maximum number of deferred ejects that will be held by +// any one worker thread is at most eject_delay * #threads. +// +template +struct acquire_retire_ibr : public memory_manager_base> { + + using base = memory_manager_base>; + + using base::increment_allocations; + using base::decrement_allocations; + using base::increment_ref_cnt; + using base::eject; + + inline static const uint64_t INVALID_TS = 0; + + private: + using counted_object_t = counted_object; + using counted_ptr_t = std::add_pointer_t; + + // Align to cache line boundary to avoid false sharing + struct alignas(64) LocalSlot { + std::atomic endTS_ann; + + LocalSlot() : endTS_ann(INVALID_TS) {} + }; + + public: + + static acquire_retire_ibr& instance() { + static acquire_retire_ibr ar{utils::num_threads()}; + return ar; + } + + // Augments a reference-counted object with a birth timestamp field that is + // initialized with the value of the current epoch when the object is created + struct stamped_counted_object : public counted_object { + template + explicit stamped_counted_object(uint64_t t, Args&&... args) + : counted_object(std::forward(args)...), birthTS(t) {} + uint64_t birthTS; + }; + + uint64_t get_birth_timestamp(counted_ptr_t p) { + return static_cast(p)->birthTS; + } + + template + counted_ptr_t create_object(Args &&... args) { + increment_allocations(); + work_toward_advancing_epoch(1); + return new stamped_counted_object(epoch_tracker::instance().get_current_epoch(), std::forward(args)...); + } + + void delete_object(counted_ptr_t p) { + delete static_cast(p); + decrement_allocations(); + } + + struct RetiredObj { + counted_ptr_t obj; uint64_t birthTS; uint64_t retireTS; RetireType type; + RetiredObj(counted_ptr_t obj, uint64_t birthTS, uint64_t retireTS, RetireType type) : + obj(obj), birthTS(birthTS), retireTS(retireTS), type(type) {} + }; + + template + using acquired_pointer = basic_acquired_pointer; + + + explicit acquire_retire_ibr(size_t num_threads_) : + base(num_threads_), + num_threads(num_threads_), + announcement_slots(num_threads), + in_progress(num_threads), + deferred_destructs(num_threads), + eject_work(num_threads), + epoch_work(num_threads) {} + + template + [[nodiscard]] acquired_pointer acquire(const std::atomic *p) { + return protect_snapshot(p); + } + + // Like acquire, but assuming that the caller already has a + // copy of the handle and knows that it is protected + template + [[nodiscard]] acquired_pointer reserve(U p) { + //if(p == nullptr) return {p, nullptr}; + auto id = utils::threadID.getTID(); + LocalSlot& slot = announcement_slots[id]; + uint64_t curTS = epoch_tracker::instance().get_current_epoch(); + if(slot.endTS_ann.load() != curTS) + slot.endTS_ann.store(curTS, std::memory_order_release); + return {p}; + } + + // Dummy function for when we need to conditionally reserve + // something, but might need to reserve nothing + template + [[nodiscard]] acquired_pointer reserve_nothing() const { + return {}; + } + + template + [[nodiscard]] acquired_pointer protect_snapshot(const std::atomic *p) { + auto id = utils::threadID.tid; + auto p_epoch = announcement_slots[id].endTS_ann.load(); + while(true) { + U result = p->load(std::memory_order_seq_cst); + uint64_t curTS = epoch_tracker::instance().get_current_epoch(); + if(p_epoch == curTS) { + if (result != nullptr && result->get_use_count() == 0) return {nullptr}; + return {result}; + } + else { + announcement_slots[id].endTS_ann.exchange(curTS); + p_epoch = curTS; + } + } + } + + void release() {} + + void retire(counted_ptr_t p, RetireType type) { + auto id = utils::threadID.getTID(); + deferred_destructs[id].push_back(RetiredObj(p, get_birth_timestamp(p), epoch_tracker::instance().get_current_epoch(), type)); + work_toward_ejects(1); + } + + // Perform any remaining deferred destruction. Need to be very careful + // about additional objects being queued for deferred destruction by + // an object that was just destructed. + ~acquire_retire_ibr() { + in_progress.assign(in_progress.size(), true); + + // Loop because the destruction of one object could trigger the deferred + // destruction of another object (possibly even in another thread), and + // so on recursively. + while (std::any_of(deferred_destructs.begin(), deferred_destructs.end(), + [](const auto &v) { return !v.empty(); })) { + + // Move all of the contents from the deferred destruction lists + // into a single local list. We don't want to just iterate the + // deferred lists because a destruction may trigger another + // deferred destruction to be added to one of the lists, which + // would invalidate its iterators + std::vector> destructs; + for (auto &v : deferred_destructs) { + for (const auto& x : v) { + destructs.emplace_back(x.obj, x.type); + } + v.clear(); + } + + // Perform all of the pending deferred ejects + for (auto [x,type] : destructs) { + eject(x,type); + } + + } + } + + private: + + void work_toward_advancing_epoch(size_t work = 1) { + auto id = utils::threadID.getTID(); + epoch_work[id] = epoch_work[id] + work; + if(epoch_work[id] >= epoch_frequency * num_threads) { + epoch_work[id] = 0; + epoch_tracker::instance().advance_global_epoch(); + } + } + + void work_toward_ejects(size_t work = 1) { + auto id = utils::threadID.getTID(); + eject_work[id] = eject_work[id] + work; + auto threshold = std::max(30, eject_delay * eject_work.size()); // Always attempt at least 30 ejects + while (!in_progress[id] && eject_work[id] >= threshold) { + eject_work[id] = 0; + if (deferred_destructs[id].size() == 0) break; // nothing to collect + in_progress[id] = true; + auto deferred = AlignedVector(std::move(deferred_destructs[id])); + + std::vector> announced; + epoch_tracker::instance().scan_announced_epochs([&](auto index, auto startTS) { + uint64_t endTS = announcement_slots[index].endTS_ann.load(); + if(startTS <= endTS) announced.push_back(std::make_pair(startTS, endTS)); + }); + + auto f = [&, this](auto x) { + bool reserved = false; + for(auto& ann : announced) { + if(x.retireTS < ann.first || x.birthTS > ann.second) continue; + reserved = true; + break; + } + + if (!reserved) { + eject(x.obj, x.type); + return true; + } else { + return false; + } + }; + + // Remove the deferred decrements that are successfully applied + deferred.erase(remove_if(deferred.begin(), deferred.end(), f), deferred.end()); + deferred_destructs[id].insert(deferred_destructs[id].end(), deferred.begin(), deferred.end()); + in_progress[id] = false; + } + } + + size_t num_threads; + std::vector announcement_slots; // Announcement array slots + std::vector in_progress; // Local flags to prevent reentrancy while destructing + std::vector> deferred_destructs; // Thread-local lists of pending deferred destructs + std::vector eject_work; // Amortized work to pay for ejecting deferred destructs + std::vector epoch_work; // Amortized work to pay for incrementing the epoch +}; + + +} // namespace internal + +} // namespace cdrc + +#endif // CDRC_SMR_ACQUIRE_RETIRE_IBR_H diff --git a/lib/cdrc/cdrc/internal/utils.h b/lib/cdrc/cdrc/internal/utils.h new file mode 100644 index 000000000..f69cca80e --- /dev/null +++ b/lib/cdrc/cdrc/internal/utils.h @@ -0,0 +1,200 @@ +#ifndef CDRC_INTERNAL_UTILS_H +#define CDRC_INTERNAL_UTILS_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../custom/rand.h" +#include "../custom/threadid.h" + + +const int PADDING = 64; + +// PARLAY_PREFETCH: Prefetch data into cache +#if defined(__GNUC__) +#define PARLAY_PREFETCH(addr, rw, locality) __builtin_prefetch ((addr), (rw), (locality)) +#else +#define PARLAY_PREFETCH(addr, rw, locality) +#endif + +namespace cdrc { + +struct empty_guard {}; + +template +[[nodiscard]] auto scope_guard(F&& f) { + return std::unique_ptr>{(void*)1, std::forward(f)}; +} + +namespace internal { + +// A vector that is stored at 128-byte-aligned memory (this +// means that the header of the vector, not the heap buffer, +// is aligned to 128 bytes) +template +struct alignas(128) AlignedVector : public std::vector { }; + +// A cache-line-aligned int to prevent false sharing +struct alignas(128) AlignedInt { + AlignedInt() : x(0) {} + /* implicit */ AlignedInt(unsigned int x_) : x(x_) {} + /* implicit */ operator unsigned int() const { return x; } + private: + unsigned int x; +}; + +// A cache-line-aligned int to prevent false sharing +struct alignas(128) AlignedLong { + AlignedLong() : x(0) {} + /* implicit */ AlignedLong(uint64_t x_) : x(x_) {} + /* implicit */ operator uint64_t() const { return x; } +private: + uint64_t x; +}; + +// A cache-line-aligned bool to prevent false sharing +struct alignas(128) AlignedBool { + AlignedBool() : b(false) {} + /* implicit */ AlignedBool(bool b_) : b(b_) {} + /* implicit */ operator bool() const { return b; } + private: + bool b; +}; + +} // namespace internal + +namespace utils { + +template +struct Padded; + +// Use user-defined conversions to pad primitive types +template +struct alignas(128) Padded::value>::type> { + Padded() = default; + /* implicit */ Padded(T _x) : x(_x) { } + /* implicit */ operator T() { return x; } + T x; +}; + +// Use inheritance to pad class types +template +struct alignas(128) Padded::value>::type> : public T { }; + +// A wait-free atomic counter that supports increment and decrement, +// such that attempting to increment the counter from zero fails and +// does not perform the increment. +// +// Useful for implementing reference counting, where the underlying +// managed memory is freed when the counter hits zero, so that other +// racing threads can not increment the counter back up from zero +// +// Assumption: The counter should never go negative. That is, the +// user should never decrement the counter by an amount greater +// than its current value +// +// Note: The counter steals the top two bits of the integer for book- +// keeping purposes. Hence the maximum representable value in the +// counter is 2^(8*sizeof(T)-2) - 1 +template +class StickyCounter { + static_assert(std::is_integral_v && std::is_unsigned_v); + +public: + + [[nodiscard]] bool is_lock_free() const { return true; } + static constexpr bool is_always_lock_free = true; + [[nodiscard]] constexpr T max_value() const { return zero_pending_flag - 1; } + + StickyCounter() noexcept : x(1) {} + explicit StickyCounter(T desired) noexcept : x(desired == 0 ? zero_flag : desired) {} + + // Increment the counter by the given amount if the counter is not zero. + // + // Returns true if the increment was successful, i.e., the counter + // was not stuck at zero. Returns false if the counter was zero + bool increment(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept { + //if (x.load() & zero_flag) return false; + auto val = x.fetch_add(arg, order); + return (val & zero_flag) == 0; + } + + // Decrement the counter by the given amount. The counter must initially be + // at least this amount, i.e., it is not permitted to decrement the counter + // to a negative number. + // + // Returns true if the counter was decremented to zero. Returns + // false if the counter was not decremented to zero + bool decrement(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept { + if (x.fetch_sub(arg, order) == arg) { + T expected = 0; + if (x.compare_exchange_strong(expected, zero_flag)) [[likely]] return true; + else if ((expected & zero_pending_flag) && (x.exchange(zero_flag) & zero_pending_flag)) return true; + } + return false; + } + + // Loads the current value of the counter. If the current value is zero, it is guaranteed + // to remain zero until the counter is reset + T load(std::memory_order order = std::memory_order_seq_cst) const noexcept { + auto val = x.load(order); + if (val == 0 && x.compare_exchange_strong(val, zero_flag | zero_pending_flag)) [[unlikely]] return 0; + return (val & zero_flag) ? 0 : val; + } + + // Resets the value of the counter to the given value. This may be called when the counter + // is zero to bring it back to a non-zero value. + // + // It is not permitted to race with an increment or decrement. + void reset(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + x.store(desired == 0 ? zero_flag : desired, order); + } + +private: + static constexpr inline T zero_flag = (T(1) << (sizeof(T)*8 - 1)); + static constexpr inline T zero_pending_flag = (T(1) << (sizeof(T)*8 - 2)); + + mutable std::atomic x; +}; + + +// a slightly cheaper, but possibly not as good version +// based on splitmix64 +inline uint64_t hash64_2(uint64_t x) { + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x = x ^ (x >> 31); + return x; +} + +template +struct CustomHash; + +template<> +struct CustomHash { + size_t operator()(uint64_t a) const { + return hash64_2(a); + } +}; + +template +struct CustomHash { + size_t operator()(T* a) const { + return hash64_2((uint64_t) a); + } +}; + +} // namespace cdrc + +} // namespace utils + +#endif // CDRC_INTERNAL_UTILS_H diff --git a/lib/cdrc/cdrc/marked_arc_ptr.h b/lib/cdrc/cdrc/marked_arc_ptr.h new file mode 100644 index 000000000..6287a7842 --- /dev/null +++ b/lib/cdrc/cdrc/marked_arc_ptr.h @@ -0,0 +1,299 @@ + +#ifndef CDRC_MARKED_ARC_PTR_H +#define CDRC_MARKED_ARC_PTR_H + +#include +#include +#include + +#include + +#include "atomic_rc_ptr.h" +#include "atomic_weak_ptr.h" + +#include "rc_ptr.h" +#include "weak_ptr.h" + +#include "snapshot_ptr.h" +#include "weak_snapshot_ptr.h" + +namespace cdrc { + +template +class marked_ptr { + + static constexpr uintptr_t ONE_BIT = 1; + static constexpr uintptr_t TWO_BIT = 1 << 1; + + public: + marked_ptr() : ptr(0) {} + + /* implicit */ marked_ptr(std::nullptr_t) : ptr(0) {} + + /* implicit */ marked_ptr(T *new_ptr) : ptr(reinterpret_cast(new_ptr)) {} + + /* implicit */ operator T* () const { return get_ptr(); } + + typename std::add_lvalue_reference_t operator*() const { return *(get_ptr()); } + + T* operator->() { return get_ptr(); } + + const T *operator->() const { return get_ptr(); } + + bool operator==(const marked_ptr &other) const { return ptr == other.ptr; } + + bool operator!=(const marked_ptr &other) const { return ptr != other.ptr; } + + bool operator==(const T *other) const { return get_ptr() == other; } + + bool operator!=(const T *other) const { return get_ptr() != other; } + + T* get_ptr() const { return reinterpret_cast(ptr & ~(ONE_BIT | TWO_BIT)); } + + void set_ptr(T* new_ptr) { ptr = reinterpret_cast(new_ptr) | get_mark(); } + + [[nodiscard]] uintptr_t get_mark() const { return ptr & (ONE_BIT | TWO_BIT); } + + void clear_mark() { ptr = ptr & ~(ONE_BIT | TWO_BIT); } + + void set_mark(uintptr_t mark) { + assert(mark < (1 << 2)); // Marks should only occupy the bottom two bits + clear_mark(); + ptr |= mark; + } + + void set_mark_bit(int bit) { + assert(bit == 1 || bit == 2); + ptr |= (1 << (bit - 1)); + } + + bool get_mark_bit(int bit) { + assert(bit == 1 || bit == 2); + return ptr & (1 << (bit - 1)); + } + + private: + uintptr_t ptr; +}; + +namespace internal { + +template +class marked_ptr_policy; + +} // namespace internal + +// Alias templates for marked pointers with the default memory manager + +template> +using marked_arc_ptr = atomic_rc_ptr>; + +template> +using marked_rc_ptr = rc_ptr>; + +template> +using marked_snapshot_ptr = snapshot_ptr>; + +template> +using marked_aw_ptr = atomic_weak_ptr>; + +template> +using marked_weak_ptr = weak_ptr>; + +template> +using marked_ws_ptr = weak_snapshot_ptr>; + + +// Alias templates for marked pointers with hazard pointers + +template +using marked_arc_ptr_hp = marked_arc_ptr>; + +template +using marked_rc_ptr_hp = marked_rc_ptr>; + +template +using marked_snapshot_ptr_hp = marked_snapshot_ptr>; + +template +using marked_aw_ptr_hp = marked_aw_ptr>; + +template +using marked_weak_ptr_hp = marked_weak_ptr>; + +template +using marked_ws_ptr_hp = marked_ws_ptr>; + + +// Alias templates for marked pointers with EBR + +template +using marked_arc_ptr_ebr = marked_arc_ptr>; + +template +using marked_rc_ptr_ebr = marked_rc_ptr>; + +template +using marked_snapshot_ptr_ebr = marked_snapshot_ptr>; + +template +using marked_aw_ptr_ebr = marked_aw_ptr>; + +template +using marked_weak_ptr_ebr = marked_weak_ptr>; + +template +using marked_ws_ptr_ebr = marked_ws_ptr>; + + +// Alias templates for marked pointers with IBR + +template +using marked_arc_ptr_ibr = marked_arc_ptr>; + +template +using marked_rc_ptr_ibr = marked_rc_ptr>; + +template +using marked_snapshot_ptr_ibr = marked_snapshot_ptr>; + +template +using marked_aw_ptr_ibr = marked_aw_ptr>; + +template +using marked_weak_ptr_ibr = marked_weak_ptr>; + +template +using marked_ws_ptr_ibr = marked_ws_ptr>; + + +// Alias templates for marked pointers with Hyaline + +template +using marked_arc_ptr_hyaline = marked_arc_ptr>; + +template +using marked_rc_ptr_hyaline = marked_rc_ptr>; + +template +using marked_snapshot_ptr_hyaline = marked_snapshot_ptr>; + +template +using marked_aw_ptr_hyaline = marked_aw_ptr>; + +template +using marked_weak_ptr_hyaline = marked_weak_ptr>; + +template +using marked_ws_ptr_hyaline = marked_ws_ptr>; + + +namespace internal { + +// Policy class for marked pointers. +// +// This policy injects the behaviour into atomic_rc_ptr, rc_ptr, and snapshot_ptr +// required to deal with marked pointers. Specifically, it adds the methods +// - get_mark() const : uintptr_t +// - set_mark(uintptr_t) : void +// to all three classes, which allow one to get and set the marked bits of the +// pointer respectively. To atomic_rc_ptr, it also adds the method +// - contains(const marked_rc_ptr&) const : bool +// - contains(const marked_snapshot_ptr&) const : bool +// - contains(const T*) const : bool +// which returns true if the atomic_rc_ptr currently contains an rc_ptr that +// manages the same object managed by the given pointers, or manages the object +// given itself, in the case of the third overload. +// +template +class marked_ptr_policy { + public: + + template + using pointer_type = marked_ptr; + + // Adds the set_mark(uintptr_t) and get_mark() methods to atomic_rc_ptr + template + class arc_ptr_policy { + public: + void set_mark(uintptr_t mark) { + auto &parent = get_parent(); + auto cur_ptr = parent.atomic_ptr.load(); + auto new_ptr = cur_ptr; + new_ptr.set_mark(mark); + while (!parent.atomic_ptr.compare_exchange_weak(cur_ptr, new_ptr)) { + new_ptr = cur_ptr; + new_ptr.set_mark(mark); + } + } + + uintptr_t get_mark() const { return get_parent().atomic_ptr.load().get_mark(); } + + void set_mark_bit(int bit) { + assert(bit == 1 || bit == 2); + auto &parent = get_parent(); + auto cur_ptr = parent.atomic_ptr.load(); + auto new_ptr = cur_ptr; + new_ptr.set_mark_bit(bit); + while (!parent.atomic_ptr.compare_exchange_weak(cur_ptr, new_ptr)) { + new_ptr = cur_ptr; + new_ptr.set_mark_bit(bit); + } + } + + bool compare_and_set_mark(const auto& expected, int desired_mark) { + auto &parent = get_parent(); + auto expected_ptr = expected.get_counted(); + auto desired_ptr = expected.get_counted(); + desired_ptr.set_mark(desired_mark); + return parent.atomic_ptr.compare_exchange_strong(expected_ptr, desired_ptr); + } + + bool get_mark_bit(int bit) { + return get_parent().atomic_ptr.load().get_mark_bit(bit); + } + + bool contains(const auto& other) const { return get_parent().atomic_ptr.load() == other.get_counted(); } + + private: + T *get_raw_ptr() const { return get_parent().atomic_ptr.load().get_ptr()->get(); } + + using parent_type = atomic_rc_ptr>; + parent_type &get_parent() { return *static_cast(this); } + const parent_type &get_parent() const { return *static_cast(this); } + }; + + // Adds the set_mark(uintptr_t) and get_mark() methods to rc_ptr + template + class rc_ptr_policy { + public: + void set_mark(uintptr_t mark) { get_parent().ptr.set_mark(mark); } + + uintptr_t get_mark() const { return get_parent().ptr.get_mark(); } + + private: + using parent_type = rc_ptr>; + parent_type &get_parent() { return *static_cast(this); } + const parent_type &get_parent() const { return *static_cast(this); } + }; + + // Adds the set_mark(uintptr_t) and get_mark() methods to snapshot_ptr + template + class snapshot_ptr_policy { + public: + void set_mark(uintptr_t mark) { get_parent().get_counted().set_mark(mark); } + + uintptr_t get_mark() const { return get_parent().get_counted().get_mark(); } + + private: + using parent_type = snapshot_ptr>; + parent_type &get_parent() { return *static_cast(this); } + const parent_type &get_parent() const { return *static_cast(this); } + }; +}; + +} // namespace internal + +} // namespace cdrc + +#endif //CDRC_MARKED_ARC_PTR_H diff --git a/lib/cdrc/cdrc/rc_ptr.h b/lib/cdrc/cdrc/rc_ptr.h new file mode 100644 index 000000000..fa7162638 --- /dev/null +++ b/lib/cdrc/cdrc/rc_ptr.h @@ -0,0 +1,161 @@ + +#ifndef CDRC_RC_PTR_H_ +#define CDRC_RC_PTR_H_ + +#include + +#include +#include + +#include "internal/counted_object.h" +#include "internal/fwd_decl.h" + +#include "weak_ptr.h" + +namespace cdrc { + +template +class rc_ptr : public pointer_policy::template rc_ptr_policy { + + using counted_object_t = internal::counted_object; + using counted_ptr_t = typename pointer_policy::template pointer_type; + + using atomic_ptr_t = atomic_rc_ptr; + using weak_ptr_t = weak_ptr; + using snapshot_ptr_t = snapshot_ptr; + using weak_snapshot_ptr_t = weak_snapshot_ptr; + using atomic_weak_ptr_t = atomic_weak_ptr; + + friend atomic_ptr_t; + friend weak_ptr_t; + friend snapshot_ptr_t; + friend weak_snapshot_ptr_t; + friend atomic_weak_ptr_t; + + friend typename pointer_policy::template arc_ptr_policy; + friend typename pointer_policy::template rc_ptr_policy; + + public: + rc_ptr() noexcept: ptr(nullptr) {} + + /* implicit */ rc_ptr(std::nullptr_t) noexcept: ptr(nullptr) {} + + /* implicit */ rc_ptr(const snapshot_ptr_t &other) noexcept: ptr(other.get_counted()) { + if (ptr) mm.increment_ref_cnt(ptr); + } + + /* implicit */ rc_ptr(const weak_ptr_t& other) noexcept : rc_ptr(other.lock()) { } + + rc_ptr(const rc_ptr &other) noexcept: ptr(other.ptr) { if (ptr) mm.increment_ref_cnt(ptr); } + + rc_ptr(rc_ptr &&other) noexcept: ptr(other.release()) {} + + ~rc_ptr() { clear(); } + + void clear() { + if (ptr) { mm.decrement_ref_cnt(ptr); } + ptr = nullptr; + } + + // Assign the managed pointer to nullptr, releasing the current reference + rc_ptr& operator=(std::nullptr_t) { + clear(); + return *this; + } + + // copy assignment + rc_ptr &operator=(const rc_ptr &other) { + auto tmp = ptr; + ptr = other.ptr; + if (ptr) mm.increment_ref_cnt(ptr); + if (tmp) mm.decrement_ref_cnt(tmp); + return *this; + } + + // move assignment + rc_ptr &operator=(rc_ptr &&other) { + auto tmp = ptr; + ptr = other.release(); + if (tmp) mm.decrement_ref_cnt(tmp); + return *this; + } + + typename std::add_lvalue_reference_t operator*() { return *(ptr->get()); } + + const typename std::add_lvalue_reference_t operator*() const { return *(ptr->get()); } + + T *get() { return (ptr == nullptr) ? nullptr : ptr->get(); } + + const T *get() const { return (ptr == nullptr) ? nullptr : ptr->get(); } + + T *operator->() { return (ptr == nullptr) ? nullptr : ptr->get(); } + + const T *operator->() const { return (ptr == nullptr) ? nullptr : ptr->get(); } + + explicit operator bool() const { return ptr != nullptr; } + + bool operator==(const rc_ptr &other) const { return get() == other.get(); } + + bool operator!=(const rc_ptr &other) const { return get() != other.get(); } + + size_t use_count() const noexcept { return (ptr == nullptr) ? 0 : ptr->get_use_count(); } + + size_t weak_count() const noexcept { return (ptr == nullptr) ? 0 : ptr->get_weak_count() - 1; } + + void swap(rc_ptr &other) { + std::swap(ptr, other.ptr); + } + + // Create a new rc_ptr containing an object of type T constructed from (args...). + template + static rc_ptr make_shared(Args &&... args) { + auto ptr = mm.create_object(std::forward(args)...); + return rc_ptr(ptr, AddRef::no); + } + + protected: + + enum class AddRef { + yes, no + }; + + explicit rc_ptr(counted_ptr_t ptr_, AddRef add_ref) : ptr(ptr_) { + if (ptr && add_ref == AddRef::yes) mm.increment_ref_cnt(ptr); + } + + [[nodiscard]] bool is_protected() const { + return false; + } + + counted_ptr_t release() { + auto p = ptr; + ptr = nullptr; + return p; + } + + counted_ptr_t get_counted() const { + return ptr; + } + + static inline memory_manager& mm = memory_manager::instance(); + + counted_ptr_t ptr; +}; + +// Create a new rc_ptr containing an object of type T constructed from (args...). +template, + typename pointer_policy = internal::default_pointer_policy, typename... Args> +static rc_ptr make_shared(Args &&... args) { + return rc_ptr::make_shared(std::forward(args)...); +} + +// Create a new rc_ptr containing an object of type T constructed from (args...). +template, + typename pointer_policy = internal::default_pointer_policy, typename... Args> +static rc_ptr make_rc(Args &&... args) { + return rc_ptr::make_shared(std::forward(args)...); +} + +} // namespace cdrc + +#endif // CDRC_RC_PTR_H_ diff --git a/lib/cdrc/cdrc/snapshot_ptr.h b/lib/cdrc/cdrc/snapshot_ptr.h new file mode 100644 index 000000000..bdb10b5a4 --- /dev/null +++ b/lib/cdrc/cdrc/snapshot_ptr.h @@ -0,0 +1,130 @@ + +#ifndef CDRC_SNAPSHOT_PTR_H_ +#define CDRC_SNAPSHOT_PTR_H_ + +#include + +#include + +#include "internal/counted_object.h" +#include "internal/fwd_decl.h" + +namespace cdrc { + +template +class snapshot_ptr : public pointer_policy::template snapshot_ptr_policy { + + using counted_object_t = internal::counted_object; + using counted_ptr_t = typename pointer_policy::template pointer_type; + + using atomic_ptr_t = atomic_rc_ptr; + using rc_ptr_t = rc_ptr; + using atomic_weak_ptr_t = atomic_weak_ptr; + + friend atomic_ptr_t; + friend rc_ptr_t; + friend atomic_weak_ptr_t; + + using acquired_pointer_t = typename memory_manager::template acquired_pointer; + + friend typename pointer_policy::template arc_ptr_policy; + friend typename pointer_policy::template snapshot_ptr_policy; + + public: + snapshot_ptr() : acquired_ptr() {} + + /* implicit */ snapshot_ptr(std::nullptr_t) : acquired_ptr() {} + + snapshot_ptr(snapshot_ptr &&other) noexcept: acquired_ptr(std::move(other.acquired_ptr)) {} + + snapshot_ptr(const snapshot_ptr&) = delete; + snapshot_ptr &operator=(const snapshot_ptr&) = delete; + + snapshot_ptr &operator=(snapshot_ptr &&other) { + clear(); + swap(other); + return *this; + } + + typename std::add_lvalue_reference_t operator*() { return *(acquired_ptr.get()->get()); } + + const typename std::add_lvalue_reference_t operator*() const { return *(acquired_ptr.get()->get()); } + + T *get() { + counted_ptr_t ptr = acquired_ptr.get(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + const T *get() const { + counted_ptr_t ptr = acquired_ptr.get(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + T *operator->() { + counted_ptr_t ptr = acquired_ptr.get(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + const T *operator->() const { + counted_ptr_t ptr = acquired_ptr.getValue(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + explicit operator bool() const { return acquired_ptr.get() != nullptr; } + + bool operator==(const snapshot_ptr &other) const { return get() == other.get(); } + + bool operator!=(const snapshot_ptr &other) const { return get() != other.get(); } + + void swap(snapshot_ptr &other) { + acquired_ptr.swap(other.acquired_ptr); + } + + ~snapshot_ptr() { clear(); } + + void clear() { + counted_ptr_t ptr = acquired_ptr.get(); + if (!acquired_ptr.is_protected() && ptr != nullptr) { + mm.decrement_ref_cnt(ptr); + } + acquired_ptr.clear(); + } + + protected: + + explicit snapshot_ptr(acquired_pointer_t&& acquired_ptr) : + acquired_ptr(std::move(acquired_ptr)) {} + + counted_ptr_t get_counted() const { + return acquired_ptr.get(); + } + + counted_ptr_t& get_counted() { + return acquired_ptr.get(); + } + + // For converting a snapshot_ptr into an rc_ptr + // If the ref count has already been incremented, + // the pointer can just be transferred, otherwise + // it should be incremented here. + counted_ptr_t release() { + auto old_ptr = acquired_ptr.getValue(); + if (acquired_ptr.is_protected()) { + mm.increment_ref_cnt(old_ptr); + } + acquired_ptr.clear(); + return old_ptr; + } + + [[nodiscard]] bool is_protected() const { + return acquired_ptr.is_protected(); + } + + static inline memory_manager& mm = memory_manager::instance(); + + acquired_pointer_t acquired_ptr; +}; + +} // namespace cdrc + +#endif // CDRC_SNAPSHOT_PTR_H_ diff --git a/lib/cdrc/cdrc/weak_ptr.h b/lib/cdrc/cdrc/weak_ptr.h new file mode 100644 index 000000000..182ce7b86 --- /dev/null +++ b/lib/cdrc/cdrc/weak_ptr.h @@ -0,0 +1,119 @@ + +#ifndef CDRC_WEAK_PTR_H_ +#define CDRC_WEAK_PTR_H_ + +#include + +#include "internal/counted_object.h" +#include "internal/fwd_decl.h" + +#include "rc_ptr.h" + +namespace cdrc { + +template +class weak_ptr : public pointer_policy::template rc_ptr_policy { + + using counted_object_t = internal::counted_object; + using counted_ptr_t = typename pointer_policy::template pointer_type; + + using atomic_ptr_t = atomic_rc_ptr; + using rc_ptr_t = rc_ptr; + using snapshot_ptr_t = snapshot_ptr; + using atomic_weak_ptr_t = atomic_weak_ptr; + using weak_snapshot_ptr_t = weak_snapshot_ptr; + + friend atomic_ptr_t; + friend snapshot_ptr_t; + friend atomic_weak_ptr_t; + + friend typename pointer_policy::template arc_ptr_policy; + friend typename pointer_policy::template rc_ptr_policy; + + public: + weak_ptr() noexcept: ptr(nullptr) {} + + /* implicit */ weak_ptr(std::nullptr_t) noexcept: ptr(nullptr) {} + + /* implicit */ weak_ptr(const weak_snapshot_ptr_t &other) noexcept: ptr(other.get_counted()) { + if (ptr && !mm.increment_weak_cnt(ptr)) ptr = nullptr; + } + + /* implicit */ weak_ptr(const rc_ptr_t& other) noexcept : ptr(other.ptr) { if (ptr) mm.increment_weak_cnt(ptr); } + + weak_ptr(const weak_ptr &other) noexcept: ptr(other.ptr) { if (ptr) mm.increment_weak_cnt(ptr); } + + weak_ptr(weak_ptr&& other) noexcept: ptr(other.release()) {} + + ~weak_ptr() { clear(); } + + void clear() { + if (ptr) mm.decrement_weak_cnt(ptr); + ptr = nullptr; + } + + // copy assignment + weak_ptr &operator=(const weak_ptr &other) { + auto tmp = ptr; + ptr = other.ptr; + if (ptr) mm.increment_weak_cnt(ptr); + if (tmp) mm.decrement_weak_cnt(tmp); + return *this; + } + + // move assignment + weak_ptr &operator=(weak_ptr &&other) { + auto tmp = ptr; + ptr = other.release(); + if (tmp) mm.decrement_weak_cnt(tmp); + return *this; + } + + rc_ptr_t lock() const noexcept { + if (ptr && mm.increment_ref_cnt(ptr)) return rc_ptr_t(ptr, rc_ptr_t::AddRef::no); + else return nullptr; + } + + size_t use_count() const noexcept { return (ptr == nullptr) ? 0 : ptr->ref_cnt.load(); } + + bool expired() const { return use_count() == 0; } + + void swap(weak_ptr &other) { + std::swap(ptr, other.ptr); + } + + protected: + + enum class AddRef { + yes, no + }; + + weak_ptr(counted_ptr_t ptr_, AddRef add_ref) : ptr(ptr_) { + if (ptr && add_ref == AddRef::yes && !mm.increment_weak_cnt(ptr)) { + ptr = nullptr; + } + } + + [[nodiscard]] bool is_protected() const { + return false; + } + + counted_ptr_t release() { + auto p = ptr; + ptr = nullptr; + return p; + } + + counted_ptr_t get_counted() const { + return ptr; + } + + static inline memory_manager& mm = memory_manager::instance(); + + counted_ptr_t ptr; +}; + + +} // namespace cdrc + +#endif // CDRC_WEAK_PTR_H_ diff --git a/lib/cdrc/cdrc/weak_snapshot_ptr.h b/lib/cdrc/cdrc/weak_snapshot_ptr.h new file mode 100644 index 000000000..eaa495e0f --- /dev/null +++ b/lib/cdrc/cdrc/weak_snapshot_ptr.h @@ -0,0 +1,147 @@ + +#ifndef CDRC_WEAK_SNAPSHOT_PTR_H_ +#define CDRC_WEAK_SNAPSHOT_PTR_H_ + +#include + +#include + +#include "internal/counted_object.h" +#include "internal/fwd_decl.h" + +#include "rc_ptr.h" + +namespace cdrc { + +template +class weak_snapshot_ptr : public pointer_policy::template snapshot_ptr_policy { + + using counted_object_t = internal::counted_object; + using counted_ptr_t = typename pointer_policy::template pointer_type; + + using atomic_ptr_t = atomic_rc_ptr; + using rc_ptr_t = rc_ptr; + using atomic_weak_ptr_t = atomic_weak_ptr; + using weak_ptr_t = weak_ptr; + + friend atomic_ptr_t; + friend rc_ptr_t; + friend atomic_weak_ptr_t; + friend weak_ptr_t; + + using acquired_pointer_t = typename memory_manager::template acquired_pointer; + + friend typename pointer_policy::template arc_ptr_policy; + friend typename pointer_policy::template snapshot_ptr_policy; + + public: + weak_snapshot_ptr() : acquired_ptr() {} + + /* implicit */ weak_snapshot_ptr(std::nullptr_t) : acquired_ptr() {} + + weak_snapshot_ptr(weak_snapshot_ptr &&other) noexcept: acquired_ptr(std::move(other.acquired_ptr)) {} + + weak_snapshot_ptr(const weak_snapshot_ptr&) = delete; + weak_snapshot_ptr &operator=(const weak_snapshot_ptr&) = delete; + + weak_snapshot_ptr &operator=(weak_snapshot_ptr &&other) { + clear(); + swap(other); + return *this; + } + + rc_ptr_t lock() const noexcept { + auto ptr = acquired_ptr.get(); + if (ptr && mm.increment_ref_cnt(ptr)) return rc_ptr_t(ptr, rc_ptr_t::AddRef::no); + else return nullptr; + } + + typename std::add_lvalue_reference_t operator*() { return *(acquired_ptr.get()->get()); } + + const typename std::add_lvalue_reference_t operator*() const { return *(acquired_ptr.get()->get()); } + + T *get() { + counted_ptr_t ptr = acquired_ptr.get(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + const T *get() const { + counted_ptr_t ptr = acquired_ptr.get(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + T *operator->() { + counted_ptr_t ptr = acquired_ptr.get(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + const T *operator->() const { + counted_ptr_t ptr = acquired_ptr.getValue(); + return (ptr == nullptr) ? nullptr : ptr->get(); + } + + explicit operator bool() const { return acquired_ptr.get() != nullptr; } + + bool operator==(const weak_snapshot_ptr &other) const { return get() == other.get(); } + + bool operator!=(const weak_snapshot_ptr &other) const { return get() != other.get(); } + + void swap(weak_snapshot_ptr &other) { + acquired_ptr.swap(other.acquired_ptr); + } + + ~weak_snapshot_ptr() { clear(); } + + void clear() { + counted_ptr_t ptr = acquired_ptr.get(); + if (!acquired_ptr.is_protected() && ptr != nullptr) { + mm.decrement_ref_cnt(ptr); + } + acquired_ptr.clear(); + } + + protected: + + explicit weak_snapshot_ptr(acquired_pointer_t&& acquired_ptr) : + acquired_ptr(std::move(acquired_ptr)) {} + + counted_ptr_t get_counted() const { + return acquired_ptr.get(); + } + + counted_ptr_t& get_counted() { + return acquired_ptr.get(); + } + + // For converting a weak_snapshot_ptr into an rc_ptr + // If the ref count has already been incremented, + // the pointer can just be transferred, otherwise + // it should be incremented here. + counted_ptr_t release() { + auto old_ptr = acquired_ptr.getValue(); + if (acquired_ptr.is_protected()) { + if (mm.increment_ref_cnt(old_ptr)) { + acquired_ptr.clear(); + return old_ptr; + } + else { + acquired_ptr.clear(); + return nullptr; + } + } + acquired_ptr.clear(); + return old_ptr; + } + + [[nodiscard]] bool is_protected() const { + return acquired_ptr.is_protected(); + } + + static inline memory_manager& mm = memory_manager::instance(); + + acquired_pointer_t acquired_ptr; +}; + +} // namespace cdrc + +#endif // CDRC_WEAK_SNAPSHOT_PTR_H_ diff --git a/lib/lib_build_flags_common_c.cmake b/lib/lib_build_flags_common_c.cmake index f3606203c..70e4e06c8 100644 --- a/lib/lib_build_flags_common_c.cmake +++ b/lib/lib_build_flags_common_c.cmake @@ -20,9 +20,7 @@ else (MSVC) set (c_compiler_options_common -pipe -fexceptions -fnon-call-exceptions -O3 - $,-ggdb3,-s> - ) - set (c_linker_options_common + $<$:-ggdb3> ) if (${CMAKE_C_COMPILER_ID} STREQUAL GNU) @@ -31,6 +29,12 @@ else (MSVC) ) endif () + #set (c_compiler_definitions_common ${c_compiler_definitions_common} + #) + + #set (c_linker_options_common ${c_linker_options_common} + #) + #if (${CMAKE_C_COMPILER_ID} STREQUAL Clang) #endif() diff --git a/lib/libev/CMakeLists.txt b/lib/libev/CMakeLists.txt index 0a4516c97..b60ac8499 100644 --- a/lib/libev/CMakeLists.txt +++ b/lib/libev/CMakeLists.txt @@ -43,5 +43,6 @@ elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU) -w # suppress all the warnings, since gcc doesn't have -Wno-extern-initializer ) endif () -target_compile_options (libev PRIVATE ${c_compiler_options_common}) -target_link_options (libev PRIVATE ${c_linker_options_common}) +target_compile_options (libev PRIVATE ${c_compiler_options_common}) +target_compile_definitions (libev PRIVATE ${c_compiler_definitions_common}) +target_link_options (libev PRIVATE ${c_linker_options_common}) diff --git a/lib/parallel_hashmap/CMakeLists.txt b/lib/parallel_hashmap/CMakeLists.txt index 2eeb2a996..e7e359bac 100644 --- a/lib/parallel_hashmap/CMakeLists.txt +++ b/lib/parallel_hashmap/CMakeLists.txt @@ -20,3 +20,10 @@ add_library(parallel_hashmap INTERFACE ${lib_SOURCES}) target_include_directories (parallel_hashmap INTERFACE .) # add to the proper directories in the solution explorer (eg. Visual Studio) source_group (lib\\parallel_hashmap FILES ${lib_SOURCES}) + +#target_compile_definitions(parallel_hashmap INTERFACE +# $<$: ADDRESS_SANITIZER> +# $<$: MEMORY_SANITIZER> +# $<$:UNDEFINED_BEHAVIOR_SANITIZER> +# $<$: THREAD_SANITIZER> +#) diff --git a/lib/parallel_hashmap/parallel_hashmap/phmap_base.h b/lib/parallel_hashmap/parallel_hashmap/phmap_base.h index 09d485478..e410124b3 100644 --- a/lib/parallel_hashmap/parallel_hashmap/phmap_base.h +++ b/lib/parallel_hashmap/parallel_hashmap/phmap_base.h @@ -18,7 +18,7 @@ // // Includes work from abseil-cpp (https://github.com/abseil/abseil-cpp) // with modifications. -// +// // Copyright 2018 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -236,7 +236,7 @@ struct negation : std::integral_constant {}; __has_trivial_copy(typename std::remove_reference::type) && std::is_copy_constructible::value && std::is_trivially_destructible::value> {}; - + template struct is_trivially_copy_assignable : std::integral_constant::value>> : std::true_type {}; #endif -struct AssertHashEnabledHelper +struct AssertHashEnabledHelper { private: static void Sink(...) {} @@ -402,7 +402,7 @@ struct AssertHashEnabledHelper template inline void AssertHashEnabled -() +() { using Helper = AssertHashEnabledHelper; Helper::Sink(Helper::DoIt()...); @@ -421,10 +421,10 @@ namespace priv { // Defines how slots are initialized/destroyed/moved. template -struct hash_policy_traits +struct hash_policy_traits { private: - struct ReturnKey + struct ReturnKey { // We return `Key` here. // When Key=T&, we forward the lvalue reference. @@ -713,7 +713,7 @@ static inline void ThrowStdRangeError(const char* what_arg) { static inline void ThrowStdOverflowError(const std::string& what_arg) { PHMAP_THROW_IMPL_MSG(std::overflow_error, what_arg); } - + static inline void ThrowStdOverflowError(const char* what_arg) { PHMAP_THROW_IMPL_MSG(std::overflow_error, what_arg); } @@ -721,15 +721,15 @@ static inline void ThrowStdOverflowError(const char* what_arg) { static inline void ThrowStdUnderflowError(const std::string& what_arg) { PHMAP_THROW_IMPL_MSG(std::underflow_error, what_arg); } - + static inline void ThrowStdUnderflowError(const char* what_arg) { PHMAP_THROW_IMPL_MSG(std::underflow_error, what_arg); } - + static inline void ThrowStdBadFunctionCall() { PHMAP_THROW_IMPL(std::bad_function_call); } - + static inline void ThrowStdBadAlloc() { PHMAP_THROW_IMPL(std::bad_alloc); } @@ -743,7 +743,7 @@ namespace phmap { namespace base_internal { template -struct StrippedAccept +struct StrippedAccept { template struct Accept : Derived::template AcceptImpl +struct MemFunAndRef : StrippedAccept { template struct AcceptImpl : std::false_type {}; @@ -779,7 +779,7 @@ struct MemFunAndRef : StrippedAccept // ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a // class T and t1 is not one of the types described in the previous item. -struct MemFunAndPtr : StrippedAccept +struct MemFunAndPtr : StrippedAccept { template struct AcceptImpl : std::false_type {}; @@ -806,7 +806,7 @@ struct MemFunAndPtr : StrippedAccept // t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is // an object of type T or a reference to an object of type T or a reference // to an object of a type derived from T. -struct DataMemAndRef : StrippedAccept +struct DataMemAndRef : StrippedAccept { template struct AcceptImpl : std::false_type {}; @@ -823,7 +823,7 @@ struct DataMemAndRef : StrippedAccept // (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 // is not one of the types described in the previous item. -struct DataMemAndPtr : StrippedAccept +struct DataMemAndPtr : StrippedAccept { template struct AcceptImpl : std::false_type {}; @@ -853,7 +853,7 @@ struct Callable // Resolves to the first matching clause. template -struct Invoker +struct Invoker { typedef typename std::conditional< MemFunAndRef::Accept::value, MemFunAndRef, @@ -906,7 +906,7 @@ namespace phmap { // user_function(make_integer_sequence()); // } template -struct integer_sequence +struct integer_sequence { using value_type = T; static constexpr size_t size() noexcept { return sizeof...(Ints); } @@ -1149,7 +1149,7 @@ T exchange(T& obj, U&& new_value) namespace phmap { template -std::unique_ptr WrapUnique(T* ptr) +std::unique_ptr WrapUnique(T* ptr) { static_assert(!std::is_array::value, "array types are unsupported"); static_assert(std::is_object::value, "non-object types are unsupported"); @@ -1175,7 +1175,7 @@ struct MakeUniqueResult { } // namespace memory_internal #if (__cplusplus > 201103L || defined(_MSC_VER)) && \ - !(defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8) + !(defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8) using std::make_unique; #else @@ -1184,12 +1184,12 @@ struct MakeUniqueResult { Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } - + template typename memory_internal::MakeUniqueResult::array make_unique(size_t n) { return std::unique_ptr(new typename phmap::remove_extent_t[n]()); } - + template typename memory_internal::MakeUniqueResult::invalid make_unique( Args&&... /* args */) = delete; @@ -1325,7 +1325,7 @@ struct RebindAlloc { } // namespace memory_internal template -struct pointer_traits +struct pointer_traits { using pointer = Ptr; @@ -1355,7 +1355,7 @@ struct pointer_traits // Specialization for T*. template -struct pointer_traits +struct pointer_traits { using pointer = T*; using element_type = T; @@ -1379,7 +1379,7 @@ struct pointer_traits // A C++11 compatible implementation of C++17's std::allocator_traits. // template -struct allocator_traits +struct allocator_traits { using allocator_type = Alloc; @@ -1613,7 +1613,7 @@ struct allocator_is_nothrow namespace memory_internal { template void ConstructRange(Allocator& alloc, Iterator first, Iterator last, - const Args&... args) + const Args&... args) { for (Iterator cur = first; cur != last; ++cur) { PHMAP_INTERNAL_TRY { @@ -1632,7 +1632,7 @@ void ConstructRange(Allocator& alloc, Iterator first, Iterator last, template void CopyRange(Allocator& alloc, Iterator destination, InputIterator first, - InputIterator last) + InputIterator last) { for (Iterator cur = destination; first != last; static_cast(++cur), static_cast(++first)) { @@ -1684,7 +1684,7 @@ using std::nullopt; namespace phmap { -class bad_optional_access : public std::exception +class bad_optional_access : public std::exception { public: bad_optional_access() = default; @@ -1696,7 +1696,7 @@ template class optional; // -------------------------------- -struct nullopt_t +struct nullopt_t { struct init_t {}; static init_t init; @@ -1718,7 +1718,7 @@ struct empty_struct {}; // It is specialized based on whether T is trivially destructible. // This is the specialization for non trivially destructible type. template ::value> -class optional_data_dtor_base +class optional_data_dtor_base { struct dummy_type { static_assert(sizeof(T) % sizeof(empty_struct) == 0, ""); @@ -1754,7 +1754,7 @@ class optional_data_dtor_base // Specialization for trivially destructible type. template -class optional_data_dtor_base +class optional_data_dtor_base { struct dummy_type { static_assert(sizeof(T) % sizeof(empty_struct) == 0, ""); @@ -1781,7 +1781,7 @@ class optional_data_dtor_base }; template -class optional_data_base : public optional_data_dtor_base +class optional_data_base : public optional_data_dtor_base { protected: using base = optional_data_dtor_base; @@ -1827,7 +1827,7 @@ class optional_data; // Trivially copyable types template -class optional_data : public optional_data_base +class optional_data : public optional_data_base { protected: #if PHMAP_OPTIONAL_USE_INHERITING_CONSTRUCTORS @@ -1842,7 +1842,7 @@ class optional_data : public optional_data_base }; template -class optional_data : public optional_data_base +class optional_data : public optional_data_base { protected: #if PHMAP_OPTIONAL_USE_INHERITING_CONSTRUCTORS @@ -1900,7 +1900,7 @@ template class optional_ctor_base; template <> -class optional_ctor_base +class optional_ctor_base { public: constexpr optional_ctor_base() = default; @@ -1911,7 +1911,7 @@ class optional_ctor_base }; template <> -class optional_ctor_base +class optional_ctor_base { public: constexpr optional_ctor_base() = default; @@ -1922,7 +1922,7 @@ class optional_ctor_base }; template <> -class optional_ctor_base +class optional_ctor_base { public: constexpr optional_ctor_base() = default; @@ -1937,7 +1937,7 @@ template class optional_assign_base; template <> -class optional_assign_base +class optional_assign_base { public: constexpr optional_assign_base() = default; @@ -1948,7 +1948,7 @@ class optional_assign_base }; template <> -class optional_assign_base +class optional_assign_base { public: constexpr optional_assign_base() = default; @@ -1959,7 +1959,7 @@ class optional_assign_base }; template <> -class optional_assign_base +class optional_assign_base { public: constexpr optional_assign_base() = default; @@ -1970,7 +1970,7 @@ class optional_assign_base }; template -constexpr copy_traits get_ctor_copy_traits() +constexpr copy_traits get_ctor_copy_traits() { return std::is_copy_constructible::value ? copy_traits::copyable @@ -1979,7 +1979,7 @@ constexpr copy_traits get_ctor_copy_traits() } template -constexpr copy_traits get_assign_copy_traits() +constexpr copy_traits get_assign_copy_traits() { return phmap::is_copy_assignable::value && std::is_copy_constructible::value @@ -2022,7 +2022,7 @@ bool convertible_to_bool(bool); // compute the hash; Otherwise, it is disabled. // Reference N4659 23.14.15 [unord.hash]. template -struct optional_hash_base +struct optional_hash_base { optional_hash_base() = delete; optional_hash_base(const optional_hash_base&) = delete; @@ -2033,7 +2033,7 @@ struct optional_hash_base template struct optional_hash_base >()( - std::declval >()))> + std::declval >()))> { using argument_type = phmap::optional; using result_type = size_t; @@ -2064,7 +2064,7 @@ class optional : private optional_internal::optional_data, private optional_internal::optional_ctor_base< optional_internal::get_ctor_copy_traits()>, private optional_internal::optional_assign_base< - optional_internal::get_assign_copy_traits()> + optional_internal::get_assign_copy_traits()> { using data_base = optional_internal::optional_data; @@ -2729,7 +2729,7 @@ struct IsTransparent> : std::true_type {}; template -struct KeyArg +struct KeyArg { // Transparent. Forward `K`. template @@ -2737,7 +2737,7 @@ struct KeyArg }; template <> -struct KeyArg +struct KeyArg { // Not transparent. Always use `key_type`. template @@ -2745,7 +2745,7 @@ struct KeyArg }; #ifdef _MSC_VER - #pragma warning(push) + #pragma warning(push) // warning C4820: '6' bytes padding added after data member #pragma warning(disable : 4820) #endif @@ -2755,7 +2755,7 @@ struct KeyArg // common API of both. // ----------------------------------------------------------------------- template -class node_handle_base +class node_handle_base { protected: using slot_type = typename PolicyTraits::slot_type; @@ -2793,7 +2793,7 @@ class node_handle_base : alloc_(a) { PolicyTraits::transfer(alloc(), slot(), s); } - + struct move_tag_t {}; node_handle_base(move_tag_t, const allocator_type& a, slot_type* s) : alloc_(a) { @@ -2832,14 +2832,14 @@ class node_handle_base }; #ifdef _MSC_VER - #pragma warning(pop) + #pragma warning(pop) #endif // For sets. // --------- template -class node_handle : public node_handle_base +class node_handle : public node_handle_base { using Base = node_handle_base; @@ -2863,7 +2863,7 @@ class node_handle : public node_handle_base template class node_handle> - : public node_handle_base + : public node_handle_base { using Base = node_handle_base; using slot_type = typename PolicyTraits::slot_type; @@ -2889,7 +2889,7 @@ class node_handle static auto GetSlot(const Node& node) -> decltype(node.slot()) { @@ -2924,7 +2924,7 @@ struct CommonAccess // Implement the insert_return_type<> concept of C++17. template -struct InsertReturnType +struct InsertReturnType { Iterator position; bool inserted; @@ -3117,7 +3117,7 @@ using EnableIfConvertibleToSpanConst = // int* my_array = new int[10]; // MyRoutine(phmap::Span(my_array, 10)); template -class Span +class Span { private: // Used to determine whether a Span can be constructed from a container of @@ -3825,7 +3825,7 @@ class LayoutImpl; // --------------------------------------------------------------------------- template class LayoutImpl, phmap::index_sequence, - phmap::index_sequence> + phmap::index_sequence> { private: static_assert(sizeof...(Elements) > 0, "At least one field is required"); @@ -4135,7 +4135,7 @@ using LayoutType = LayoutImpl< // by `Layout`. // --------------------------------------------------------------------------- template -class Layout : public internal_layout::LayoutType +class Layout : public internal_layout::LayoutType { public: static_assert(sizeof...(Ts) > 0, "At least one field is required"); @@ -4166,7 +4166,7 @@ class Layout : public internal_layout::LayoutType #ifdef _MSC_VER - #pragma warning(push) + #pragma warning(push) // warning warning C4324: structure was padded due to alignment specifier #pragma warning(disable : 4324) #endif @@ -4212,7 +4212,7 @@ void Deallocate(Alloc* alloc, void* p, size_t n) { } #ifdef _MSC_VER - #pragma warning(pop) + #pragma warning(pop) #endif // Helper functions for asan and msan. @@ -4382,7 +4382,7 @@ struct OffsetOf { }; template -struct OffsetOf::type> +struct OffsetOf::type> { static constexpr size_t kFirst = offsetof(Pair, first); static constexpr size_t kSecond = offsetof(Pair, second); @@ -4390,7 +4390,7 @@ struct OffsetOf::type> // ---------------------------------------------------------------------------- template -struct IsLayoutCompatible +struct IsLayoutCompatible { private: struct Pair { @@ -4448,7 +4448,7 @@ struct IsLayoutCompatible // https://timsong-cpp.github.io/cppwp/n3337/class.mem#19 (9.2.19) // ---------------------------------------------------------------------------- template -union map_slot_type +union map_slot_type { map_slot_type() {} ~map_slot_type() = delete; @@ -4466,7 +4466,7 @@ union map_slot_type // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- template -struct map_slot_policy +struct map_slot_policy { using slot_type = map_slot_type; using value_type = std::pair; @@ -4595,8 +4595,8 @@ namespace phmap { // ----------------------------------------------------------------------------- // NullMutex // ----------------------------------------------------------------------------- -// A class that implements the Mutex interface, but does nothing. This is to be -// used as a default template parameters for classes who provide optional +// A class that implements the Mutex interface, but does nothing. This is to be +// used as a default template parameters for classes who provide optional // internal locking (like phmap::parallel_flat_hash_map). // ----------------------------------------------------------------------------- class NullMutex { @@ -4613,13 +4613,13 @@ class NullMutex { // ------------------------ lockable object used internally ------------------------- template -class LockableBaseImpl +class LockableBaseImpl { public: // ---------------------------------------------------- struct DoNothing { - using mutex_type = MutexType; + using mutex_type = MutexType; DoNothing() noexcept {} explicit DoNothing(mutex_type& ) noexcept {} explicit DoNothing(mutex_type& , mutex_type&) noexcept {} @@ -4646,22 +4646,22 @@ class LockableBaseImpl WriteLock() : m_(nullptr), locked_(false) {} - explicit WriteLock(mutex_type &m) : m_(&m) { - m_->lock(); - locked_ = true; + explicit WriteLock(mutex_type &m) : m_(&m) { + m_->lock(); + locked_ = true; } WriteLock(mutex_type& m, adopt_lock_t) noexcept : - m_(&m), locked_(true) + m_(&m), locked_(true) {} WriteLock(mutex_type& m, defer_lock_t) noexcept : - m_(&m), locked_(false) + m_(&m), locked_(false) {} WriteLock(mutex_type& m, try_to_lock_t) : - m_(&m), locked_(false) { - m_->try_lock(); + m_(&m), locked_(false) { + m_->try_lock(); } WriteLock(WriteLock &&o) noexcept : @@ -4677,40 +4677,40 @@ class LockableBaseImpl } ~WriteLock() { - if (locked_) - m_->unlock(); + if (locked_) + m_->unlock(); } - void lock() { - if (!locked_) { - m_->lock(); - locked_ = true; + void lock() { + if (!locked_) { + m_->lock(); + locked_ = true; } } - void unlock() { + void unlock() { if (locked_) { - m_->unlock(); + m_->unlock(); locked_ = false; } - } + } - bool try_lock() { + bool try_lock() { if (locked_) return true; - locked_ = m_->try_lock(); + locked_ = m_->try_lock(); return locked_; } - + bool owns_lock() const noexcept { return locked_; } - void swap(WriteLock &o) noexcept { + void swap(WriteLock &o) noexcept { std::swap(m_, o.m_); std::swap(locked_, o.locked_); } mutex_type *mutex() const noexcept { return m_; } - + bool switch_to_unique() { return false; } private: @@ -4726,22 +4726,22 @@ class LockableBaseImpl ReadLock() : m_(nullptr), locked_(false) {} - explicit ReadLock(mutex_type &m) : m_(&m) { - m_->lock_shared(); - locked_ = true; + explicit ReadLock(mutex_type &m) : m_(&m) { + m_->lock_shared(); + locked_ = true; } ReadLock(mutex_type& m, adopt_lock_t) noexcept : - m_(&m), locked_(true) + m_(&m), locked_(true) {} ReadLock(mutex_type& m, defer_lock_t) noexcept : - m_(&m), locked_(false) + m_(&m), locked_(false) {} ReadLock(mutex_type& m, try_to_lock_t) : - m_(&m), locked_(false) { - m_->try_lock_shared(); + m_(&m), locked_(false) { + m_->try_lock_shared(); } ReadLock(ReadLock &&o) noexcept : @@ -4757,34 +4757,34 @@ class LockableBaseImpl } ~ReadLock() { - if (locked_) - m_->unlock_shared(); + if (locked_) + m_->unlock_shared(); } - void lock() { - if (!locked_) { - m_->lock_shared(); - locked_ = true; + void lock() { + if (!locked_) { + m_->lock_shared(); + locked_ = true; } } - void unlock() { + void unlock() { if (locked_) { - m_->unlock_shared(); + m_->unlock_shared(); locked_ = false; } - } + } - bool try_lock() { + bool try_lock() { if (locked_) return true; - locked_ = m_->try_lock_shared(); + locked_ = m_->try_lock_shared(); return locked_; } - + bool owns_lock() const noexcept { return locked_; } - void swap(ReadLock &o) noexcept { + void swap(ReadLock &o) noexcept { std::swap(m_, o.m_); std::swap(locked_, o.locked_); } @@ -4807,7 +4807,7 @@ class LockableBaseImpl ReadWriteLock() : m_(nullptr), locked_(false), locked_shared_(false) {} explicit ReadWriteLock(mutex_type &m) : m_(&m), locked_(false), locked_shared_(true) { - m_->lock_shared(); + m_->lock_shared(); } ReadWriteLock(mutex_type& m, defer_lock_t) noexcept : @@ -4828,46 +4828,46 @@ class LockableBaseImpl } ~ReadWriteLock() { - if (locked_shared_) + if (locked_shared_) m_->unlock_shared(); - else if (locked_) + else if (locked_) m_->unlock(); } void lock_shared() { assert(!locked_); - if (!locked_shared_) { - m_->lock_shared(); - locked_shared_ = true; + if (!locked_shared_) { + m_->lock_shared(); + locked_shared_ = true; } } - void unlock_shared() { + void unlock_shared() { if (locked_shared_) { - m_->unlock_shared(); + m_->unlock_shared(); locked_shared_ = false; } - } + } void lock() { assert(!locked_shared_); - if (!locked_) { - m_->lock(); - locked_ = true; + if (!locked_) { + m_->lock(); + locked_ = true; } } - void unlock() { + void unlock() { if (locked_) { - m_->unlock(); + m_->unlock(); locked_ = false; } - } + } bool owns_lock() const noexcept { return locked_; } bool owns_shared_lock() const noexcept { return locked_shared_; } - void swap(ReadWriteLock &o) noexcept { + void swap(ReadWriteLock &o) noexcept { std::swap(m_, o.m_); std::swap(locked_, o.locked_); std::swap(locked_shared_, o.locked_shared_); @@ -4892,12 +4892,12 @@ class LockableBaseImpl class WriteLocks { public: - using mutex_type = MutexType; + using mutex_type = MutexType; - explicit WriteLocks(mutex_type& m1, mutex_type& m2) : + explicit WriteLocks(mutex_type& m1, mutex_type& m2) : _m1(m1), _m2(m2) - { - std::lock(m1, m2); + { + std::lock(m1, m2); } WriteLocks(adopt_lock_t, mutex_type& m1, mutex_type& m2) : @@ -4922,13 +4922,13 @@ class LockableBaseImpl class ReadLocks { public: - using mutex_type = MutexType; + using mutex_type = MutexType; - explicit ReadLocks(mutex_type& m1, mutex_type& m2) : + explicit ReadLocks(mutex_type& m1, mutex_type& m2) : _m1(m1), _m2(m2) - { - _m1.lock_shared(); - _m2.lock_shared(); + { + _m1.lock_shared(); + _m2.lock_shared(); } ReadLocks(adopt_lock_t, mutex_type& m1, mutex_type& m2) : @@ -4951,15 +4951,15 @@ class LockableBaseImpl }; // ------------------------ holds a mutex ------------------------------------ -// Default implementation for Lockable, should work fine for std::mutex +// Default implementation for Lockable, should work fine for std::mutex // ----------------------------------- // use as: // using Lockable = phmap::LockableImpl; // Lockable m; -// +// // Lockable::ReadWriteLock read_lock(m); // take a lock (read if supported, otherwise write) // ... do something -// +// // m.switch_to_unique(); // returns true if we had a read lock and switched to write // // now locked for write // @@ -4988,9 +4988,9 @@ class LockableImpl: public phmap::NullMutex public: using mutex_type = phmap::NullMutex; using Base = LockableBaseImpl; - using SharedLock = typename Base::DoNothing; + using SharedLock = typename Base::DoNothing; using ReadWriteLock = typename Base::DoNothing; - using UniqueLock = typename Base::DoNothing; + using UniqueLock = typename Base::DoNothing; using SharedLocks = typename Base::DoNothing; using UniqueLocks = typename Base::DoNothing; }; @@ -5000,7 +5000,7 @@ class LockableImpl: public phmap::NullMutex // use: `phmap::AbslMutex` instead of `std::mutex` // -------------------------------------------------------------------------- #ifdef ABSL_SYNCHRONIZATION_MUTEX_H_ - + struct AbslMutex : protected absl::Mutex { void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { this->Lock(); } @@ -5010,7 +5010,7 @@ class LockableImpl: public phmap::NullMutex void unlock_shared() ABSL_UNLOCK_FUNCTION() { this->ReaderUnlock(); } void try_lock_shared() ABSL_SHARED_TRYLOCK_FUNCTION(true) { this->ReaderTryLock(); } }; - + template <> class LockableImpl : public AbslMutex { @@ -5063,7 +5063,7 @@ class LockableImpl: public phmap::NullMutex // -------------------------------------------------------------------------- // Boost shared_mutex support (read and write lock support) // -------------------------------------------------------------------------- -#ifdef BOOST_THREAD_SHARED_MUTEX_HPP +#ifdef BOOST_MT_SHARED_MUTEX_HPP // --------------------------------------------------------------------------- template <> @@ -5079,7 +5079,7 @@ class LockableImpl: public phmap::NullMutex using UniqueLocks = typename Base::WriteLocks; }; -#endif // BOOST_THREAD_SHARED_MUTEX_HPP +#endif // BOOST_MT_SHARED_MUTEX_HPP // -------------------------------------------------------------------------- // std::shared_mutex support (read and write lock support) @@ -5105,7 +5105,7 @@ class LockableImpl: public phmap::NullMutex } // phmap #ifdef _MSC_VER - #pragma warning(pop) + #pragma warning(pop) #endif diff --git a/lib/rand/rand/xorshift.hpp b/lib/rand/rand/xorshift.hpp index 8c1ccd8d0..516db728c 100644 --- a/lib/rand/rand/xorshift.hpp +++ b/lib/rand/rand/xorshift.hpp @@ -1,7 +1,6 @@ -// https://gist.github.com/imneme/9b769cefccac1f2bd728596da3a856dd - #ifndef XORSHIFT_HPP_INCLUDED #define XORSHIFT_HPP_INCLUDED 1 +// https://gist.github.com/imneme/9b769cefccac1f2bd728596da3a856dd /* * A C++ implementation of a family of truncated XorShift* generators. diff --git a/lib/sqlite/CMakeLists.txt b/lib/sqlite/CMakeLists.txt index e6d44d1c6..d01fac06e 100644 --- a/lib/sqlite/CMakeLists.txt +++ b/lib/sqlite/CMakeLists.txt @@ -17,5 +17,6 @@ target_include_directories (sqlite PUBLIC .) # set compiler flags include("../lib_build_flags_common_c.cmake") -target_compile_options (sqlite PRIVATE ${c_compiler_options_common}) -target_link_options (sqlite PRIVATE ${c_linker_options_common}) \ No newline at end of file +target_compile_options (sqlite PRIVATE ${c_compiler_options_common}) +target_compile_definitions (sqlite PRIVATE ${c_compiler_definitions_common}) +target_link_options (sqlite PRIVATE ${c_linker_options_common}) diff --git a/lib/twofish/CMakeLists.txt b/lib/twofish/CMakeLists.txt index ed8916729..c9f7baf2a 100644 --- a/lib/twofish/CMakeLists.txt +++ b/lib/twofish/CMakeLists.txt @@ -18,5 +18,6 @@ target_include_directories (twofish PUBLIC .) # set compiler flags include("../lib_build_flags_common_c.cmake") -target_compile_options (twofish PRIVATE ${c_compiler_options_common}) -target_link_options (twofish PRIVATE ${c_linker_options_common}) +target_compile_options (twofish PRIVATE ${c_compiler_options_common}) +target_compile_definitions (twofish PRIVATE ${c_compiler_definitions_common}) +target_link_options (twofish PRIVATE ${c_linker_options_common}) diff --git a/lib/zlib/CMakeLists.txt b/lib/zlib/CMakeLists.txt index 34c78f838..fb8aea13d 100644 --- a/lib/zlib/CMakeLists.txt +++ b/lib/zlib/CMakeLists.txt @@ -41,5 +41,9 @@ target_include_directories (zlib PUBLIC .) # set compiler flags include("../lib_build_flags_common_c.cmake") -target_compile_options (zlib PRIVATE ${c_compiler_options_common}) -target_link_options (zlib PRIVATE ${c_linker_options_common}) +target_compile_options (zlib PRIVATE ${c_compiler_options_common}) +target_compile_definitions (zlib PRIVATE ${c_compiler_definitions_common}) +if (MSVC) + target_compile_definitions (zlib PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_WARNINGS) +endif () +target_link_options (zlib PRIVATE ${c_linker_options_common}) diff --git a/src/CMakeSources.cmake b/src/CMakeSources.cmake index 965d1ca21..d6976347d 100644 --- a/src/CMakeSources.cmake +++ b/src/CMakeSources.cmake @@ -240,6 +240,7 @@ src/common/sphere_library/CSObjList.h src/common/sphere_library/CSObjListRec.h src/common/sphere_library/CSObjSortArray.h src/common/sphere_library/CSPtrTypeArray.h +src/common/sphere_library/CSReferenceCount.h src/common/sphere_library/CSTypedArray.h src/common/sphere_library/CSQueue.cpp src/common/sphere_library/CSQueue.h @@ -252,6 +253,7 @@ src/common/sphere_library/CSTime.h src/common/sphere_library/CSWindow.cpp src/common/sphere_library/CSWindow.h src/common/sphere_library/scontainer_ops.h +src/common/sphere_library/sfastmath.h src/common/sphere_library/smap.h src/common/sphere_library/smutex.h src/common/sphere_library/smutex.cpp @@ -333,6 +335,8 @@ src/game/CWorldGameTime.h src/game/CWorldImport.cpp src/game/CWorldMap.cpp src/game/CWorldMap.h +src/game/CWorldSearch.cpp +src/game/CWorldSearch.h src/game/CWorldTicker.cpp src/game/CWorldTicker.h src/game/CWorldTickingList.cpp diff --git a/src/common/.CScriptObj.cpp.kate-swp b/src/common/.CScriptObj.cpp.kate-swp new file mode 100644 index 000000000..89d199009 Binary files /dev/null and b/src/common/.CScriptObj.cpp.kate-swp differ diff --git a/src/common/CCacheableScriptFile.cpp b/src/common/CCacheableScriptFile.cpp index 99cb96946..25f50267a 100644 --- a/src/common/CCacheableScriptFile.cpp +++ b/src/common/CCacheableScriptFile.cpp @@ -15,7 +15,7 @@ CCacheableScriptFile::CCacheableScriptFile() _iCurrentLine = 0; } -CCacheableScriptFile::~CCacheableScriptFile() +CCacheableScriptFile::~CCacheableScriptFile() { //_Close(); // No need to Close(), since it's already done by CSFileText destructor. if ( _fRealFile && _fileContent ) // be sure that i'm the original file and not a copy/link @@ -25,12 +25,12 @@ CCacheableScriptFile::~CCacheableScriptFile() } } -bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) +bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) { ADDTOCALLSTACK("CCacheableScriptFile::_Open"); _uiMode = uiModeFlags; - if ( _useDefaultFile() ) + if ( _useDefaultFile() ) return CSFileText::_Open(ptcFilename, uiModeFlags); if ( !ptcFilename ) @@ -84,19 +84,33 @@ bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) else { // Fastest method: read the script file all at once. + /* auto fileContentCopy = std::make_unique((size_t)iFileLength + 1u); fread(fileContentCopy.get(), sizeof(char), (size_t)iFileLength, _pStream); + */ + auto fileContentCopy = std::make_unique_for_overwrite((size_t)iFileLength + 1u); + fread(fileContentCopy.get(), sizeof(char), (size_t)iFileLength, _pStream); + fileContentCopy[(size_t)iFileLength] = '\0'; + //if (iFileLength > 0) { + // fileContentCopy[(size_t)iFileLength] = '\0'; + //} // Allocate string vectors for each script line. _fileContent = new std::vector; _fileContent->reserve(iFileLength / 25); + const char *fileCursor = fileContentCopy.get(); + size_t uiFileCursorRemainingLegth = iFileLength; ssize_t iStrLen; - for (const char *fileCursor = fileContentCopy.get();; fileCursor += (size_t)iStrLen) + for (;; fileCursor += (size_t)iStrLen, uiFileCursorRemainingLegth -= (size_t)iStrLen) { - iStrLen = sGetLine_StaticBuf(fileCursor, SCRIPT_MAX_LINE_LEN); - if (iStrLen < 0) + if (uiFileCursorRemainingLegth == 0) break; + + iStrLen = sGetLine_StaticBuf(fileCursor, minimum(uiFileCursorRemainingLegth, SCRIPT_MAX_LINE_LEN)); + if (iStrLen < 0) + break; + if (iStrLen < 1 /*|| (fileCursor[iStrLen] != '\n') It can also be a '\0' value, but it might not be necessary to check for either of the two...*/) { ++ iStrLen; // Skip \n @@ -123,8 +137,9 @@ bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) len_to_copy -= 1; iStrLen += 1; } - else + else { break; + } } if (len_to_copy == 0) @@ -134,6 +149,8 @@ bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) fFirstLine = false; fUTF = false; } // closes while + + ASSERT(uiFileCursorRemainingLegth == 0); // Ensure i have consumed the whole file. } // closes else fclose(_pStream); @@ -146,10 +163,10 @@ bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) return true; } -bool CCacheableScriptFile::Open(lpctstr ptcFilename, uint uiModeFlags) +bool CCacheableScriptFile::Open(lpctstr ptcFilename, uint uiModeFlags) { ADDTOCALLSTACK("CCacheableScriptFile::Open"); - THREAD_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_Open(ptcFilename, uiModeFlags)); } void CCacheableScriptFile::_Close() @@ -159,7 +176,7 @@ void CCacheableScriptFile::_Close() { CSFileText::_Close(); } - else + else { _iCurrentLine = 0; _fClosed = true; @@ -168,51 +185,51 @@ void CCacheableScriptFile::_Close() void CCacheableScriptFile::Close() { ADDTOCALLSTACK("CCacheableScriptFile::Close"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; _Close(); } -bool CCacheableScriptFile::_IsFileOpen() const +bool CCacheableScriptFile::_IsFileOpen() const { ADDTOCALLSTACK("CCacheableScriptFile::_IsFileOpen"); - if ( _useDefaultFile() ) + if ( _useDefaultFile() ) return CSFileText::_IsFileOpen(); return (!_fClosed); } -bool CCacheableScriptFile::IsFileOpen() const +bool CCacheableScriptFile::IsFileOpen() const { ADDTOCALLSTACK("CCacheableScriptFile::IsFileOpen"); - THREAD_SHARED_LOCK_SET; - if ( _useDefaultFile() ) - TS_RETURN(CSFileText::_IsFileOpen()); + MT_SHARED_LOCK_SET; + if ( _useDefaultFile() ) + MT_RETURN(CSFileText::_IsFileOpen()); - TS_RETURN(!_fClosed); + MT_RETURN(!_fClosed); } -bool CCacheableScriptFile::_IsEOF() const +bool CCacheableScriptFile::_IsEOF() const { //ADDTOCALLSTACK("CCacheableScriptFile::_IsEOF"); - if ( _useDefaultFile() ) + if ( _useDefaultFile() ) return CSFileText::_IsEOF(); return (_fileContent->empty() || ((uint)_iCurrentLine == _fileContent->size()) ); } -bool CCacheableScriptFile::IsEOF() const +bool CCacheableScriptFile::IsEOF() const { //ADDTOCALLSTACK("CCacheableScriptFile::IsEOF"); - THREAD_SHARED_LOCK_RETURN(_IsEOF()); + MT_SHARED_LOCK_RETURN(_IsEOF()); } -tchar * CCacheableScriptFile::_ReadString(tchar *pBuffer, int sizemax) +tchar * CCacheableScriptFile::_ReadString(tchar *pBuffer, int sizemax) { // This function is called for each script line which is being parsed (so VERY frequently), and ADDTOCALLSTACK is expensive if called - // this much often, so here it's to be preferred ADDTOCALLSTACK_INTENSIVE, even if we'll lose stack trace precision. - //ADDTOCALLSTACK_INTENSIVE("CCacheableScriptFile::_ReadString"); + // this much often, so here it's to be preferred ADDTOCALLSTACK_DEBUG, even if we'll lose stack trace precision. + //ADDTOCALLSTACK_DEBUG("CCacheableScriptFile::_ReadString"); ASSERT(sizemax > 0); - if ( _useDefaultFile() ) + if ( _useDefaultFile() ) return CSFileText::_ReadString(pBuffer, sizemax); //*pBuffer = '\0'; @@ -220,7 +237,7 @@ tchar * CCacheableScriptFile::_ReadString(tchar *pBuffer, int sizemax) if (_fileContent->empty() || ((uint)_iCurrentLine >= _fileContent->size())) return nullptr; - + std::string const& cur_line = (*_fileContent)[_iCurrentLine]; ++_iCurrentLine; if (cur_line.empty()) @@ -239,15 +256,15 @@ tchar * CCacheableScriptFile::_ReadString(tchar *pBuffer, int sizemax) return pBuffer; } -tchar * CCacheableScriptFile::ReadString(tchar *pBuffer, int sizemax) +tchar * CCacheableScriptFile::ReadString(tchar *pBuffer, int sizemax) { - //ADDTOCALLSTACK_INTENSIVE("CCacheableScriptFile::ReadString"); - THREAD_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_ReadString(pBuffer, sizemax)); + //ADDTOCALLSTACK_DEBUG("CCacheableScriptFile::ReadString"); + MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_ReadString(pBuffer, sizemax)); } -void CCacheableScriptFile::_dupeFrom(CCacheableScriptFile *other) +void CCacheableScriptFile::_dupeFrom(CCacheableScriptFile *other) { - if ( _useDefaultFile() ) + if ( _useDefaultFile() ) return; _strFileName = other->_strFileName; @@ -255,9 +272,9 @@ void CCacheableScriptFile::_dupeFrom(CCacheableScriptFile *other) _fRealFile = false; _fileContent = other->_fileContent; } -void CCacheableScriptFile::dupeFrom(CCacheableScriptFile *other) +void CCacheableScriptFile::dupeFrom(CCacheableScriptFile *other) { - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; _dupeFrom(other); } @@ -269,19 +286,19 @@ bool CCacheableScriptFile::_HasCache() const } bool CCacheableScriptFile::HasCache() const { - THREAD_SHARED_LOCK_RETURN(_HasCache()); + MT_SHARED_LOCK_RETURN(_HasCache()); } -bool CCacheableScriptFile::_useDefaultFile() const +bool CCacheableScriptFile::_useDefaultFile() const { - if ( _IsWriteMode() || ( _GetFullMode() & OF_DEFAULTMODE )) + if ( _IsWriteMode() || ( _GetFullMode() & OF_DEFAULTMODE )) return true; return false; } /* -bool CCacheableScriptFile::useDefaultFile() const +bool CCacheableScriptFile::useDefaultFile() const { - THREAD_SHARED_LOCK_RETURN(_useDefaultFile()); + MT_SHARED_LOCK_RETURN(_useDefaultFile()); }*/ int CCacheableScriptFile::_Seek(int iOffset, int iOrigin) @@ -305,7 +322,7 @@ int CCacheableScriptFile::_Seek(int iOffset, int iOrigin) int CCacheableScriptFile::Seek(int iOffset, int iOrigin) { ADDTOCALLSTACK("CCacheableScriptFile::Seek"); - THREAD_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_Seek(iOffset, iOrigin)); } int CCacheableScriptFile::_GetPosition() const @@ -319,5 +336,5 @@ int CCacheableScriptFile::_GetPosition() const int CCacheableScriptFile::GetPosition() const { ADDTOCALLSTACK("CCacheableScriptFile::GetPosition"); - THREAD_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_GetPosition()); + MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_GetPosition()); } diff --git a/src/common/CException.cpp b/src/common/CException.cpp index 8a1122d18..33a3971e1 100644 --- a/src/common/CException.cpp +++ b/src/common/CException.cpp @@ -9,7 +9,6 @@ #include "../game/CServer.h" #include "../game/CWorld.h" -#ifdef _DEBUG #include int IsDebuggerPresent(void) { @@ -35,12 +34,10 @@ int IsDebuggerPresent(void) return debugger_present; } -#endif // _DEBUG #endif // !_WIN32 -#ifdef _DEBUG void NotifyDebugger() { if (IsDebuggerPresent()) @@ -58,7 +55,6 @@ void NotifyDebugger() } } -#endif // _DEBUG #ifdef _WIN32 @@ -340,17 +336,35 @@ void _cdecl Signal_Children(int sig = 0) #endif #ifndef _WIN32 -void SetUnixSignals( bool bSet ) // Signal handlers are installed only in secure mode +void SetUnixSignals( bool fSet ) // Signal handlers are installed only in secure mode { - signal( SIGHUP, bSet ? &Signal_Hangup : SIG_DFL ); - signal( SIGTERM, bSet ? &Signal_Terminate : SIG_DFL ); - signal( SIGQUIT, bSet ? &Signal_Terminate : SIG_DFL ); - signal( SIGABRT, bSet ? &Signal_Terminate : SIG_DFL ); - signal( SIGILL, bSet ? &Signal_Terminate : SIG_DFL ); - signal( SIGINT, bSet ? &Signal_Break : SIG_DFL ); - signal( SIGSEGV, bSet ? &Signal_Illegal_Instruction : SIG_DFL ); - signal( SIGFPE, bSet ? &Signal_Illegal_Instruction : SIG_DFL ); - signal( SIGPIPE, bSet ? SIG_IGN : SIG_DFL ); - signal( SIGCHLD, bSet ? &Signal_Children : SIG_DFL ); + +#ifdef _SANITIZERS + const bool fSan = true; +#else + const bool fSan = false; +#endif + +#ifdef _DEBUG + const bool fDebugger = IsDebuggerPresent(); +#else + const bool fDebugger = false; +#endif + + if (!fDebugger && !fSan) + { + signal( SIGHUP, fSet ? &Signal_Hangup : SIG_DFL ); + signal( SIGTERM, fSet ? &Signal_Terminate : SIG_DFL ); + signal( SIGQUIT, fSet ? &Signal_Terminate : SIG_DFL ); + signal( SIGABRT, fSet ? &Signal_Terminate : SIG_DFL ); + signal( SIGILL, fSet ? &Signal_Terminate : SIG_DFL ); + signal( SIGINT, fSet ? &Signal_Break : SIG_DFL ); + signal( SIGSEGV, fSet ? &Signal_Illegal_Instruction : SIG_DFL ); + signal( SIGFPE, fSet ? &Signal_Illegal_Instruction : SIG_DFL ); + } + + signal( SIGPIPE, fSet ? SIG_IGN : SIG_DFL ); + signal( SIGCHLD, fSet ? &Signal_Children : SIG_DFL ); + } #endif diff --git a/src/common/CException.h b/src/common/CException.h index b543e0b89..83456ba54 100644 --- a/src/common/CException.h +++ b/src/common/CException.h @@ -22,14 +22,10 @@ void SetExceptionTranslator(); #ifndef _WIN32 void SetUnixSignals( bool ); - #ifdef _DEBUG - int IsDebuggerPresent(); // Windows already has this function - #endif + int IsDebuggerPresent(); // Windows already has this function #endif -#ifdef _DEBUG - void NotifyDebugger(); -#endif +void NotifyDebugger(); // ------------------------------------------------------------------- // ------------------------------------------------------------------- @@ -159,6 +155,7 @@ class CAssert : public CSError { \ _EXC_CATCH_EXCEPTION_GENERIC(&e, "CAssert"); \ GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); \ + EXC_NOTIFY_DEBUGGER; \ } \ catch ( const CSError& e ) \ { \ @@ -312,4 +309,11 @@ class CAssert : public CSError #endif //_EXCEPTIONS_DEBUG +/*--- Advanced tooling --- */ + +//https://rkd.me.uk/posts/2020-04-11-stack-corruption-and-how-to-debug-it.html +#define STACK_RA_CHECK_SETUP void* ra_start = __builtin_return_address(0); +#define STACK_RA_CHECK assert(ra_start == __builtin_return_address(0)); assert(((intptr_t)ra_start & 0xffffffff00000000) == ((intptr_t)main & 0xffffffff00000000)); + + #endif // _INC_CEXCEPTION_H diff --git a/src/common/CExpression.cpp b/src/common/CExpression.cpp index 8355b0ca0..7b4ad2167 100644 --- a/src/common/CExpression.cpp +++ b/src/common/CExpression.cpp @@ -499,7 +499,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) if ( *pszArgs ) { iCount = 1; - iResult = RES_GET_INDEX( GetVal(pszArgs) ); + iResult = ResGetIndex((dword)GetVal(pszArgs)); } else { diff --git a/src/common/CExpression.h b/src/common/CExpression.h index d70ec4599..2a1067d07 100644 --- a/src/common/CExpression.h +++ b/src/common/CExpression.h @@ -63,7 +63,7 @@ enum DEFMSG_TYPE DEFMSG_QTY }; -enum INTRINSIC_TYPE +enum INTRINSIC_TYPE : int // Even if it would implicitly be set to int, specify it to silence false warning by UBSanitizer. { INTRINSIC_ABS = 0, INTRINSIC_ARCCOS, diff --git a/src/common/CFloatMath.cpp b/src/common/CFloatMath.cpp index 5fcdbc0cc..231afabed 100644 --- a/src/common/CFloatMath.cpp +++ b/src/common/CFloatMath.cpp @@ -275,7 +275,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) if ( *pArgs ) { iCount = 1; - rResult = RES_GET_INDEX(int(MakeFloatMath(pArgs))); // RES_GET_INDEX + rResult = ResGetIndex(int(MakeFloatMath(pArgs))); // ResGetIndex } else { diff --git a/src/common/CLog.cpp b/src/common/CLog.cpp index 9d59844f2..582d6ec0b 100644 --- a/src/common/CLog.cpp +++ b/src/common/CLog.cpp @@ -101,7 +101,7 @@ const CScript * CLog::_SetScriptContext( const CScript * pScriptContext ) const CScript * CLog::SetScriptContext(const CScript * pScriptContext) { - THREAD_UNIQUE_LOCK_RETURN(CLog::_SetScriptContext(pScriptContext)); + MT_UNIQUE_LOCK_RETURN(CLog::_SetScriptContext(pScriptContext)); } const CScriptObj * CLog::_SetObjectContext( const CScriptObj * pObjectContext ) @@ -113,7 +113,7 @@ const CScriptObj * CLog::_SetObjectContext( const CScriptObj * pObjectContext ) const CScriptObj * CLog::SetObjectContext(const CScriptObj * pObjectContext) { - THREAD_UNIQUE_LOCK_RETURN(CLog::_SetObjectContext(pObjectContext)); + MT_UNIQUE_LOCK_RETURN(CLog::_SetObjectContext(pObjectContext)); } bool CLog::SetFilePath( lpctstr pszName ) @@ -210,7 +210,7 @@ bool CLog::_OpenLog( lpctstr pszBaseDirName ) // name set previously. bool CLog::OpenLog(lpctstr pszBaseDirName) // name set previously. { ADDTOCALLSTACK("CLog::OpenLog"); - THREAD_UNIQUE_LOCK_RETURN(CLog::_OpenLog(pszBaseDirName)); + MT_UNIQUE_LOCK_RETURN(CLog::_OpenLog(pszBaseDirName)); } int CLog::EventStr( dword dwMask, lpctstr pszMsg, ConsoleTextColor iLogColor) noexcept @@ -303,7 +303,7 @@ int CLog::EventStr( dword dwMask, lpctstr pszMsg, ConsoleTextColor iLogColor) no // Print to log file. if ( !(dwMask & LOGF_CONSOLE_ONLY) ) { - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; if ( datetime.GetDay() != m_dateStamp.GetDay()) { diff --git a/src/common/CLog.h b/src/common/CLog.h index afbe6a070..810d1dffa 100644 --- a/src/common/CLog.h +++ b/src/common/CLog.h @@ -16,7 +16,7 @@ // CEventLog // ----------------------------- -enum LOG_TYPE +enum LOG_TYPE : uint { // --severity Level // it can be only one of them, so we don't need to use a bitmask @@ -85,7 +85,7 @@ class CEventLog extern struct CLog : public CSFileText, public CEventLog { private: - // THREAD_CMUTEX_DEF; // There's already the CSFileText::THREAD_CMUTEX + // MT_CMUTEX_DEF; // There's already the CSFileText::MT_CMUTEX dword m_dwMsgMask; // Level of log detail messages. IsLogMsg() CSTime m_dateStamp; // last real time stamp. @@ -104,7 +104,7 @@ public: const CScript * SetScriptContext( const CScript * pScriptContext ); protected: const CScriptObj * _SetObjectContext( const CScriptObj * pObjectContext ); public: const CScriptObj * SetObjectContext( const CScriptObj * pObjectContext ); - + protected: bool _OpenLog(lpctstr pszName = nullptr); // name set previously. public: bool OpenLog(lpctstr pszName = nullptr); diff --git a/src/common/CPointBase.cpp b/src/common/CPointBase.cpp index 11dba566f..5f09b249c 100644 --- a/src/common/CPointBase.cpp +++ b/src/common/CPointBase.cpp @@ -110,46 +110,44 @@ bool CPointBase::operator!= ( const CPointBase & pt ) const noexcept return ( ! ( *this == pt )); } -const CPointBase& CPointBase::operator+= ( const CPointBase & pt ) noexcept +const CPointBase& CPointBase::operator+= ( const CPointBase & pt ) { + ASSERT(m_map == pt.m_map); m_x += pt.m_x; m_y += pt.m_y; m_z += pt.m_z; return( * this ); } -const CPointBase& CPointBase::operator-= ( const CPointBase & pt ) noexcept +const CPointBase& CPointBase::operator-= ( const CPointBase & pt ) { + ASSERT(m_map == pt.m_map); m_x -= pt.m_x; m_y -= pt.m_y; m_z -= pt.m_z; return( * this ); } -void CPointBase::InitPoint() noexcept +CPointBase& CPointBase::InitPoint() noexcept { m_x = m_y = -1; // invalid location. m_z = 0; m_map = 0; + return *this; } -void CPointBase::ZeroPoint() noexcept +CPointBase& CPointBase::ZeroPoint() noexcept { m_x = m_y = 0; // invalid location. m_z = 0; m_map = 0; -} - -int CPointBase::GetDistZ( const CPointBase & pt ) const noexcept -{ - //return abs(int(m_z) - int(pt.m_z)); - return (m_z > pt.m_z) ? (m_z - pt.m_z) : (pt.m_z - m_z); + return *this; } int CPointBase::GetDistBase( const CPointBase & pt ) const noexcept // Distance between points { // This method is one of the most called in the whole app (maybe the most). ADDTOCALLSTACK unneededly sucks cpu. // This has to be optimized as much as possible. - //ADDTOCALLSTACK_INTENSIVE("CPointBase::GetDistBase"); + //ADDTOCALLSTACK_DEBUG("CPointBase::GetDistBase"); switch (g_Cfg.m_iDistanceFormula) { @@ -162,20 +160,31 @@ int CPointBase::GetDistBase( const CPointBase & pt ) const noexcept // Distance const int dy = abs(m_y - pt.m_y); */ - // This is faster than the above in MSVC 2022 x86_64 debug version. - // It should be benchmarked for Nightly (optimized) builds, to see if this becomes slower than the call to abs. - // (The presence of a conditional expression might reduce the efficacy of the CPU branch predictor) + /* + // This is faster than the above. const int dx = (m_x > pt.m_x) ? (m_x - pt.m_x) : (pt.m_x - m_x); const int dy = (m_y > pt.m_y) ? (m_y - pt.m_y) : (pt.m_y - m_y); - return maximum(dx, dy); + */ + // return maximum(dx, dy); + + // This is even faster. + const int dx = (m_x > pt.m_x) * (m_x - pt.m_x) + (m_x < pt.m_x) * (pt.m_x - m_x); + const int dy = (m_y > pt.m_y) * (m_y - pt.m_y) + (m_y < pt.m_y) * (pt.m_y - m_y); + return (dx >= dy) * dx + (dx < dy) * dy; + } case DISTANCE_FORMULA_DIAGONAL_NOZ: { const int dx = m_x - pt.m_x; const int dy = m_y - pt.m_y; + const double dist = sqrt(static_cast((dx * dx) + (dy * dy))); - const double flr = floor(dist); - return (int)(((dist - flr) > 0.5) ? ceil(dist) : flr); + //const double dist = hypot(dx, dy); // To test if faster + + return (int)round(dist); + + //const double flr = floor(dist); + //return (int)(((dist - flr) > 0.5) ? ceil(dist) : flr); // Test, avoids another function call? // return (((dist - floor(dist)) > 0.5) ? int(dist) : int(dist + 1)); @@ -195,7 +204,7 @@ int CPointBase::GetDistBase( const CPointBase & pt ) const noexcept // Distance int CPointBase::GetDist( const CPointBase & pt ) const noexcept // Distance between points { // This method is called very frequently, ADDTOCALLSTACK unneededly sucks cpu - //ADDTOCALLSTACK_INTENSIVE("CPointBase::GetDist"); + //ADDTOCALLSTACK_DEBUG("CPointBase::GetDist"); // Get the basic 2d distance. if ( !pt.IsValidPoint() || (pt.m_map != m_map)) @@ -207,9 +216,12 @@ int CPointBase::GetDistSightBase( const CPointBase & pt ) const noexcept // Dist { //const int dx = abs(m_x - pt.m_x); //const int dy = abs(m_y - pt.m_y); - const int dx = (m_x > pt.m_x) ? (m_x - pt.m_x) : (pt.m_x - m_x); - const int dy = (m_y > pt.m_y) ? (m_y - pt.m_y) : (pt.m_y - m_y); - return maximum(dx, dy); + //const int dx = (m_x > pt.m_x) ? (m_x - pt.m_x) : (pt.m_x - m_x); + //const int dy = (m_y > pt.m_y) ? (m_y - pt.m_y) : (pt.m_y - m_y); + //return maximum(dx, dy); + const int dx = (m_x > pt.m_x) * (m_x - pt.m_x) + (m_x < pt.m_x) * (pt.m_x - m_x); + const int dy = (m_y > pt.m_y) * (m_y - pt.m_y) + (m_y < pt.m_y) * (pt.m_y - m_y); + return (dx >= dy) * dx + (dx < dy) * dy; } int CPointBase::GetDistSight( const CPointBase & pt ) const noexcept // Distance between points based on UO sight @@ -221,9 +233,12 @@ int CPointBase::GetDistSight( const CPointBase & pt ) const noexcept // Distance //const int dx = abs(m_x - pt.m_x); //const int dy = abs(m_y - pt.m_y); - const int dx = (m_x > pt.m_x) ? (m_x - pt.m_x) : (pt.m_x - m_x); - const int dy = (m_y > pt.m_y) ? (m_y - pt.m_y) : (pt.m_y - m_y); - return maximum(dx, dy); + //const int dx = (m_x > pt.m_x) ? (m_x - pt.m_x) : (pt.m_x - m_x); + //const int dy = (m_y > pt.m_y) ? (m_y - pt.m_y) : (pt.m_y - m_y); + //return maximum(dx, dy); + const int dx = (m_x > pt.m_x) * (m_x - pt.m_x) + (m_x < pt.m_x) * (pt.m_x - m_x); + const int dy = (m_y > pt.m_y) * (m_y - pt.m_y) + (m_y < pt.m_y) * (pt.m_y - m_y); + return (dx >= dy) * dx + (dx < dy) * dy; } int CPointBase::GetDist3D( const CPointBase & pt ) const noexcept // Distance between points @@ -238,10 +253,12 @@ int CPointBase::GetDist3D( const CPointBase & pt ) const noexcept // Distance be const int dist = GetDist(pt); // Get the deltas and correct the Z for height first - int dz = (m_z > pt.m_z) ? (m_z - pt.m_z) : (pt.m_z - m_z); + //int dz = (m_z > pt.m_z) ? (m_z - pt.m_z) : (pt.m_z - m_z); + int dz = (m_z > pt.m_z) * (m_z - pt.m_z) + (m_z < pt.m_z) * (pt.m_z - m_z); dz /= (PLAYER_HEIGHT / 2); // Take player height into consideration - return maximum(dz, dist); + //return maximum(dz, dist); + return (dz >= dist) * dz + (dz < dist) * dist; } case DISTANCE_FORMULA_DIAGONAL_Z: { @@ -252,9 +269,7 @@ int CPointBase::GetDist3D( const CPointBase & pt ) const noexcept // Distance be //dz /= (PLAYER_HEIGHT / 2); const double dist = sqrt(static_cast((dx * dx) + (dy * dy) + (dz * dz))); - const double flr = floor(dist); - return (int)(((dist - flr) > 0.5) ? ceil(dist) : flr); - // Or just use std::round. In any case, we need to round to give the best result, because with a simple cast the compiler will truncate the decimal part. + return (int)round(dist); } } } @@ -294,8 +309,9 @@ void CPointBase::ValidatePoint() noexcept m_y = iMaxY - 1; } -bool CPointBase::IsSame2D( const CPointBase & pt ) const noexcept +bool CPointBase::IsSame2D( const CPointBase & pt ) const { + ASSERT(m_map == pt.m_map); return ( m_x == pt.m_x && m_y == pt.m_y ); } @@ -368,7 +384,7 @@ bool CPointBase::r_WriteVal( lpctstr ptcKey, CSString & sVal ) const ptcKey += 6; SKIP_SEPARATORS( ptcKey ); iStatic = Exp_GetVal( ptcKey ); - type = RES_GET_TYPE( iStatic ); + type = ResGetType( iStatic ); if ( type == 0 ) type = RES_ITEMDEF; SKIP_SEPARATORS( ptcKey ); @@ -376,12 +392,12 @@ bool CPointBase::r_WriteVal( lpctstr ptcKey, CSString & sVal ) const else { iStatic = Exp_GetVal( ptcKey ); - type = RES_GET_TYPE( iStatic ); + type = ResGetType( iStatic ); } if ( type == RES_ITEMDEF ) { - const CItemBase * pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(RES_GET_INDEX(iStatic))); + const CItemBase * pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex(iStatic))); if ( !pItemDef ) { sVal.FormatVal( 0 ); @@ -511,7 +527,7 @@ bool CPointBase::r_WriteVal( lpctstr ptcKey, CSString & sVal ) const ptcKey += 6; SKIP_SEPARATORS( ptcKey ); iComponent = Exp_GetVal( ptcKey ); - type = RES_GET_TYPE( iComponent ); + type = ResGetType( iComponent ); if ( type == 0 ) type = RES_ITEMDEF; SKIP_SEPARATORS( ptcKey ); @@ -519,12 +535,12 @@ bool CPointBase::r_WriteVal( lpctstr ptcKey, CSString & sVal ) const else { iComponent = Exp_GetVal( ptcKey ); - type = RES_GET_TYPE( iComponent ); + type = ResGetType( iComponent ); } if ( type == RES_ITEMDEF ) { - const CItemBase * pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(RES_GET_INDEX(iComponent))); + const CItemBase * pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex(iComponent))); if ( pItemDef == nullptr ) { sVal.FormatVal( 0 ); @@ -757,7 +773,7 @@ bool CPointBase::r_WriteVal( lpctstr ptcKey, CSString & sVal ) const sVal = pTypeDef->GetResourceName(); else sVal.Clear(); - } return true; + } return true; case PT_TERRAIN: { ptcKey += 7; @@ -807,7 +823,7 @@ bool CPointBase::r_LoadVal( lpctstr ptcKey, lpctstr pszArgs ) DIR_TYPE CPointBase::GetDir( const CPointBase & pt, DIR_TYPE DirDefault ) const // Direction to point pt { - ADDTOCALLSTACK_INTENSIVE("CPointBase::GetDir"); + ADDTOCALLSTACK_DEBUG("CPointBase::GetDir"); // Get the 2D direction between points. const int dx = (m_x-pt.m_x); const int dy = (m_y-pt.m_y); @@ -866,7 +882,7 @@ int CPointBase::StepLinePath( const CPointBase & ptSrc, int iSteps ) tchar * CPointBase::WriteUsed( tchar * ptcBuffer, size_t uiBufferLen) const noexcept { - ADDTOCALLSTACK_INTENSIVE("CPointBase::WriteUsed"); + ADDTOCALLSTACK_DEBUG("CPointBase::WriteUsed"); if ( m_map ) snprintf(ptcBuffer, uiBufferLen, "%" PRId16 ",%" PRId16 ",%" PRId8 ",%" PRIu8, m_x, m_y, m_z, m_map); else if ( m_z ) @@ -927,7 +943,7 @@ int CPointBase::Read( tchar * pszVal ) case 0: break; } - + if (!ptTest.IsValidPoint()) { InitPoint(); @@ -941,7 +957,7 @@ int CPointBase::Read( tchar * pszVal ) CSector * CPointBase::GetSector() const { // This function is called SO frequently that's better to not add it to the call stack. - //ADDTOCALLSTACK_INTENSIVE("CPointBase::GetSector"); + //ADDTOCALLSTACK_DEBUG("CPointBase::GetSector"); if ( !IsValidXY() ) { @@ -956,7 +972,7 @@ CSector * CPointBase::GetSector() const CRegion * CPointBase::GetRegion( dword dwType ) const { - ADDTOCALLSTACK_INTENSIVE("CPointBase::GetRegion"); + ADDTOCALLSTACK_DEBUG("CPointBase::GetRegion"); // What region in the current CSector am i in ? // We only need to update this every 8 or so steps ? // REGION_TYPE_AREA @@ -973,7 +989,7 @@ CRegion * CPointBase::GetRegion( dword dwType ) const size_t CPointBase::GetRegions( dword dwType, CRegionLinks *pRLinks ) const { // This function is called SO frequently that's better to not add it to the call stack. - // ADDTOCALLSTACK_INTENSIVE("CPointBase::GetRegions"); + // ADDTOCALLSTACK_DEBUG("CPointBase::GetRegions"); if ( !IsValidPoint() ) return 0; diff --git a/src/common/CPointBase.h b/src/common/CPointBase.h index 4f647985c..6ad0f0157 100644 --- a/src/common/CPointBase.h +++ b/src/common/CPointBase.h @@ -40,8 +40,8 @@ struct CPointBase // Non initialized 3d point. uchar m_map; // another map? (only if top level.) public: - void InitPoint() noexcept; - void ZeroPoint() noexcept; + CPointBase& InitPoint() noexcept; + CPointBase& ZeroPoint() noexcept; CPointBase() noexcept; @@ -59,24 +59,22 @@ struct CPointBase // Non initialized 3d point. CPointBase& operator = (const CPointBase&) noexcept = default; bool operator == ( const CPointBase & pt ) const noexcept; bool operator != ( const CPointBase & pt ) const noexcept; - const CPointBase& operator += ( const CPointBase & pt ) noexcept; - const CPointBase& operator -= ( const CPointBase & pt ) noexcept; - - - int GetDistZ( const CPointBase & pt ) const noexcept; - int GetDistBase( const CPointBase & pt ) const noexcept; // Distance between points - int GetDist( const CPointBase & pt ) const noexcept; // Distance between points - int GetDistSightBase( const CPointBase & pt ) const noexcept; // Distance between points based on UO sight - int GetDistSight( const CPointBase & pt ) const noexcept; // Distance between points based on UO sight - int GetDist3D( const CPointBase & pt ) const noexcept; // 3D Distance between points - - inline bool IsValidZ() const noexcept + const CPointBase& operator += ( const CPointBase & pt ); + const CPointBase& operator -= ( const CPointBase & pt ); + + [[nodiscard]] int GetDistBase( const CPointBase & pt ) const noexcept; // Distance between points + [[nodiscard]] int GetDist( const CPointBase & pt ) const noexcept; // Distance between points + [[nodiscard]] int GetDistSightBase( const CPointBase & pt ) const noexcept; // Distance between points based on UO sight + [[nodiscard]] int GetDistSight( const CPointBase & pt ) const noexcept; // Distance between points based on UO sight + [[nodiscard]] int GetDist3D( const CPointBase & pt ) const noexcept; // 3D Distance between points + + [[nodiscard]] inline bool IsValidZ() const noexcept { return ((m_z > -127 /* -UO_SIZE_Z */) && (m_z < 127 /* UO_SIZE_Z */)); } - bool IsValidXY() const noexcept; - bool IsCharValid() const noexcept; - inline bool IsValidPoint() const noexcept + [[nodiscard]] bool IsValidXY() const noexcept; + [[nodiscard]] bool IsCharValid() const noexcept; + [[nodiscard]] inline bool IsValidPoint() const noexcept { // Called a LOT of times, it's worth inlining it. return (IsValidXY() && IsValidZ()); @@ -84,7 +82,7 @@ struct CPointBase // Non initialized 3d point. void ValidatePoint() noexcept; - bool IsSame2D( const CPointBase & pt ) const noexcept; + bool IsSame2D( const CPointBase & pt ) const; void Set( const CPointBase & pt ) noexcept; void Set( short x, short y, char z = 0, uchar map = 0 ) noexcept; @@ -140,6 +138,19 @@ struct CPointMap : public CPointBase CPointMap& operator = (const CPointBase& pt) noexcept { return CPointMap::operator=(static_cast(pt)); } + + // Trying to avoid "creating a class section" warning, with mixed results... + /* + explicit operator CPointBase() const noexcept { + return CPointBase(m_x, m_y, m_z, m_map); + } + explicit operator CPointBase const & () const noexcept { + return CPointBase(m_x, m_y, m_z, m_map); + } + explicit operator CPointBase &&() const noexcept { + return CPointBase(m_x, m_y, m_z, m_map); + } + */ }; struct CPointSort : public CPointMap diff --git a/src/common/CRect.cpp b/src/common/CRect.cpp index 85d01721d..c25aea5a0 100644 --- a/src/common/CRect.cpp +++ b/src/common/CRect.cpp @@ -110,7 +110,7 @@ bool CRect::IsEqual( const CRect & rect ) const noexcept m_map == rect.m_map; } -void CRect::NormalizeRect() +void CRect::NormalizeRect() noexcept { if ( m_bottom < m_top ) { @@ -128,7 +128,7 @@ void CRect::NormalizeRect() m_map = 0; } -void CRect::SetRect( int left, int top, int right, int bottom, int map ) +void CRect::SetRect( int left, int top, int right, int bottom, int map ) noexcept { m_left = left; m_top = top; @@ -138,7 +138,7 @@ void CRect::SetRect( int left, int top, int right, int bottom, int map ) NormalizeRect(); } -void CRect::NormalizeRectMax( int cx, int cy ) +void CRect::NormalizeRectMax( int cx, int cy ) noexcept { if ( m_left < 0 ) m_left = 0; @@ -279,9 +279,9 @@ CPointBase CRect::GetRectCorner( DIR_TYPE dir ) const return pt; } -CSector * CRect::GetSector( int i ) const // ge all the sectors that make up this rect. +CSector * CRect::GetSector( int i ) const noexcept // ge all the sectors that make up this rect. { - ADDTOCALLSTACK_INTENSIVE("CRect::GetSector"); + //ADDTOCALLSTACK_DEBUG("CRect::GetSector"); // get all the CSector(s) that overlap this rect. // RETURN: nullptr = no more @@ -348,15 +348,15 @@ bool CRectMap::IsValid() const noexcept return true; } -void CRectMap::NormalizeRect() +void CRectMap::NormalizeRect() noexcept { - //ADDTOCALLSTACK_INTENSIVE("CRectMap::NormalizeRect"); + //ADDTOCALLSTACK_DEBUG("CRectMap::NormalizeRect"); CRect::NormalizeRect(); NormalizeRectMax(); } -void CRectMap::NormalizeRectMax() +void CRectMap::NormalizeRectMax() noexcept { - //ADDTOCALLSTACK_INTENSIVE("CRectMap::NormalizeRectMax"); + //ADDTOCALLSTACK_DEBUG("CRectMap::NormalizeRectMax"); CRect::NormalizeRectMax( g_MapList.GetMapSizeX(m_map), g_MapList.GetMapSizeY(m_map)); } diff --git a/src/common/CRect.h b/src/common/CRect.h index a516863c6..629cbb479 100644 --- a/src/common/CRect.h +++ b/src/common/CRect.h @@ -73,14 +73,14 @@ struct CRect // Basic rectangle, similar to _WIN32 RECT (May not be on the map) bool IsOverlapped( const CRect & rect ) const noexcept; bool IsEqual( const CRect & rect ) const noexcept; - virtual void NormalizeRect(); - void NormalizeRectMax( int cx, int cy ); + virtual void NormalizeRect() noexcept; + void NormalizeRectMax( int cx, int cy ) noexcept; CPointBase GetCenter() const; CPointBase GetRectCorner( DIR_TYPE dir ) const; - CSector * GetSector( int i ) const; // ge all the sectors that make up this rect. + CSector * GetSector( int i ) const noexcept; // ge all the sectors that make up this rect. - void SetRect( int left, int top, int right, int bottom, int map ); + void SetRect( int left, int top, int right, int bottom, int map ) noexcept; size_t Read( lpctstr pVal ); tchar * Write( tchar * ptcBuffer, uint uiBufferLen ) const; @@ -108,8 +108,8 @@ struct CRectMap : public CRect bool IsValid() const noexcept; - virtual void NormalizeRect() override; - void NormalizeRectMax(); + virtual void NormalizeRect() noexcept override; + void NormalizeRectMax() noexcept; }; diff --git a/src/common/CSVFile.cpp b/src/common/CSVFile.cpp index 7be8b7fa0..df11d540e 100644 --- a/src/common/CSVFile.cpp +++ b/src/common/CSVFile.cpp @@ -26,12 +26,12 @@ CSVFile::~CSVFile() int CSVFile::GetColumnCount() const { ADDTOCALLSTACK("CSVFile::GetColumnCount"); - THREAD_SHARED_LOCK_RETURN(_iColumnCount); + MT_SHARED_LOCK_RETURN(_iColumnCount); } int CSVFile::GetCurrentRow() const { ADDTOCALLSTACK("CSVFile::GetCurrentRow"); - THREAD_SHARED_LOCK_RETURN(_iCurrentRow); + MT_SHARED_LOCK_RETURN(_iCurrentRow); } bool CSVFile::_Open(lpctstr ptcFilename, uint uiModeFlags) @@ -65,7 +65,7 @@ bool CSVFile::_Open(lpctstr ptcFilename, uint uiModeFlags) _Close(); return false; } - + // second row lets us validate the column count if ( _ReadRowContent(ppColumnNames, 1) != _iColumnCount ) { @@ -91,7 +91,7 @@ bool CSVFile::_Open(lpctstr ptcFilename, uint uiModeFlags) bool CSVFile::Open(lpctstr ptcFilename, uint uiModeFlags) { ADDTOCALLSTACK("CSVFile::Open"); - THREAD_UNIQUE_LOCK_RETURN(CSVFile::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(CSVFile::_Open(ptcFilename, uiModeFlags)); } int CSVFile::_ReadRowContent(tchar ** ppOutput, int rowIndex, int columns) @@ -142,6 +142,6 @@ bool CSVFile::_ReadNextRowContent(CSVRowData& target) bool CSVFile::ReadNextRowContent(CSVRowData& target) { ADDTOCALLSTACK("CSVFile::ReadNextRowContent"); - THREAD_UNIQUE_LOCK_RETURN(CSVFile::_ReadNextRowContent(target)); + MT_UNIQUE_LOCK_RETURN(CSVFile::_ReadNextRowContent(target)); } diff --git a/src/common/CScript.cpp b/src/common/CScript.cpp index 46268ed57..3aea48af9 100644 --- a/src/common/CScript.cpp +++ b/src/common/CScript.cpp @@ -239,7 +239,7 @@ uint8 CScriptKey::GetArgU8Val() ADDTOCALLSTACK("CScriptKey::GetArgU8Val"); ASSERT(m_pszKey); ASSERT(m_pszArg); - return Exp_Get8Val( m_pszArg ); + return Exp_GetU8Val( m_pszArg ); } uint16 CScriptKey::GetArgU16Val() @@ -287,7 +287,7 @@ CScriptKey::CScriptKey() : { } -CScriptKey::CScriptKey( tchar * ptcKey, tchar * ptcArg ) : +CScriptKey::CScriptKey( tchar * ptcKey, tchar * ptcArg ) : m_pszKey( ptcKey ), m_pszArg( ptcArg ) { } @@ -298,7 +298,7 @@ CScriptKey::CScriptKey( tchar * ptcKey, tchar * ptcArg ) : tchar * CScriptKeyAlloc::_GetKeyBufferRaw( size_t iLen ) { - //ADDTOCALLSTACK_INTENSIVE("CScriptKeyAlloc::_GetKeyBufferRaw"); + //ADDTOCALLSTACK_DEBUG("CScriptKeyAlloc::_GetKeyBufferRaw"); // iLen = length of the string we want to hold. if ( iLen > SCRIPT_MAX_LINE_LEN ) iLen = SCRIPT_MAX_LINE_LEN; @@ -316,7 +316,7 @@ tchar * CScriptKeyAlloc::_GetKeyBufferRaw( size_t iLen ) tchar * CScriptKeyAlloc::GetKeyBufferRaw( size_t iLen ) { ADDTOCALLSTACK("CScriptKeyAlloc::GetKeyBufferRaw"); - THREAD_UNIQUE_LOCK_RETURN(CScriptKeyAlloc::_GetKeyBufferRaw(iLen)); + MT_UNIQUE_LOCK_RETURN(CScriptKeyAlloc::_GetKeyBufferRaw(iLen)); } */ @@ -509,14 +509,14 @@ bool CScript::_Open( lpctstr ptcFilename, uint uiFlags ) bool CScript::Open( lpctstr ptcFilename, uint uiFlags ) { ADDTOCALLSTACK("CScript::Open"); - THREAD_UNIQUE_LOCK_RETURN(CScript::_Open(ptcFilename, uiFlags)); + MT_UNIQUE_LOCK_RETURN(CScript::_Open(ptcFilename, uiFlags)); } bool CScript::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened script file { // This function is called for each script line which is being parsed (so VERY frequently), and ADDTOCALLSTACK is expensive if called - // this much often, so here it's to be preferred ADDTOCALLSTACK_INTENSIVE, even if we'll lose stack trace precision. - //ADDTOCALLSTACK_INTENSIVE("CScript::_ReadTextLine"); + // this much often, so here it's to be preferred ADDTOCALLSTACK_DEBUG, even if we'll lose stack trace precision. + //ADDTOCALLSTACK_DEBUG("CScript::_ReadTextLine"); // ARGS: // fRemoveBlanks = Don't report any blank lines, (just keep reading) @@ -537,7 +537,7 @@ bool CScript::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened } bool CScript::ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened script file { - THREAD_UNIQUE_LOCK_RETURN(CScript::_ReadTextLine(fRemoveBlanks)); + MT_UNIQUE_LOCK_RETURN(CScript::_ReadTextLine(fRemoveBlanks)); } bool CScript::FindTextHeader( lpctstr pszName ) // Find a section in the current script @@ -580,7 +580,7 @@ int CScript::_Seek( int iOffset, int iOrigin ) int CScript::Seek( int iOffset, int iOrigin ) { ADDTOCALLSTACK("CScript::Seek"); - THREAD_UNIQUE_LOCK_RETURN(CScript::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(CScript::_Seek(iOffset, iOrigin)); } bool CScript::FindNextSection() @@ -731,7 +731,7 @@ bool CScript::ReadKeyParse() // Read line from script tchar* pszArgs = m_pszArg; pszArgs += 2; GETNONWHITESPACE( pszArgs ); - + const int iKeyIndex = (strnicmp(m_pszKey, "FLOAT.", 6) == 0) ? 1 : 0; TemporaryString tsBuf; if ( m_pszArg[0] == '.' ) // ".=" concatenation operator @@ -816,7 +816,7 @@ bool CScript::_SeekContext( CScriptLineContext LineContext ) } bool CScript::SeekContext( CScriptLineContext LineContext ) { - THREAD_UNIQUE_LOCK_RETURN(CScript::_SeekContext(LineContext)); + MT_UNIQUE_LOCK_RETURN(CScript::_SeekContext(LineContext)); } CScriptLineContext CScript::_GetContext() const @@ -829,7 +829,7 @@ CScriptLineContext CScript::_GetContext() const CScriptLineContext CScript::GetContext() const { - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; CScriptLineContext LineContext; LineContext.m_iLineNum = m_iLineNum; LineContext.m_iOffset = _GetPosition(); @@ -838,7 +838,7 @@ CScriptLineContext CScript::GetContext() const bool _cdecl CScript::WriteSection( lpctstr ptcSection, ... ) { - ADDTOCALLSTACK_INTENSIVE("CScript::WriteSection"); + ADDTOCALLSTACK_DEBUG("CScript::WriteSection"); // Write out the section header. // EndSection(); // End any previous section. @@ -852,13 +852,13 @@ bool _cdecl CScript::WriteSection( lpctstr ptcSection, ... ) va_end(vargs); } - + memcpy(tsHeader.buffer(), "\n[", 2u); // 2 -> not counting the string terminator size_t offset = 2; offset += Str_CopyLimitNull(tsHeader.buffer() + offset, tsSectionFormatted.buffer(), tsHeader.capacity() - 2); offset += Str_ConcatLimitNull(tsHeader.buffer() + offset, "]\n", tsHeader.capacity() - offset); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; _Write(tsHeader.buffer(), int(offset)); return true; @@ -866,7 +866,7 @@ bool _cdecl CScript::WriteSection( lpctstr ptcSection, ... ) bool CScript::WriteKeySingle(lptstr ptcKey) { - ADDTOCALLSTACK_INTENSIVE("CScript::WriteKeySingle"); + ADDTOCALLSTACK_DEBUG("CScript::WriteKeySingle"); if (ptcKey == nullptr || ptcKey[0] == '\0') { return false; @@ -896,7 +896,7 @@ bool CScript::WriteKeySingle(lptstr ptcKey) bool CScript::WriteKeyStr(lpctstr ptcKey, lpctstr ptcVal) { - ADDTOCALLSTACK_INTENSIVE("CScript::WriteKeyStr"); + ADDTOCALLSTACK_DEBUG("CScript::WriteKeyStr"); if (ptcKey == nullptr || ptcKey[0] == '\0') { return false; @@ -942,7 +942,7 @@ bool CScript::WriteKeyStr(lpctstr ptcKey, lpctstr ptcVal) static thread_local tchar ptcWriteKeyBuf[SCRIPT_MAX_LINE_LEN]; void _cdecl CScript::WriteKeyFormat(lpctstr ptcKey, lpctstr pszVal, ...) { - ADDTOCALLSTACK_INTENSIVE("CScript::WriteKeyFormat"); + ADDTOCALLSTACK_DEBUG("CScript::WriteKeyFormat"); va_list vargs; va_start( vargs, pszVal ); vsnprintf(ptcWriteKeyBuf, sizeof(ptcWriteKeyBuf), pszVal, vargs); diff --git a/src/common/CScriptContexts.cpp b/src/common/CScriptContexts.cpp index 410d490a8..8e479309f 100644 --- a/src/common/CScriptContexts.cpp +++ b/src/common/CScriptContexts.cpp @@ -40,7 +40,7 @@ void CScriptFileContext::_OpenScript( const CScript * pScriptContext ) void CScriptFileContext::OpenScript( const CScript * pScriptContext ) { ADDTOCALLSTACK("CScriptFileContext::OpenScript"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; _OpenScript(pScriptContext); } @@ -56,7 +56,7 @@ void CScriptFileContext::Close() void CScriptFileContext::_Close() { ADDTOCALLSTACK("CScriptFileContext::_Close"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; Close(); } diff --git a/src/common/CScriptContexts.h b/src/common/CScriptContexts.h index d59793ab5..8195540b2 100644 --- a/src/common/CScriptContexts.h +++ b/src/common/CScriptContexts.h @@ -25,7 +25,7 @@ struct CScriptLineContext class CScriptFileContext { - THREAD_CMUTEX_DEF; + MT_CMUTEX_DEF; friend class CResourceLock; // Track a temporary context into a script. // NOTE: This should ONLY be stack based ! diff --git a/src/common/CScriptObj.cpp b/src/common/CScriptObj.cpp index 2e411cf60..d418ebc78 100644 --- a/src/common/CScriptObj.cpp +++ b/src/common/CScriptObj.cpp @@ -15,6 +15,7 @@ #include "../game/CServer.h" #include "../game/CWorld.h" #include "../game/CWorldMap.h" +#include "../game/CWorldSearch.h" #include "../game/CWorldTimedFunctions.h" #include "../sphere/ProfileTask.h" #include "crypto/CBCrypt.h" @@ -199,14 +200,14 @@ lpctstr const CScriptObj::sm_szLoadKeys[SSC_QTY+1] = size_t CScriptObj::r_GetFunctionIndex(lpctstr pszFunction) // static { - ADDTOCALLSTACK_INTENSIVE("CScriptObj::r_GetFunctionIndex"); + ADDTOCALLSTACK_DEBUG("CScriptObj::r_GetFunctionIndex"); GETNONWHITESPACE( pszFunction ); return g_Cfg.m_Functions.find_sorted(pszFunction); } bool CScriptObj::r_CanCall(size_t uiFunctionIndex) // static { - ADDTOCALLSTACK_INTENSIVE("CScriptObj::r_CanCall"); + ADDTOCALLSTACK_DEBUG("CScriptObj::r_CanCall"); if (uiFunctionIndex == sl::scont_bad_index()) return false; ASSERT(uiFunctionIndex < g_Cfg.m_Functions.size()); @@ -595,10 +596,10 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc switch ( index ) { case SSC_RESOURCEINDEX: - sVal.FormatHex(RES_GET_INDEX(Exp_GetVal(ptcKey))); + sVal.FormatHex(ResGetIndex(Exp_GetVal(ptcKey))); break; case SSC_RESOURCETYPE: - sVal.FormatHex(RES_GET_TYPE(Exp_GetVal(ptcKey))); + sVal.FormatHex(ResGetType(Exp_GetVal(ptcKey))); break; case SSC_LISTCOL: @@ -741,7 +742,7 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc { const int64 val = Exp_GetLLVal(ptcKey); SKIP_ARGSEP(ptcKey); - + const uint bit = Exp_GetUVal(ptcKey); if (bit >= 64) { @@ -810,15 +811,15 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc int iQty = Str_ParseCmds(const_cast(ptcKey), ppArgs, ARRAY_COUNT(ppArgs)); if ( iQty < 3 ) return false; - + int64 iPos = Exp_GetVal( ppArgs[0] ); int64 iCnt = Exp_GetVal( ppArgs[1] ); if (iCnt < 0) return false; - + if ( *ppArgs[2] == '"') ++ppArgs[2]; - + for (tchar *pEnd = ppArgs[2] + strlen(ppArgs[2]) - 1; pEnd >= ppArgs[2]; --pEnd) { if ( *pEnd == '"') @@ -828,7 +829,7 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc } } int64 iLen = strlen(ppArgs[2]); - + const bool fBackwards = (iPos < 0); if (fBackwards) iPos = iLen - iCnt; @@ -1007,17 +1008,17 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc int iQty = Str_ParseCmdsAdv(const_cast(ptcKey), ppArgs, ARRAY_COUNT(ppArgs), ","); if ( iQty < 3 ) return false; - + tchar *iSep = Str_UnQuote(ppArgs[2]); //New function, trim (") and (') chars directly. for (tchar *iSeperator = iSep + strlen(iSep) - 1; iSeperator > iSep; --iSeperator) *iSeperator = '\0'; - + tchar *pArgs = Str_UnQuote(ppArgs[0]); sVal.Clear(); tchar *ppCmd[255]; int count = Str_ParseCmdsAdv(pArgs, ppCmd, ARRAY_COUNT(ppCmd), iSep); //Remove unnecessary chars from seperator to avoid issues. tchar *ppArrays[2]; - + //Getting range of array index... int iArrays = Str_ParseCmdsAdv(ppArgs[1], ppArrays, ARRAY_COUNT(ppArrays), "-"); llong iValue = Exp_GetLLVal(ppArgs[1]); @@ -1029,7 +1030,7 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc if (iValueEnd <= 0 || iValueEnd > count) iValueEnd = count; } - + if (iValue < 0) return false; else if (iValue > 0) @@ -1249,7 +1250,7 @@ bool CScriptObj::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command f pSrc = &g_Serv; ASSERT(pSrc); } - } + } } return pRef->r_Verb( script, pSrc ); @@ -1487,7 +1488,8 @@ bool CScriptObj::r_Load( CScript & s ) { if ( s.IsKeyHead( "ON", 2 ) ) // trigger scripting marks the end break; - r_LoadVal(s); + + r_LoadVal(s); } return true; } @@ -1609,7 +1611,7 @@ bool CScriptObj::Evaluate_Conditional(lptstr ptcExpr, CTextConsole* pSrc, CScrip } // We have some subexpressions, connected between them by logical operators. - + bool fWholeExprVal = false; for (int i = 0; i < iQty; ++i) { @@ -1640,13 +1642,13 @@ bool CScriptObj::Evaluate_Conditional(lptstr ptcExpr, CTextConsole* pSrc, CScrip fWholeExprVal = (i == 1) ? fVal : (fWholeExprVal && fVal); } - + if (sCur.uiType & SType::None) { ASSERT(i == iQty - 1); // It should be the last subexpression ASSERT((sPrev.uiType & SType::Or) || (sPrev.uiType & SType::And)); } - + } return fWholeExprVal; @@ -1693,7 +1695,7 @@ static void Evaluate_QvalConditional_ParseArg(tchar* ptcSrc, tchar** ptcDest, lp { // The separator we have found is before the nested QVAL. ptcSrc = ptcSepPos; - break; + break; } // Found a nested QVAL. Skip it, otherwise we'll catch the wrong separator @@ -1746,7 +1748,7 @@ bool CScriptObj::Evaluate_QvalConditional(lpctstr ptcKey, CSString& sVal, CTextC return true; } -size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int iFlags, CScriptTriggerArgs * pArgs, std::shared_ptr pContext) +int CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int iFlags, CScriptTriggerArgs * pArgs, std::shared_ptr pContext) { ADDTOCALLSTACK("CScriptObj::ParseScriptText"); //ASSERT(ptcResponse[0] != ' '); // Not needed: i remove whitespaces and invalid characters here. @@ -1777,11 +1779,12 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int const tchar chEnd = fHTML ? '%' : '>'; // Variables used to handle the QVAL special case and do lazy evaluation, instead of fully evaluating the whole string on the first pass. + // As an aftertought QVAL parsing could have been moved into a separate function, but it's intricate enough and it's working, so let's leave as it is...' enum class QvalStatus { None, Condition, Returns, End } eQval = QvalStatus::None; int iQvalOpenBrackets = 0; - size_t iBegin = 0; - size_t i = 0; + size_t uiSubstitutionBegin = 0; + int i = 0; EXC_TRY("ParseScriptText Main Loop"); for ( i = 0; ptcResponse[i] != '\0'; ++i) { @@ -1815,7 +1818,8 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int } // Set the statement start - iBegin = i; + ASSERT(i >= 0); + uiSubstitutionBegin = (size_t)i; pContext->_fParseScriptText_Brackets = true; // Set-up to process special statements: is it a QVAL? @@ -1855,7 +1859,7 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int { // Otherwise, it might be the << operator. - // This shouldn't be necessary... but + // This shouldn't be necessary... but /* // Is a << operator? I want a whitespace after the operator. if ((ptcResponse[i + 2] != '\0') && (ptcResponse[i + 3] != '\0') && IsWhitespace(ptcResponse[i + 2])) @@ -1924,12 +1928,12 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int // Parse what's inside the open bracket tchar* ptcRecurseParse = ptcResponse + i; - const size_t ilen = ParseScriptText(ptcRecurseParse, pSrc, 2, pArgs ); + const int iLen = ParseScriptText(ptcRecurseParse, pSrc, 2, pArgs ); pContext->_fParseScriptText_Brackets = true; --pContext->_iParseScriptText_Reentrant; - i += ilen; + i += iLen; continue; } @@ -1975,7 +1979,7 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int // If i'm here it means that finally i'm at the end of the statement inside brackets. pContext->_fParseScriptText_Brackets = false; // Close the statement. - + if ((eQval == QvalStatus::End) && (iQvalOpenBrackets != 0)) { // I had an incomplete QVAL statement. @@ -1987,7 +1991,7 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int EXC_SET_BLOCK("writeval"); ptcResponse[i] = '\0'; // Needed for r_WriteVal - lpctstr ptcKey = ptcResponse + iBegin + 1; // move past the opening bracket + lpctstr ptcKey = ptcResponse + uiSubstitutionBegin + 1; // move past the opening bracket CSString sVal; bool fRes; @@ -2012,7 +2016,7 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int fRes = true; } } - + if ( fRes == false ) { @@ -2029,19 +2033,20 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int //-- In the output string, substitute the raw substring with its parsed value EXC_SET_BLOCK("mem shifting"); - const size_t iWriteValLen = sVal.GetLength(); + const size_t uiWriteValLen = sVal.GetLength(); // Make room for the obtained value, moving to left (if it's shorter than the scripted statement) or right (if longer) the string characters after it. - tchar* ptcDest = ptcResponse + iBegin + iWriteValLen; // + iWriteValLen because we need to leave the space for the replacing keyword + tchar* ptcDest = ptcResponse + uiSubstitutionBegin + uiWriteValLen; // + iWriteValLen because we need to leave the space for the replacing keyword const tchar * const ptcLeftover = ptcResponse + i + 1; // End of the statement we just evaluated - const size_t iLeftoverLen = strlen(ptcLeftover) + 1; - memmove(ptcDest, ptcLeftover, iLeftoverLen); + const size_t uiLeftoverLen = strlen(ptcLeftover) + 1; + memmove(ptcDest, ptcLeftover, uiLeftoverLen); // Insert the obtained value in the room we created. - ptcDest = ptcResponse + iBegin; - memcpy(ptcDest, sVal.GetBuffer(), iWriteValLen); + ptcDest = ptcResponse + uiSubstitutionBegin; + memcpy(ptcDest, sVal.GetBuffer(), uiWriteValLen); - i = iBegin + iWriteValLen - 1; + // This can be negative. + i = (int)(uiSubstitutionBegin + uiWriteValLen) - 1; if (fNoRecurseBrackets) // just do this one then bail out. { @@ -2055,7 +2060,7 @@ size_t CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int EXC_DEBUG_START; g_Log.EventDebug("response '%s' source addr '0%p' flags '%d' args '%p'\n", ptcResponse, static_cast(pSrc), iFlags, static_cast(pArgs)); EXC_DEBUG_END; - + pContext->_fParseScriptText_Brackets = false; return i; } @@ -2115,7 +2120,7 @@ bool CScriptObj::Execute_Call(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs else fRes = pRef->r_Call(argRaw, pSrc, pArgs, &sVal); } - + return fRes; } @@ -2533,14 +2538,14 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol CPointMap pt = pObjTop->GetTopPoint(); if (iType & 1) // FORITEM, FOROBJ { - CWorldSearch AreaItems(pt, iDist); + auto AreaItems = CWorldSearchHolder::GetInstance(pt, iDist); for (;;) { ++LoopsMade; if (g_Cfg.m_iMaxLoopTimes && (LoopsMade >= g_Cfg.m_iMaxLoopTimes)) goto toomanyloops; - CItem* pItem = AreaItems.GetItem(); + CItem* pItem = AreaItems->GetItem(); if (pItem == nullptr) break; TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); @@ -2560,15 +2565,15 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol } if (iType & 2) // FORCHAR, FOROBJ { - CWorldSearch AreaChars(pt, iDist); - AreaChars.SetAllShow((iType & 0x20) ? true : false); + auto AreaChars = CWorldSearchHolder::GetInstance(pt, iDist); + AreaChars->SetAllShow((iType & 0x20) ? true : false); for (;;) { ++LoopsMade; if (g_Cfg.m_iMaxLoopTimes && (LoopsMade >= g_Cfg.m_iMaxLoopTimes)) goto toomanyloops; - CChar* pChar = AreaChars.GetChar(); + CChar* pChar = AreaChars->GetChar(); if (pChar == nullptr) break; if ((iType & 0x10) && (!pChar->IsClientActive())) // FORCLIENTS @@ -2621,6 +2626,9 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol if (rid.IsValidUID()) { const dword dwTotal = g_World.GetUIDCount(); + if (dwTotal == 0) + return TRIGRET_ENDIF; + dword dwCount = dwTotal - 1; dword dwTotalInstances = 0; // Will acquire the correct value for this during the loop dword dwUID = 0; diff --git a/src/common/CScriptObj.h b/src/common/CScriptObj.h index 2f2a52047..a952de5fc 100644 --- a/src/common/CScriptObj.h +++ b/src/common/CScriptObj.h @@ -90,13 +90,13 @@ class CScriptObj * It does check if we are requesting another ref. */ virtual bool r_WriteVal(lpctstr pKey, CSString& sVal, CTextConsole* pSrc = nullptr, bool fNoCallParent = false, bool fNoCallChildren = false); - + /* * @brief Do the first-level parsing of a script line and eventually replace requested values got by r_WriteVal. */ - size_t ParseScriptText( tchar * pszResponse, CTextConsole * pSrc, int iFlags = 0, CScriptTriggerArgs * pArgs = nullptr, + int ParseScriptText( tchar * pszResponse, CTextConsole * pSrc, int iFlags = 0, CScriptTriggerArgs * pArgs = nullptr, std::shared_ptr pContext = std::make_shared() ); - + /* * @brief Execute a script command. * Called when parsing a script section with OnTriggerRun or if issued by a CClient. diff --git a/src/common/CServerMap.cpp b/src/common/CServerMap.cpp index 79e2309f4..29ed1bfc6 100644 --- a/src/common/CServerMap.cpp +++ b/src/common/CServerMap.cpp @@ -97,20 +97,20 @@ lpctstr CServerMapBlockingState::GetTileName( dword dwID ) // static if ( dwID < TERRAIN_QTY ) { const CUOTerrainInfo land( (TERRAIN_TYPE)dwID ); - strcpy( pStr, land.m_name ); + strncpy( pStr, land.m_name, Str_TempLength() ); } else { dwID -= TERRAIN_QTY; const CUOItemInfo item((ITEMID_TYPE)dwID); - strcpy( pStr, item.m_name ); + strncpy( pStr, item.m_name, Str_TempLength()); } return pStr; } -bool CServerMapBlockingState::CheckTile( uint64 uiItemBlockFlags, char zBottom, height_t zHeight, dword dwID ) +bool CServerMapBlockingState::CheckTile( uint64 uiItemBlockFlags, char zBottom, height_t zHeight, dword dwID ) noexcept { - ADDTOCALLSTACK("CServerMapBlockingState::CheckTile"); + //ADDTOCALLSTACK("CServerMapBlockingState::CheckTile"); // RETURN: // true = continue processing @@ -175,9 +175,9 @@ bool CServerMapBlockingState::CheckTile( uint64 uiItemBlockFlags, char zBottom, return true; } -bool CServerMapBlockingState::CheckTile_Item( uint64 uiItemBlockFlags, char zBottom, height_t zHeight, dword dwID ) +bool CServerMapBlockingState::CheckTile_Item( uint64 uiItemBlockFlags, char zBottom, height_t zHeight, dword dwID ) noexcept { - ADDTOCALLSTACK("CServerMapBlockingState::CheckTile_Item"); + //ADDTOCALLSTACK("CServerMapBlockingState::CheckTile_Item"); // RETURN: // true = continue processing @@ -262,9 +262,9 @@ bool CServerMapBlockingState::CheckTile_Item( uint64 uiItemBlockFlags, char zBot } -bool CServerMapBlockingState::CheckTile_Terrain( uint64 uiItemBlockFlags, char z, dword dwID ) +bool CServerMapBlockingState::CheckTile_Terrain( uint64 uiItemBlockFlags, char z, dword dwID ) noexcept { - ADDTOCALLSTACK("CServerMapBlockingState::CheckTile_Terrain"); + //ADDTOCALLSTACK("CServerMapBlockingState::CheckTile_Terrain"); // RETURN: // true = i can step on it, so continue processing @@ -567,6 +567,12 @@ CUOMulti::~CUOMulti() Release(); } +void CUOMulti::HitCacheTime() noexcept +{ + // When was this last referenced. + CCachedMulItem::m_timeRef = CWorldGameTime::GetCurrentTime().GetTimeRaw(); +} + const CUOMultiItemRec_HS * CUOMulti::GetItem( size_t i ) const { ASSERT( i @@ -138,9 +137,8 @@ struct CServerMapDiffBlockArray : public CSObjSortArray< CServerMapDiffBlock*, d CServerMapDiffBlockArray() = default; int CompareKey( dword id, CServerMapDiffBlock* pBase, bool fNoSpaces ) const; -private: - CServerMapDiffBlockArray(const CServerMapDiffBlockArray& copy); - CServerMapDiffBlockArray& operator=(const CServerMapDiffBlockArray& other); + CServerMapDiffBlockArray(const CServerMapDiffBlockArray& copy) = delete; + CServerMapDiffBlockArray& operator=(const CServerMapDiffBlockArray& other) = delete; }; class CServerMapDiffCollection @@ -157,9 +155,8 @@ class CServerMapDiffCollection CServerMapDiffCollection(); ~CServerMapDiffCollection(); -private: - CServerMapDiffCollection(const CServerMapDiffCollection& copy); - CServerMapDiffCollection& operator=(const CServerMapDiffCollection& other); + CServerMapDiffCollection(const CServerMapDiffCollection& copy) = delete; + CServerMapDiffCollection& operator=(const CServerMapDiffCollection& other) = delete; public: void Init(); @@ -188,9 +185,8 @@ class CServerMapBlock : // Cache this from the MUL files. 8x8 block of the world CServerMapBlock(int bx, int by, int map); virtual ~CServerMapBlock(); -private: - CServerMapBlock(const CServerMapBlock& copy); - CServerMapBlock& operator=(const CServerMapBlock& other); + CServerMapBlock(const CServerMapBlock& copy) = delete; + CServerMapBlock& operator=(const CServerMapBlock& other) = delete; public: inline int GetOffsetX(int x) const @@ -214,25 +210,28 @@ class CServerMapBlock : // Cache this from the MUL files. 8x8 block of the world } }; -class CUOMulti : public CCachedMulItem +class CUOMulti : private CCachedMulItem { // Load all the relivant info for the -private: - MULTI_TYPE m_id; protected: CUOMultiItemRec_HS * m_pItems; uint m_iItemQty; +private: + MULTI_TYPE m_id; + public: static const char *m_sClassName; + CUOMulti(); explicit CUOMulti( MULTI_TYPE id ); - virtual ~CUOMulti(); + ~CUOMulti(); -private: - CUOMulti(const CUOMulti& copy); - CUOMulti& operator=(const CUOMulti& other); + CUOMulti(const CUOMulti& copy) = delete; + CUOMulti& operator=(const CUOMulti& other) = delete; + + void HitCacheTime() noexcept; private: void Init(); diff --git a/src/common/CUID.cpp b/src/common/CUID.cpp index 19cff7fa9..fae5417b8 100644 --- a/src/common/CUID.cpp +++ b/src/common/CUID.cpp @@ -59,19 +59,6 @@ bool CUID::IsChar(dword dwPrivateUID) noexcept // static } -bool CUID::IsObjDisconnected() const noexcept -{ - // Not in the game world for some reason. - return ((m_dwInternalVal & (UID_F_RESOURCE | UID_O_DISCONNECT)) == UID_O_DISCONNECT); -} - -bool CUID::IsObjTopLevel() const noexcept -{ - // on the ground in the world. - // might be static in client ? - return ((m_dwInternalVal & (UID_F_RESOURCE | UID_O_DISCONNECT)) == 0); -} - bool CUID::IsItemEquipped() const noexcept { if ( (m_dwInternalVal & (UID_F_RESOURCE|UID_F_ITEM|UID_O_DISCONNECT)) == (UID_F_ITEM|UID_O_EQUIPPED)) diff --git a/src/common/CUID.h b/src/common/CUID.h index ff2dd6413..e64c7dc26 100644 --- a/src/common/CUID.h +++ b/src/common/CUID.h @@ -81,8 +81,18 @@ class CUID // A unique system serial id. 4 bytes long return IsChar(m_dwInternalVal); } - bool IsObjDisconnected() const noexcept; - bool IsObjTopLevel() const noexcept; + inline bool IsObjDisconnected() const noexcept // Called very frequently + { + // Not in the game world for some reason. + return ((m_dwInternalVal & (UID_F_RESOURCE | UID_O_DISCONNECT)) == UID_O_DISCONNECT); + } + + bool IsObjTopLevel() const noexcept + { + // on the ground in the world. + // might be static in client ? + return ((m_dwInternalVal & (UID_F_RESOURCE | UID_O_DISCONNECT)) == 0); + } bool IsItemEquipped() const noexcept; bool IsItemInContainer() const noexcept; diff --git a/src/common/CUOClientVersion.cpp b/src/common/CUOClientVersion.cpp index e29ea2248..3d393c1e3 100644 --- a/src/common/CUOClientVersion.cpp +++ b/src/common/CUOClientVersion.cpp @@ -117,11 +117,16 @@ CUOClientVersion::CUOClientVersion(lpctstr ptcVersion) noexcept : if ((ptcVersion == nullptr) || (*ptcVersion == '\0')) return; - ptrdiff_t count = std::ranges::count(std::string_view(ptcVersion), '.'); + // Ranges algorithms not yet supported by Apple Clang... + // const ptrdiff_t count = std::ranges::count(std::string_view(ptcVersion), '.'); + const auto svVersion = std::string_view(ptcVersion); + const auto count = std::count(svVersion.cbegin(), svVersion.cend(), '_'); if (count == 2) ApplyVersionFromStringOldFormat(ptcVersion); - if (count == 3) + else if (count == 3) ApplyVersionFromStringOldFormat(ptcVersion); + else + ASSERT(false); // Malformed string? } diff --git a/src/common/CVarDefMap.cpp b/src/common/CVarDefMap.cpp index 6a22d9f2a..48a4f98b8 100644 --- a/src/common/CVarDefMap.cpp +++ b/src/common/CVarDefMap.cpp @@ -16,7 +16,7 @@ inline static int VarDefCompare(const CVarDefCont* pVar, lpctstr ptcKey) noexcep lpctstr CVarDefCont::GetValStrZeroed(const CVarDefCont* pVar, bool fZero) // static { - ADDTOCALLSTACK_INTENSIVE("CVarDefCont::GetValStrZeroed"); + ADDTOCALLSTACK_DEBUG("CVarDefCont::GetValStrZeroed"); if (pVar) { lpctstr ptcValStr = pVar->GetValStr(); @@ -137,7 +137,7 @@ CVarDefMap::~CVarDefMap() lpctstr CVarDefMap::FindValStr( lpctstr pVal ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::FindValStr"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::FindValStr"); for ( const CVarDefCont * pVarBase : m_Container ) { ASSERT( pVarBase ); @@ -155,7 +155,7 @@ lpctstr CVarDefMap::FindValStr( lpctstr pVal ) const lpctstr CVarDefMap::FindValNum( int64 iVal ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::FindValNum"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::FindValNum"); for (const CVarDefCont* pVarBase : m_Container) { ASSERT( pVarBase ); @@ -172,7 +172,7 @@ lpctstr CVarDefMap::FindValNum( int64 iVal ) const CVarDefCont * CVarDefMap::GetAt( size_t at ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::GetAt"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::GetAt"); if ( at > m_Container.size() ) return nullptr; return m_Container[at]; @@ -180,7 +180,7 @@ CVarDefCont * CVarDefMap::GetAt( size_t at ) const CVarDefCont * CVarDefMap::GetAtKey( lpctstr ptcKey ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::GetAtKey"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::GetAtKey"); const size_t idx = m_Container.find_predicate(ptcKey, VarDefCompare); if ( idx != sl::scont_bad_index() ) @@ -190,7 +190,7 @@ CVarDefCont * CVarDefMap::GetAtKey( lpctstr ptcKey ) const void CVarDefMap::DeleteAt( size_t at ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::DeleteAt"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::DeleteAt"); if ( at > m_Container.size() ) return; @@ -215,7 +215,7 @@ void CVarDefMap::DeleteAt( size_t at ) void CVarDefMap::DeleteAtKey( lpctstr ptcKey ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::DeleteAtKey"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::DeleteAtKey"); const size_t idx = m_Container.find_predicate(ptcKey, VarDefCompare); if (idx != sl::scont_bad_index()) DeleteAt(idx); @@ -223,14 +223,14 @@ void CVarDefMap::DeleteAtKey( lpctstr ptcKey ) void CVarDefMap::DeleteKey( lpctstr key ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::DeleteKey"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::DeleteKey"); if ( key && *key) DeleteAtKey(key); } void CVarDefMap::Clear() { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::Empty"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::Clear"); iterator it = m_Container.begin(); while ( it != m_Container.end() ) { @@ -244,7 +244,7 @@ void CVarDefMap::Clear() void CVarDefMap::Copy( const CVarDefMap * pArray, bool fClearThis ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::Copy"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::Copy"); if ( !pArray || pArray == this ) return; @@ -262,7 +262,7 @@ void CVarDefMap::Copy( const CVarDefMap * pArray, bool fClearThis ) bool CVarDefMap::Compare( const CVarDefMap * pArray ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::Compare"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::Compare"); if ( !pArray ) return false; if ( pArray == this ) @@ -287,7 +287,7 @@ bool CVarDefMap::Compare( const CVarDefMap * pArray ) bool CVarDefMap::CompareAll( const CVarDefMap * pArray ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::CompareAll"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::CompareAll"); if ( !pArray ) return false; if ( pArray == this ) @@ -323,7 +323,7 @@ size_t CVarDefMap::GetCount() const noexcept CVarDefContNum* CVarDefMap::SetNumNew( lpctstr pszName, int64 iVal ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::SetNumNew"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::SetNumNew"); CVarDefContNum * pVarNum = new CVarDefContNum( pszName, iVal ); if ( !pVarNum ) return nullptr; @@ -340,7 +340,7 @@ CVarDefContNum* CVarDefMap::SetNumNew( lpctstr pszName, int64 iVal ) CVarDefContNum* CVarDefMap::SetNumOverride( lpctstr ptcKey, int64 iVal ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::SetNumOverride"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::SetNumOverride"); CVarDefContNum* pKeyNum = dynamic_cast(GetKey(ptcKey)); if (pKeyNum) { @@ -353,7 +353,7 @@ CVarDefContNum* CVarDefMap::SetNumOverride( lpctstr ptcKey, int64 iVal ) CVarDefContNum* CVarDefMap::ModNum(lpctstr pszName, int64 iMod, bool fDeleteZero) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::ModNum"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::ModNum"); ASSERT(pszName); CVarDefCont* pVarDef = GetKey(pszName); if (pVarDef) @@ -378,7 +378,7 @@ CVarDefContNum* CVarDefMap::ModNum(lpctstr pszName, int64 iMod, bool fDeleteZero CVarDefContNum* CVarDefMap::SetNum( lpctstr pszName, int64 iVal, bool fDeleteZero, bool fWarnOverwrite ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::SetNum"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::SetNum"); ASSERT(pszName); if ( pszName[0] == '\0' ) @@ -418,7 +418,7 @@ CVarDefContNum* CVarDefMap::SetNum( lpctstr pszName, int64 iVal, bool fDeleteZer CVarDefContStr* CVarDefMap::SetStrNew( lpctstr pszName, lpctstr pszVal ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::SetStrNew"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::SetStrNew"); CVarDefContStr * pVarStr = new CVarDefContStr( pszName, pszVal ); if ( !pVarStr ) return nullptr; @@ -435,7 +435,7 @@ CVarDefContStr* CVarDefMap::SetStrNew( lpctstr pszName, lpctstr pszVal ) CVarDefContStr* CVarDefMap::SetStrOverride( lpctstr ptcKey, lpctstr pszVal ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::SetStrOverride"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::SetStrOverride"); CVarDefContStr* pKeyStr = dynamic_cast(GetKey(ptcKey)); if (pKeyStr) { @@ -448,7 +448,7 @@ CVarDefContStr* CVarDefMap::SetStrOverride( lpctstr ptcKey, lpctstr pszVal ) CVarDefCont* CVarDefMap::SetStr( lpctstr pszName, bool fQuoted, lpctstr pszVal, bool fDeleteZero, bool fWarnOverwrite ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::SetStr"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::SetStr"); // ASSUME: This has been clipped of unwanted beginning and trailing spaces. ASSERT(pszName); if ( !pszName[0] ) @@ -498,7 +498,7 @@ CVarDefCont* CVarDefMap::SetStr( lpctstr pszName, bool fQuoted, lpctstr pszVal, CVarDefCont * CVarDefMap::GetKey( lpctstr ptcKey ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::GetKey"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::GetKey"); CVarDefCont * pReturn = nullptr; if ( ptcKey ) @@ -514,7 +514,7 @@ CVarDefCont * CVarDefMap::GetKey( lpctstr ptcKey ) const int64 CVarDefMap::GetKeyNum( lpctstr ptcKey ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::GetKeyNum"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::GetKeyNum"); const CVarDefCont * pVar = GetKey(ptcKey); if ( pVar == nullptr ) return 0; @@ -523,14 +523,14 @@ int64 CVarDefMap::GetKeyNum( lpctstr ptcKey ) const lpctstr CVarDefMap::GetKeyStr( lpctstr ptcKey, bool fZero ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::GetKeyStr"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::GetKeyStr"); const CVarDefCont * pVar = GetKey(ptcKey); return CVarDefCont::GetValStrZeroed(pVar, fZero); } CVarDefCont * CVarDefMap::CheckParseKey( lpctstr pszArgs ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::CheckParseKey"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::CheckParseKey"); tchar szTag[ EXPRESSION_MAX_KEY_LEN ]; GetIdentifierString( szTag, pszArgs ); CVarDefCont * pVar = GetKey(szTag); @@ -542,7 +542,7 @@ CVarDefCont * CVarDefMap::CheckParseKey( lpctstr pszArgs ) const CVarDefCont * CVarDefMap::GetParseKey_Advance( lpctstr & pszArgs ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::GetParseKey_Advance"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::GetParseKey_Advance"); // Skip to the end of the expression name. // The name can only be valid. @@ -558,7 +558,7 @@ CVarDefCont * CVarDefMap::GetParseKey_Advance( lpctstr & pszArgs ) const bool CVarDefMap::GetParseVal_Advance( lpctstr & pszArgs, llong * pllVal ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::GetParseVal_Advance"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::GetParseVal_Advance"); CVarDefCont * pVarBase = GetParseKey_Advance( pszArgs ); if ( pVarBase == nullptr ) return false; @@ -632,7 +632,7 @@ void CVarDefMap::ClearKeys(lpctstr mask) /* bool CVarDefMap::r_LoadVal( CScript & s ) { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::r_LoadVal"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::r_LoadVal"); bool fQuoted = false; lpctstr ptcVal = s.GetArgStr( &fQuoted ); return ( SetStr( s.GetKey(), fQuoted, ptcVal) ? true : false ); @@ -641,7 +641,7 @@ bool CVarDefMap::r_LoadVal( CScript & s ) void CVarDefMap::r_WritePrefix( CScript & s, lpctstr ptcPrefix, lpctstr ptcKeyExclude ) const { - ADDTOCALLSTACK_INTENSIVE("CVarDefMap::r_WritePrefix"); + ADDTOCALLSTACK_DEBUG("CVarDefMap::r_WritePrefix"); if (m_Container.empty()) return; diff --git a/src/common/assertion.h b/src/common/assertion.h index ceb2c8fd7..dd30c68d1 100644 --- a/src/common/assertion.h +++ b/src/common/assertion.h @@ -1,23 +1,32 @@ #ifndef _INC_ASSERTION_H #define _INC_ASSERTION_H +#ifndef STATIC_ANALYSIS + #if defined(__COVERITY__) + #define STATIC_ANALYSIS + #endif +#endif extern void Assert_Fail(const char * pExp, const char *pFile, long long llLine); -#define PERSISTANT_ASSERT(exp) if ( !(exp) ) Assert_Fail(#exp, __FILE__, __LINE__) +#define PERSISTANT_ASSERT(exp) if ( !(exp) ) Assert_Fail(#exp, __FILE__, __LINE__) -#if defined(_NIGHTLY) || defined(_DEBUG) || defined(__COVERITY__) - #define ASSERT(exp) if ( !(exp) ) Assert_Fail(#exp, __FILE__, __LINE__) +#if defined(STATIC_ANALYSIS) + #include + #define ASSERT(exp) assert(exp) +#elif defined(_NIGHTLY) || defined(_DEBUG) + #define ASSERT(exp) if ( !(exp) ) Assert_Fail(#exp, __FILE__, __LINE__) #else - #define ASSERT(exp) (void)0 + #define ASSERT(exp) (void)0 #endif -#if defined(_DEBUG) || defined(__COVERITY__) - #define DEBUG_ASSERT(exp) if ( !(exp) ) Assert_Fail(#exp, __FILE__, __LINE__) +#if defined(_DEBUG) || defined(STATIC_ANALYSIS) + #define DEBUG_ASSERT(exp) if ( !(exp) ) Assert_Fail(#exp, __FILE__, __LINE__) #else - #define DEBUG_ASSERT(exp) (void)0 + #define DEBUG_ASSERT(exp) (void)0 #endif +#undef STATIC_ANALYSIS #endif // ! _INC_ASSERTION_H diff --git a/src/common/basic_threading.h b/src/common/basic_threading.h index 75689914f..5f1d802ca 100644 --- a/src/common/basic_threading.h +++ b/src/common/basic_threading.h @@ -5,49 +5,72 @@ * Defines for easy C++17 thread-locking for class methods. **/ -//#ifdef _DEBUG +#define MT_ENGINES 0 // are we using/working on true multithreaded network/scripting engines? #include #include -#define THREAD_CMUTEX _classMutex // Name of the Class Mutex -#define THREAD_CMUTEX_DEF mutable std::shared_mutex THREAD_CMUTEX // Use this in the class definition to add the class mutex. -#define THREAD_SHARED_LOCK_SET std::shared_lock _shared_lock(THREAD_CMUTEX) // Read-Only: multiple threads can read the same resource -#define THREAD_SHARED_LOCK _shared_lock.lock() // Locking is already done by THREAD_SHARED_LOCK_SET. Use it only if you know what you are doing! -#define THREAD_SHARED_UNLOCK _shared_lock.unlock() // Unlocking is done automatically then the function ends (when the lock created with THREAD_SHARED_LOCK_SET goes out of scope). Use it only if you know what you are doing! -#define THREAD_UNIQUE_LOCK_SET std::unique_lock _unique_lock(THREAD_CMUTEX) // Read/Write: exclusive access to a thread at a time -#define THREAD_UNIQUE_LOCK _unique_lock.lock() // Locking is already done by THREAD_UNIQUE_LOCK_SET. Use it only if you know what you are doing! -#define THREAD_UNIQUE_UNLOCK _unique_lock.unlock() // Unlocking is done automatically then the function ends (when the lock created with THREAD_UNIQUE_LOCK_SET goes out of scope). Use it only if you know what you are doing! + +// Name of the Class Mutex +#define MT_CMUTEX _classMutex +// Use this in the class definition to add the class mutex. +#define MT_CMUTEX_DEF mutable std::shared_mutex MT_CMUTEX + +// Read-Only: multiple threads can read the same resource +#define MT_SHARED_LOCK_SET std::shared_lock _shared_lock(this->MT_CMUTEX) +// Locking is already done by MT_SHARED_LOCK_SET. Use it only if you know what you are doing! +#define MT_SHARED_LOCK _shared_lock.lock() +// Unlocking is done automatically then the function ends (when the lock created with MT_SHARED_LOCK_SET goes out of scope). Use it only if you know what you are doing! +#define MT_SHARED_UNLOCK _shared_lock.unlock() + +// Read/Write: exclusive access to a thread at a time +#define MT_UNIQUE_LOCK_SET std::unique_lock _unique_lock(this->MT_CMUTEX) +// Locking is already done by MT_UNIQUE_LOCK_SET. Use it only if you know what you are doing! +#define MT_UNIQUE_LOCK _unique_lock.lock() +// Unlocking is done automatically then the function ends (when the lock created with MT_UNIQUE_LOCK_SET goes out of scope). Use it only if you know what you are doing! +#define MT_UNIQUE_UNLOCK _unique_lock.unlock() // Thread-safe return macro. If we directly do return without using a temporary storage variable, the return value will be stored in the returned register // after the thread lock is destroyed, so after the mutex is unlocked. In this way, we have a variable holding the value of the return expression, but before the mutex unlocking. -// Do not use to return references! (It will return the address of the local-scoped, temporary variable _ts_return_val) -#define TS_RETURN(x) \ - {volatile auto _ts_return_val = (x); \ - return _ts_return_val;} +// Do not use to return references! (It will return the address of the local-scoped, temporary variable _MT_RETURN_val) +#define MT_RETURN(x) \ + {volatile auto _MT_RETURN_val = (x); \ + return _MT_RETURN_val;} // A single macro to lock the class mutex and return the value in a thread-safe way. -// Do not use to return references! (It will return the address of the local-scoped, temporary variable _ts_return_val) -#define THREAD_SHARED_LOCK_RETURN(x) \ - {THREAD_SHARED_LOCK_SET; \ - TS_RETURN(x);} - -#define THREAD_UNIQUE_LOCK_RETURN(x) \ - {THREAD_UNIQUE_LOCK_SET; \ - TS_RETURN(x);} -/* +// Do not use to return references! (It will return the address of the local-scoped, temporary variable _MT_RETURN_val) +#define MT_SHARED_LOCK_RETURN(x) \ + {MT_SHARED_LOCK_SET; \ + MT_RETURN(x);} + +#define MT_UNIQUE_LOCK_RETURN(x) \ + {MT_UNIQUE_LOCK_SET; \ + MT_RETURN(x);} + + +#if MT_ENGINES == 1 //_DEBUG + +#define MT_ENGINE_SHARED_LOCK_SET MT_SHARED_LOCK_SET +#define MT_ENGINE_SHARED_LOCK MT_SHARED_LOCK +#define MT_ENGINE_SHARED_UNLOCK MT_SHARED_UNLOCK +#define MT_ENGINE_UNIQUE_LOCK_SET MT_UNIQUE_LOCK_SET +#define MT_ENGINE_UNIQUE_UNLOCK MT_UNIQUE_UNLOCK + +#define MT_ENGINE_RETURN(x) MT_RETURN(x) +#define MT_ENGINE_SHARED_LOCK_RETURN(x) MT_SHARED_LOCK_RETURN(x) +#define MT_ENGINE_UNIQUE_LOCK_RETURN(x) MT_UNIQUE_LOCK_RETURN(x) + #else - #define THREAD_CMUTEX_DEF (void)0 - #define THREAD_SHARED_LOCK_SET (void)0 - #define THREAD_SHARED_LOCK (void)0 - #define THREAD_SHARED_UNLOCK (void)0 - #define THREAD_UNIQUE_LOCK_SET (void)0 - #define THREAD_UNIQUE_LOCK (void)0 - #define THREAD_UNIQUE_UNLOCK (void)0 - #define TS_RETURN(x) return (x) - #define THREAD_SHARED_LOCK_RETURN(x) return (x) - #define THREAD_UNIQUE_LOCK_RETURN(x) return (x) + #define MT_ENGINE_SHARED_LOCK_SET (void)0 + #define MT_ENGINE_SHARED_LOCK (void)0 + #define MT_ENGINE_SHARED_UNLOCK (void)0 + #define MT_ENGINE_UNIQUE_LOCK_SET (void)0 + #define MT_ENGINE_UNIQUE_LOCK (void)0 + #define MT_ENGINE_UNIQUE_UNLOCK (void)0 + #define MT_ENGINE_RETURN(x) return (x) + #define MT_ENGINE_SHARED_LOCK_RETURN(x) return (x) + #define MT_ENGINE_UNIQUE_LOCK_RETURN(x) return (x) #endif -*/ + #endif // _INC_BASIC_THREADING_H diff --git a/src/common/common.h b/src/common/common.h index baa0799c2..36312c656 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -33,23 +33,27 @@ #endif +// Strings #define _STRINGIFY_AUX(x) #x #define STRINGIFY(x) _STRINGIFY_AUX(x) -#define ARRAY_COUNT(a) (sizeof(a)/sizeof((a)[0])) -#define HAS_FLAG(var, flag) (((var) & (flag)) == flag) +// Sizes +#define ARRAY_COUNT(a) (sizeof(a)/sizeof((a)[0])) +// Flags and bitmasks. Those macros works with 1 or multiple ORed flags together. +#define HAS_FLAGS_STRICT(var, flag) (((var) & (flag)) == flag) // Every one of the passed flags has to be set +#define HAS_FLAGS_ANY(var, flag) (static_cast((var) & (flag))) // True if even only one of the passed flags are set -#if __cplusplus >= 201703L // is C++17 enabled? - #define FALLTHROUGH [[fallthrough]] - #define NODISCARD [[nodiscard]] +// Cpp attributes +#define FALLTHROUGH [[fallthrough]] +#define NODISCARD [[nodiscard]] + +#ifdef _DEBUG + #define NOEXCEPT_NODEBUG #else - #define FALLTHROUGH // fall through - /* yep, the comment appears to silence the warning with GCC, dunno for clang */ - #define NODISCARD + #define NOEXCEPT_NODEBUG noexcept #endif - /* There is a problem with the UnreferencedParameter macro from mingw and sphereserver. operator= is on many clases private and the UnreferencedParameter macro from mingw is (P)=(P), @@ -64,18 +68,52 @@ inline void UnreferencedParameter(T const&) noexcept { /* Sanitizers utility */ -#ifndef _DEBUG - #define NO_SANITIZE_ADDRESS -#else - #ifdef _MSC_VER - #ifdef __SANITIZE_ADDRESS__ - #define NO_SANITIZE_ADDRESS __declspec(no_sanitize_address) - #else - #define NO_SANITIZE_ADDRESS - #endif +#if defined(_MSC_VER) + + #if defined(__SANITIZE_ADDRESS__) || defined(ADDRESS_SANITIZER) + #define NO_SANITIZE_ADDRESS __declspec(no_sanitize_address) #else - #define NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address"))) + #define NO_SANITIZE_ADDRESS #endif + + // Not yet implemented on MSVC, as of 2022 ver. + #define NO_SANITIZE_UNDEFINED + +#elif defined(__clang__) + + #if defined(__SANITIZE_ADDRESS__) || defined(ADDRESS_SANITIZER) + #define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) + #else + #define NO_SANITIZE_ADDRESS + #endif + + //#if __has_feature(undefined_behavior_sanitizer) || defined(UNDEFINED_BEHAVIOR_SANITIZER) + // Clang doesn't have an attribute nor a pragma for this (yet?) + // #define NO_SANITIZE_UNDEFINED __attribute__((no_sanitize_undefined)) + //#else + #define NO_SANITIZE_UNDEFINED + //#endif + +#elif defined(__GNUC__) + + #if defined(__SANITIZE_ADDRESS__) || defined(ADDRESS_SANITIZER) + #define NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address"))) + #else + #define NO_SANITIZE_ADDRESS + #endif + + // GCC still hasn't __SANITIZE_UNDEFINED__ ? + //#if defined(__SANITIZE_UNDEFINED__) || defined(UNDEFINED_BEHAVIOR_SANITIZER) + // #define NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined"))) + //#else + #define NO_SANITIZE_UNDEFINED + //#endif + +#else + + #define NO_SANITIZE_ADDRESS + #define NO_SANITIZE_UNDEFINED + #endif diff --git a/src/common/crypto/CCryptoBlowFish.cpp b/src/common/crypto/CCryptoBlowFish.cpp index 79a2ce8b2..fea1bf5b2 100644 --- a/src/common/crypto/CCryptoBlowFish.cpp +++ b/src/common/crypto/CCryptoBlowFish.cpp @@ -249,6 +249,7 @@ bool CCrypto::sm_fBFishTablesReady = false; // static static dword s_dwCodingData[CRYPT_GAMEKEY_COUNT][18+1024]; // to be filled by InitTables +NO_SANITIZE_UNDEFINED // integer overflow is expected void CCrypto::PrepareKey(CCryptoKeysHolder::CCryptoKey & key, int iTable ) // static { ADDTOCALLSTACK("CCrypto::PrepareKey"); @@ -257,8 +258,8 @@ void CCrypto::PrepareKey(CCryptoKeysHolder::CCryptoKey & key, int iTable ) // st key.u_iKey[1] ^= pCodes[0]; for(int i=0; i<8; ++i) { - key.u_iKey[0] ^= pCodes[i*2+1]^(((pCodes[18+key.u_cKey[7]]+pCodes[18+key.u_cKey[6]+0x100])^pCodes[18+key.u_cKey[5]+0x200])+pCodes[18+key.u_cKey[4]+0x300]); - key.u_iKey[1] ^= pCodes[i*2+2]^(((pCodes[18+key.u_cKey[3]]+pCodes[18+key.u_cKey[2]+0x100])^pCodes[18+key.u_cKey[1]+0x200])+pCodes[18+key.u_cKey[0]+0x300]); + key.u_iKey[0] ^= pCodes[i*2+1] ^ (((pCodes[18+key.u_cKey[7]]+pCodes[18+key.u_cKey[6]+0x100]) ^ pCodes[18+key.u_cKey[5]+0x200]) + pCodes[18+key.u_cKey[4]+0x300]); + key.u_iKey[1] ^= pCodes[i*2+2] ^ (((pCodes[18+key.u_cKey[3]]+pCodes[18+key.u_cKey[2]+0x100]) ^ pCodes[18+key.u_cKey[1]+0x200]) + pCodes[18+key.u_cKey[0]+0x300]); } key.u_iKey[0] ^= pCodes[17]; @@ -267,7 +268,8 @@ void CCrypto::PrepareKey(CCryptoKeysHolder::CCryptoKey & key, int iTable ) // st key.u_iKey[1] = tmp; } -void CCrypto::InitTables() // static +NO_SANITIZE_UNDEFINED // integer overflow is expected +void CCrypto::InitTables() // static { ADDTOCALLSTACK("CCrypto::InitTables"); for (int i=0; i>= 4; - while ( nBits-- ) + byte nBits = (byte)(value & (word)0xF); + value >>= 4u; + while ( nBits ) { if (iLen >= outLen) return 0; // error: i'm trying to write more bytes than the output buffer length - xOutVal <<= 1; - xOutVal |= (value >> nBits) & 0x1; - if ( ++bitidx == 8) + + nBits -= 1u; + + //xOutVal <<= 1; + xOutVal = (byte)((uint)xOutVal << 1u); + + //xOutVal |= ((value >> nBits) & 0x1); + xOutVal |= (((uint)value >> (uint)nBits) & 0x1u); + + if ( ++bitidx == 8) { bitidx = 0; - pOutput[iLen++] = xOutVal; + pOutput[iLen++] = xOutVal; } } } @@ -78,8 +86,10 @@ uint CHuffman::Compress( byte * pOutput, const byte * pInput, uint outLen, uint { if (iLen >= outLen) return 0; - pOutput[iLen++] = xOutVal << (8 - bitidx); + + // pOutput[iLen++] = xOutVal << (8u - bitidx); + pOutput[iLen++] = (byte)((uint)xOutVal << (uint)(8u - bitidx)); } - + return iLen; } diff --git a/src/common/resource/CResourceDef.cpp b/src/common/resource/CResourceDef.cpp index 89d6191eb..15ff928a1 100644 --- a/src/common/resource/CResourceDef.cpp +++ b/src/common/resource/CResourceDef.cpp @@ -44,7 +44,7 @@ bool CResourceDef::SetResourceName( lpctstr pszName ) return true; } - const int iKeyIndex = (int)RES_GET_INDEX(dwKeyVal); + const int iKeyIndex = (int)ResGetIndex(dwKeyVal); if ( iKeyIndex == iResIndex) DEBUG_WARN(( "DEFNAME=%s: redefinition with a strange type mismatch? (0%" PRIx32 "!=0%" PRIx32 ")\n", pszName, dwKeyVal, dwResPrivateUID )); else diff --git a/src/common/resource/CResourceHolder.cpp b/src/common/resource/CResourceHolder.cpp index 43dbc1893..196af300d 100644 --- a/src/common/resource/CResourceHolder.cpp +++ b/src/common/resource/CResourceHolder.cpp @@ -380,8 +380,8 @@ CResourceID CResourceHolder::ResourceGetID_EatStr(RES_TYPE restype, lpctstr &ptc lpctstr ptcNameStart = ptcName; dword dwEvalPrivateUID = Exp_GetDWVal(ptcName); // May be some complex expression {} - int iEvalResType = RES_GET_TYPE(dwEvalPrivateUID); - int iEvalResIndex = RES_GET_INDEX(dwEvalPrivateUID); + int iEvalResType = ResGetType(dwEvalPrivateUID); + int iEvalResIndex = ResGetIndex(dwEvalPrivateUID); // We are NOT creating. if ((restype != RES_UNKNOWN) && (iEvalResType == RES_UNKNOWN)) @@ -477,6 +477,6 @@ bool CResourceHolder::ResourceLock( CResourceLock & s, const CResourceID& rid ) CResourceLink* pResourceLink = dynamic_cast (ResourceGetDefRef(rid).get()); if (pResourceLink) return pResourceLink->ResourceLock(s); - + return false; } diff --git a/src/common/resource/CResourceID.h b/src/common/resource/CResourceID.h index 5dee21a15..b887e8b60 100644 --- a/src/common/resource/CResourceID.h +++ b/src/common/resource/CResourceID.h @@ -95,22 +95,37 @@ enum RES_TYPE // all the script resource sections we know how to deal with ! #define RES_NEWBIE_PROF_NINJA (10000+10) -struct CResourceIDBase : public CUID // It has not the "page" part/variable. Use it to store defnames or UIDs of world objects (items, chars...) or spawns and templates. -{ - // What is a Resource? Look at the comment made to the RES_TYPE enum. - // RES_TYPE: Resource Type (look at the RES_TYPE enum entries). - // RES_INDEX: Resource Index +/* + What is a Resource? Look at the comment made to the RES_TYPE enum. + RES_TYPE: Resource Type (look at the RES_TYPE enum entries). + RES_INDEX: Resource Index + + CResourceIDBase.m_dwInternalVal structure: + - Uppper 4 bits: RESERVED for flags UID_F_RESOURCE, UID_F_ITEM, UID_O_EQUIPPED, UID_O_CONTAINED. + - Usable size: 8 bits (res_type) + 20 bits (index) = 28 --> it's a 28 bits number. + +*/ - // m_dwInternalVal: - // - Uppper 4 bits: RESERVED for flags UID_F_RESOURCE, UID_F_ITEM, UID_O_EQUIPPED, UID_O_CONTAINED. - // - Usable size: 8 bits (res_type) + 20 bits (index) = 28 --> it's a 28 bits number. #define RES_TYPE_SHIFT 20 // skip first 20 bits, use next 8 bits = 0xFFFF = 65535 possible unique RES_TYPEs; #define RES_TYPE_MASK 0xFF // 0xFF = 8 bits. #define RES_INDEX_SHIFT 0 // use first 20 bits = 0xFFFFF = 1048575 possible unique indexes. #define RES_INDEX_MASK 0xFFFFF // 0xFFFFF = 20 bits. -#define RES_GET_TYPE(dw) ( ( (dw) >> RES_TYPE_SHIFT ) & RES_TYPE_MASK ) -#define RES_GET_INDEX(dw) ( (dw) & (dword)RES_INDEX_MASK ) +[[nodiscard]] inline constexpr +dword ResGetType(dword dwObjUid) noexcept { + return ( (dwObjUid >> RES_TYPE_SHIFT) & RES_TYPE_MASK ); +} + +[[nodiscard]] inline constexpr +dword ResGetIndex(dword dwObjUid) noexcept { + return (dwObjUid & (dword)RES_INDEX_MASK); +} + + +struct CResourceIDBase : public CUID +{ + // Unlike CResourceID, CResourceIDBase has not the "page" part/variable. + // Use it to store defnames or UIDs of world objects (items, chars...) or spawns and templates. void InitUID() = delete; void ClearUID() = delete; @@ -143,11 +158,11 @@ struct CResourceIDBase : public CUID // It has not the "page" part/variable. inline RES_TYPE GetResType() const noexcept { - return (RES_TYPE)(RES_GET_TYPE(m_dwInternalVal)); + return (RES_TYPE)(ResGetType(m_dwInternalVal)); } inline uint GetResIndex() const noexcept { - return RES_GET_INDEX(m_dwInternalVal); + return ResGetIndex(m_dwInternalVal); } inline bool operator == (const CResourceIDBase & rid) const noexcept { @@ -177,6 +192,8 @@ struct CResourceID : public CResourceIDBase // It has the "page" part. Use i void Init() noexcept; void Clear() noexcept; + // TODO: set iIndex to unsigned. + CResourceID() : CResourceIDBase(), m_wPage(0) {} // Create an empty, valid CResourceID of a given type diff --git a/src/common/resource/CResourceLink.cpp b/src/common/resource/CResourceLink.cpp index bc623f036..10b957d1c 100644 --- a/src/common/resource/CResourceLink.cpp +++ b/src/common/resource/CResourceLink.cpp @@ -166,11 +166,11 @@ void CResourceLink::SetTrigger(int i) ADDTOCALLSTACK("CResourceLink::SetTrigger"); if ( i >= 0 ) { - for ( int j = 0; j < MAX_TRIGGERS_ARRAY; ++j ) + for ( dword j = 0; j < MAX_TRIGGERS_ARRAY; ++j ) { if ( i < 32 ) { - const dword flag = 1 << i; + const dword flag = 1u << i; m_dwOnTriggers[j] |= flag; return; } @@ -186,11 +186,11 @@ bool CResourceLink::HasTrigger(int i) const if ( i < XTRIG_UNKNOWN ) i = XTRIG_UNKNOWN; - for ( int j = 0; j < MAX_TRIGGERS_ARRAY; ++j ) + for ( dword j = 0; j < MAX_TRIGGERS_ARRAY; ++j ) { if ( i < 32 ) { - dword flag = 1 << i; + const dword flag = 1u << i; return ((m_dwOnTriggers[j] & flag) != 0); } i -= 32; diff --git a/src/common/resource/CResourceLink.h b/src/common/resource/CResourceLink.h index c834256d7..93e942dfa 100644 --- a/src/common/resource/CResourceLink.h +++ b/src/common/resource/CResourceLink.h @@ -12,7 +12,8 @@ class CResourceScript; -#define MAX_TRIGGERS_ARRAY 6 +// Each array can store 8 bytes * 4 bits per byte = 32 bits, or 32 values for 32 triggers. +#define MAX_TRIGGERS_ARRAY 10 class CResourceLink : public CResourceDef { @@ -27,8 +28,8 @@ class CResourceLink : public CResourceDef dword _dwRefInstances; // How many CResourceRef objects refer to this ? public: - static const char *m_sClassName; dword m_dwOnTriggers[MAX_TRIGGERS_ARRAY]; + static const char *m_sClassName; #define XTRIG_UNKNOWN 0 // bit 0 is reserved to say there are triggers here that do not conform. diff --git a/src/common/resource/CResourceLock.cpp b/src/common/resource/CResourceLock.cpp index 6736c1cd2..253989f60 100644 --- a/src/common/resource/CResourceLock.cpp +++ b/src/common/resource/CResourceLock.cpp @@ -31,7 +31,7 @@ bool CResourceLock::_Open(lpctstr ptcUnused, uint uiUnused) bool CResourceLock::Open(lpctstr ptcUnused, uint uiUnused) { ADDTOCALLSTACK("CResourceLock::Open"); - THREAD_UNIQUE_LOCK_RETURN(CResourceLock::_Open(ptcUnused, uiUnused)); + MT_UNIQUE_LOCK_RETURN(CResourceLock::_Open(ptcUnused, uiUnused)); } void CResourceLock::_Close() @@ -58,14 +58,14 @@ void CResourceLock::_Close() void CResourceLock::Close() { ADDTOCALLSTACK("CResourceLock::Close"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; CResourceLock::_Close(); } bool CResourceLock::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened script file { // This function is called for each script line which is being parsed (so VERY frequently), and ADDTOCALLSTACK is expensive if called - // this much often, so here it's to be preferred ADDTOCALLSTACK_INTENSIVE, even if we'll lose stack trace precision. + // this much often, so here it's to be preferred ADDTOCALLSTACK_DEBUG, even if we'll lose stack trace precision. ADDTOCALLSTACK("CResourceLock::_ReadTextLine"); // ARGS: // fRemoveBlanks = Don't report any blank lines, (just keep reading) @@ -90,8 +90,8 @@ bool CResourceLock::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the } bool CResourceLock::ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened script file { - ADDTOCALLSTACK_INTENSIVE("CResourceLock::ReadTextLine"); - THREAD_UNIQUE_LOCK_RETURN(CResourceLock::_ReadTextLine(fRemoveBlanks)); + ADDTOCALLSTACK_DEBUG("CResourceLock::ReadTextLine"); + MT_UNIQUE_LOCK_RETURN(CResourceLock::_ReadTextLine(fRemoveBlanks)); } int CResourceLock::OpenLock( CResourceScript * pLock, CScriptLineContext context ) diff --git a/src/common/resource/CResourceQty.cpp b/src/common/resource/CResourceQty.cpp index ed511b652..c7336084b 100644 --- a/src/common/resource/CResourceQty.cpp +++ b/src/common/resource/CResourceQty.cpp @@ -197,6 +197,7 @@ size_t CResourceQtyArray::Load(lpctstr pszCmds) ( pszCmds[1] == '\0' || pszCmds[1] == ',' )) { clear(); // clear any previous stuff. + reserve(4); ++pszCmds; } else @@ -226,6 +227,7 @@ size_t CResourceQtyArray::Load(lpctstr pszCmds) ++pszCmds; } + shrink_to_fit(); return iValid; } diff --git a/src/common/resource/CResourceQty.h b/src/common/resource/CResourceQty.h index d902071b2..fbe795b58 100644 --- a/src/common/resource/CResourceQty.h +++ b/src/common/resource/CResourceQty.h @@ -15,29 +15,30 @@ struct CResourceQty private: CResourceID m_rid; // A RES_SKILL, RES_ITEMDEF, or RES_TYPEDEF int64 m_iQty; // How much of this ? + public: - inline const CResourceID& GetResourceID() const + inline const CResourceID& GetResourceID() const noexcept { return m_rid; } - void SetResourceID(const CResourceID& rid, int iQty) + void SetResourceID(const CResourceID& rid, int iQty) noexcept { m_rid = rid; m_iQty = iQty; } - inline RES_TYPE GetResType() const + inline RES_TYPE GetResType() const noexcept { return m_rid.GetResType(); } - inline int GetResIndex() const + inline int GetResIndex() const noexcept { return m_rid.GetResIndex(); } - inline int64 GetResQty() const + inline int64 GetResQty() const noexcept { return m_iQty; } - inline void SetResQty(int64 iQuantity) + inline void SetResQty(int64 iQuantity) noexcept { m_iQty = iQuantity; } @@ -56,8 +57,12 @@ struct CResourceQty class CResourceQtyArray : public std::vector { // Define a list of index id's (not references) to resource objects. (Not owned by the list) + + bool m_mergeOnLoad; + public: static const char *m_sClassName; + CResourceQtyArray(); explicit CResourceQtyArray(lpctstr pszCmds); bool operator == ( const CResourceQtyArray & array ) const; @@ -89,9 +94,6 @@ class CResourceQtyArray : public std::vector } void setNoMergeOnLoad(); - -private: - bool m_mergeOnLoad; }; diff --git a/src/common/resource/CResourceRef.cpp b/src/common/resource/CResourceRef.cpp index 5f0822185..e8939441c 100644 --- a/src/common/resource/CResourceRef.cpp +++ b/src/common/resource/CResourceRef.cpp @@ -201,7 +201,7 @@ size_t CResourceRefArray::FindResourceName( RES_TYPE restype, lpctstr ptcKey ) c void CResourceRefArray::r_Write( CScript & s, lpctstr ptcKey ) const { - ADDTOCALLSTACK_INTENSIVE("CResourceRefArray::r_Write"); + ADDTOCALLSTACK_DEBUG("CResourceRefArray::r_Write"); for ( size_t j = 0, sz = size(); j < sz; ++j ) { s.WriteKeyStr( ptcKey, GetResourceName( j )); diff --git a/src/common/resource/CResourceScript.cpp b/src/common/resource/CResourceScript.cpp index 350746cbf..6069b3bff 100644 --- a/src/common/resource/CResourceScript.cpp +++ b/src/common/resource/CResourceScript.cpp @@ -44,7 +44,7 @@ bool CResourceScript::_CheckForChange() bool CResourceScript::CheckForChange() { ADDTOCALLSTACK("CResourceScript::CheckForChange"); - THREAD_UNIQUE_LOCK_RETURN(CResourceScript::_CheckForChange()); + MT_UNIQUE_LOCK_RETURN(CResourceScript::_CheckForChange()); } void CResourceScript::ReSync() diff --git a/src/common/resource/CValueDefs.cpp b/src/common/resource/CValueDefs.cpp index ea3d71d52..2f281a454 100644 --- a/src/common/resource/CValueDefs.cpp +++ b/src/common/resource/CValueDefs.cpp @@ -45,7 +45,7 @@ bool CValueRangeDef::Load( tchar * pszDef ) const tchar * CValueRangeDef::Write() const { - ADDTOCALLSTACK("CValueRangeDef::Write"); + //ADDTOCALLSTACK("CValueRangeDef::Write"); return nullptr; } diff --git a/src/common/resource/CValueDefs.h b/src/common/resource/CValueDefs.h index b8f926174..5cdb33e52 100644 --- a/src/common/resource/CValueDefs.h +++ b/src/common/resource/CValueDefs.h @@ -182,11 +182,11 @@ struct CValueCurveDef int GetRandomLinear( int iPercent ) const; public: - CValueCurveDef() { }; + CValueCurveDef() noexcept = default; + ~CValueCurveDef() noexcept = default; -private: - CValueCurveDef(const CValueCurveDef& copy); - CValueCurveDef& operator=(const CValueCurveDef& other); + CValueCurveDef(const CValueCurveDef& copy) = delete; + CValueCurveDef& operator=(const CValueCurveDef& other) = delete; }; diff --git a/src/common/sphere_library/CSFile.cpp b/src/common/sphere_library/CSFile.cpp index a34572ccb..27788a0a4 100644 --- a/src/common/sphere_library/CSFile.cpp +++ b/src/common/sphere_library/CSFile.cpp @@ -79,7 +79,7 @@ void CSFile::Close() { ADDTOCALLSTACK("CSFile::Close"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; CSFile::_Close(); } @@ -140,7 +140,7 @@ bool CSFile::_Open( lpctstr ptcFilename, uint uiModeFlags ) uint uiFilePermissions = 0; if (uiModeFlags & OF_CREATE) uiFilePermissions = (S_IRWXU | S_IRWXG | S_IRWXO); //777 - + _fileDescriptor = open( ptcFilename, uiModeFlags, uiFilePermissions); #endif // _WIN32 @@ -150,7 +150,7 @@ bool CSFile::_Open( lpctstr ptcFilename, uint uiModeFlags ) bool CSFile::Open( lpctstr ptcFilename, uint uiModeFlags ) { ADDTOCALLSTACK("CSFile::Open"); - THREAD_UNIQUE_LOCK_RETURN(CSFile::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(CSFile::_Open(ptcFilename, uiModeFlags)); } bool CSFile::_IsFileOpen() const @@ -159,7 +159,7 @@ bool CSFile::_IsFileOpen() const } bool CSFile::IsFileOpen() const { - THREAD_SHARED_LOCK_RETURN(_fileDescriptor != _kInvalidFD); + MT_SHARED_LOCK_RETURN(_fileDescriptor != _kInvalidFD); } lpctstr CSFile::_GetFilePath() const @@ -168,7 +168,7 @@ lpctstr CSFile::_GetFilePath() const } lpctstr CSFile::GetFilePath() const { - THREAD_SHARED_LOCK_RETURN(_strFileName.GetBuffer()); + MT_SHARED_LOCK_RETURN(_strFileName.GetBuffer()); } bool CSFile::_SetFilePath( lpctstr pszName ) @@ -191,7 +191,7 @@ bool CSFile::_SetFilePath( lpctstr pszName ) bool CSFile::SetFilePath( lpctstr pszName ) { ADDTOCALLSTACK("CFile::SetFilePath"); - THREAD_UNIQUE_LOCK_RETURN(CSFile::_SetFilePath(pszName)); + MT_UNIQUE_LOCK_RETURN(CSFile::_SetFilePath(pszName)); } @@ -221,7 +221,7 @@ int CSFile::_GetLength() int CSFile::GetLength() { ADDTOCALLSTACK("CSFile::GetLength"); - THREAD_UNIQUE_LOCK_RETURN(CSFile::_GetLength()); + MT_UNIQUE_LOCK_RETURN(CSFile::_GetLength()); } int CSFile::_GetPosition() const @@ -253,13 +253,13 @@ int CSFile::_GetPosition() const int CSFile::GetPosition() const { ADDTOCALLSTACK("CSFile::GetPosition"); - THREAD_UNIQUE_LOCK_RETURN(CSFile::_GetPosition()); + MT_UNIQUE_LOCK_RETURN(CSFile::_GetPosition()); } int CSFile::Read( void * pData, int iLength ) const { ADDTOCALLSTACK("CSFile::Read"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; #ifdef _WIN32 DWORD ret; @@ -309,7 +309,7 @@ int CSFile::_Seek( int iOffset, int iOrigin ) int CSFile::Seek( int iOffset, int iOrigin ) { ADDTOCALLSTACK("CSFile::Seek"); - THREAD_UNIQUE_LOCK_RETURN(CSFile::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(CSFile::_Seek(iOffset, iOrigin)); } void CSFile::_SeekToBegin() @@ -337,6 +337,15 @@ int CSFile::SeekToEnd() bool CSFile::_Write( const void * pData, int iLength ) { ADDTOCALLSTACK("CSFile::_Write"); + ASSERT(iLength >= 0); + if (iLength <= 0) + return true; + +#if defined(_WIN32) + ASSERT(_fileDescriptor != nullptr); +#elif defined(__unix__) or defined(unix) + ASSERT(_fileDescriptor >= 0); +#endif #ifdef _WIN32 DWORD dwWritten; @@ -361,7 +370,7 @@ bool CSFile::_Write( const void * pData, int iLength ) bool CSFile::Write(const void* pData, int iLength) { ADDTOCALLSTACK("CSFile::Write"); - THREAD_UNIQUE_LOCK_RETURN(CSFile::_Write(pData, iLength)); + MT_UNIQUE_LOCK_RETURN(CSFile::_Write(pData, iLength)); } // CSFile:: File name operations. @@ -458,7 +467,7 @@ uint CSFile::_GetFullMode() const } uint CSFile::GetFullMode() const { - THREAD_SHARED_LOCK_RETURN(_uiMode); + MT_SHARED_LOCK_RETURN(_uiMode); } uint CSFile::_GetMode() const @@ -467,7 +476,7 @@ uint CSFile::_GetMode() const } uint CSFile::GetMode() const { - THREAD_SHARED_LOCK_RETURN(_uiMode & 0x0FFFFFFF); + MT_SHARED_LOCK_RETURN(_uiMode & 0x0FFFFFFF); } bool CSFile::_IsWriteMode() const @@ -476,7 +485,7 @@ bool CSFile::_IsWriteMode() const } bool CSFile::IsWriteMode() const { - THREAD_SHARED_LOCK_RETURN(_uiMode & OF_WRITE); + MT_SHARED_LOCK_RETURN(_uiMode & OF_WRITE); } diff --git a/src/common/sphere_library/CSFile.h b/src/common/sphere_library/CSFile.h index f96c8de3b..4929cd1e1 100644 --- a/src/common/sphere_library/CSFile.h +++ b/src/common/sphere_library/CSFile.h @@ -34,7 +34,7 @@ class CSFile { protected: - THREAD_CMUTEX_DEF; + MT_CMUTEX_DEF; public: static const char * m_sClassName; @@ -107,7 +107,7 @@ public: lpctstr GetFilePath() const; */ protected: virtual bool _SetFilePath( lpctstr pszName ); public: virtual bool SetFilePath( lpctstr pszName ); - + ///@} /** @name Content Management: */ diff --git a/src/common/sphere_library/CSFileText.cpp b/src/common/sphere_library/CSFileText.cpp index 1ad461578..1dae1d039 100644 --- a/src/common/sphere_library/CSFileText.cpp +++ b/src/common/sphere_library/CSFileText.cpp @@ -29,7 +29,7 @@ bool CSFileText::_IsFileOpen() const } bool CSFileText::IsFileOpen() const { - THREAD_SHARED_LOCK_RETURN(_pStream != nullptr); + MT_SHARED_LOCK_RETURN(_pStream != nullptr); } bool CSFileText::_Open(lpctstr ptcFilename, uint uiModeFlags) @@ -37,7 +37,7 @@ bool CSFileText::_Open(lpctstr ptcFilename, uint uiModeFlags) ADDTOCALLSTACK("CSFileText::_Open"); // Open a text file. - + if ( !ptcFilename ) ptcFilename = _strFileName.GetBuffer(); else @@ -45,10 +45,10 @@ bool CSFileText::_Open(lpctstr ptcFilename, uint uiModeFlags) if ( _strFileName.IsEmpty() ) return false; - + _uiMode = uiModeFlags; lpctstr ptcModeStr = _GetModeStr(); - + _pStream = fopen( ptcFilename, ptcModeStr ); if ( _pStream == nullptr ) return false; @@ -61,7 +61,7 @@ bool CSFileText::_Open(lpctstr ptcFilename, uint uiModeFlags) bool CSFileText::Open(lpctstr ptcFilename, uint uiModeFlags) { ADDTOCALLSTACK("CSFileText::Open"); - THREAD_UNIQUE_LOCK_RETURN(CSFileText::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(CSFileText::_Open(ptcFilename, uiModeFlags)); } void CSFileText::_Close() @@ -84,7 +84,7 @@ void CSFileText::_Close() void CSFileText::Close() { ADDTOCALLSTACK("CSFileText::Close"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; CSFileText::_Close(); } @@ -120,7 +120,7 @@ int CSFileText::Seek( int iOffset, int iOrigin ) // RETURN: // true = success ADDTOCALLSTACK("CSFileText::Seek"); - THREAD_UNIQUE_LOCK_RETURN(CSFileText::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(CSFileText::_Seek(iOffset, iOrigin)); } void CSFileText::_Flush() const @@ -136,7 +136,7 @@ void CSFileText::_Flush() const void CSFileText::Flush() const { ADDTOCALLSTACK("CSFileText::Flush"); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; CSFileText::_Flush(); } @@ -152,7 +152,7 @@ bool CSFileText::_IsEOF() const bool CSFileText::IsEOF() const { //ADDTOCALLSTACK("CSFileText::IsEOF"); - THREAD_SHARED_LOCK_RETURN(CSFileText::_IsEOF()); + MT_SHARED_LOCK_RETURN(CSFileText::_IsEOF()); } @@ -173,7 +173,7 @@ int _cdecl CSFileText::Printf( lpctstr pFormat, ... ) ADDTOCALLSTACK("CSFileText::Printf"); ASSERT(pFormat); - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; va_list vargs; va_start( vargs, pFormat ); @@ -193,7 +193,7 @@ int CSFileText::Read( void * pBuffer, int sizemax ) const if ( IsEOF() ) return 0; // LINUX will ASSERT if we read past end. - THREAD_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET; size_t ret = fread( pBuffer, 1, sizemax, _pStream ); if (ret > INT_MAX) { @@ -218,7 +218,7 @@ tchar * CSFileText::_ReadString( tchar * pBuffer, int sizemax ) tchar * CSFileText::ReadString( tchar * pBuffer, int sizemax ) { ADDTOCALLSTACK("CSFileText::ReadString"); - THREAD_UNIQUE_LOCK_RETURN(CSFileText::_ReadString(pBuffer, sizemax)); + MT_UNIQUE_LOCK_RETURN(CSFileText::_ReadString(pBuffer, sizemax)); } int CSFileText::_VPrintf( lpctstr pFormat, va_list args ) @@ -237,7 +237,7 @@ int CSFileText::VPrintf(lpctstr pFormat, va_list args) ADDTOCALLSTACK("CSFileText::VPrintf"); ASSERT(pFormat); - THREAD_UNIQUE_LOCK_RETURN(CSFileText::_VPrintf(pFormat, args)); + MT_UNIQUE_LOCK_RETURN(CSFileText::_VPrintf(pFormat, args)); } bool CSFileText::_Write( const void * pData, int iLen ) @@ -267,7 +267,7 @@ bool CSFileText::Write(const void* pData, int iLen) { // RETURN: 1 = success else fail. ADDTOCALLSTACK("CSFileText::Write"); - THREAD_UNIQUE_LOCK_RETURN(CSFileText::_Write(pData, iLen)); + MT_UNIQUE_LOCK_RETURN(CSFileText::_Write(pData, iLen)); } bool CSFileText::_WriteString( lpctstr pStr ) @@ -282,7 +282,7 @@ bool CSFileText::_WriteString( lpctstr pStr ) bool CSFileText::WriteString(lpctstr pStr) { ADDTOCALLSTACK("CSFileText::WriteString"); - THREAD_UNIQUE_LOCK_RETURN(CSFileText::_WriteString(pStr)); + MT_UNIQUE_LOCK_RETURN(CSFileText::_WriteString(pStr)); } // CSFileText:: Mode operations. diff --git a/src/common/sphere_library/CSObjCont.cpp b/src/common/sphere_library/CSObjCont.cpp index d201e8f92..6e4914bea 100644 --- a/src/common/sphere_library/CSObjCont.cpp +++ b/src/common/sphere_library/CSObjCont.cpp @@ -4,9 +4,9 @@ #include -CSObjContRec::CSObjContRec() +CSObjContRec::CSObjContRec() : + m_pParent(nullptr) { - m_pParent = nullptr; } CSObjContRec::~CSObjContRec() @@ -16,55 +16,73 @@ CSObjContRec::~CSObjContRec() void CSObjContRec::RemoveSelf() { - if (m_pParent) + if (m_pParent) { m_pParent->OnRemoveObj(this); // call any approriate virtuals. + } } //--- // CSObjCont:: Constructors, Destructor, Assign operator. -CSObjCont::CSObjCont() +CSObjCont::CSObjCont() : + _fClearingContainer(false) { - _fIsClearing = false; } CSObjCont::~CSObjCont() { - ClearContainer(); + ClearContainer(true); } // CSObjCont:: Modifiers. -void CSObjCont::ClearContainer() +void CSObjCont::ClearContainer(bool fClosingWorld) { if (_Contents.empty()) return; // delete all entries. - ASSERT(!_fIsClearing); - _fIsClearing = true; // Loop through a copy of the current state of the container, since by deleting other container objects it could happen that // other objects are deleted and appended to this list, thus invalidating the iterators used by the for loop. const auto stateCopy = GetIterationSafeContReverse(); _Contents.clear(); - - for (CSObjContRec* pRec : stateCopy) // iterate the list. - { - EXC_TRY("Deleting objects scheduled for deletion"); - - if (pRec->GetParent() == this) - { - // I still haven't figured why sometimes, when force closing sectors, an item is stored in both the g_World.m_ObjDelete and the sector object lists - OnRemoveObj(pRec); - delete pRec; - } - - EXC_CATCH; - } - - _fIsClearing = false; + _fClearingContainer = true; + + EXC_TRY("Deleting objects"); + + if (fClosingWorld) + { + EXC_SET_BLOCK("Closing world cleanup."); + // Do not notify the parent element. + for (CSObjContRec* pRec : stateCopy) + { + // This might not be true for some containers. + // Example: sectors. We might want to delete the object here just after it's been detatched from its sector. + //ASSERT(pRec->GetParent() == this); + + //pRec->m_pParent->_ContentsAlreadyDeleted.emplace_back(pRec); + pRec->m_pParent = nullptr; + delete pRec; + } + } + else + { + EXC_SET_BLOCK("Standard cleanup."); + for (CSObjContRec* pRec : stateCopy) + { + // This might not be true for some containers. + // Example: sectors. We might want to delete the object here just after it's been detatched from its sector. + //ASSERT(pRec->GetParent() == this); + + OnRemoveObj(pRec); + delete pRec; + } + } + + _fClearingContainer = false; + EXC_CATCH; } /* @@ -101,15 +119,27 @@ void CSObjCont::OnRemoveObj(CSObjContRec* pObjRec) // Override this = called whe { // just remove from list. DON'T delete ! ASSERT(pObjRec); + ASSERT(pObjRec->GetParent() == this); pObjRec->m_pParent = nullptr; // We are now unlinked. - if (!_fIsClearing) - { - const iterator itEnd = end(); - iterator itObjRec = std::find(begin(), itEnd, pObjRec); - ASSERT(itObjRec != itEnd); - _Contents.erase(itObjRec); - } + iterator itObjRec = std::find(begin(), end(), pObjRec); + if (itObjRec == end()) + return; + + /* + if (!_fClearingContainer) + { + // _fCleaning == true happens when a CSObjCont deletes its CSObjContRec. + // CSObjContRec::RemoveSelf calls its CSObjCont::OnRemoveObj, so we are here, but in this case _Contents is empty, + // so it's expected not to find the object here.. + iterator itObjRec = std::find(begin(), end(), pObjRec); + ASSERT(itObjRec != end()); + + _Contents.erase(itObjRec); + } + */ + + _Contents.erase(itObjRec); } diff --git a/src/common/sphere_library/CSObjCont.h b/src/common/sphere_library/CSObjCont.h index 8f1f174ce..29e1fb91b 100644 --- a/src/common/sphere_library/CSObjCont.h +++ b/src/common/sphere_library/CSObjCont.h @@ -47,7 +47,7 @@ class CSObjCont { protected: BASECONT _Contents; - bool _fIsClearing; + bool _fClearingContainer; public: friend class CSObjContRec; @@ -97,6 +97,8 @@ class CSObjCont inline size_t size() const noexcept { return _Contents.size(); } inline const CSObjContRec *const * data() const noexcept{ return _Contents.data(); } + inline BASECONT::iterator erase(BASECONT::iterator it) { return _Contents.erase(it); } + /** * @brief Returns a copy of the CSObjCont base container, which is safe to iterate on even if one of its elements is ::Delete'd. * When an element is deleted from the CSObjCont with CObjBase::Delete or its virtuals, the element is removed from the CSObjCont, thus invalidating @@ -167,8 +169,9 @@ class CSObjCont /** * @brief Remove all records of the CSObjCont. + * @param fClosingWorld Am i closing world UIDs or just destroyng everything before shutdown? */ - void ClearContainer(); + void ClearContainer(bool fClosingWorld); /** * @brief Insert a record at head. diff --git a/src/common/sphere_library/CSQueue.cpp b/src/common/sphere_library/CSQueue.cpp index 01a0130f0..22b76e8d1 100644 --- a/src/common/sphere_library/CSQueue.cpp +++ b/src/common/sphere_library/CSQueue.cpp @@ -29,7 +29,7 @@ byte * CSQueueBytes::AddNewDataLock( size_t iLen ) // re-alloc a bigger buffer. as needed. ASSERT(m_iDataQty<=m_Mem.GetDataLength()); - m_Mem.Resize( ( iLenNew + 0x1000 ) &~ 0xFFF ); + m_Mem.Resize( ( iLenNew + 0x1000u ) & (size_t)~0xFFF ); } return ( m_Mem.GetData() + m_iDataQty ); diff --git a/src/common/sphere_library/CSReferenceCount.h b/src/common/sphere_library/CSReferenceCount.h new file mode 100644 index 000000000..c698962c8 --- /dev/null +++ b/src/common/sphere_library/CSReferenceCount.h @@ -0,0 +1,194 @@ +/** +* @file CSReferenceCount.h +* @brief Lightweight wrapped pointer reference counter to a master/owner object. +*/ + +#ifndef _INC_CSREFERENCECOUNT_H +#define _INC_CSREFERENCECOUNT_H + +#include +#include +//#include + +template +class CSReferenceCountedOwned; +//template +//class CSReferenceCountedVariant; + +// This one is a reference to a CSReferenceCountedOwner. +// CSReferenceCountedOwner holds the original object/value and the number of child references. +// The CSReferenceCountedOwner object must not be moved in memory, it will invalidate _owner address. +template +class CSReferenceCounted +{ + friend class CSReferenceCountedOwned; + CSReferenceCountedOwned * _owner; + +protected: + /* + template + CSReferenceCounted(CSReferenceCounter* owner, ConstructorParams&& ...params) noexcept : + _owner(owner), _heldObj(std::forward(params)...) + { + _owner->_counted_references += 1; + } + */ + + explicit CSReferenceCounted(CSReferenceCountedOwned* owner) noexcept : + _owner(owner) + { + _owner->_counted_references += 1; + } + +public: + CSReferenceCounted() noexcept = delete; + explicit CSReferenceCounted(CSReferenceCounted const& other) noexcept : + _owner(other._owner) + { + _owner->_counted_references += 1; + } + + ~CSReferenceCounted() noexcept + { + _owner->_counted_references -= 1; + } + + CSReferenceCounted& operator=(CSReferenceCounted const& other) noexcept + { + _owner = other._owner; + _owner->_counted_references += 1; + return *this; + } + + inline T& get() noexcept + { + return _owner->_heldObj; + } + + T& operator*() noexcept + { + return this->get(); + } + T* operator->() noexcept + { + return &(this->get()); + } +}; + + +template +class CSReferenceCountedOwned +{ + //friend class CSReferenceCountedVariant; + //CSReferenceCountedOwned(CSReferenceCountedOwned const& other) noexcept = default; + //CSReferenceCountedOwned& operator=(CSReferenceCountedOwned const& other) noexcept = default; +public: + CSReferenceCountedOwned(CSReferenceCountedOwned const& other) noexcept = delete; + CSReferenceCountedOwned& operator=(CSReferenceCountedOwned const& other) noexcept = delete; + + +public: + unsigned int _counted_references; + T _heldObj; + + CSReferenceCountedOwned() noexcept : + _counted_references(1), _heldObj() + {} + + //template + //CSReferenceCountedOwned(ConstructorParams&& ...params) noexcept requires (sizeof...(ConstructorParams) > 0) : + // _counted_references(1), _heldObj(std::forward(params)...) + //{ + //} + ~CSReferenceCountedOwned() noexcept = default; + + CSReferenceCountedOwned(CSReferenceCountedOwned&& other) noexcept = default; + CSReferenceCountedOwned& operator=(CSReferenceCountedOwned&& other) noexcept = default; + + inline T& get() noexcept + { + return _heldObj; + } + + T& operator*() noexcept + { + return this->get(); + } + T* operator->() noexcept + { + return &(this->get()); + } + + auto GetRef() noexcept + { + return CSReferenceCounted(this); + } +}; + +/* +Not yet working + +template +class CSReferenceCountedVariant : protected std::variant, CSReferenceCountedOwned> +{ + using base = typename std::variant, CSReferenceCountedOwned>; + using base::base; + //using base::operator=; + +public: + CSReferenceCountedVariant() : base() {} + CSReferenceCountedVariant(CSReferenceCounted&& arg) : base(std::in_place_index<0>, std::move(arg)) {} + CSReferenceCountedVariant(CSReferenceCountedOwned&& arg) : base(std::in_place_index<1>, std::move(arg)) {} + + //template + //explicit CSReferenceCountedVariant(_Args&&... args) requires (sizeof...(_Args) > 0) : + // base(std::forward(args)...) {} + + CSReferenceCountedVariant(CSReferenceCountedVariant && other) + { + base::operator=(std::move(other)); + } + CSReferenceCountedVariant& operator=(CSReferenceCountedVariant && other) + { + base::operator=(std::move(other)); + return *this; + } + + CSReferenceCountedVariant(CSReferenceCountedVariant const& other) = delete; + CSReferenceCountedVariant& operator=(CSReferenceCountedVariant const& other) = delete; + + T& operator*() + { + if (std::holds_alternative>(*this)) + { + return std::get_if>(this)->get(); + } + else if (std::holds_alternative>(*this)) + { + return std::get_if>(this)->get(); + } + else + { + throw std::bad_variant_access(); + } + } + T* operator->() + { + if (std::holds_alternative>(*this)) + { + return &(std::get_if>(this)->get()); + } + else if (std::holds_alternative>(*this)) + { + return &(std::get_if>(this)->get()); + } + else + { + throw std::bad_variant_access(); + } + } +}; + +*/ + +#endif // _INC_CSREFERENCECOUNT_H diff --git a/src/common/sphere_library/CSString.cpp b/src/common/sphere_library/CSString.cpp index d8900992a..e2dec70eb 100644 --- a/src/common/sphere_library/CSString.cpp +++ b/src/common/sphere_library/CSString.cpp @@ -9,32 +9,6 @@ #include // for vsnprintf #endif - -/** -* @brief Default memory alloc size for CSString. -* -* Those tests are OUTDATED! -* -* - Empty World! (total strings on start =480,154) -* - 16 bytes : memory: 8,265,143 [Mem=42,516 K] [reallocations=12,853] -* - 32 bytes : memory: 16,235,008 [Mem=50,916 K] [reallocations=11,232] -* - 36 bytes : memory: 17,868,026 [Mem=50,592 K] [reallocations=11,144] -* - 40 bytes : memory: 19,627,696 [Mem=50,660 K] [reallocations=11,108] -* - 42 bytes : memory: 20,813,400 [Mem=50,240 K] [reallocations=11,081] BEST -* - 48 bytes : memory: 23,759,788 [Mem=58,704 K] [reallocations=11,048] -* - 56 bytes : memory: 27,689,157 [Mem=57,924 K] [reallocations=11,043] -* - 64 bytes : memory: 31,618,882 [Mem=66,260 K] [reallocations=11,043] -* - 128 bytes : memory: 62,405,304 [Mem=98,128 K] [reallocations=11,042] <- was in [0.55R4.0.2 - 0.56a] -* - Full World! (total strings on start ~1,388,081) -* - 16 bytes : memory: 29,839,759 [Mem=227,232 K] [reallocations=269,442] -* - 32 bytes : memory: 53,335,580 [Mem=250,568 K] [reallocations=224,023] -* - 40 bytes : memory: 63,365,178 [Mem=249,978 K] [reallocations=160,987] -* - 42 bytes : memory: 66,120,092 [Mem=249,896 K] [reallocations=153,181] BEST -* - 48 bytes : memory: 74,865,847 [Mem=272,016 K] [reallocations=142,813] -* - 56 bytes : memory: 87,050,665 [Mem=273,108 K] [reallocations=141,507] -* - 64 bytes : memory: 99,278,582 [Mem=295,932 K] [reallocations=141,388] -* - 128 bytes : memory: 197,114,039 [Mem=392,056 K] [reallocations=141,234] <- was in [0.55R4.0.2 - 0.56a] -*/ #define STRING_DEFAULT_SIZE 42 //#define DEBUG_STRINGS @@ -69,23 +43,23 @@ CSString::CSString(bool fDefaultInit) : } CSString::CSString(lpctstr pStr) : - m_pchData(nullptr) + m_pchData(nullptr), m_iLength(0), m_iMaxLength(0) { - Init(); + //Init(); Copy(pStr); } CSString::CSString(lpctstr pStr, int iLen) : - m_pchData(nullptr) + m_pchData(nullptr), m_iLength(0), m_iMaxLength(0) { - Init(); + //Init(); CopyLen(pStr, iLen); } CSString::CSString(const CSString& s) : - m_pchData(nullptr) + m_pchData(nullptr), m_iLength(0), m_iMaxLength(0) { - Init(); + //Init(); Copy(s.GetBuffer()); } @@ -124,12 +98,10 @@ void CSString::Clear(bool fTotal) noexcept bool CSString::IsValid() const noexcept { - if (!m_pchData || !m_iMaxLength) - return false; - return (m_pchData[m_iLength] == '\0'); + return ((m_iMaxLength != 0) && (m_pchData != nullptr) && (m_pchData[m_iLength] == '\0')); } -int CSString::Resize(int iNewLength) +int CSString::Resize(int iNewLength, bool fPreciseSize) { if (iNewLength >= m_iMaxLength) { @@ -137,15 +109,27 @@ int CSString::Resize(int iNewLength) #ifdef DEBUG_STRINGS gMemAmount -= m_iMaxLength; #endif - m_iMaxLength = iNewLength + (STRING_DEFAULT_SIZE >> 1); // allow grow, and always expand only + + // allow grow, and expand only + if (fPreciseSize) + { + m_iMaxLength = iNewLength; + } + else + { + //m_iMaxLength = iNewLength + (STRING_DEFAULT_SIZE >> 1); // Probably too much... + m_iMaxLength = (iNewLength * 3) >> 1; // >> 2 is equal to doing / 2 + } + #ifdef DEBUG_STRINGS gMemAmount += m_iMaxLength; ++gReallocs; #endif + tchar *pNewData = new tchar[size_t(m_iMaxLength + 1)]; // new operator throws on error if (fValid) { - const int iMinLength = minimum(iNewLength, m_iLength + 1); + const int iMinLength = 1 + minimum(iNewLength, m_iLength); Str_CopyLimitNull(pNewData, m_pchData, iMinLength); delete[] m_pchData; } @@ -200,7 +184,7 @@ void CSString::Copy(lpctstr pszStr) if ((pszStr != m_pchData) && pszStr) { const size_t uiLen = strlen(pszStr); - Resize((int)uiLen); // it adds a +1 + Resize((int)uiLen, true); // it adds a +1 Str_CopyLimitNull(m_pchData, pszStr, size_t(uiLen + 1)); } } @@ -209,7 +193,7 @@ void CSString::CopyLen(lpctstr pszStr, int iLen) { if ((pszStr != m_pchData) && pszStr) { - Resize(iLen); // it adds a +1 + Resize(iLen, true); // it adds a +1 Str_CopyLimitNull(m_pchData, pszStr, size_t(iLen + 1)); } } diff --git a/src/common/sphere_library/CSString.h b/src/common/sphere_library/CSString.h index cfe577d4a..ecadf8b94 100644 --- a/src/common/sphere_library/CSString.h +++ b/src/common/sphere_library/CSString.h @@ -22,17 +22,18 @@ class CSString static const char *m_sClassName; private: - /** + tchar* m_pchData; // Data pointer. + // Both the following lengths do not count the '\0', so we can always assume it's +1 char longer. + int m_iLength; // Length of string. + int m_iMaxLength; // Size of memory allocated pointed by m_pchData. + + /** * @brief Initializes internal data. * * Allocs STRING_DEFAULT_SIZE by default. If DEBUG_STRINGS setted, updates statistical information (total memory allocated). */ void Init(); - tchar* m_pchData; // Data pointer. - int m_iLength; // Length of string. - int m_iMaxLength; // Size of memory allocated pointed by m_pchData. - public: /** @name Constructors, Destructor, Asign operator: */ @@ -148,7 +149,7 @@ class CSString * @param iLen new length of the string. * @return the new length of the CSString. */ - int Resize(int iLen); + int Resize(int iLen, bool fPreciseSize = false); /** * @brief Get the length of the held string. @@ -326,7 +327,7 @@ class CSString */ void FormatHex(dword dwVal); - + /** * @brief Print a char value into the string. * @see Format() @@ -512,7 +513,7 @@ class CSString */ NODISCARD inline lpctstr GetBuffer() const noexcept; - + // Provide only a read-only buffer: if we modify it we'll break the internal length counter, other than possibly write past the end of the string (the buffer is small). //inline lptstr GetBuffer() noexcept; diff --git a/src/common/sphere_library/CSTime.cpp b/src/common/sphere_library/CSTime.cpp index bfe6ec6bf..9ad60d751 100644 --- a/src/common/sphere_library/CSTime.cpp +++ b/src/common/sphere_library/CSTime.cpp @@ -15,8 +15,8 @@ // Unix epoch is January 1, 1970 (adjustment in "ticks" 100 nanosecond) #define UNIX_TICKS_PER_SECOND 10000000 //a tick is 100ns #if _WIN32 -# define UNIX_TIME_START 0x019DB1DED53E8000LL // January 1, 1970 (start of Unix epoch) in "ticks" -# define WINDOWS_UNIX_EPOCH_OFFSET 11644473600 // (number of seconds between January 1, 1601 and January 1, 1970). +//# define UNIX_TIME_START 0x019DB1DED53E8000LL // January 1, 1970 (start of Unix epoch) in "ticks" +//# define WINDOWS_UNIX_EPOCH_OFFSET 11644473600 // (number of seconds between January 1, 1601 and January 1, 1970). #endif #ifdef _WIN32 @@ -343,6 +343,8 @@ time_t CSTime::GetTime() const noexcept { // Although not defined by the C standard, this is almost always an integral value holding the number of seconds // (not counting leap seconds) since 00:00, Jan 1 1970 UTC, corresponding to UNIX time. + // + // TODO: Is this on Windows defined since January 1, 1601 ? return m_time; } diff --git a/src/common/sphere_library/sfastmath.h b/src/common/sphere_library/sfastmath.h new file mode 100644 index 000000000..ed14f1f47 --- /dev/null +++ b/src/common/sphere_library/sfastmath.h @@ -0,0 +1,63 @@ +#ifndef _INC_SFASTMATH_H +#define _INC_SFASTMATH_H + +// Adapted from: https://github.com/ermig1979/Simd/blob/master/src/Simd/SimdMath.h + +namespace sfmath +{ + + inline int Average(int a, int b) noexcept + { + return (a + b + 1) >> 1; + } + + inline int Average(int a, int b, int c, int d) noexcept + { + return (a + b + c + d + 2) >> 2; + } + + inline void SortU8(int & a, int & b) noexcept + { + const int d = a - b; + const int m = ~(d >> 8); + b += d & m; + a -= d & m; + } + + inline int AbsDifferenceU8(int a, int b) noexcept + { + const int d = a - b; + const int m = d >> 8; + return (d & ~m) | (-d & m); + } + + inline int MaxU8(int a, int b) noexcept + { + const int d = a - b; + const int m = ~(d >> 8); + return b + (d & m); + } + + inline int MinU8(int a, int b) noexcept + { + const int d = a - b; + const int m = ~(d >> 8); + return a - (d & m); + } + + inline int SaturatedSubtractionU8(int a, int b) noexcept + { + const int d = a - b; + const int m = ~(d >> 8); + return (d & m); + } + + inline float NotF16(float f) noexcept + { + const int i = ~(int&)f; + return (float&)i; + } + +} // namespace + +#endif // _INC_SFASTMATH_H diff --git a/src/common/sphere_library/smutex.h b/src/common/sphere_library/smutex.h index d69744d39..13c673618 100644 --- a/src/common/sphere_library/smutex.h +++ b/src/common/sphere_library/smutex.h @@ -108,6 +108,9 @@ class SimpleThreadLock inline ~SimpleThreadLock() noexcept { m_mutex.unlock(); } + inline void unlock() noexcept { + m_mutex.unlock(); + } SimpleThreadLock(const SimpleThreadLock& copy) = delete; SimpleThreadLock& operator=(const SimpleThreadLock& other) = delete; diff --git a/src/common/sphere_library/sstring.cpp b/src/common/sphere_library/sstring.cpp index 90693451b..78fea0a92 100644 --- a/src/common/sphere_library/sstring.cpp +++ b/src/common/sphere_library/sstring.cpp @@ -165,7 +165,7 @@ tchar* Str_FromLL_Fast (llong val, tchar* buf, size_t buf_length, uint base) noe STR_FROM_SET_ZEROSTR; return buf; } - + const bool sign = (val < 0); ullong uval; if (sign) @@ -194,7 +194,7 @@ tchar* Str_FromLL_Fast (llong val, tchar* buf, size_t buf_length, uint base) noe buf[--buf_length] = chars[uval % base]; uval /= base; } while (uval); - + if (hex) { buf[--buf_length] = '0'; } @@ -385,7 +385,7 @@ size_t Str_CopyLimitNull(tchar * pDst, lpctstr pSrc, size_t uiMaxSize) noexcept pDst[0] = '\0'; return 0; } - + size_t qty = 0; // how much bytes do i have to copy? (1 based) do { @@ -408,14 +408,14 @@ size_t Str_CopyLen(tchar * pDst, lpctstr pSrc) noexcept // the number of characters in a multibyte string is the sum of mblen()'s // note: the simpler approach is std::mbstowcs(NULL, s.c_str(), s.size()) /* -size_t strlen_mb(const char* ptr) +size_t strlen_mb(const char* ptr) { // From: https://en.cppreference.com/w/c/string/multibyte/mblen // ensure that at some point we have called setlocale: //-- // allow mblen() to work with UTF-8 multibyte encoding //-- std::setlocale(LC_ALL, "en_US.utf8"); - + size_t result = 0; const char* end = ptr + strlen(ptr); mblen(nullptr, 0); // reset the conversion state @@ -454,6 +454,9 @@ size_t Str_LengthUTF8(const char* strInUTF8MB) noexcept // Adapted from: OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 size_t Str_ConcatLimitNull(tchar *dst, const tchar *src, size_t siz) noexcept { + if (siz == 0) + return 0; + tchar *d = dst; size_t n = siz; size_t dlen; @@ -489,6 +492,9 @@ size_t Str_ConcatLimitNull(tchar *dst, const tchar *src, size_t siz) noexcept tchar* Str_FindSubstring(tchar* str, const tchar* substr, size_t str_len, size_t substr_len) noexcept { + if (str_len == 0 || substr_len == 0) + return nullptr; + tchar c, sc; if ((c = *substr++) != '\0') { @@ -496,10 +502,11 @@ tchar* Str_FindSubstring(tchar* str, const tchar* substr, size_t str_len, size_t { do { - if (str_len-- < 1 || (sc = *str++) == '\0') + if (str_len < 1 || (sc = *str++) == '\0') { return nullptr; } + str_len -= 1; } while (sc != c); if (substr_len > str_len) { @@ -1433,7 +1440,7 @@ bool Str_ParseAdv(tchar * pLine, tchar ** ppArg, lpctstr pszSep) noexcept else if (sep == '<' || sep == '>') fSepHasAngle = true; } - + for (; ; ++pLine) { tchar * pLineNext = pLine; @@ -1453,12 +1460,12 @@ bool Str_ParseAdv(tchar * pLine, tchar ** ppArg, lpctstr pszSep) noexcept ++pLineNext; chNext = *pLineNext; } - + if ((chNext == '\0') || (chNext == ',') || (chNext == ' ') || (chNext == '\'')) --iQuotes; else ++iQuotes; - + if (iQuotes < 0) { iQuotes = 0; @@ -1526,7 +1533,7 @@ bool Str_ParseAdv(tchar * pLine, tchar ** ppArg, lpctstr pszSep) noexcept --iAngle; } } - + // separate the string when i encounter a separator, but only if at this point of the string we aren't inside an argument // enclosed by brackets. but, if one of the separators is a bracket, don't care if we are inside or outside, separate anyways. @@ -1589,11 +1596,11 @@ int Str_ParseCmdsAdv(tchar * pszCmdLine, tchar ** ppCmd, int iMax, lpctstr pszSe tchar * Str_UnQuote(tchar * pStr) noexcept { GETNONWHITESPACE(pStr); - + tchar ch = *pStr; if ((ch == '"') || (ch == '\'')) ++pStr; - + for (tchar *pEnd = pStr + strlen(pStr) - 1; pEnd >= pStr; --pEnd) { if ((*pEnd == '"') || (*pEnd == '\'')) @@ -1850,26 +1857,27 @@ ssize_t fReadUntilDelimiter_StaticBuf(char *buf, const size_t bufsiz, const int return -1; } -ssize_t sGetDelimiter_StaticBuf(const int delimiter, const char *sp, const size_t datasize) noexcept +ssize_t sGetDelimiter_StaticBuf(const int delimiter, const char *ptr_string, const size_t datasize) noexcept { // Returns the number of chars before the delimiter (or the end of the string). // buf: line array - const char *ptr, *eptr; + const char *ptr_cursor, *ptr_end; - if (*sp == '\0') + if (*ptr_string == '\0') { return -1; + } - for (ptr = sp, eptr = sp + datasize;; ++ptr) { - if (*ptr == '\0') { - if (ptr != sp) { - return ptr - sp; + for (ptr_cursor = ptr_string, ptr_end = ptr_string + datasize;; ++ptr_cursor) { + if (*ptr_cursor == '\0') { + if (ptr_cursor != ptr_string) { + return ptr_cursor - ptr_string; } return -1; } - if (*ptr == delimiter) { - return ptr - sp; + if (*ptr_cursor == delimiter) { + return ptr_cursor - ptr_string; } - if (ptr + 2 >= eptr) { + if (ptr_cursor + 1 > ptr_end) { return -1; // buffer too small } } diff --git a/src/common/sphere_library/sstring.h b/src/common/sphere_library/sstring.h index 78e4f3d61..0bf5063b3 100644 --- a/src/common/sphere_library/sstring.h +++ b/src/common/sphere_library/sstring.h @@ -411,7 +411,7 @@ inline ssize_t fReadLine_StaticBuf(char *buf, const size_t bufsiz, FILE *fp) noe { return fReadUntilDelimiter_StaticBuf(buf, bufsiz, '\n', fp); } -ssize_t sGetDelimiter_StaticBuf(const int delimiter, const char *data, const size_t datasize) noexcept; +ssize_t sGetDelimiter_StaticBuf(const int delimiter, const char* ptr_string, const size_t datasize) noexcept; inline ssize_t sGetLine_StaticBuf(const char *data, const size_t datasize) noexcept { return sGetDelimiter_StaticBuf('\n', data, datasize); diff --git a/src/common/sphereproto.h b/src/common/sphereproto.h index 49186f126..be460764d 100644 --- a/src/common/sphereproto.h +++ b/src/common/sphereproto.h @@ -369,7 +369,7 @@ enum SECURE_TRADE_TYPE SECURE_TRADE_UPDATELEDGER = 4 }; -enum SEASON_TYPE +enum SEASON_TYPE : uchar { // The seasons can be: SEASON_Spring = 0, @@ -539,7 +539,7 @@ enum MAPWAYPOINT_TYPE MAPWAYPOINT_GreenDotFlashing = 0xF }; -enum WEATHER_TYPE +enum WEATHER_TYPE : uchar { WEATHER_DEFAULT = 0xFE, WEATHER_DRY = 0xFF, diff --git a/src/game/CBase.h b/src/game/CBase.h index a45fb8472..2a77f661c 100644 --- a/src/game/CBase.h +++ b/src/game/CBase.h @@ -14,6 +14,88 @@ #include "components/CCFaction.h" #include "CEntityProps.h" + +#define RANGE_MAKE(iHi, iLo) (((iHi & 0xFF) << 8) | (iLo & 0xFF)) // Highest byte contains highest value, lowest contains the lowest value +#define RANGE_GET_HI(iRange) ((iRange >> 8) & 0xFF) +#define RANGE_GET_LO(iRange) (iRange & 0xFF) + +// Map Movement flags. +// TODO: convert them to uint64_t static variables +#define CAN_C_GHOST 0x0001 // Moves thru doors etc. +#define CAN_C_SWIM 0x0002 // dolphin, elemental or is water. +#define CAN_C_WALK 0x0004 // Can walk on land, climbed on walked over else Frozen by nature(Corpser). +#define CAN_C_PASSWALLS 0x0008 // Walk thru walls. +#define CAN_C_FLY 0x0010 // Can walk to a point with a much higher height difference from our current z. +#define CAN_C_FIRE_IMMUNE 0x0020 // Has some immunity to fire ? (will walk into it (lava)). +#define CAN_C_NOINDOORS 0x0040 // Can't go under roof. +#define CAN_C_HOVER 0x0080 // Can hover. + +#define CAN_I_DOOR 0x0001 // Is a door UFLAG4_DOOR. +#define CAN_I_WATER 0x0002 // Need to swim in it. UFLAG1_WATER. +#define CAN_I_PLATFORM 0x0004 // we can walk on top of it. (even tho the item itself might block) UFLAG2_PLATFORM. +#define CAN_I_BLOCK 0x0008 // need to walk thru walls or fly over. UFLAG1_BLOCK. +#define CAN_I_CLIMB 0x0010 // Item: step up on it, UFLAG2_CLIMBABLE. +#define CAN_I_FIRE 0x0020 // Is a fire. Usually blocks as well. UFLAG1_DAMAGE. +#define CAN_I_ROOF 0x0040 // We are under a roof. can't rain on us. UFLAG4_ROOF. +#define CAN_I_HOVER 0x0080 // We are hovering. UFLAG4_HOVEROVER. + +// CItemBase specific defs. +#define CAN_I_PILE 0x0100 // Can item be piled UFLAG2_STACKABLE (*.mul). +#define CAN_I_DYE 0x0200 // Can item be dyed UFLAG3_CLOTH? (sort of). +#define CAN_I_FLIP 0x0400 // will flip by default. +#define CAN_I_LIGHT 0x0800 // UFLAG3_LIGHT. +#define CAN_I_REPAIR 0x1000 // Is it repairable (difficulty based on value). +#define CAN_I_REPLICATE 0x2000 // Things like arrows are pretty much all the same. +#define CAN_I_DCIGNORELOS 0x4000 // when dclicked, ignore LOS checks. +#define CAN_I_DCIGNOREDIST 0x8000 // when dclicked, ignore distance checks. +#define CAN_I_BLOCKLOS 0x10000 // blocks LOS without blocking walkchecks. +#define CAN_I_EXCEPTIONAL 0x20000 // can items be exceptional. -> TODO: convert to property, to free limited attr values +#define CAN_I_MAKERSMARK 0x40000 // can items hold makers mark. -> TODO: convert to property, to free limited attr values +#define CAN_I_RETAINCOLOR 0x80000 // can items retain material colors.-> TODO: convert to property, to free limited attr values +#define CAN_I_ENCHANT 0x100000 // can items be enchanted (runic). -> TODO: convert to property, to free limited attr values +#define CAN_I_IMBUE 0x200000 // can items be imbued (imbuing). -> TODO: convert to property, to free limited attr values +#define CAN_I_RECYCLE 0x400000 // Can items be recycled. -> TODO: convert to property, to free limited attr values +#define CAN_I_REFORGE 0x800000 // Can items be Runic Reforged. -> TODO: convert to property, to free limited attr values +#define CAN_I_FORCEDC 0x1000000 // Can force DClick skipping other checks (LOS,Distance, Cont...). +#define CAN_I_DAMAGEABLE 0x2000000 // Display item health bar on HS clients >= 7.0.30.0 (MORE1L = cur hitpoints / MORE1H = max hitpoints) +#define CAN_I_BLOCKLOS_HEIGHT 0x4000000 // blocks LOS without blocking walkchecks, but only if the item is too high for the viewer. +#define CAN_I_EQUIPONCAST 0x8000000 // Allow items to stay equipped while EquippedCast disabled in sphere.ini. +// 0x10000000 // CAN_O_NOSLEEP, defined below +#define CAN_I_SCRIPTEDMORE 0x20000000 // This item doesn't use an hardcoded behavior, so its MORE value might be used for custom stuff. +#define CAN_I_TIMER_CONTAINED 0x40000000 // This item (if not "sleeping") can have a timer even if inside a container (overrides global default behavior). + +// (CItemBase) CanEquip specific defs. +#define CAN_U_ALL 0x000 // Can be used by everyone. +#define CAN_U_MALE 0x001 // Can be used by males. +#define CAN_U_FEMALE 0x002 // Can be used by females. +#define CAN_U_HUMAN 0x004 // Can be used by humans. +#define CAN_U_ELF 0x008 // Can be used by elfs. +#define CAN_U_GARGOYLE 0x010 // Can be used by gargoyles. +#define CAN_U_NONE 0x020 // Can not be used. + +// CCharBase specific defs. +#define CAN_C_EQUIP 0x00100 // Can equip stuff. (humanoid). +#define CAN_C_USEHANDS 0x00200 // Can wield weapons (INT dependant), open doors ?, pick up/manipulate things. +#define CAN_C_MOUNT 0x00400 // can mount rides. +#define CAN_C_FEMALE 0x00800 // It is female by nature. +#define CAN_C_NONHUMANOID 0x01000 // Body type for combat messages. +#define CAN_C_RUN 0x02000 // Can run (not needed if they can fly). +#define CAN_C_DCIGNORELOS 0x04000 // when dclicking sth., ignore LOS checks. +#define CAN_C_DCIGNOREDIST 0x08000 // when dclicking sth., ignore distance checks. +#define CAN_C_NONMOVER 0x10000 // Just stay in place, avoid movement actions. +#define CAN_C_NOBLOCKHEIGHT 0x20000 // Do not consider char's height when checking if it can move to a point +#define CAN_C_STATUE 0x40000 // Statue: do not move, PacketStatueAnimation with tag.statue_animation and tag.statue_frame +#define CAN_C_NONSELECTABLE 0x80000 // Can't be selected/targeted (useful for chars with mt_statue which need to act as an item) + +// Special flags common to items and chars (O = Object, generic) +#define CAN_O_NOSLEEP 0x10000000 + +// masks for movement-affecting flags +#define CAN_C_MOVEMENTCAPABLEMASK (CAN_C_SWIM| CAN_C_WALK|CAN_C_FLY|CAN_C_RUN|CAN_C_HOVER) +#define CAN_C_MOVEMASK (CAN_C_GHOST|CAN_C_SWIM|CAN_C_WALK|CAN_C_PASSWALLS|CAN_C_FLY|CAN_C_FIRE_IMMUNE|CAN_C_NOINDOORS|CAN_C_HOVER|CAN_C_NOBLOCKHEIGHT) +#define CAN_I_MOVEMASK (CAN_I_DOOR|CAN_I_WATER|CAN_I_PLATFORM|CAN_I_BLOCK|CAN_I_CLIMB|CAN_I_FIRE|CAN_I_ROOF|CAN_I_HOVER) + + struct CBaseBaseDef : public CResourceLink, public CEntityProps { // Minimal amount of common info to define RES_ITEMDEF or RES_CHARDEF, (it might just be a DUPE) @@ -32,6 +114,9 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps height_t m_Height; // Height of the object. // -------------- ResLevel ------------- +public: + RESDISPLAY_VERSION _iEraLimitProps; // Don't allow to have properties newer than the given era. +private: byte m_Expansion; byte m_ResLevel; // ResLevel required for players to see me HUE_TYPE m_ResDispDnHue; // Hue shown to players who don't have my ResLevel @@ -53,7 +138,6 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps word m_defenseRange; // variable range of defense. uint64 m_Can; // Base attribute flags. CAN_C_GHOST, etc - RESDISPLAY_VERSION _iEraLimitProps; // Don't allow to have properties newer than the given era. CCFaction _pFaction; @@ -114,89 +198,12 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps m_BaseDefs.DeleteKey(ptcKey); } -// Map Movement flags. -#define CAN_C_GHOST 0x0001 // Moves thru doors etc. -#define CAN_C_SWIM 0x0002 // dolphin, elemental or is water. -#define CAN_C_WALK 0x0004 // Can walk on land, climbed on walked over else Frozen by nature(Corpser). -#define CAN_C_PASSWALLS 0x0008 // Walk thru walls. -#define CAN_C_FLY 0x0010 // Can walk to a point with a much higher height difference from our current z. -#define CAN_C_FIRE_IMMUNE 0x0020 // Has some immunity to fire ? (will walk into it (lava)). -#define CAN_C_NOINDOORS 0x0040 // Can't go under roof. -#define CAN_C_HOVER 0x0080 // Can hover. - -#define CAN_I_DOOR 0x0001 // Is a door UFLAG4_DOOR. -#define CAN_I_WATER 0x0002 // Need to swim in it. UFLAG1_WATER. -#define CAN_I_PLATFORM 0x0004 // we can walk on top of it. (even tho the item itself might block) UFLAG2_PLATFORM. -#define CAN_I_BLOCK 0x0008 // need to walk thru walls or fly over. UFLAG1_BLOCK. -#define CAN_I_CLIMB 0x0010 // Item: step up on it, UFLAG2_CLIMBABLE. -#define CAN_I_FIRE 0x0020 // Is a fire. Usually blocks as well. UFLAG1_DAMAGE. -#define CAN_I_ROOF 0x0040 // We are under a roof. can't rain on us. UFLAG4_ROOF. -#define CAN_I_HOVER 0x0080 // We are hovering. UFLAG4_HOVEROVER. - -// CItemBase specific defs. -#define CAN_I_PILE 0x0100 // Can item be piled UFLAG2_STACKABLE (*.mul). -#define CAN_I_DYE 0x0200 // Can item be dyed UFLAG3_CLOTH? (sort of). -#define CAN_I_FLIP 0x0400 // will flip by default. -#define CAN_I_LIGHT 0x0800 // UFLAG3_LIGHT. -#define CAN_I_REPAIR 0x1000 // Is it repairable (difficulty based on value). -#define CAN_I_REPLICATE 0x2000 // Things like arrows are pretty much all the same. -#define CAN_I_DCIGNORELOS 0x4000 // when dclicked, ignore LOS checks. -#define CAN_I_DCIGNOREDIST 0x8000 // when dclicked, ignore distance checks. -#define CAN_I_BLOCKLOS 0x10000 // blocks LOS without blocking walkchecks. -#define CAN_I_EXCEPTIONAL 0x20000 // can items be exceptional. -> TODO: convert to property, to free limited attr values -#define CAN_I_MAKERSMARK 0x40000 // can items hold makers mark. -> TODO: convert to property, to free limited attr values -#define CAN_I_RETAINCOLOR 0x80000 // can items retain material colors.-> TODO: convert to property, to free limited attr values -#define CAN_I_ENCHANT 0x100000 // can items be enchanted (runic). -> TODO: convert to property, to free limited attr values -#define CAN_I_IMBUE 0x200000 // can items be imbued (imbuing). -> TODO: convert to property, to free limited attr values -#define CAN_I_RECYCLE 0x400000 // Can items be recycled. -> TODO: convert to property, to free limited attr values -#define CAN_I_REFORGE 0x800000 // Can items be Runic Reforged. -> TODO: convert to property, to free limited attr values -#define CAN_I_FORCEDC 0x1000000 // Can force DClick skipping other checks (LOS,Distance, Cont...). -#define CAN_I_DAMAGEABLE 0x2000000 // Display item health bar on HS clients >= 7.0.30.0 (MORE1L = cur hitpoints / MORE1H = max hitpoints) -#define CAN_I_BLOCKLOS_HEIGHT 0x4000000 // blocks LOS without blocking walkchecks, but only if the item is too high for the viewer. -#define CAN_I_EQUIPONCAST 0x8000000 // Allow items to stay equipped while EquippedCast disabled in sphere.ini. -// 0x10000000 // CAN_O_NOSLEEP, defined below -#define CAN_I_SCRIPTEDMORE 0x20000000 // This item doesn't use an hardcoded behavior, so its MORE value might be used for custom stuff. -#define CAN_I_TIMER_CONTAINED 0x40000000 // This item (if not "sleeping") can have a timer even if inside a container (overrides global default behavior). - -// (CItemBase) CanEquip specific defs. -#define CAN_U_ALL 0x000 // Can be used by everyone. -#define CAN_U_MALE 0x001 // Can be used by males. -#define CAN_U_FEMALE 0x002 // Can be used by females. -#define CAN_U_HUMAN 0x004 // Can be used by humans. -#define CAN_U_ELF 0x008 // Can be used by elfs. -#define CAN_U_GARGOYLE 0x010 // Can be used by gargoyles. -#define CAN_U_NONE 0x020 // Can not be used. - -// CCharBase specific defs. -#define CAN_C_EQUIP 0x00100 // Can equip stuff. (humanoid). -#define CAN_C_USEHANDS 0x00200 // Can wield weapons (INT dependant), open doors ?, pick up/manipulate things. -#define CAN_C_MOUNT 0x00400 // can mount rides. -#define CAN_C_FEMALE 0x00800 // It is female by nature. -#define CAN_C_NONHUMANOID 0x01000 // Body type for combat messages. -#define CAN_C_RUN 0x02000 // Can run (not needed if they can fly). -#define CAN_C_DCIGNORELOS 0x04000 // when dclicking sth., ignore LOS checks. -#define CAN_C_DCIGNOREDIST 0x08000 // when dclicking sth., ignore distance checks. -#define CAN_C_NONMOVER 0x10000 // Just stay in place, avoid movement actions. -#define CAN_C_NOBLOCKHEIGHT 0x20000 // Do not consider char's height when checking if it can move to a point -#define CAN_C_STATUE 0x40000 // Statue: do not move, PacketStatueAnimation with tag.statue_animation and tag.statue_frame -#define CAN_C_NONSELECTABLE 0x80000 // Can't be selected/targeted (useful for chars with mt_statue which need to act as an item) - -// Special flags common to items and chars (O = Object, generic) -#define CAN_O_NOSLEEP 0x10000000 - -// masks for movement-affecting flags -#define CAN_C_MOVEMENTCAPABLEMASK (CAN_C_SWIM| CAN_C_WALK|CAN_C_FLY|CAN_C_RUN|CAN_C_HOVER) -#define CAN_C_MOVEMASK (CAN_C_GHOST|CAN_C_SWIM|CAN_C_WALK|CAN_C_PASSWALLS|CAN_C_FLY|CAN_C_FIRE_IMMUNE|CAN_C_NOINDOORS|CAN_C_HOVER|CAN_C_NOBLOCKHEIGHT) -#define CAN_I_MOVEMASK (CAN_I_DOOR|CAN_I_WATER|CAN_I_PLATFORM|CAN_I_BLOCK|CAN_I_CLIMB|CAN_I_FIRE|CAN_I_ROOF|CAN_I_HOVER) - - public: CBaseBaseDef( CResourceID id ); virtual ~CBaseBaseDef(); -private: - CBaseBaseDef(const CBaseBaseDef& copy); - CBaseBaseDef& operator=(const CBaseBaseDef& other); + CBaseBaseDef(const CBaseBaseDef& copy) = delete; + CBaseBaseDef& operator=(const CBaseBaseDef& other) = delete; public: /** @@ -332,9 +339,6 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps * @return Value. */ static ushort ConvertRangeStr(lpctstr ptcRange); -#define RANGE_MAKE(iHi, iLo) (((iHi & 0xFF) << 8) | (iLo & 0xFF)) // Highest byte contains highest value, lowest contains the lowest value -#define RANGE_GET_HI(iRange) ((iRange >> 8) & 0xFF) -#define RANGE_GET_LO(iRange) (iRange & 0xFF) }; #endif // _INC_CBASE_H diff --git a/src/game/CComponent.h b/src/game/CComponent.h index ff6085d79..aaaa84e76 100644 --- a/src/game/CComponent.h +++ b/src/game/CComponent.h @@ -15,7 +15,7 @@ class CTextConsole; class CObjBase; -enum COMP_TYPE : uchar +enum COMP_TYPE : ushort { COMP_CHAMPION, COMP_SPAWN, @@ -36,7 +36,7 @@ enum CCRET_TYPE class CComponent { COMP_TYPE _iType; - + protected: CComponent(COMP_TYPE type); diff --git a/src/game/CContainer.cpp b/src/game/CContainer.cpp index 46177bed0..a9b1b46c6 100644 --- a/src/game/CContainer.cpp +++ b/src/game/CContainer.cpp @@ -23,7 +23,7 @@ void CContainer::_GoAwake() for (CSObjContRec* pObjRec : GetIterationSafeContReverse()) { CItem* pItem = static_cast(pObjRec); - //std::unique_lock lock(pItem->THREAD_CMUTEX); + //std::unique_lock lock(pItem->MT_CMUTEX); if (pItem->IsSleeping()) pItem->GoAwake(); } @@ -35,7 +35,7 @@ void CContainer::_GoSleep() for (CSObjContRec* pObjRec : GetIterationSafeContReverse()) { CItem* pItem = static_cast(pObjRec); - //std::unique_lock lock(pItem->THREAD_CMUTEX); + //std::unique_lock lock(pItem->MT_CMUTEX); if (!pItem->CanTick(true)) { pItem->GoSleep(); @@ -51,14 +51,13 @@ void CContainer::ContentDelete(bool fForce) return; // delete all entries. - ASSERT(!_fIsClearing); - _fIsClearing = true; // Loop through a copy of the current state of the container, since by deleting other container objects it could happen that // other objects are deleted and appended to this list, thus invalidating the iterators used by the for loop. const auto stateCopy = GetIterationSafeContReverse(); _Contents.clear(); + for (CSObjContRec* pRec : stateCopy) // iterate the list. { EXC_TRY("Scheduling objects for deletion"); @@ -69,8 +68,6 @@ void CContainer::ContentDelete(bool fForce) EXC_CATCH; } - - _fIsClearing = false; } void CContainer::ContentNotifyDelete() @@ -191,7 +188,8 @@ void CContainer::OnRemoveObj( CSObjContRec *pObRec ) // Override this = called w ADDTOCALLSTACK("CContainer::OnRemoveObj"); // remove this object from the container list. // Overload the RemoveAt for general lists to come here. - CItem *pItem = static_cast(pObRec); + DEBUG_ASSERT(dynamic_cast(pObRec)); + CItem *pItem = static_cast(pObRec); ASSERT(pItem); CSObjCont::OnRemoveObj(pItem); @@ -501,7 +499,7 @@ void CContainer::ContentsDump( const CPointMap &pt, uint64 iAttrLeave ) // Just dump the contents onto the ground. iAttrLeave |= ATTR_NEWBIE|ATTR_MOVE_NEVER|ATTR_CURSED2|ATTR_BLESSED2; - + for (size_t i = 0; i < GetContentCount(); ) { CItem* pItem = static_cast(GetContentIndex(i)); diff --git a/src/game/CEntity.cpp b/src/game/CEntity.cpp index b383968a3..08a4ba113 100644 --- a/src/game/CEntity.cpp +++ b/src/game/CEntity.cpp @@ -19,7 +19,7 @@ CEntity::~CEntity() void CEntity::Delete(bool fForce) { - ADDTOCALLSTACK_INTENSIVE("CEntity::Delete"); + ADDTOCALLSTACK_DEBUG("CEntity::Delete"); if (_lComponents.empty()) return; for (auto it = _lComponents.begin(), itEnd = _lComponents.end(); it != itEnd; ++it) @@ -33,7 +33,7 @@ void CEntity::Delete(bool fForce) void CEntity::ClearComponents() { - ADDTOCALLSTACK_INTENSIVE("CEntity::ClearComponents"); + ADDTOCALLSTACK_DEBUG("CEntity::ClearComponents"); if (_lComponents.empty()) return; for (auto it = _lComponents.begin(), itEnd = _lComponents.end(); it != itEnd; ++it) @@ -47,7 +47,7 @@ void CEntity::ClearComponents() void CEntity::SubscribeComponent(CComponent * pComponent) { - ADDTOCALLSTACK_INTENSIVE("CEntity::SubscribeComponent"); + ADDTOCALLSTACK_DEBUG("CEntity::SubscribeComponent"); const COMP_TYPE compType = pComponent->GetType(); const auto pairResult = _lComponents.try_emplace(compType, pComponent); if (pairResult.second == false) @@ -62,7 +62,7 @@ void CEntity::SubscribeComponent(CComponent * pComponent) void CEntity::UnsubscribeComponent(CComponent *pComponent) { - ADDTOCALLSTACK_INTENSIVE("CEntity::UnsubscribeComponent"); + ADDTOCALLSTACK_DEBUG("CEntity::UnsubscribeComponent"); if (_lComponents.empty()) { return; @@ -81,13 +81,13 @@ void CEntity::UnsubscribeComponent(CComponent *pComponent) bool CEntity::IsComponentSubscribed(CComponent *pComponent) const { - ADDTOCALLSTACK_INTENSIVE("CEntity::IsComponentSubscribed"); + ADDTOCALLSTACK_DEBUG("CEntity::IsComponentSubscribed"); return ( !_lComponents.empty() && (_lComponents.end() != _lComponents.find(pComponent->GetType())) ); } CComponent * CEntity::GetComponent(COMP_TYPE type) const { - ADDTOCALLSTACK_INTENSIVE("CEntity::GetComponent"); + ADDTOCALLSTACK_DEBUG("CEntity::GetComponent"); ASSERT(type < COMP_QTY); if (_lComponents.empty()) { @@ -99,7 +99,7 @@ CComponent * CEntity::GetComponent(COMP_TYPE type) const bool CEntity::r_GetRef(lpctstr & ptcKey, CScriptObj * & pRef) { - ADDTOCALLSTACK_INTENSIVE("CEntity::r_GetRef"); + ADDTOCALLSTACK_DEBUG("CEntity::r_GetRef"); if (_lComponents.empty()) return false; for (auto it = _lComponents.begin(), itEnd = _lComponents.end(); it != itEnd; ++it) @@ -116,7 +116,7 @@ bool CEntity::r_GetRef(lpctstr & ptcKey, CScriptObj * & pRef) void CEntity::r_Write(CScript & s) // Storing data in the worldsave. { - ADDTOCALLSTACK_INTENSIVE("CEntity::r_Write"); + ADDTOCALLSTACK_DEBUG("CEntity::r_Write"); if (_lComponents.empty() && !s.IsWriteMode()) return; for (auto it = _lComponents.begin(), itEnd = _lComponents.end(); it != itEnd; ++it) @@ -129,7 +129,7 @@ void CEntity::r_Write(CScript & s) // Storing data in the worldsave. bool CEntity::r_WriteVal(lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc) { - ADDTOCALLSTACK_INTENSIVE("CEntity::r_WriteVal"); + ADDTOCALLSTACK_DEBUG("CEntity::r_WriteVal"); if (_lComponents.empty()) return false; for (auto it = _lComponents.begin(), itEnd = _lComponents.end(); it != itEnd; ++it) @@ -146,7 +146,7 @@ bool CEntity::r_WriteVal(lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc) bool CEntity::r_LoadVal(CScript & s) { - ADDTOCALLSTACK_INTENSIVE("CEntity::r_LoadVal"); + ADDTOCALLSTACK_DEBUG("CEntity::r_LoadVal"); if (_lComponents.empty()) return false; for (auto it = _lComponents.begin(), itEnd = _lComponents.end(); it != itEnd; ++it) @@ -163,7 +163,7 @@ bool CEntity::r_LoadVal(CScript & s) bool CEntity::r_Verb(CScript & s, CTextConsole * pSrc) // Execute command from script. { - ADDTOCALLSTACK_INTENSIVE("CEntity::r_Verb"); + ADDTOCALLSTACK_DEBUG("CEntity::r_Verb"); EXC_TRY("Verb"); if (_lComponents.empty()) @@ -184,7 +184,7 @@ bool CEntity::r_Verb(CScript & s, CTextConsole * pSrc) // Execute command from s void CEntity::Copy(const CEntity *target) { - ADDTOCALLSTACK_INTENSIVE("CEntity::Copy"); + ADDTOCALLSTACK_DEBUG("CEntity::Copy"); if (_lComponents.empty()) return; for (auto it = target->_lComponents.begin(), itEnd = target->_lComponents.end(); it != itEnd; ++it) @@ -201,7 +201,7 @@ void CEntity::Copy(const CEntity *target) CCRET_TYPE CEntity::_OnTick() { - ADDTOCALLSTACK("CEntity::_OnTick"); + ADDTOCALLSTACK_DEBUG("CEntity::_OnTick"); if (_lComponents.empty()) return CCRET_CONTINUE; diff --git a/src/game/CObjBase.cpp b/src/game/CObjBase.cpp index 20792a812..18fbbb946 100644 --- a/src/game/CObjBase.cpp +++ b/src/game/CObjBase.cpp @@ -26,6 +26,38 @@ #include "CObjBase.h" +DIR_TYPE GetDirStr(lpctstr pszDir) +{ + char iDir2, iDir = static_cast(toupper(pszDir[0])); + + switch (iDir) + { + case 'E': + return DIR_E; + case 'W': + return DIR_W; + case 'N': + iDir2 = static_cast(toupper(pszDir[1])); + if (iDir2 == 'E') + return DIR_NE; + if (iDir2 == 'W') + return DIR_NW; + return DIR_N; + case 'S': + iDir2 = static_cast(toupper(pszDir[1])); + if (iDir2 == 'E') + return DIR_SE; + if (iDir2 == 'W') + return DIR_SW; + return DIR_S; + default: + if ((iDir >= '0') && (iDir <= '7')) + return static_cast(iDir - '0'); + } + return DIR_QTY; +} + + static bool GetDeltaStr( CPointMap & pt, tchar * pszDir ) { tchar * ppCmd[3]; @@ -55,6 +87,7 @@ static bool GetDeltaStr( CPointMap & pt, tchar * pszDir ) return true; } + ///////////////////////////////////////////////////////////////// // -CObjBase stuff // Either a player, npc or item. @@ -63,12 +96,12 @@ CObjBase::CObjBase( bool fItem ) // PROFILE_TIME_QTY is unused, CObjBase is not { ++ sm_iCount; - _fDeleting = false; + _uiInternalStateFlags = 0; _iCreatedResScriptIdx = _iCreatedResScriptLine = -1; _iRunningTriggerId = _iCallingObjTriggerId = -1; - m_timestamp = 0; + m_iTimeStampS = 0; m_CanMask = 0; m_attackBase = m_attackRange = 0; @@ -148,7 +181,7 @@ bool CObjBase::_IsIdle() const bool CObjBase::IsIdle() const { - THREAD_SHARED_LOCK_RETURN(_IsIdle()); + MT_ENGINE_SHARED_LOCK_RETURN(_IsIdle()); } bool CObjBase::_IsDeleted() const @@ -158,24 +191,47 @@ bool CObjBase::_IsDeleted() const bool CObjBase::IsDeleted() const { - THREAD_SHARED_LOCK_RETURN(_IsDeleted()); + MT_ENGINE_SHARED_LOCK_RETURN(_IsDeleted()); } void CObjBase::DeletePrepare() { ADDTOCALLSTACK("CObjBase::DeletePrepare"); - // Prepare to delete. - CObjBase::_GoSleep(); // virtual, but superclass methods are called in their ::DeletePrepare methods - RemoveFromView(); + // Preparations before actually deleting the object. Unlink it from the world and from other objects. + + CObjBase::_GoSleep(); // virtual, but superclass methods are called in their ::DeletePrepare methods + + const SERVMODE_TYPE servMode = g_Serv.GetServerMode(); + const bool fDestroyingWorld = (servMode == SERVMODE_Exiting || servMode == SERVMODE_Loading); + if (!fDestroyingWorld) + { + RemoveFromView(); + } + + const bool fTopLevel = dynamic_cast(GetParent()); +/* +#ifdef _DEBUG + if (fTopLevel) + { + // This will not always work. When you clear a CSObjCont, OnRemoveObj isn't called automatically + // (in most of the cases it isn't actually needed). Though, this means that for some superclasses of CSObjCont + // (es containers for spawned objects), the top level flag (UID_O_DISCONNECT) is not set. + //DEBUG_ASSERT(GetUID().IsObjTopLevel()); + } +#endif +*/ + + // This HAS to be called before the CItem and CChar parts are destroyed. RemoveSelf(); + + if (fTopLevel) + _uiInternalStateFlags |= SF_TOPLEVEL; } void CObjBase::DeleteCleanup(bool fForce) { ADDTOCALLSTACK("CObjBase::DeleteCleanup"); - _fDeleting = true; - - RemoveSelf(); + _uiInternalStateFlags |= SF_DELETING; CEntity::Delete(fForce); CWorldTimedFunctions::ClearUID(GetUID()); @@ -185,10 +241,30 @@ bool CObjBase::Delete(bool fForce) { ADDTOCALLSTACK("CObjBase::Delete"); - DeletePrepare(); // virtual! - DeleteCleanup(fForce); // not virtual! + bool fScheduleDeletion = true; + const SERVMODE_TYPE servMode = g_Serv.GetServerMode(); + const bool fDestroyingWorld = (servMode == SERVMODE_Exiting || servMode == SERVMODE_Loading); + if (fDestroyingWorld) + { + // Why resort to _uiInternalStateFlags and not simply check if GetParent() is a CSectorObjCont* ? + // Because at this point CSObjContRec::RemoveSelf might have been called (it depends on how CObjBase::Delete was called, + // if via a destructor or else), and GetParent() might already be nullptr. + // There are cases (like sector closing) where the item is stored inside its CSectorObjCont just to later call the object destructor, + // even if its GetParent() is nullptr. That's also the reason because we set fScheduleDeletion to false: this CObjBase will be + // deleted/destructed by its parent container. + if (HAS_FLAGS_ANY(_uiInternalStateFlags, SF_TOPLEVEL)) + { + // If top level, the sector will tale care of destroying it. + fScheduleDeletion = false; + } + } + + DeletePrepare(); // virtual, but if called by the destructor this will fail to call upper (CChar, CItem, etc) virtual methods. + DeleteCleanup(fForce); // not virtual! - g_World.ScheduleObjDeletion(this); + if (fScheduleDeletion) { + g_World.ScheduleObjDeletion(this); + } return true; } @@ -204,14 +280,14 @@ bool CObjBase::IsContainer() const return (dynamic_cast (this) != nullptr); } -int64 CObjBase::GetTimeStamp() const +int64 CObjBase::GetTimeStampS() const { - return m_timestamp; + return m_iTimeStampS; } -void CObjBase::SetTimeStamp(int64 t_time) +void CObjBase::SetTimeStampS(int64 t_time) { - m_timestamp = t_time; + m_iTimeStampS = t_time; } void CObjBase::TimeoutRecursiveResync(int64 iDelta) @@ -286,7 +362,7 @@ HUE_TYPE CObjBase::GetHue() const int CObjBase::IsWeird() const { - ADDTOCALLSTACK_INTENSIVE("CObjBase::IsWeird"); + ADDTOCALLSTACK_DEBUG("CObjBase::IsWeird"); int iResultCode = CObjBaseTemplate::IsWeird(); if ( iResultCode ) { @@ -659,7 +735,7 @@ void CObjBase::Emote2(lpctstr pText, lpctstr pText1, CClient * pClientExclude, b // ASCII packet void CObjBase::Speak( lpctstr pText, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font ) { - ADDTOCALLSTACK_INTENSIVE("CObjBase::Speak"); + ADDTOCALLSTACK_DEBUG("CObjBase::Speak"); CWorldComm::Speak( this, pText, wHue, mode, font ); } @@ -667,7 +743,7 @@ void CObjBase::Speak( lpctstr pText, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYP // Unicode packet void CObjBase::SpeakUTF8( lpctstr pText, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font, CLanguageID lang ) { - ADDTOCALLSTACK_INTENSIVE("CObjBase::SpeakUTF8"); + ADDTOCALLSTACK_DEBUG("CObjBase::SpeakUTF8"); // convert UTF8 to UTF16 UNICODE. nachar szBuffer[ MAX_TALK_BUFFER ]; CvtSystemToNETUTF16( szBuffer, ARRAY_COUNT(szBuffer), pText, -1 ); @@ -679,7 +755,7 @@ void CObjBase::SpeakUTF8( lpctstr pText, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT // Difference with SpeakUTF8: this method accepts as text input an nachar, which is a network aligned utf16 unicode characters array void CObjBase::SpeakUTF8Ex( const nachar * pText, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font, CLanguageID lang ) { - ADDTOCALLSTACK_INTENSIVE("CObjBase::SpeakUTF8Ex"); + ADDTOCALLSTACK_DEBUG("CObjBase::SpeakUTF8Ex"); CWorldComm::SpeakUNICODE( this, pText, wHue, mode, font, lang ); } @@ -717,7 +793,7 @@ bool CObjBase::MoveNear(CPointMap pt, ushort iSteps ) return MoveTo(pt); } -void CObjBase::UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font, bool bUnicode ) const +void CObjBase::UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font, bool fUnicode ) const { ADDTOCALLSTACK("CObjBase::UpdateObjMessage"); // Show everyone a msg coming from this object. @@ -731,9 +807,9 @@ void CObjBase::UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * continue; if (( pClient->GetChar() == this ) && pTextYou != nullptr ) - pClient->addBarkParse(pTextYou, this, wHue, mode, font, bUnicode ); + pClient->addBarkParse(pTextYou, this, wHue, mode, font, fUnicode ); else if (( pClient->GetChar() != this ) && pTextThem != nullptr ) - pClient->addBarkParse(pTextThem, this, wHue, mode, font, bUnicode ); + pClient->addBarkParse(pTextThem, this, wHue, mode, font, fUnicode ); //pClient->addBarkParse(( pClient->GetChar() == this )? pTextYou : pTextThem, this, wHue, mode, font, bUnicode ); } @@ -1147,7 +1223,7 @@ bool CObjBase::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc, if ( !strnicmp(ptcKey, "ID", 2) ) { - sVal.Format("%s", g_Cfg.ResourceGetName( CResourceID(RES_DIALOG, RES_GET_INDEX(itGumpFound->first), RES_PAGE_ANY)) ); + sVal.Format("%s", g_Cfg.ResourceGetName( CResourceID(RES_DIALOG, ResGetIndex(itGumpFound->first), RES_PAGE_ANY)) ); } else if ( !strnicmp(ptcKey, "COUNT", 5) ) { @@ -1560,7 +1636,7 @@ bool CObjBase::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc, sVal.FormatVal( pItem->GetSpeed() ); } break; case OC_TIMESTAMP: - sVal.FormatLLVal( GetTimeStamp() / MSECS_PER_TENTH ); // in tenths of second. + sVal.FormatLLVal( GetTimeStampS() ); break; case OC_VERSION: sVal = SPHERE_BUILD_INFO_STR; @@ -1716,17 +1792,6 @@ bool CObjBase::r_LoadVal( CScript & s ) } } - if (!strnicmp("FOLLOWER", ptcKey, 8)) - { - if (ptcKey[8] == '.') - { - ptcKey = ptcKey + 4; - CUID ptcArg = (CUID)s.GetArgDWVal(); - m_followers.emplace_back(ptcArg); - return true; - } - } - int index = FindTableSorted( ptcKey, sm_szLoadKeys, ARRAY_COUNT( sm_szLoadKeys )-1 ); if ( index < 0 ) { @@ -1938,7 +2003,7 @@ bool CObjBase::r_LoadVal( CScript & s ) fResendTooltip = true; break; case OC_TIMESTAMP: - SetTimeStamp(s.GetArgLLVal()); + SetTimeStampS(s.GetArgLLVal()); break; case OC_SPAWNITEM: if ( !g_Serv.IsLoading() ) // SPAWNITEM is read-only @@ -1968,7 +2033,7 @@ bool CObjBase::r_LoadVal( CScript & s ) void CObjBase::r_Write( CScript & s ) { - ADDTOCALLSTACK_INTENSIVE("CObjBase::r_Write"); + ADDTOCALLSTACK_DEBUG("CObjBase::r_Write"); s.WriteKeyHex( "SERIAL", GetUID().GetObjUID()); if ( IsIndividualName() ) s.WriteKeyStr( "NAME", GetIndividualName()); @@ -1976,8 +2041,8 @@ void CObjBase::r_Write( CScript & s ) s.WriteKeyHex( "COLOR", GetHue()); if ( _IsTimerSet() ) s.WriteKeyVal( "TIMER", _GetTimerAdjusted()); - if ( m_timestamp > 0 ) - s.WriteKeyVal( "TIMESTAMP", GetTimeStamp()); + if ( m_iTimeStampS > 0 ) + s.WriteKeyVal( "TIMESTAMP", GetTimeStampS()); if ( const CCSpawn* pSpawn = GetSpawn() ) s.WriteKeyHex("SPAWNITEM", pSpawn->GetLink()->GetUID().GetObjUID()); if ( m_ModAr ) @@ -1990,14 +2055,6 @@ void CObjBase::r_Write( CScript & s ) m_TagDefs.r_WritePrefix(s, "TAG"); m_OEvents.r_Write(s, "EVENTS"); - - for (CUID uid : m_followers) - { - dword dUID = (dword)uid; - char* pszTag = Str_GetTemp(); - snprintf(pszTag, Str_TempLength(), "FOLLOWER.%d", dUID); - s.WriteKeyHex(pszTag, dUID); - } } enum OV_TYPE @@ -2199,7 +2256,7 @@ bool CObjBase::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command fro } } //DEBUG_ERR(("this->GetUID() 0%x pThis->GetUID() 0%x pCharSrc->GetUID() 0%x\n",(dword)this->GetUID(),(dword)pThis->GetUID(),(dword)pCharSrc->GetUID())); - pThis->Effect( (EFFECT_TYPE)(piCmd[0]), (ITEMID_TYPE)(RES_GET_INDEX(piCmd[1]) ), + pThis->Effect( (EFFECT_TYPE)(piCmd[0]), (ITEMID_TYPE)(ResGetIndex((dword)piCmd[1]) ), pCharSrc, (iArgQty >= 3)? (uchar)(piCmd[2]) : 5, // byte bSpeedSeconds = 5, (iArgQty >= 4)? (uchar)(piCmd[3]) : 1, // byte bLoop = 1, @@ -2239,7 +2296,7 @@ bool CObjBase::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command fro } //DEBUG_ERR(("this->GetUID() 0%x pThis->GetUID() 0%x pCharSrc->GetUID() 0%x\n",(dword)this->GetUID(),(dword)pThis->GetUID(),(dword)pCharSrc->GetUID())); - pThis->EffectLocation((EFFECT_TYPE)(piCmd[3]), (ITEMID_TYPE)(RES_GET_INDEX(piCmd[4])), + pThis->EffectLocation((EFFECT_TYPE)(piCmd[3]), (ITEMID_TYPE)(ResGetIndex((dword)piCmd[4])), pCharSrc ? &pCharSrc->GetTopPoint() : nullptr, &ptDest, (iArgQty >= 3) ? (uchar)(piCmd[5]) : 5, // byte bSpeedSeconds = 5, @@ -2520,7 +2577,7 @@ bool CObjBase::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command fro default: break; } - OnSpellEffect((SPELL_TYPE)(RES_GET_INDEX(piCmd[0])), pCharSrc, (int)(piCmd[1]), pItemSrc); + OnSpellEffect((SPELL_TYPE)(ResGetIndex((dword)piCmd[0])), pCharSrc, (int)(piCmd[1]), pItemSrc); } break; @@ -3112,7 +3169,7 @@ dword CObjBase::GetPropertyHash() const void CObjBase::OnTickStatusUpdate() { // process m_fStatusUpdate flags - + //ADDTOCALLSTACK("CObjBase::OnTickStatusUpdate"); // Called very frequently. EXC_TRY("CObjBase::OnTickStatusUpdate"); @@ -3162,13 +3219,13 @@ void CObjBase::_GoSleep() bool CObjBase::_CanTick(bool fParentGoingToSleep) const { - //ADDTOCALLSTACK_INTENSIVE("CObjBase::_CanTick"); // Called very frequently. + //ADDTOCALLSTACK_DEBUG("CObjBase::_CanTick"); // Called very frequently. // This doesn't check the sector sleeping status, it's only about this object. EXC_TRY("Can tick?"); // Directly call the method specifying the belonging class, to avoid the overhead of vtable lookup under the hood. bool fCanTick = fParentGoingToSleep ? false : !CTimedObject::_IsSleeping(); - const bool fIgnoreCont = (HAS_FLAG(g_Cfg.m_uiItemTimers, ITEM_CANTIMER_IN_CONTAINER) || Can(CAN_I_TIMER_CONTAINED)); + const bool fIgnoreCont = (HAS_FLAGS_STRICT(g_Cfg.m_uiItemTimers, ITEM_CANTIMER_IN_CONTAINER) || Can(CAN_I_TIMER_CONTAINED)); if (fCanTick) { @@ -3183,7 +3240,6 @@ bool CObjBase::_CanTick(bool fParentGoingToSleep) const else if (!fIgnoreCont) fCanTick = pObjParent->CanTick(fParentGoingToSleep); } - } } @@ -3638,34 +3694,3 @@ bool CObjBase::CallPersonalTrigger(tchar * pArgs, CTextConsole * pSrc, TRIGRET_T return false; } - -DIR_TYPE GetDirStr( lpctstr pszDir ) -{ - char iDir2, iDir = static_cast< char >( toupper( pszDir[ 0 ] ) ); - - switch ( iDir ) - { - case 'E': - return DIR_E; - case 'W': - return DIR_W; - case 'N': - iDir2 = static_cast< char >( toupper( pszDir[ 1 ] ) ); - if ( iDir2 == 'E' ) - return DIR_NE; - if ( iDir2 == 'W' ) - return DIR_NW; - return DIR_N; - case 'S': - iDir2 = static_cast< char >( toupper( pszDir[ 1 ] ) ); - if ( iDir2 == 'E' ) - return DIR_SE; - if ( iDir2 == 'W' ) - return DIR_SW; - return DIR_S; - default: - if ( ( iDir >= '0' ) && ( iDir <= '7' ) ) - return static_cast< DIR_TYPE >( iDir - '0' ); - } - return DIR_QTY; -} diff --git a/src/game/CObjBase.h b/src/game/CObjBase.h index 071ff4ee1..41b68084b 100644 --- a/src/game/CObjBase.h +++ b/src/game/CObjBase.h @@ -15,6 +15,7 @@ #include "CEntity.h" #include "CBase.h" #include "CServerConfig.h" +#include class PacketSend; @@ -24,6 +25,15 @@ class CCSpawn; class CSector; class CWorldTicker; + +/** + * @brief Gets dir string. + * @param pszDir The dir. + * @return The dir string. + */ +DIR_TYPE GetDirStr(lpctstr pszDir); + + class CObjBase : public CObjBaseTemplate, public CScriptObj, public CEntity, public CEntityProps, public virtual CTimedObject { static lpctstr const sm_szLoadKeys[]; // All Instances of CItem or CChar have these base attributes. @@ -34,11 +44,10 @@ class CObjBase : public CObjBaseTemplate, public CScriptObj, public CEntity, pub friend class CWorldTicker; private: - int64 m_timestamp; // TimeStamp + int64 m_iTimeStampS; // TimeStamp protected: CResourceRef m_BaseRef; // Pointer to the resource that describes this type. - bool _fDeleting; std::string _sRunningTrigger; // Name of the running trigger (can be custom!) [use std::string instead of CSString because the former is allocated on-demand] short _iRunningTriggerId; // Current trigger being run on this object. Used to prevent the same trigger being called over and over. @@ -48,7 +57,7 @@ class CObjBase : public CObjBaseTemplate, public CScriptObj, public CEntity, pub static const char *m_sClassName; static dword sm_iCount; // how many total objects in the world ? - + int _iCreatedResScriptIdx; // index in g_Cfg.m_ResourceFiles of the script file where this obj was created int _iCreatedResScriptLine; // line in the script file where this obj was created @@ -63,17 +72,34 @@ class CObjBase : public CObjBaseTemplate, public CScriptObj, public CEntity, pub word m_defenseRange; // variable range of defense. int m_ModMaxWeight; // ModMaxWeight prop. HUE_TYPE m_wHue; // Hue or skin color. (CItems must be < 0x4ff or so) + int m_ModAr; CUID _uidSpawn; // SpawnItem for this item CResourceRefArray m_OEvents; - std::vector m_followers; - + + std::vector> m_TooltipData; // Storage for tooltip data while in trigger + +# define SU_UPDATE_HITS 0x01 // update hits to others +# define SU_UPDATE_MODE 0x02 // update mode to all +# define SU_UPDATE_TOOLTIP 0x04 // update tooltip to all + uchar m_fStatusUpdate; // update flags for next tick + +# define SF_DELETING 0x01 +# define SF_TOPLEVEL 0x02 + uchar _uiInternalStateFlags; + +protected: + PacketPropertyList* m_PropertyList; // currently cached property list packet + dword m_PropertyHash; // latest property list hash + dword m_PropertyRevision; // current property list revision + + public: explicit CObjBase(bool fItem); virtual ~CObjBase(); -private: - CObjBase(const CObjBase& copy); - CObjBase& operator=(const CObjBase& other); + + CObjBase(const CObjBase& copy) = delete; + CObjBase& operator=(const CObjBase& other) = delete; protected: /** @@ -86,7 +112,7 @@ class CObjBase : public CObjBaseTemplate, public CScriptObj, public CEntity, pub public: inline bool _IsBeingDeleted() const noexcept { - return _fDeleting; + return HAS_FLAGS_ANY(_uiInternalStateFlags, SF_DELETING); } protected: virtual bool _IsIdle() const; @@ -127,7 +153,7 @@ public: virtual bool IsDeleted() const override; // If a flag in m_CanMask isn't enabled in m_Can, it is considered as enabled in this Can check // So m_CanMask is useful to dynamically switch on/off some of the read-only CAN flags in the ITEMDEF/CHARDEF. return (GetCanFlagsBase() ^ m_CanMask); - } + } bool Can(uint64 uiCan) const noexcept { @@ -178,13 +204,13 @@ public: virtual bool IsDeleted() const override; * @brief Gets timestamp of the item (it's a property and not related at all with TIMER). * @return The timestamp. */ - int64 GetTimeStamp() const; + int64 GetTimeStampS() const; /** * @brief Sets time stamp. * @param t_time The time. */ - void SetTimeStamp(int64 t_time); + void SetTimeStampS(int64 t_time); /* * @brief Add iDelta to this object's timer (if active) and its child objects. @@ -577,7 +603,7 @@ public: virtual bool IsDeleted() const override; public: /** - * @fn virtual bool CObjBase::MoveTo(CPointMap pt, bool bForceFix = false) = 0; + * @fn virtual bool CObjBase::MoveTo(CPointMap pt, bool fCheckLocationEffects = true, bool fForceFix = false) = 0; * * @brief Move To Location. * @@ -663,7 +689,7 @@ public: virtual bool IsDeleted() const override; */ void Effect(EFFECT_TYPE motion, ITEMID_TYPE id, const CObjBase * pSource = nullptr, byte bspeedseconds = 5, byte bloop = 1, bool fexplode = false, dword color = 0, dword render = 0, word effectid = 0, word explodeid = 0, word explodesound = 0, dword effectuid = 0, byte type = 0) const; - + /** * @fn void CObjBase::EffectLocation(EFFECT_TYPE motion, ITEMID_TYPE id, CPointMap &pt, const CObjBase * pSource = nullptr, byte bspeedseconds = 5, byte bloop = 1, bool fexplode = false, dword color = 0, dword render = 0, word effectid = 0, word explodeid = 0, word explodesound = 0, dword effectuid = 0, byte type = 0) const; * @@ -881,16 +907,6 @@ public: virtual bool IsDeleted() const override; */ TRIGRET_TYPE Spell_OnTrigger( SPELL_TYPE spell, SPTRIG_TYPE stage, CChar * pSrc, CScriptTriggerArgs * pArgs ); -public: - // Some global object variables - int m_ModAr; - -#define SU_UPDATE_HITS 0x01 // update hits to others -#define SU_UPDATE_MODE 0x02 // update mode to all -#define SU_UPDATE_TOOLTIP 0x04 // update tooltip to all - uchar m_fStatusUpdate; // update flags for next tick - - protected: virtual void _GoAwake() override; virtual void _GoSleep() override; @@ -904,13 +920,6 @@ public: virtual bool IsDeleted() const override; virtual bool _CanTick(bool fParentGoingToSleep = false) const override; //virtual bool CanTick(bool fParentGoingToSleep = false) const override; // Not needed: the right virtual is called by CTimedObj::_CanTick. -public: - std::vector> m_TooltipData; // Storage for tooltip data while in trigger -protected: - PacketPropertyList* m_PropertyList; // currently cached property list packet - dword m_PropertyHash; // latest property list hash - dword m_PropertyRevision; // current property list revision - public: /** @@ -964,53 +973,9 @@ public: virtual bool IsDeleted() const override; * @brief Updates the property status update flag. */ void UpdatePropertyFlag(); -}; - -/** -* @enum MEMORY_TYPE -* -* @brief Values that represent memory types ( IT_EQ_MEMORY_OBJ ). -* -* Types of memory a CChar has about a game object. (m_wHue) -*/ -enum MEMORY_TYPE -{ - MEMORY_NONE = 0, - MEMORY_SAWCRIME = 0x0001, // I saw them commit a crime or i was attacked criminally. I can call the guards on them. the crime may not have been against me. - MEMORY_IPET = 0x0002, // I am a pet. (this link is my master) (never time out) - MEMORY_FIGHT = 0x0004, // Active fight going on now. may not have done any damage. and they may not know they are fighting me. - MEMORY_IAGGRESSOR = 0x0008, // I was the agressor here. (good or evil) - MEMORY_HARMEDBY = 0x0010, // I was harmed by them. (but they may have been retaliating) - MEMORY_IRRITATEDBY = 0x0020, // I saw them snoop from me or someone, or i have been attacked, or someone used Provocation on me. - MEMORY_SPEAK = 0x0040, // We spoke about something at some point. (or was tamed) (NPC_MEM_ACT_TYPE) - MEMORY_AGGREIVED = 0x0080, // I was attacked and was the inocent party here ! - MEMORY_GUARD = 0x0100, // Guard this item (never time out) - MEMORY_LEGACY_ISPAWNED = 0x0200, // UNUSED (but keep this for backwards compatibility). I am spawned from this item. (never time out) - MEMORY_GUILD = 0x0400, // This is my guild stone. (never time out) only have 1 - MEMORY_TOWN = 0x0800, // This is my town stone. (never time out) only have 1 - MEMORY_UNUSED = 0x1000, // UNUSED!!!! I am following this Object (never time out) - MEMORY_UNUSED2 = 0x2000, // UNUSED!!!! (MEMORY_WAR_TARG) This is one of my current war targets. - MEMORY_FRIEND = 0x4000, // They can command me but not release me. (not primary blame) - MEMORY_UNUSED3 = 0x8000 // UNUSED!!!! Gump record memory (More1 = Context, More2 = Uid) }; -enum NPC_MEM_ACT_TYPE // A simgle primary memory about the object. -{ - NPC_MEM_ACT_NONE = 0, // we spoke about something non-specific, - NPC_MEM_ACT_SPEAK_TRAIN, // I am speaking about training. Waiting for money - NPC_MEM_ACT_SPEAK_HIRE, // I am speaking about being hired. Waiting for money - NPC_MEM_ACT_FIRSTSPEAK, // I attempted (or could have) to speak to player. but have had no response. - NPC_MEM_ACT_TAMED, // I was tamed by this person previously. - NPC_MEM_ACT_IGNORE // I looted or looked at and discarded this item (ignore it) -}; - -enum STONEALIGN_TYPE // Types of Guild/Town stones -{ - STONEALIGN_STANDARD = 0, - STONEALIGN_ORDER, - STONEALIGN_CHAOS -}; enum ITRIG_TYPE { @@ -1076,16 +1041,6 @@ enum ITRIG_TYPE ITRIG_QTY }; -enum WAR_SWING_TYPE // m_Act_War_Swing_State -{ - WAR_SWING_INVALID = -1, - WAR_SWING_EQUIPPING = 0, // we are recoiling our weapon. - WAR_SWING_READY, // we can swing at any time. - WAR_SWING_SWINGING, // we are swinging our weapon. - //-- - WAR_SWING_EQUIPPING_NOWAIT = 10 // Special return value for CChar::Fight_Hit, DON'T USE IT IN SCRIPTS! -}; - enum CTRIG_TYPE : short { CTRIG_AAAUNUSED = 0, @@ -1298,12 +1253,4 @@ enum CTRIG_TYPE : short }; -/** - * @brief Gets dir string. - * @param pszDir The dir. - * @return The dir string. - */ -DIR_TYPE GetDirStr( lpctstr pszDir ); - - #endif // _INC_COBJBASE_H diff --git a/src/game/CObjBaseTemplate.cpp b/src/game/CObjBaseTemplate.cpp index 92517d7d0..a1542fdd6 100644 --- a/src/game/CObjBaseTemplate.cpp +++ b/src/game/CObjBaseTemplate.cpp @@ -61,7 +61,7 @@ void CObjBaseTemplate::SetEquipLayer( LAYER_TYPE layer ) void CObjBaseTemplate::SetContainedLayer( byte layer ) noexcept { // used for corpse or Restock count as well in Vendor container. - m_pt.m_z = layer; + m_pt.m_z = (char)layer; } void CObjBaseTemplate::SetContainedPoint( const CPointMap & pt ) noexcept diff --git a/src/game/CObjBaseTemplate.h b/src/game/CObjBaseTemplate.h index fd7c9d5d2..af8a2ec44 100644 --- a/src/game/CObjBaseTemplate.h +++ b/src/game/CObjBaseTemplate.h @@ -31,13 +31,12 @@ class CObjBaseTemplate : public CSObjContRec CObjBaseTemplate(); virtual ~CObjBaseTemplate() = default; -private: - CObjBaseTemplate(const CObjBaseTemplate& copy); - CObjBaseTemplate& operator=(const CObjBaseTemplate& other); + CObjBaseTemplate(const CObjBaseTemplate& copy) = delete; + CObjBaseTemplate& operator=(const CObjBaseTemplate& other) = delete; public: const CUID& GetUID() const noexcept { - return m_UID; + return m_UID; } bool IsItem() const noexcept { return m_UID.IsItem(); diff --git a/src/game/CRegion.cpp b/src/game/CRegion.cpp index 2476af4af..4de1a06e8 100644 --- a/src/game/CRegion.cpp +++ b/src/game/CRegion.cpp @@ -21,7 +21,7 @@ CRegion::CRegion( CResourceID rid, lpctstr pszName ) : { ADDTOCALLSTACK("CRegion::CRegion()"); m_dwFlags = 0; - m_iModified = 0; + m_dwModifiedFlags = 0; m_iLinkedSectors = 0; if ( pszName ) SetName( pszName ); @@ -35,14 +35,12 @@ CRegion::~CRegion() UnRealizeRegion(); } -void CRegion::SetModified( int iModFlag ) noexcept +void CRegion::SetModified( dword dwModFlag ) noexcept { -#ifdef _MSC_VER - //_CrtCheckMemory(); -#endif if ( !m_iLinkedSectors ) return; - m_iModified = m_iModified | iModFlag; + + m_dwModifiedFlags = m_dwModifiedFlags | dwModFlag; } void CRegion::UnRealizeRegion() @@ -86,7 +84,7 @@ bool CRegion::RealizeRegion() // Yes, this sector overlapped, so add it to the sector list if ( !pSector->LinkRegion(this) ) { - g_Log.EventError("Linking sector #%d for map %d for region %s failed (fatal for this region).\n", i, m_pt.m_map, GetName()); + g_Log.EventError("Linking sector #%d (map %d) for region %s failed (fatal for this region).\n", i, int(m_pt.m_map), GetName()); return false; } ++m_iLinkedSectors; @@ -210,7 +208,7 @@ bool CRegion::MakeRegionDefname() return true; } -enum RC_TYPE +enum RC_TYPE : int // Even if it would implicitly be set to int, specify it to silence UBSanitizer. { RC_ANNOUNCE, RC_ARENA, @@ -638,18 +636,18 @@ void CRegion::r_WriteBody( CScript & s, lpctstr pszPrefix ) void CRegion::r_WriteModified( CScript &s ) { ADDTOCALLSTACK("CRegion::r_WriteModified"); - if ( m_iModified & REGMOD_NAME ) + if ( m_dwModifiedFlags & REGMOD_NAME ) s.WriteKeyStr("NAME", GetName() ); - if ( m_iModified & REGMOD_GROUP ) + if ( m_dwModifiedFlags & REGMOD_GROUP ) s.WriteKeyStr("GROUP", m_sGroup.GetBuffer() ); - if ( m_iModified & REGMOD_FLAGS ) + if ( m_dwModifiedFlags & REGMOD_FLAGS ) { s.WriteKeyHex("FLAGS", GetRegionFlags() ); } - if ( m_iModified & REGMOD_EVENTS ) + if ( m_dwModifiedFlags & REGMOD_EVENTS ) { CSString sVal; m_Events.WriteResourceRefList( sVal ); @@ -684,7 +682,7 @@ void CRegion::r_WriteBase( CScript &s ) void CRegion::r_Write( CScript &s ) { - ADDTOCALLSTACK_INTENSIVE("CRegion::r_Write"); + ADDTOCALLSTACK_DEBUG("CRegion::r_Write"); s.WriteSection( "ROOMDEF %s", GetResourceName() ); r_WriteBase( s ); } @@ -792,7 +790,7 @@ bool CRegion::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command from CChar * pChar = pClient->GetChar(); if ( !pChar || ( pChar->GetRegion() != this )) continue; - + CScript script(s.GetArgRaw()); script.CopyParseState(s); pChar->r_Verb(script, pSrc); @@ -1006,7 +1004,7 @@ void CRegionWorld::r_WriteModified( CScript &s ) ADDTOCALLSTACK("CRegionWorld::r_WriteModified"); CRegion::r_WriteModified( s ); - if ( m_iModified & REGMOD_TAGS ) + if ( m_dwModifiedFlags & REGMOD_TAGS ) { m_TagDefs.r_WritePrefix(s, "TAG", "GUARDOWNER"); } @@ -1022,7 +1020,7 @@ void CRegionWorld::r_WriteBody( CScript &s, lpctstr pszPrefix ) void CRegionWorld::r_Write( CScript &s ) { - ADDTOCALLSTACK_INTENSIVE("CRegionWorld::r_Write"); + ADDTOCALLSTACK_DEBUG("CRegionWorld::r_Write"); s.WriteSection( "AREADEF %s", GetResourceName()); r_WriteBase( s ); diff --git a/src/game/CRegion.h b/src/game/CRegion.h index 5f8d45265..c72755abc 100644 --- a/src/game/CRegion.h +++ b/src/game/CRegion.h @@ -1,6 +1,6 @@ /** * @file CRegion.h -* @brief +* @brief */ #ifndef _INC_CREGION_H @@ -65,7 +65,7 @@ class CRegion : public CResourceDef, public CRegionBase static const char *m_sClassName; CPointMap m_pt; // safe point in the region. (for teleporting to) int m_iLinkedSectors; // just for statistics tracking. How many sectors are linked ? - int m_iModified; + dword m_dwModifiedFlags; static lpctstr const sm_szLoadKeys[]; static lpctstr const sm_szTrigName[RTRIG_QTY+1]; @@ -116,7 +116,7 @@ class CRegion : public CResourceDef, public CRegionBase #define REGMOD_NAME 0x0008 #define REGMOD_GROUP 0x0010 - void SetModified( int iModFlag ) noexcept; + void SetModified( dword dwModFlag ) noexcept; void SetName( lpctstr pszName ); virtual lpctstr GetName() const override { diff --git a/src/game/CRegionBase.cpp b/src/game/CRegionBase.cpp index 96b47516d..d04911925 100644 --- a/src/game/CRegionBase.cpp +++ b/src/game/CRegionBase.cpp @@ -51,7 +51,7 @@ CPointMap CRegionBase::GetRegionCorner( DIR_TYPE dir ) const bool CRegionBase::IsInside2d( const CPointMap & pt ) const { - ADDTOCALLSTACK_INTENSIVE("CRegionBase::IsInside2d"); + ADDTOCALLSTACK_DEBUG("CRegionBase::IsInside2d"); if ( ! m_rectUnion.IsInside2d( pt )) return false; diff --git a/src/game/CSector.cpp b/src/game/CSector.cpp index ea005d21c..d792531e2 100644 --- a/src/game/CSector.cpp +++ b/src/game/CSector.cpp @@ -207,7 +207,7 @@ void CSector::_GoSleep() void CSector::GoSleep() { ADDTOCALLSTACK("CSector::GoSleep"); - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; CSector::_GoSleep(); } @@ -269,7 +269,7 @@ void CSector::_GoAwake() void CSector::GoAwake() { ADDTOCALLSTACK("CSector::GoAwake"); - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; CSector::_GoAwake(); } @@ -407,7 +407,7 @@ bool CSector::r_Verb( CScript & s, CTextConsole * pSrc ) void CSector::r_Write() { - ADDTOCALLSTACK_INTENSIVE("CSector::r_Write"); + ADDTOCALLSTACK_DEBUG("CSector::r_Write"); if ( m_fSaveParity == g_World.m_fSaveParity ) return; // already saved. CPointMap pt = GetBasePoint(); @@ -660,7 +660,7 @@ byte CSector::GetLightCalc( bool fQuickSet ) const return m_Env.m_Light; if ( IsInDungeon() ) - return (uchar)g_Cfg.m_iLightDungeon; + return (uchar)std::clamp(g_Cfg.m_iLightDungeon, LIGHT_BRIGHT, LIGHT_DARK); int localtime = GetLocalTime(); @@ -678,26 +678,21 @@ byte CSector::GetLightCalc( bool fQuickSet ) const // 0... x ...12*60 int iTargLight = ((localtime * ( g_Cfg.m_iLightNight - g_Cfg.m_iLightDay ))/(12*60) + g_Cfg.m_iLightDay); - if ( iTargLight < LIGHT_BRIGHT ) - iTargLight = LIGHT_BRIGHT; - if ( iTargLight > LIGHT_DARK ) - iTargLight = LIGHT_DARK; - - return (uchar)(iTargLight); + return (uchar)std::clamp(iTargLight, LIGHT_BRIGHT, LIGHT_DARK);; } const int hour = ( localtime / ( 60)) % 24; const bool fNight = ( hour < 6 || hour > 12+8 ); // Is it night or day ? - int iTargLight = (fNight) ? g_Cfg.m_iLightNight : g_Cfg.m_iLightDay; // Target light level. + byte uiTargLight = (byte)std::min((int)UINT8_MAX, (fNight) ? g_Cfg.m_iLightNight : g_Cfg.m_iLightDay); // Target light level. // Check for clouds...if it is cloudy, then we don't even need to check for the effects of the moons... if ( GetWeather()) { // Clouds of some sort... if (fNight) - iTargLight += ( g_Rand.GetValFast( 2 ) + 1 ); // 1-2 light levels darker if cloudy at night + uiTargLight += byte( g_Rand.Get16ValFast( 2 ) + 1 ); // 1-2 light levels darker if cloudy at night else - iTargLight += ( g_Rand.GetValFast( 4 ) + 1 ); // 1-4 light levels darker if cloudy during the day. + uiTargLight += byte( g_Rand.Get16ValFast( 4 ) + 1 ); // 1-4 light levels darker if cloudy during the day. } if ( fNight ) @@ -709,7 +704,7 @@ byte CSector::GetLightCalc( bool fQuickSet ) const // Check to see if Trammel is up here... if ( IsMoonVisible( iTrammelPhase, localtime )) { - static const byte sm_TrammelPhaseBrightness[] = + static constexpr byte sm_TrammelPhaseBrightness[] = { 0, // New Moon TRAMMEL_FULL_BRIGHTNESS / 4, // Crescent Moon @@ -722,14 +717,14 @@ byte CSector::GetLightCalc( bool fQuickSet ) const }; ASSERT( iTrammelPhase < ARRAY_COUNT(sm_TrammelPhaseBrightness)); - iTargLight -= sm_TrammelPhaseBrightness[iTrammelPhase]; + uiTargLight -= std::min(uiTargLight, sm_TrammelPhaseBrightness[iTrammelPhase]); } // Felucca uint iFeluccaPhase = CWorldGameTime::GetMoonPhase( true ); if ( IsMoonVisible( iFeluccaPhase, localtime )) { - static const byte sm_FeluccaPhaseBrightness[] = + static constexpr byte sm_FeluccaPhaseBrightness[] = { 0, // New Moon FELUCCA_FULL_BRIGHTNESS / 4, // Crescent Moon @@ -742,23 +737,23 @@ byte CSector::GetLightCalc( bool fQuickSet ) const }; ASSERT( iFeluccaPhase < ARRAY_COUNT(sm_FeluccaPhaseBrightness)); - iTargLight -= sm_FeluccaPhaseBrightness[iFeluccaPhase]; + uiTargLight -= std::min(uiTargLight, sm_FeluccaPhaseBrightness[iFeluccaPhase]); } } - if ( iTargLight < LIGHT_BRIGHT ) - iTargLight = LIGHT_BRIGHT; - if ( iTargLight > LIGHT_DARK ) - iTargLight = LIGHT_DARK; + //if ( uiTargLight < LIGHT_BRIGHT /* 0 */) + // uiTargLight = LIGHT_BRIGHT; + if ( uiTargLight > LIGHT_DARK ) + uiTargLight = LIGHT_DARK; - if ( fQuickSet || m_Env.m_Light == iTargLight ) // Initializing the sector - return (uchar)(iTargLight); + if ( fQuickSet || m_Env.m_Light == uiTargLight ) // Initializing the sector + return uiTargLight; // Gradual transition to global light level. - if ( m_Env.m_Light > iTargLight ) - return ( m_Env.m_Light - 1 ); + if ( m_Env.m_Light > uiTargLight ) + return ((m_Env.m_Light > LIGHT_BRIGHT) ? (m_Env.m_Light - 1) : m_Env.m_Light); else - return ( m_Env.m_Light + 1 ); + return ((m_Env.m_Light < UINT8_MAX) ? (m_Env.m_Light + 1) : m_Env.m_Light); } void CSector::SetLightNow( bool fFlash ) @@ -976,7 +971,7 @@ bool CSector::MoveCharToSector( CChar * pChar ) // Already here? if (IsCharActiveIn(pChar)) - return false; + return false; // Check my save parity vs. this sector's if ( pChar->IsStatFlag( STATF_SAVEPARITY ) != m_fSaveParity ) @@ -1029,7 +1024,7 @@ bool CSector::MoveCharToSector( CChar * pChar ) bool CSector::_CanSleep(bool fCheckAdjacents) const { - ADDTOCALLSTACK_INTENSIVE("CSector::_CanSleep"); + ADDTOCALLSTACK_DEBUG("CSector::_CanSleep"); if ( (g_Cfg._iSectorSleepDelay == 0) || IsFlagSet(SECF_NoSleep) ) return false; // never sleep if (m_Chars_Active.GetClientsNumber() > 0) @@ -1073,16 +1068,17 @@ void CSector::SetSectorWakeStatus() } } -void CSector::Close() +void CSector::Close(bool fClosingWorld) { ADDTOCALLSTACK("CSector::Close"); + // Clear up all dynamic data for this sector. - m_Items.ClearContainer(); - m_Chars_Active.ClearContainer(); - m_Chars_Disconnect.ClearContainer(); + m_Items.ClearContainer(fClosingWorld); + m_Chars_Active.ClearContainer(fClosingWorld); + m_Chars_Disconnect.ClearContainer(fClosingWorld); - // These are resource type things. - //m_Teleports.clear(); + // These are resource type things, loaded from scripts, not save files. Do not delete them. + //m_Teleports.ClearFree(); //m_RegionLinks.clear(); } diff --git a/src/game/CSector.h b/src/game/CSector.h index 8fd9a1a4d..556191c58 100644 --- a/src/game/CSector.h +++ b/src/game/CSector.h @@ -28,12 +28,13 @@ class CSector : public CScriptObj, public CSectorBase, public CTimedObject // sq private: bool m_fSaveParity; // has the sector been saved relative to the char entering it ? - CSectorEnviron m_Env; // Current Environment byte m_RainChance; // 0 to 100% byte m_ColdChance; // Will be snow if rain chance success. byte m_ListenItems; // Items on the ground that listen ? + CSectorEnviron m_Env; // Current Environment + private: WEATHER_TYPE GetWeatherCalc() const; byte GetLightCalc( bool fQuickSet ) const; @@ -45,9 +46,8 @@ class CSector : public CScriptObj, public CSectorBase, public CTimedObject // sq CSector(); ~CSector(); -private: - CSector(const CSector& copy); - CSector& operator=(const CSector& other); + CSector(const CSector& copy) = delete; + CSector& operator=(const CSector& other) = delete; public: virtual void Init(int index, uchar map, short x, short y) override; @@ -134,7 +134,7 @@ public: virtual bool IsDeleted() const override; void Restock(); void RespawnDeadNPCs(); - void Close(); + void Close(bool fClosingWorld); virtual lpctstr GetName() const override { return "Sector"; } }; diff --git a/src/game/CSectorEnviron.h b/src/game/CSectorEnviron.h index a0cb4bccf..b4e90bb48 100644 --- a/src/game/CSectorEnviron.h +++ b/src/game/CSectorEnviron.h @@ -13,7 +13,7 @@ struct CSectorEnviron // When these change it is an CTRIG_EnvironChange, { #define LIGHT_OVERRIDE 0x80 // are we overriding the sector's light? (character LIGHT property doesn't count as overriding the sector's light) public: - byte m_Light; // the calculated light level in this area. |0x80 = override. + byte m_Light; // the calculated light level in this area. |0x80 = override. SEASON_TYPE m_Season; // What is the season for this sector. WEATHER_TYPE m_Weather; // the weather in this area now. public: diff --git a/src/game/CSectorList.cpp b/src/game/CSectorList.cpp index c3bdba135..1f97e80bd 100644 --- a/src/game/CSectorList.cpp +++ b/src/game/CSectorList.cpp @@ -11,7 +11,7 @@ CSectorList::CSectorList() : _SectorData{} CSectorList::~CSectorList() { - Close(); + Close(true); } const CSectorList* CSectorList::Get() noexcept // static @@ -28,7 +28,7 @@ void CSectorList::Init() // Initialize sector data for every map plane TemporaryString ts; TemporaryString tsConcat; - + for (int iMap = 0; iMap < MAP_SUPPORTED_QTY; ++iMap) { /* @@ -40,7 +40,7 @@ void CSectorList::Init() MapSectorsData& sd = _SectorData[iMap]; sd._iSectorSize = sd._iSectorColumns = sd._iSectorRows = sd._iSectorQty = 0; - sd._pSectors.release(); + sd._pSectors.reset(); if (!g_MapList.IsMapSupported(iMap)) continue; @@ -70,7 +70,7 @@ void CSectorList::Init() ASSERT(pSector); pSector->Init(iSectorIndex, (uchar)iMap, iSectorX, iSectorY); } - + } for (MapSectorsData& sd : _SectorData) @@ -88,7 +88,7 @@ void CSectorList::Init() g_Log.Event(LOGM_INIT, "Allocated map sectors:%s\n", tsConcat.buffer()); } -void CSectorList::Close() +void CSectorList::Close(bool fClosingWorld) { ADDTOCALLSTACK("CSectorList::Close"); @@ -100,7 +100,7 @@ void CSectorList::Close() // Delete everything in sector for (int iSector = 0; iSector < sd._iSectorQty; ++iSector) { - sd._pSectors[iSector].Close(); + sd._pSectors[iSector].Close(fClosingWorld); } } _fInitialized = false; diff --git a/src/game/CSectorList.h b/src/game/CSectorList.h index e4cf0684e..840e3c190 100644 --- a/src/game/CSectorList.h +++ b/src/game/CSectorList.h @@ -35,7 +35,7 @@ class CSectorList public: CSectorList(); ~CSectorList(); - + CSectorList(const CSectorList& copy) = delete; CSectorList& operator=(const CSectorList& other) = delete; @@ -43,7 +43,7 @@ class CSectorList static const CSectorList* Get() noexcept; void Init(); - void Close(); + void Close(bool fClosingWorld); int GetSectorSize(int map) const noexcept; int GetSectorQty(int map) const noexcept; diff --git a/src/game/CSectorTemplate.cpp b/src/game/CSectorTemplate.cpp index 9932e2f3b..5151dc1e0 100644 --- a/src/game/CSectorTemplate.cpp +++ b/src/game/CSectorTemplate.cpp @@ -2,6 +2,7 @@ #include "../common/CException.h" #include "../common/CLog.h" #include "../common/CRect.h" +#include "../game/CWorld.h" #include "../sphere/ProfileTask.h" #include "chars/CChar.h" #include "items/CItemShip.h" @@ -34,6 +35,7 @@ void CCharsActiveList::OnRemoveObj(CSObjContRec* pObjRec ) { ADDTOCALLSTACK("CCharsActiveList::OnRemoveObj"); ASSERT(pObjRec); + DEBUG_ASSERT(nullptr != dynamic_cast(pObjRec)); // Override this = called when removed from group. CSObjCont::OnRemoveObj(pObjRec); @@ -76,6 +78,7 @@ bool CItemsList::sm_fNotAMove = false; void CItemsList::OnRemoveObj(CSObjContRec* pObjRec) { ADDTOCALLSTACK("CItemsList::OnRemoveObj"); + DEBUG_ASSERT(nullptr != dynamic_cast(pObjRec)); // Item is picked up off the ground. (may be put right back down though) CItem * pItem = static_cast(pObjRec); @@ -85,9 +88,9 @@ void CItemsList::OnRemoveObj(CSObjContRec* pObjRec) pItem->OnMoveFrom(); // IT_MULTI, IT_SHIP and IT_COMM_CRYSTAL } - //ASSERT(pObjRec->GetParent() == this); + DEBUG_ASSERT(pObjRec->GetParent() == this); CSObjCont::OnRemoveObj(pObjRec); - //ASSERT(pObjRec->GetParent() == nullptr); + DEBUG_ASSERT(pObjRec->GetParent() == nullptr); pItem->SetUIDContainerFlags(UID_O_DISCONNECT); // It is no place for the moment. } @@ -98,13 +101,28 @@ void CItemsList::AddItemToSector( CItem * pItem ) // Add to top level. // Either MoveTo() or SetTimeout is being called. ASSERT( pItem ); + //DEBUG_ASSERT(nullptr != dynamic_cast(pItem)); CSObjCont* pParent = pItem->GetParent(); if (pParent != this) { - //ASSERT((pItem->GetParent() == nullptr) || (pItem->GetParent() == &g_World.m_ObjNew)); + DEBUG_ASSERT( + (pParent == nullptr) || + (pParent == g_World.GetObjectsNew()) || + (nullptr != dynamic_cast(pParent)) + ); + CSObjCont::InsertContentTail(pItem); // this also removes the Char from the old sector - //ASSERT(pItem->GetParent() == this); + + DEBUG_ASSERT(pItem->GetParent() == this); +#ifdef _DEBUG + const CSObjCont* pObjNew = g_World.GetObjectsNew(); + auto itNew = std::find(pObjNew->cbegin(), pObjNew->cend(), pItem); + DEBUG_ASSERT(itNew == pObjNew->cend()); + + auto itOldParent = std::find(pParent->cbegin(), pParent->cend(), pItem); + DEBUG_ASSERT(itOldParent == pParent->cend()); +#endif } pItem->RemoveUIDFlags(UID_O_DISCONNECT); @@ -221,7 +239,7 @@ bool CSectorBase::IsInDungeon() const CRegion * CSectorBase::GetRegion( const CPointBase & pt, dword dwType ) const { - ADDTOCALLSTACK_INTENSIVE("CSectorBase::GetRegion"); + ADDTOCALLSTACK_DEBUG("CSectorBase::GetRegion"); // Does it match the mask of types we care about ? // Assume sorted so that the smallest are first. // @@ -271,7 +289,7 @@ CRegion * CSectorBase::GetRegion( const CPointBase & pt, dword dwType ) const // Balkon: get regions list (to cycle through intercepted house regions) size_t CSectorBase::GetRegions( const CPointBase & pt, dword dwType, CRegionLinks *pRLinks ) const { - //ADDTOCALLSTACK_INTENSIVE("CSectorBase::GetRegions"); // Called very frequently + //ADDTOCALLSTACK_DEBUG("CSectorBase::GetRegions"); // Called very frequently size_t iQty = m_RegionLinks.size(); for ( size_t i = 0; i < iQty; ++i ) { @@ -306,7 +324,7 @@ size_t CSectorBase::GetRegions( const CPointBase & pt, dword dwType, CRegionLink continue; if ( ! pRegion->IsInside2d( pt )) continue; - pRLinks->push_back(pRegion); + pRLinks->emplace_back(pRegion); } return pRLinks->size(); } @@ -340,7 +358,7 @@ bool CSectorBase::LinkRegion( CRegion * pRegionNew ) ASSERT(pRegion); if ( pRegionNew == pRegion ) { - DEBUG_ERR(( "region already linked!\n" )); + DEBUG_ERR(( "Region already linked!\n" )); return false; } @@ -412,11 +430,11 @@ bool CSectorBase::IsFlagSet( dword dwFlag ) const noexcept CPointMap CSectorBase::GetBasePoint() const { - // ADDTOCALLSTACK_INTENSIVE("CSectorBase::GetBasePoint"); // It's commented because it's slow and this method is called VERY often! + // ADDTOCALLSTACK_DEBUG("CSectorBase::GetBasePoint"); // It's commented because it's slow and this method is called VERY often! // What is the coord base of this sector. upper left point. const CSectorList* pSectors = CSectorList::Get(); #if _DEBUG - ASSERT( (m_index >= 0) && (m_index < pSectors->GetSectorQty(m_map)) ); + DEBUG_ASSERT( (m_index >= 0) && (m_index < pSectors->GetSectorQty(m_map)) ); // Again this method is called very often, so call the least functions possible and do the minimum amount of checks required #endif const int iCols = pSectors->GetSectorCols(m_map); @@ -434,7 +452,7 @@ CPointMap CSectorBase::GetBasePoint() const CRectMap CSectorBase::GetRect() const noexcept { - //ADDTOCALLSTACK_INTENSIVE("CSectorBase::GetRect"); // It's commented because it's slow and this method is called VERY often! + //ADDTOCALLSTACK_DEBUG("CSectorBase::GetRect"); // It's commented because it's slow and this method is called VERY often! // Get a rectangle for the sector. const CPointMap& pt = GetBasePoint(); const int iSectorSize = CSectorList::Get()->GetSectorSize(pt.m_map); diff --git a/src/game/CSectorTemplate.h b/src/game/CSectorTemplate.h index fb2243776..cae6dfa38 100644 --- a/src/game/CSectorTemplate.h +++ b/src/game/CSectorTemplate.h @@ -16,7 +16,12 @@ class CItem; class CSector; class CTeleport; -struct CCharsDisconnectList : public CSObjCont +struct CSectorObjCont +{ + // Marker class. +}; + +struct CCharsDisconnectList : public CSObjCont, public CSectorObjCont { CCharsDisconnectList() = default; CCharsDisconnectList(const CCharsDisconnectList& copy) = delete; @@ -25,12 +30,12 @@ struct CCharsDisconnectList : public CSObjCont void AddCharDisconnected(CChar* pChar); }; -struct CCharsActiveList : public CSObjCont +struct CCharsActiveList : public CSObjCont, public CSectorObjCont { private: int m_iClients; // How many clients in this sector now? int64 m_iTimeLastClient; // age the sector based on last client here. - + protected: void OnRemoveObj(CSObjContRec* pObjRec); // Override this = called when removed from list. @@ -51,7 +56,7 @@ struct CCharsActiveList : public CSObjCont } }; -struct CItemsList : public CSObjCont +struct CItemsList : public CSObjCont, public CSectorObjCont { static bool sm_fNotAMove; // hack flag to prevent items from bouncing around too much. diff --git a/src/game/CServer.cpp b/src/game/CServer.cpp index b10090bbc..d93589ce9 100644 --- a/src/game/CServer.cpp +++ b/src/game/CServer.cpp @@ -78,13 +78,17 @@ void CServer::SetSignals( bool fMsg ) #ifndef _WIN32 SetUnixSignals(g_Cfg.m_fSecure); + LOG_TYPE lt = LOGM_INIT; + if (!IsLoading()) { + lt = (LOG_TYPE)((uint)lt | (uint)LOGL_EVENT); + } if ( g_Cfg.m_fSecure ) { - g_Log.Event( (IsLoading() ? 0 : LOGL_EVENT) | LOGM_INIT, "Signal handlers installed.\n" ); + g_Log.Event( lt, "Signal handlers installed.\n" ); } else { - g_Log.Event( (IsLoading() ? 0 : LOGL_EVENT) | LOGM_INIT, "Signal handlers UNinstalled.\n" ); + g_Log.Event( lt, "Signal handlers UNinstalled.\n" ); } #endif @@ -131,11 +135,6 @@ bool CServer::SetProcessPriority(int iPriorityLevel) return fSuccess; } -SERVMODE_TYPE CServer::GetServerMode() const -{ - return m_iModeCode.load(std::memory_order_acquire); -} - void CServer::SetServerMode( SERVMODE_TYPE mode ) { ADDTOCALLSTACK("CServer::SetServerMode"); @@ -165,7 +164,7 @@ bool CServer::IsValidBusy() const return false; } -int CServer::GetExitFlag() const +int CServer::GetExitFlag() const noexcept { return m_iExitFlag.load(std::memory_order_acquire); } @@ -178,12 +177,12 @@ void CServer::SetExitFlag(int iFlag) m_iExitFlag.store(iFlag, std::memory_order_release); } -bool CServer::IsLoading() const +bool CServer::IsLoading() const noexcept { return ( m_fResyncPause || (GetServerMode() > SERVMODE_Run) ); } -bool CServer::IsResyncing() const +bool CServer::IsResyncing() const noexcept { return m_fResyncPause || (GetServerMode() == SERVMODE_ResyncLoad); } @@ -1960,6 +1959,7 @@ extern void defragSphere(char *); bool CServer::CommandLine( int argc, tchar * argv[] ) { + ADDTOCALLSTACK("CServer::CommandLine"); // Console Command line. // This runs after script file enum but before loading the world file. // RETURN: @@ -2286,6 +2286,7 @@ void CServer::_OnTick() bool CServer::Load() { EXC_TRY("Load"); + ADDTOCALLSTACK("CServer::Load"); EXC_SET_BLOCK("print sphere infos"); g_Log.Event(LOGM_INIT, "%s.\n", g_sServerDescription.c_str()); diff --git a/src/game/CServer.h b/src/game/CServer.h index 9bac4dc8a..608df7a48 100644 --- a/src/game/CServer.h +++ b/src/game/CServer.h @@ -79,13 +79,16 @@ extern class CServer : public CServerDef, public CTextConsole CServer& operator=(const CServer& other) = delete; public: - SERVMODE_TYPE GetServerMode() const; + inline SERVMODE_TYPE GetServerMode() const noexcept { + return m_iModeCode.load(std::memory_order_acquire); + } + void SetServerMode( SERVMODE_TYPE mode ); bool IsValidBusy() const; - int GetExitFlag() const; + int GetExitFlag() const noexcept; void SetExitFlag(int iFlag); - bool IsLoading() const; - bool IsResyncing() const; + bool IsLoading() const noexcept; + bool IsResyncing() const noexcept; void Shutdown( int64 iMinutes ); void SetSignals( bool fMsg = true ); bool SetProcessPriority(int iPriorityLevel); diff --git a/src/game/CServerConfig.cpp b/src/game/CServerConfig.cpp index 707a165ca..56c6df51d 100644 --- a/src/game/CServerConfig.cpp +++ b/src/game/CServerConfig.cpp @@ -729,7 +729,7 @@ enum RC_TYPE const CAssocReg CServerConfig::sm_szLoadKeys[RC_QTY + 1] -{ +{ { "ACCTFILES", { ELEM_CSTRING, static_castOFFSETOF(CServerConfig,m_sAcctBaseDir) }}, { "ADVANCEDLOS", { ELEM_INT, static_castOFFSETOF(CServerConfig,m_iAdvancedLos) }}, { "AGREE", { ELEM_BOOL, static_castOFFSETOF(CServerConfig,m_fAgree) }}, @@ -818,9 +818,9 @@ const CAssocReg CServerConfig::sm_szLoadKeys[RC_QTY + 1] { "DUNGEONLIGHT", { ELEM_INT, static_castOFFSETOF(CServerConfig,m_iLightDungeon) }}, { "EMOTEFLAGS", { ELEM_MASK_INT,static_castOFFSETOF(CServerConfig,m_iEmoteFlags) }}, { "EQUIPPEDCAST", { ELEM_BOOL, static_castOFFSETOF(CServerConfig,m_fEquippedCast) }}, - { "ERALIMITGEAR", { ELEM_INT, static_castOFFSETOF(CServerConfig,_iEraLimitGear) }}, - { "ERALIMITLOOT", { ELEM_INT, static_castOFFSETOF(CServerConfig,_iEraLimitLoot) }}, - { "ERALIMITPROPS", { ELEM_INT, static_castOFFSETOF(CServerConfig,_iEraLimitProps) }}, + { "ERALIMITGEAR", { ELEM_BYTE, static_castOFFSETOF(CServerConfig,_iEraLimitGear) }}, + { "ERALIMITLOOT", { ELEM_BYTE, static_castOFFSETOF(CServerConfig,_iEraLimitLoot) }}, + { "ERALIMITPROPS", { ELEM_BYTE, static_castOFFSETOF(CServerConfig,_iEraLimitProps) }}, { "EVENTSITEM", { ELEM_CSTRING, static_castOFFSETOF(CServerConfig,m_sEventsItem) }}, { "EVENTSPET", { ELEM_CSTRING, static_castOFFSETOF(CServerConfig,m_sEventsPet) }}, { "EVENTSPLAYER", { ELEM_CSTRING, static_castOFFSETOF(CServerConfig,m_sEventsPlayer) }}, @@ -2329,13 +2329,13 @@ static constexpr lpctstr _ptcStatName[STAT_QTY] = // not alphabetically sorted o STAT_TYPE CServerConfig::GetStatKey( lpctstr ptcKey ) // static { - //ADDTOCALLSTACK_INTENSIVE("CServerConfig::GetStatKey"); + //ADDTOCALLSTACK_DEBUG("CServerConfig::GetStatKey"); return (STAT_TYPE) FindTable( ptcKey, _ptcStatName, ARRAY_COUNT(_ptcStatName)); } lpctstr CServerConfig::GetStatName(STAT_TYPE iKey) // static { - //ADDTOCALLSTACK_INTENSIVE("CServerConfig::GetStatName"); + //ADDTOCALLSTACK_DEBUG("CServerConfig::GetStatName"); ASSERT(iKey >= STAT_STR && iKey < STAT_QTY); return _ptcStatName[iKey]; } @@ -3975,7 +3975,7 @@ CResourceID CServerConfig::ResourceGetNewID( RES_TYPE restype, lpctstr pszName, { // For a book the page is... the page number // For a REGIONTYPE block, the page (pArg2) is the landtile type associated with the REGIONTYPE - int iArgPage = RES_GET_INDEX(Exp_GetVal(pArg2)); + int iArgPage = ResGetIndex(Exp_GetDWVal(pArg2)); if ( iArgPage < RES_PAGE_MAX ) wPage = (word)iArgPage; else @@ -4651,11 +4651,11 @@ bool CServerConfig::Load( bool fResync ) { if ( ! OpenResourceFind( m_scpTables, SPHERE_FILE "tables" SPHERE_SCRIPT )) { - g_Log.Event( LOGL_FATAL|LOGM_INIT, "Error opening table definitions file (" SPHERE_FILE "tables." SPHERE_SCRIPT ")...\n" ); + g_Log.Event( LOGL_FATAL|LOGM_INIT, "Error opening table definitions file (" SPHERE_FILE "tables" SPHERE_SCRIPT ")...\n" ); return false; } - g_Log.Event(LOGL_EVENT|LOGM_INIT, "Loading table definitions file (" SPHERE_FILE "tables." SPHERE_SCRIPT ")...\n"); + g_Log.Event(LOGL_EVENT|LOGM_INIT, "Loading table definitions file (" SPHERE_FILE "tables" SPHERE_SCRIPT ")...\n"); LoadResourcesOpen(&m_scpTables); m_scpTables.Close(); } diff --git a/src/game/CServerTime.cpp b/src/game/CServerTime.cpp index 338933bac..2378698fd 100644 --- a/src/game/CServerTime.cpp +++ b/src/game/CServerTime.cpp @@ -1,25 +1,6 @@ #include "CServerConfig.h" #include "CServerTime.h" -int64 CServerTime::GetTimeRaw() const noexcept -{ - return (m_llPrivateTime < 0) ? 0 : m_llPrivateTime; -} - -void CServerTime::Init() noexcept -{ - m_llPrivateTime = 0; -} - -void CServerTime::InitTime( int64 llTimeBase ) noexcept -{ - m_llPrivateTime = (llTimeBase < 0) ? 0 : llTimeBase; -} - -bool CServerTime::IsTimeValid() const noexcept -{ - return bool(m_llPrivateTime > 0); -} CServerTime CServerTime::operator+( int64 llTimeDiff ) const noexcept { diff --git a/src/game/CServerTime.h b/src/game/CServerTime.h index 7d5c4f744..e503d7f0f 100644 --- a/src/game/CServerTime.h +++ b/src/game/CServerTime.h @@ -27,14 +27,14 @@ struct CServerTime inline CServerTime() noexcept; inline CServerTime(int64 iTimeInMilliseconds) noexcept; - void Init() noexcept; - void InitTime(int64 iTimeBase) noexcept; - bool IsTimeValid() const noexcept; + inline void Init() noexcept; + inline void InitTime(int64 iTimeBase) noexcept; + inline bool IsTimeValid() const noexcept; /* * @brief Get the time value in milliseconds. */ - int64 GetTimeRaw() const noexcept; + inline int64 GetTimeRaw() const noexcept; CServerTime operator+(int64 iTimeDiff) const noexcept; CServerTime operator-(int64 iTimeDiff) const noexcept; @@ -62,6 +62,27 @@ CServerTime::CServerTime(int64 iTimeInMilliseconds) noexcept InitTime(iTimeInMilliseconds); } +int64 CServerTime::GetTimeRaw() const noexcept +{ + return (m_llPrivateTime < 0) ? 0 : m_llPrivateTime; +} + +void CServerTime::Init() noexcept +{ + m_llPrivateTime = 0; +} + +void CServerTime::InitTime(int64 llTimeBase) noexcept +{ + m_llPrivateTime = (llTimeBase < 0) ? 0 : llTimeBase; +} + + +bool CServerTime::IsTimeValid() const noexcept +{ + return bool(m_llPrivateTime > 0); +} + int64 CServerTime::GetTimeDiff(const CServerTime & time) const noexcept { return ( m_llPrivateTime - time.m_llPrivateTime ); diff --git a/src/game/CTimedFunction.cpp b/src/game/CTimedFunction.cpp index ab0daed8b..1e803fbca 100644 --- a/src/game/CTimedFunction.cpp +++ b/src/game/CTimedFunction.cpp @@ -74,11 +74,11 @@ bool CTimedFunction::_OnTick() // virtual CUID uid(_uidAttached); CScript s(_ptcCommand); - + delete this; // This has to be the last function call to ever access this object! // From now on, this object does NOT exist anymore! - return _ExecTimedFunction(std::move(uid), std::move(s)); + return _ExecTimedFunction(std::move(uid), std::move(s)); } bool CTimedFunction::OnTick() // virtual @@ -88,7 +88,7 @@ bool CTimedFunction::OnTick() // virtual CUID uid; CScript s; { - THREAD_SHARED_LOCK_SET; + MT_ENGINE_SHARED_LOCK_SET; uid.SetPrivateUID(_uidAttached); s.ParseKey(_ptcCommand); } @@ -97,4 +97,4 @@ bool CTimedFunction::OnTick() // virtual // From now on, this object does NOT exist anymore! return _ExecTimedFunction(std::move(uid), std::move(s)); -} \ No newline at end of file +} diff --git a/src/game/CTimedFunctionHandler.cpp b/src/game/CTimedFunctionHandler.cpp index a0a77d574..02984615f 100644 --- a/src/game/CTimedFunctionHandler.cpp +++ b/src/game/CTimedFunctionHandler.cpp @@ -61,7 +61,7 @@ void CTimedFunctionHandler::Clear() { ADDTOCALLSTACK("CTimedFunctionHandler::Clear"); - _timedFunctions.ClearContainer(); + _timedFunctions.ClearContainer(false); } TRIGRET_TYPE CTimedFunctionHandler::Loop(lpctstr ptcCommand, int iLoopsMade, CScriptLineContext StartContext, diff --git a/src/game/CTimedObject.cpp b/src/game/CTimedObject.cpp index 3c635112b..ae4a6b771 100644 --- a/src/game/CTimedObject.cpp +++ b/src/game/CTimedObject.cpp @@ -41,26 +41,26 @@ void CTimedObject::_GoAwake() bool CTimedObject::_CanTick(bool fParentGoingToSleep) const { - //ADDTOCALLSTACK_INTENSIVE("CTimedObject::_CanTick"); + //ADDTOCALLSTACK_DEBUG("CTimedObject::_CanTick"); UnreferencedParameter(fParentGoingToSleep); return !_IsSleeping(); } bool CTimedObject::CanTick(bool fParentGoingToSleep) const { - //ADDTOCALLSTACK_INTENSIVE("CTimedObject::CanTick"); - THREAD_SHARED_LOCK_RETURN(_CanTick(fParentGoingToSleep)); + //ADDTOCALLSTACK_DEBUG("CTimedObject::CanTick"); + MT_ENGINE_SHARED_LOCK_RETURN(_CanTick(fParentGoingToSleep)); } bool CTimedObject::OnTick() { ADDTOCALLSTACK("CTimedObject::OnTick"); - THREAD_UNIQUE_LOCK_RETURN(_OnTick()); + MT_ENGINE_UNIQUE_LOCK_RETURN(_OnTick()); } void CTimedObject::_SetTimeout(int64 iDelayInMsecs) { - ADDTOCALLSTACK("CTimedObject::_SetTimeout"); + ADDTOCALLSTACK_DEBUG("CTimedObject::_SetTimeout"); // Assume we have the mutex already locked here const ProfileTask timersTask(PROFILE_TIMERS); // profile the settimeout proccess. @@ -93,8 +93,8 @@ void CTimedObject::_SetTimeout(int64 iDelayInMsecs) void CTimedObject::SetTimeout(int64 iDelayInMsecs) { - ADDTOCALLSTACK("CTimedObject::SetTimeout"); - THREAD_UNIQUE_LOCK_SET; + ADDTOCALLSTACK_DEBUG("CTimedObject::SetTimeout"); + MT_ENGINE_UNIQUE_LOCK_SET; _SetTimeout(iDelayInMsecs); } @@ -137,7 +137,7 @@ int64 CTimedObject::_GetTimerAdjusted() const noexcept } int64 CTimedObject::GetTimerAdjusted() const noexcept { - THREAD_SHARED_LOCK_RETURN(_GetTimerAdjusted()); + MT_ENGINE_SHARED_LOCK_RETURN(_GetTimerAdjusted()); } int64 CTimedObject::_GetTimerDAdjusted() const noexcept @@ -154,7 +154,7 @@ int64 CTimedObject::_GetTimerDAdjusted() const noexcept } int64 CTimedObject::GetTimerDAdjusted() const noexcept { - THREAD_SHARED_LOCK_RETURN(_GetTimerDAdjusted()); + MT_ENGINE_SHARED_LOCK_RETURN(_GetTimerDAdjusted()); } int64 CTimedObject::_GetTimerSAdjusted() const noexcept @@ -171,44 +171,44 @@ int64 CTimedObject::_GetTimerSAdjusted() const noexcept } int64 CTimedObject::GetTimerSAdjusted() const noexcept { - THREAD_SHARED_LOCK_RETURN(_GetTimerSAdjusted()); + MT_ENGINE_SHARED_LOCK_RETURN(_GetTimerSAdjusted()); } void CTimedObject::GoSleep() { - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; _GoSleep(); } void CTimedObject::GoAwake() { - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; _GoAwake(); // Call virtuals! } PROFILE_TYPE CTimedObject::GetProfileType() const noexcept { - THREAD_SHARED_LOCK_RETURN(CTimedObject::_GetProfileType()); + MT_ENGINE_SHARED_LOCK_RETURN(CTimedObject::_GetProfileType()); } void CTimedObject::ClearTimeout() noexcept { - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; CTimedObject::_ClearTimeout(); } bool CTimedObject::IsSleeping() const noexcept { - THREAD_SHARED_LOCK_RETURN(CTimedObject::_IsSleeping()); + MT_ENGINE_SHARED_LOCK_RETURN(CTimedObject::_IsSleeping()); } bool CTimedObject::IsTimerSet() const noexcept { - THREAD_SHARED_LOCK_RETURN(CTimedObject::_IsTimerSet()); + MT_ENGINE_SHARED_LOCK_RETURN(CTimedObject::_IsTimerSet()); } bool CTimedObject::IsTimerExpired() const noexcept { - THREAD_SHARED_LOCK_RETURN(CTimedObject::_IsTimerExpired()); + MT_ENGINE_SHARED_LOCK_RETURN(CTimedObject::_IsTimerExpired()); } diff --git a/src/game/CTimedObject.h b/src/game/CTimedObject.h index edda2406d..e0452468c 100644 --- a/src/game/CTimedObject.h +++ b/src/game/CTimedObject.h @@ -20,7 +20,7 @@ class CTimedObject static const char* m_sClassName; protected: - THREAD_CMUTEX_DEF; + MT_CMUTEX_DEF; private: int64 _iTimeout; @@ -129,7 +129,7 @@ public: bool IsTimerExpired() const noexcept; /** * @brief Gets timer (in milliseconds). * @return The adjusted timer. - + */ protected: int64 _GetTimerAdjusted() const noexcept; public: int64 GetTimerAdjusted() const noexcept; @@ -192,4 +192,4 @@ PROFILE_TYPE CTimedObject::_GetProfileType() const noexcept return _profileType; } -#endif //_INC_CTIMEDOBJECT_H \ No newline at end of file +#endif //_INC_CTIMEDOBJECT_H diff --git a/src/game/CWorld.cpp b/src/game/CWorld.cpp index 3503a6102..ed896a707 100644 --- a/src/game/CWorld.cpp +++ b/src/game/CWorld.cpp @@ -245,9 +245,26 @@ void CWorldThread::InitUIDs() void CWorldThread::CloseAllUIDs() { ADDTOCALLSTACK("CWorldThread::CloseAllUIDs"); - m_ObjDelete.ClearContainer(); // empty our list of unplaced objects (and delete the objects in the list) + + for (auto const& elem : m_ObjNew) + { + auto itObjDel = std::find(m_ObjDelete.begin(), m_ObjDelete.end(), elem); + if (itObjDel != m_ObjDelete.end()) { + // Avoid duplicates between the containers, for some reason it happens... + // This will just remove the pointer from the container, it doesn't call the destructor.' + // Not doing this might result in a double free. + m_ObjDelete.erase(itObjDel); + } + } + + // empty our list of unplaced objects (and delete the objects in the list) + m_ObjNew.ClearContainer(true); + + // empty our list of objects to delete (and delete the objects in the list) + m_ObjDelete.ClearContainer(true); + + // special internal objects m_ObjSpecialDelete.ClearContainer(); - m_ObjNew.ClearContainer(); // empty our list of objects to delete (and delete the objects in the list) if (_ppUIDObjArray != nullptr) { @@ -340,7 +357,7 @@ dword CWorldThread::AllocUID( dword dwIndex, CObjBase * pObj ) setcount: // We have run out of free UID's !!! Grow the array const size_t uiOldArraySize = _uiUIDObjArraySize; - _uiUIDObjArraySize = ((dwIndex + 0x1000) & ~0xFFF); + _uiUIDObjArraySize = size_t((dwIndex + 0x1000u) & ~0xFFFu); CObjBase** pNewBlock = (CObjBase**)realloc(_ppUIDObjArray, _uiUIDObjArraySize * sizeof(CObjBase*)); if (pNewBlock == nullptr) @@ -390,7 +407,17 @@ void CWorldThread::AddIdleObj(CSObjContRec* obj) void CWorldThread::ScheduleObjDeletion(CSObjContRec* obj) { - m_ObjDelete.InsertContentTail(obj); + const auto servMode = g_Serv.GetServerMode(); + const bool fDestroy = (servMode == SERVMODE_Exiting || servMode == SERVMODE_Loading); + // If the world is being destroyed, do not schedule the object for deletion but delete it right away. + + if (fDestroy) + { + obj->RemoveSelf(); + delete obj; + } + else + m_ObjDelete.InsertContentTail(obj); } void CWorldThread::ScheduleSpecialObjDeletion(CSObjListRec* obj) @@ -415,7 +442,7 @@ bool CWorldThread::IsScheduledSpecialObjDeletion(const CSObjListRec* obj) const int CWorldThread::FixObjTry( CObjBase * pObj, dword dwUID ) { - ADDTOCALLSTACK_INTENSIVE("CWorldThread::FixObjTry"); + ADDTOCALLSTACK_DEBUG("CWorldThread::FixObjTry"); // RETURN: 0 = success. if ( !pObj ) return 0x7102; @@ -515,15 +542,23 @@ void CWorldThread::GarbageCollection_NewObjs() for (size_t i = 0; i < iObjCount; ++i) { + auto itObjDel = std::find(m_ObjDelete.begin(), m_ObjDelete.end(), m_ObjNew.data()[i]); + if (itObjDel != m_ObjDelete.end()) { + // This will just remove the pointer from the container, it doesn't call the destructor.' + // Not doing this might result in a double free. + m_ObjDelete.erase(itObjDel); + } + CObjBase * pObj = dynamic_cast(m_ObjNew.GetContentIndex(i)); if (pObj == nullptr) continue; ReportGarbageCollection(pObj, 0x3202); } - m_ObjNew.ClearContainer(); // empty our list of unplaced objects (and delete the objects in the list) + + m_ObjNew.ClearContainer(false); // empty our list of unplaced objects (and delete the objects in the list) } - m_ObjDelete.ClearContainer(); // empty our list of objects to delete (and delete the objects in the list) + m_ObjDelete.ClearContainer(false); // empty our list of objects to delete (and delete the objects in the list) m_ObjSpecialDelete.ClearContainer(); @@ -808,7 +843,7 @@ bool CWorld::SaveStage() // Save world state in stages. for ( size_t i = 0; i < iQty; ++i ) { CRegion *pRegion = dynamic_cast (g_Cfg.m_RegionDefs[i]); - if ( !pRegion || !pRegion->HasResourceName() || !pRegion->m_iModified ) + if ( !pRegion || !pRegion->HasResourceName() || !pRegion->m_dwModifiedFlags ) continue; m_FileData.WriteSection("WORLDSCRIPT %s", pRegion->GetResourceName()); @@ -932,7 +967,7 @@ bool CWorld::SaveForce() // Save world state pCurBlock = save_msgs[5]; fSave = SaveStage(); - if ( !(_iSaveStage & 0x1FF) ) + if ( !(_iSaveStage & 0x7F) ) { g_Serv.PrintPercent( _iSaveStage, (ssize_t)iSectorsQty + 3 ); } @@ -1072,7 +1107,7 @@ bool CWorld::CheckAvailableSpaceForSave(bool fStatics) uiPreviousSaveSize += uiCurSavefileSize; } else - fSizeErr = true; + fSizeErr = true; }; if (fStatics) @@ -1278,7 +1313,8 @@ bool CWorld::LoadFile( lpctstr pszLoadName, bool fError ) // Load world from scr while ( s.FindNextSection() ) { - if (! ( ++iLoadStage & 0x1FF )) // don't update too often + // Print the percent state of the current file loading. + if (! ( ++iLoadStage & 0x7F )) // don't update too often g_Serv.PrintPercent( s.GetPosition(), iLoadSize ); try @@ -1358,7 +1394,7 @@ bool CWorld::LoadWorld() // Load world from script m_Parties.clear(); m_GMPages.clear(); - _Sectors.Close(); + _Sectors.Close(true); CloseAllUIDs(); _GameClock.Init(); @@ -1414,7 +1450,7 @@ bool CWorld::LoadAll() // Load world from script // Set all the sector light levels now that we know the time. // This should not look like part of the load. (CTRIG_EnvironChange triggers should run) - + for (int m = 0; m < MAP_SUPPORTED_QTY; ++m) { if (!g_MapList.IsMapSupported(m)) @@ -1674,7 +1710,9 @@ void CWorld::Close() m_Multis.clear(); { - std::unique_lock lock_su(_Ticker._ObjStatusUpdates.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock_su(_Ticker._ObjStatusUpdates.MT_CMUTEX); +#endif _Ticker._ObjStatusUpdates.clear(); } @@ -1690,7 +1728,7 @@ void CWorld::Close() pClient->CharDisconnect(); } - _Sectors.Close(); + _Sectors.Close(true); g_MapList.m_mapGeoData.clear(); if ( g_MapList.m_pMapDiffCollection != nullptr ) @@ -1730,7 +1768,7 @@ void CWorld::_OnTick() _Ticker.Tick(); EXC_SET_BLOCK("Delete objects"); - m_ObjDelete.ClearContainer(); // clean up our delete list (this DOES delete the objects, thanks to the virtual destructors). + m_ObjDelete.ClearContainer(false); // clean up our delete list (this DOES delete the objects, thanks to the virtual destructors). m_ObjSpecialDelete.ClearContainer(); int64 iCurTime = _GameClock.GetCurrentTime().GetTimeRaw(); diff --git a/src/game/CWorld.h b/src/game/CWorld.h index 7f06e945d..ab0544c29 100644 --- a/src/game/CWorld.h +++ b/src/game/CWorld.h @@ -92,6 +92,19 @@ class CWorldThread NODISCARD bool IsScheduledObjDeletion(const CSObjContRec* obj) const noexcept; NODISCARD bool IsScheduledSpecialObjDeletion(const CSObjListRec* obj) const noexcept; + + NODISCARD inline const CSObjCont* GetObjectsNew() noexcept { + return &m_ObjNew; + } + /* + NODISCARD inline const CSObjCont* GetObjectsDeleting() noexcept { + return &m_ObjDelete; + } + NODISCARD inline const CSObjList* GetObjectsSpecialDeleting() noexcept { + return &m_ObjSpecialDelete; + } + */ + int FixObjTry( CObjBase * pObj, dword dwUID = 0 ); int FixObj( CObjBase * pObj, dword dwUID = 0 ); diff --git a/src/game/CWorldCache.cpp b/src/game/CWorldCache.cpp index aa860e30b..a5f3459b4 100644 --- a/src/game/CWorldCache.cpp +++ b/src/game/CWorldCache.cpp @@ -52,9 +52,9 @@ void CWorldCache::CheckMapBlockCache(int64 iCurTime, int64 iCacheTime) continue; if (iCacheTime <= 0 || (block->m_CacheTime.GetCacheAge() >= iCacheTime)) - block.release(); + block.reset(); } } _iTimeLastMapBlockCacheCheck = iCurTime + iCacheTime; -} \ No newline at end of file +} diff --git a/src/game/CWorldCache.h b/src/game/CWorldCache.h index c39ee542c..6d3f3e384 100644 --- a/src/game/CWorldCache.h +++ b/src/game/CWorldCache.h @@ -14,9 +14,9 @@ class CWorldCache int64 _iTimeLastMapBlockCacheCheck; - using MapBlockCacheCont = std::unique_ptr; - using MapBlockCache = std::unique_ptr; - MapBlockCache _mapBlocks[MAP_SUPPORTED_QTY]; + using MapBlockCacheCont = std::unique_ptr; // Info about a single map block + using MapBlockCache = std::unique_ptr; // An element per each map block + MapBlockCache _mapBlocks[MAP_SUPPORTED_QTY]; // An element per each map public: static const char* m_sClassName; @@ -28,4 +28,4 @@ class CWorldCache void CheckMapBlockCache(int64 iCurTime, int64 iCacheTime); }; -#endif // _INC_CWORLDCACHE_H \ No newline at end of file +#endif // _INC_CWORLDCACHE_H diff --git a/src/game/CWorldGameTime.cpp b/src/game/CWorldGameTime.cpp index 64ef5461b..caf99086f 100644 --- a/src/game/CWorldGameTime.cpp +++ b/src/game/CWorldGameTime.cpp @@ -10,7 +10,7 @@ const CServerTime& CWorldGameTime::GetCurrentTime() noexcept // static { - //ADDTOCALLSTACK_INTENSIVE("CWorldGameTime::GetCurrentTime"); + //ADDTOCALLSTACK_DEBUG("CWorldGameTime::GetCurrentTime"); return g_World._GameClock.GetCurrentTime(); // Time in milliseconds } diff --git a/src/game/CWorldImport.cpp b/src/game/CWorldImport.cpp index 557b53e81..0c8ca381f 100644 --- a/src/game/CWorldImport.cpp +++ b/src/game/CWorldImport.cpp @@ -3,7 +3,7 @@ #include "items/CItem.h" #include "CObjBase.h" #include "CWorld.h" -#include "CWorldMap.h" +#include "CWorldSearch.h" struct CImportSer : public CSObjListRec { @@ -17,23 +17,22 @@ struct CImportSer : public CSObjListRec LAYER_TYPE m_layer; // UOX does this diff than us. so store this here. public: - bool IsTopLevel() const + bool IsTopLevel() const noexcept { return( m_dwContSer == UID_UNUSED ); } - CImportSer( dword dwSer ) : + CImportSer( dword dwSer ) noexcept : m_dwSer( dwSer ) { m_pObj = nullptr; m_dwContSer = UID_UNUSED; m_layer = LAYER_NONE; } - ~CImportSer() = default; + ~CImportSer() noexcept = default; -private: - CImportSer(const CImportSer& copy); - CImportSer& operator=(const CImportSer& other); + CImportSer(const CImportSer& copy) = delete; + CImportSer& operator=(const CImportSer& other) = delete; }; struct CImportFile @@ -62,9 +61,8 @@ struct CImportFile m_pszArg2 = nullptr; } -private: - CImportFile(const CImportFile& copy); - CImportFile& operator=(const CImportFile& other); + CImportFile(const CImportFile& copy) = delete; + CImportFile& operator=(const CImportFile& other) = delete; public: void CheckLast(); @@ -138,10 +136,11 @@ void CImportFile::ImportFix() CItem * pItemCheck = dynamic_cast ( m_pCurSer->m_pObj ); ASSERT(pItemCheck); pItemCheck->SetAttr(ATTR_MOVE_NEVER); - CWorldSearch AreaItems( m_pCurSer->m_pObj->GetTopPoint()); + + auto AreaItems = CWorldSearchHolder::GetInstance(m_pCurSer->m_pObj->GetTopPoint()); for (;;) { - CItem * pItem = AreaItems.GetItem(); + CItem * pItem = AreaItems->GetItem(); if ( pItem == nullptr ) break; if ( ! pItem->IsSameType( m_pCurSer->m_pObj )) @@ -763,11 +762,11 @@ bool CWorld::Export( lpctstr pszFilename, const CChar * pSrc, word wModeFlags, i { // Export as UOX format. for world forge stuff. int index = 0; - CWorldSearch AreaItems( pSrc->GetTopPoint(), iDist ); - AreaItems.SetSearchSquare(true); + auto AreaItems = CWorldSearchHolder::GetInstance( pSrc->GetTopPoint(), iDist ); + AreaItems->SetSearchSquare(true); for (;;) { - CItem * pItem = AreaItems.GetItem(); + CItem * pItem = AreaItems->GetItem(); if ( pItem == nullptr ) break; pItem->WriteUOX( s, index++ ); @@ -778,12 +777,12 @@ bool CWorld::Export( lpctstr pszFilename, const CChar * pSrc, word wModeFlags, i // (???NPC) Chars and the stuff they are carrying. if ( wModeFlags & IMPFLAGS_CHARS ) { - CWorldSearch AreaChars( pSrc->GetTopPoint(), iDist ); - AreaChars.SetSearchSquare(true); - AreaChars.SetAllShow( pSrc->IsPriv( PRIV_ALLSHOW )); // show logged out chars? + auto AreaChars = CWorldSearchHolder::GetInstance( pSrc->GetTopPoint(), iDist ); + AreaChars->SetSearchSquare(true); + AreaChars->SetAllShow( pSrc->IsPriv( PRIV_ALLSHOW )); // show logged out chars? for (;;) { - CChar * pChar = AreaChars.GetChar(); + CChar * pChar = AreaChars->GetChar(); if ( pChar == nullptr ) break; pChar->r_WriteSafe( s ); @@ -793,12 +792,12 @@ bool CWorld::Export( lpctstr pszFilename, const CChar * pSrc, word wModeFlags, i if ( wModeFlags & IMPFLAGS_ITEMS ) { // Items on the ground. - CWorldSearch AreaItems( pSrc->GetTopPoint(), iDist ); - AreaItems.SetSearchSquare(true); - AreaItems.SetAllShow( pSrc->IsPriv( PRIV_ALLSHOW )); // show logged out chars? + auto AreaItems = CWorldSearchHolder::GetInstance( pSrc->GetTopPoint(), iDist ); + AreaItems->SetSearchSquare(true); + AreaItems->SetAllShow( pSrc->IsPriv( PRIV_ALLSHOW )); // show logged out chars? for (;;) { - CItem * pItem = AreaItems.GetItem(); + CItem * pItem = AreaItems->GetItem(); if ( pItem == nullptr ) break; pItem->r_WriteSafe( s ); diff --git a/src/game/CWorldMap.cpp b/src/game/CWorldMap.cpp index eac0cdf8e..b05df9f72 100644 --- a/src/game/CWorldMap.cpp +++ b/src/game/CWorldMap.cpp @@ -15,6 +15,7 @@ #include "triggers.h" #include "CWorld.h" #include "CWorldCache.h" +#include "CWorldSearch.h" #include "CWorldMap.h" //************************ @@ -54,10 +55,10 @@ CItem * CWorldMap::CheckNaturalResource(const CPointMap & pt, IT_TYPE iType, boo // Find the resource object. EXC_SET_BLOCK("find existant bit"); CItem * pResBit; - CWorldSearch Area(pt); + auto Area = CWorldSearchHolder::GetInstance(pt); for (;;) { - pResBit = Area.GetItem(); + pResBit = Area->GetItem(); if ( !pResBit ) break; // NOTE: ??? Not all resource objects are world gems. should they be ? @@ -82,11 +83,11 @@ CItem * CWorldMap::CheckNaturalResource(const CPointMap & pt, IT_TYPE iType, boo if ( !pRegion ) return nullptr; - CWorldSearch AreaItems( pt ); - AreaItems.SetAllShow(true); + Area->RestartSearch(); + Area->SetAllShow(true); for (;;) { - CItem *pItem = AreaItems.GetItem(); + CItem *pItem = Area->GetItem(); if ( !pItem ) break; if ( pItem->GetType() != iType ) @@ -224,7 +225,7 @@ IT_TYPE CWorldMap::GetTerrainItemType(dword dwTerrainIndex) // static // gets sector # from one map CSector* CWorldMap::GetSector(int map, int index) noexcept // static { - //ADDTOCALLSTACK_INTENSIVE("CWorldMap::GetSector(index)"); + //ADDTOCALLSTACK_DEBUG("CWorldMap::GetSector(index)"); const int iMapSectorQty = g_World._Sectors.GetSectorQty(map); if (index >= iMapSectorQty) @@ -238,14 +239,14 @@ CSector* CWorldMap::GetSector(int map, int index) noexcept // static CSector* CWorldMap::GetSector(int map, short x, short y) noexcept // static { - //ADDTOCALLSTACK_INTENSIVE("CWorldMap::GetSector(x,y)"); + //ADDTOCALLSTACK_DEBUG("CWorldMap::GetSector(x,y)"); return g_World._Sectors.GetSector(map, x, y); } const CServerMapBlock* CWorldMap::GetMapBlock(const CPointMap& pt) // static { - //ADDTOCALLSTACK_INTENSIVE("CWorldMap::GetMapBlock"); + //ADDTOCALLSTACK_DEBUG("CWorldMap::GetMapBlock"); // Get a map block from the cache. load it if not. EXC_TRY("GetMapBlock"); @@ -345,13 +346,13 @@ CPointMap CWorldMap::FindTypeNear_Top( const CPointMap & pt, IT_TYPE iType, int bool fElem[4] = { false, false, false, false }; // Check dynamics - CWorldSearch Area( pt, iDistance ); - Area.SetAllShow( true ); + auto Area = CWorldSearchHolder::GetInstance( pt, iDistance ); + Area->SetAllShow( true ); for (;;) { z = 0; Height = 0; - pItem = Area.GetItem(); + pItem = Area->GetItem(); if ( pItem == nullptr ) break; @@ -646,10 +647,10 @@ CPointMap CWorldMap::FindItemTypeNearby(const CPointMap & pt, IT_TYPE iType, int int iTestDistance; // Check dynamics first since they are the easiest. - CWorldSearch Area( pt, iDistance ); + auto Area = CWorldSearchHolder::GetInstance( pt, iDistance ); for (;;) { - const CItem * pItem = Area.GetItem(); + const CItem * pItem = Area->GetItem(); if ( pItem == nullptr ) break; @@ -1107,11 +1108,11 @@ void CWorldMap::GetFixPoint( const CPointMap & pt, CServerMapBlockingState & blo // Any dynamic items here ? // NOTE: This could just be an item that an NPC could just move ? - CWorldSearch Area( pt ); + auto Area = CWorldSearchHolder::GetInstance( pt ); for (;;) { - pItem = Area.GetItem(); + pItem = Area->GetItem(); if ( !pItem ) break; @@ -1229,7 +1230,7 @@ void CWorldMap::GetFixPoint( const CPointMap & pt, CServerMapBlockingState & blo void CWorldMap::GetHeightPoint(const CPointMap & pt, CServerMapBlockingState & block, bool fHouseCheck) // static { - ADDTOCALLSTACK_INTENSIVE("CWorldMap::GetHeightPoint"); + ADDTOCALLSTACK_DEBUG("CWorldMap::GetHeightPoint"); const CItemBase * pItemDef = nullptr; const CItemBaseDupe * pDupeDef = nullptr; CItem * pItem = nullptr; @@ -1406,11 +1407,11 @@ void CWorldMap::GetHeightPoint(const CPointMap & pt, CServerMapBlockingState & b // Any dynamic items here ? // NOTE: This could just be an item that an NPC could just move ? - CWorldSearch Area( pt ); + auto Area = CWorldSearchHolder::GetInstance( pt ); for (;;) { - pItem = Area.GetItem(); + pItem = Area->GetItem(); if ( !pItem ) break; @@ -1536,7 +1537,7 @@ CUOMapMeter CWorldMap::CheckMapTerrain(CUOMapMeter pDefault, short x, short y, u char CWorldMap::GetHeightPoint(const CPointMap & pt, uint64 & uiBlockFlags, bool fHouseCheck) // static { - ADDTOCALLSTACK_INTENSIVE("CWorldMap::GetHeightPoint"); + ADDTOCALLSTACK_DEBUG("CWorldMap::GetHeightPoint"); const uint64 uiCan = uiBlockFlags; CServerMapBlockingState block( uiBlockFlags, pt.m_z + (PLAYER_HEIGHT / 2), pt.m_z + PLAYER_HEIGHT ); GetHeightPoint(pt, block, fHouseCheck); @@ -1571,7 +1572,7 @@ void CWorldMap::GetHeightPoint2( const CPointMap & pt, CServerMapBlockingState & { EXC_TRYSUB("GHP2 with blockFlags"); - //ADDTOCALLSTACK_INTENSIVE("CWorldMap::GetHeightPoint2(blockingState)"); + //ADDTOCALLSTACK_DEBUG("CWorldMap::GetHeightPoint2(blockingState)"); // Height of statics at/above given coordinates // do gravity here for the z. @@ -1608,7 +1609,7 @@ void CWorldMap::GetHeightPoint2( const CPointMap & pt, CServerMapBlockingState & // Any multi items here ? if ( fHouseCheck ) { - CRegionLinks rlinks; + static thread_local CRegionLinks rlinks; size_t iRegionQty = pt.GetRegions( REGION_TYPE_MULTI, &rlinks ); if ( iRegionQty > 0 ) { @@ -1647,15 +1648,16 @@ void CWorldMap::GetHeightPoint2( const CPointMap & pt, CServerMapBlockingState & } } } - } + rlinks.clear(); + } { // Any dynamic items here ? // NOTE: This could just be an item that an NPC could just move ? - CWorldSearch Area( pt ); + auto Area = CWorldSearchHolder::GetInstance( pt ); for (;;) { - const CItem * pItem = Area.GetItem(); + const CItem * pItem = Area->GetItem(); if ( pItem == nullptr ) break; @@ -1723,7 +1725,7 @@ void CWorldMap::GetHeightPoint2( const CPointMap & pt, CServerMapBlockingState & char CWorldMap::GetHeightPoint2( const CPointMap & pt, uint64 & uiBlockFlags, bool fHouseCheck ) // static { EXC_TRYSUB("GHP2 with blockFlags"); - //ADDTOCALLSTACK_INTENSIVE("CWorldMap::GetHeightPoint2(blockFlags)"); + //ADDTOCALLSTACK_DEBUG("CWorldMap::GetHeightPoint2(blockFlags)"); // Given our coords at pt including pt.m_z // What is the height that gravity would put me at should i step here ? // Assume my head height is PLAYER_HEIGHT/2 @@ -1792,265 +1794,3 @@ char CWorldMap::GetHeightPoint2( const CPointMap & pt, uint64 & uiBlockFlags, bo return UO_SIZE_MIN_Z; } - -////////////////////////////////////////////////////////////////// -// -CWorldSearch - -CWorldSearch::CWorldSearch(const CPointMap& pt, int iDist) noexcept : - _pt(pt), _iDist(iDist), _fAllShow(false), _fSearchSquare(false), - _eSearchType(ws_search_e::None), _fInertToggle(false), - _ppCurContObjs(nullptr), _pObj(nullptr), - _idxObj(0), _idxObjMax(0), - _iSectorCur(0) // Get upper left of search rect. -{ - // define a search of the world. - _pSectorBase = _pSector = pt.GetSector(); - _rectSector.SetRect( - pt.m_x - iDist, - pt.m_y - iDist, - pt.m_x + iDist + 1, - pt.m_y + iDist + 1, - pt.m_map); -} - -CWorldSearch::~CWorldSearch() noexcept -{ - if (nullptr != _ppCurContObjs) - delete[] _ppCurContObjs; -} - -void CWorldSearch::SetAllShow(bool fView) -{ - //ADDTOCALLSTACK_INTENSIVE("CWorldSearch::SetAllShow"); - _fAllShow = fView; -} - -void CWorldSearch::SetSearchSquare(bool fSquareSearch) -{ - //ADDTOCALLSTACK_INTENSIVE("CWorldSearch::SetSearchSquare"); - _fSearchSquare = fSquareSearch; -} - -void CWorldSearch::RestartSearch() -{ - //ADDTOCALLSTACK_INTENSIVE("CWorldSearch::RestartSearch"); - _eSearchType = ws_search_e::None; - _pObj = nullptr; - _idxObj = _idxObjMax = 0; -} - -bool CWorldSearch::GetNextSector() -{ - ADDTOCALLSTACK("CWorldSearch::GetNextSector"); - // Move search into nearby CSector(s) if necessary - - if (!_iDist) - return false; - - while (true) - { - _pSector = _rectSector.GetSector(_iSectorCur++); - if (_pSector == nullptr) - return false; // done searching. - if (_pSectorBase == _pSector) - continue; // same as base. - - _eSearchType = ws_search_e::None; - _pObj = nullptr; // start at head of next Sector. - _idxObj = _idxObjMax = 0; - - return true; - } -} - -CItem* CWorldSearch::GetItem() -{ - // This method is called very frequently, ADDTOCALLSTACK unneededly sucks cpu - //ADDTOCALLSTACK_INTENSIVE("CWorldSearch::GetItem"); - - constexpr size_t kuiContainerScaleFactor = 2; - while (true) - { - if (_pObj == nullptr) - { - ASSERT(_eSearchType == ws_search_e::None); - _eSearchType = ws_search_e::Items; - - const size_t sector_obj_num = _pSector->m_Items.size(); - if (0 != sector_obj_num) - { - if (_ppCurContObjs != nullptr) - { - if (_idxObjMax < sector_obj_num * kuiContainerScaleFactor) - { - delete[] _ppCurContObjs; - _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; - } - } - else - { - _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; - } - - memcpy(_ppCurContObjs, _pSector->m_Items.data(), sector_obj_num * sizeof(CSObjContRec*)); // I need this to be as fast as possible - } - - _idxObjMax = sector_obj_num; - _idxObj = 0; - } - else - { - ++_idxObj; - } - - ASSERT(_eSearchType == ws_search_e::Items); - _pObj = (_idxObj >= _idxObjMax) ? nullptr : static_cast (_ppCurContObjs[_idxObj]); - if (_pObj == nullptr) - { - if (GetNextSector()) - continue; - - return nullptr; - } - - const CPointMap& ptObj = _pObj->GetTopPoint(); - if (_fSearchSquare) - { - if (_fAllShow) - { - if (_pt.GetDistSightBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDistSight(ptObj) <= _iDist) - return static_cast (_pObj); - } - } - else - { - if (_fAllShow) - { - if (_pt.GetDistBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDist(ptObj) <= _iDist) - return static_cast (_pObj); - } - } - } -} - -CChar* CWorldSearch::GetChar() -{ - // This method is called very frequently, ADDTOCALLSTACK unneededly sucks cpu - //ADDTOCALLSTACK_INTENSIVE("CWorldSearch::GetChar"); - - constexpr size_t kuiContainerScaleFactor = 2; - while (true) - { - if (_pObj == nullptr) - { - ASSERT(_eSearchType == ws_search_e::None); - _eSearchType = ws_search_e::Chars; - _fInertToggle = false; - - const size_t sector_obj_num = _pSector->m_Chars_Active.size(); - if (0 != sector_obj_num) - { - if (_ppCurContObjs != nullptr) - { - if (_idxObjMax < sector_obj_num * kuiContainerScaleFactor) - { - delete[] _ppCurContObjs; - _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; - } - } - else - { - _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; - } - memcpy(_ppCurContObjs, _pSector->m_Chars_Active.data(), sector_obj_num * sizeof(CSObjContRec*)); // I need this to be as fast as possible - } - - _idxObjMax = sector_obj_num; - _idxObj = 0; - } - else - { - ++_idxObj; - } - - ASSERT(_eSearchType == ws_search_e::Chars); - _pObj = (_idxObj >= _idxObjMax) ? nullptr : static_cast (_ppCurContObjs[_idxObj]); - if (_pObj == nullptr) - { - if (!_fInertToggle && _fAllShow) - { - _fInertToggle = true; - - const size_t sector_obj_num = _pSector->m_Chars_Disconnect.size(); - if (0 != sector_obj_num) - { - if (_ppCurContObjs != nullptr) - { - if (_idxObjMax < sector_obj_num * kuiContainerScaleFactor) - { - delete[] _ppCurContObjs; - _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; - } - } - else - { - _ppCurContObjs = new CSObjContRec * [sector_obj_num * 2]; - } - memcpy(_ppCurContObjs, _pSector->m_Chars_Disconnect.data(), sector_obj_num * sizeof(CSObjContRec*)); // I need this to be as fast as possible - } - - _idxObjMax = sector_obj_num; - _idxObj = 0; - - _pObj = (_idxObj >= _idxObjMax) ? nullptr : static_cast (_ppCurContObjs[_idxObj]); - if (_pObj != nullptr) - goto jumpover; - } - - if (GetNextSector()) - continue; - - return nullptr; - } - - jumpover: - const CPointMap& ptObj = _pObj->GetTopPoint(); - - if (_fSearchSquare) - { - if (_fAllShow) - { - if (_pt.GetDistSightBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDistSight(ptObj) <= _iDist) - return static_cast (_pObj); - } - } - else - { - if (_fAllShow) - { - if (_pt.GetDistBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDist(ptObj) <= _iDist) - return static_cast (_pObj); - } - } - } -} diff --git a/src/game/CWorldMap.h b/src/game/CWorldMap.h index 0181d209b..9b22f4140 100644 --- a/src/game/CWorldMap.h +++ b/src/game/CWorldMap.h @@ -66,49 +66,4 @@ class CWorldMap }; -class CWorldSearch // define a search of the world. -{ - enum class ws_search_e - { - None = 0, - Items, - Chars - }; - - const CPointMap _pt; // Base point of our search. - const int _iDist; // How far from the point are we interested in - bool _fAllShow; // Include Even inert items. - bool _fSearchSquare; // Search in a square (uo-sight distance) rather than a circle (standard distance). - - ws_search_e _eSearchType; - bool _fInertToggle; // We are now doing the inert chars. - - CSObjContRec** _ppCurContObjs; // Sector-attached object container in which we are searching right now. - CObjBase* _pObj; // The current object of interest. - size_t _idxObj, _idxObjMax; - - int _iSectorCur; // What is the current Sector index in m_rectSector - CSector * _pSectorBase; // Don't search the center sector 2 times. - CSector * _pSector; // current Sector - CRectMap _rectSector; // A rectangle containing our sectors we can search. - -public: - static const char *m_sClassName; - CWorldSearch(const CWorldSearch& copy) = delete; - CWorldSearch& operator=(const CWorldSearch& other) = delete; - - explicit CWorldSearch( const CPointMap & pt, int iDist = 0 ) noexcept; - ~CWorldSearch() noexcept; - - void SetAllShow( bool fView ); - void SetSearchSquare( bool fSquareSearch ); - void RestartSearch(); - CChar * GetChar(); - CItem * GetItem(); - -private: - bool GetNextSector(); -}; - - #endif // _INC_CWORLDMAP_H diff --git a/src/game/CWorldSearch.cpp b/src/game/CWorldSearch.cpp new file mode 100644 index 000000000..f3f4f1fa5 --- /dev/null +++ b/src/game/CWorldSearch.cpp @@ -0,0 +1,360 @@ +#include "CWorldSearch.h" +#include "CSector.h" +#include "../common/CException.h" +#include "../common/CLog.h" +#include "../game/chars/CChar.h" +#include "../game/items/CItem.h" +#include "../sphere/threads.h" +#include + +static constexpr size_t kuiContainerScaleFactor = 2; + + +class CWorldSearchHolderInternal +{ + static constexpr size_t kuiPreallocateSize = 20; + static constexpr size_t kuiCachedInstances = 20; + std::array, kuiCachedInstances> _instances; + //std::vector> _instances; + //std::array, kuiCachedInstances> _instances; + +public: + ~CWorldSearchHolderInternal() noexcept = default; + CWorldSearchHolderInternal() noexcept + { + //for (auto& inst : _instances) { + // inst = cdrc::make_rc(kuiPreallocateSize); + //} + //_instances.resize(kuiCachedInstances); + } + + //cdrc::rc_ptr GetOne(const CPointMap& pt, int iDist) + CSReferenceCounted GetOne(const CPointMap& pt, int iDist) + { + for (auto& inst : _instances) + { + //if (inst.use_count() == 1) + if (inst._counted_references == 1) + { + // It's free, it's only referenced by me (i'm ownling it). + inst->Reset(pt, iDist); + //return inst; + return inst.GetRef(); + } + } + + // No free instances! + throw CSError(LOGL_CRIT, 0, "Not enough instances of CWorldSearch!"); + + /* + g_Log.Event(LOGL_ERROR, "Not enough instances of CWorldSearch! Creating a new, un-cached one. This will be slower!\n"); + auto inst = cdrc::make_rc(kuiPreallocateSize); + inst->Reset(pt, iDist); + return inst; + */ + + } +}; + +//-------------- + +//cdrc::rc_ptr CWorldSearch::GetInstance(const CPointMap& pt, int iDist) // static +CSReferenceCounted CWorldSearchHolder::GetInstance(const CPointMap& pt, int iDist) // static +{ + static CWorldSearchHolderInternal holder; + return holder.GetOne(pt, iDist); +} + +CWorldSearch::CWorldSearch() noexcept : + _iDist(0), _fAllShow(false), _fSearchSquare(false), + _eSearchType(ws_search_e::None), _fInertToggle(false), + _ppCurContObjs(nullptr), _pObj(nullptr), + _idxObj(0), _idxObjMax(0), + _iSectorCur(0), // Get upper left of search rect. + _pSectorBase(nullptr), _pSector(nullptr) +{ +} + +CWorldSearch::CWorldSearch(size_t uiPreallocateSize) : + CWorldSearch() +{ + if (!uiPreallocateSize) + return; + + _idxObjMax = uiPreallocateSize; + _ppCurContObjs = new CSObjContRec* [uiPreallocateSize * kuiContainerScaleFactor]; +} + +CWorldSearch::~CWorldSearch() noexcept +{ + if (nullptr != _ppCurContObjs) + delete[] _ppCurContObjs; +} + +void CWorldSearch::Reset(const CPointMap& pt, int iDist) +{ + //ADDTOCALLSTACK("CWorldSearch::Reset"); + // define a search of the world. + + /* + if (_ppCurContObjs && _idxObjMax) + { + memset(_ppCurContObjs, (int)nullptr, _idxObjMax * sizeof(CSObjContRec*)); + } + */ + + _fAllShow = false; + _fSearchSquare = false; + _eSearchType = ws_search_e::None; + _fInertToggle = false; + _pObj = nullptr; + _idxObj = 0; + //_idxObjMax = 0; // Don't! Recycle the allocated space for _ppCurContObjs. + _iSectorCur = 0; // Get upper left of search rect. + + _pt = pt; + _iDist = iDist; + _pSectorBase = _pSector = pt.GetSector(); + _rectSector.SetRect( + pt.m_x - iDist, + pt.m_y - iDist, + pt.m_x + iDist + 1, + pt.m_y + iDist + 1, + pt.m_map); +} + +void CWorldSearch::SetAllShow(bool fView) +{ + //ADDTOCALLSTACK_DEBUG("CWorldSearch::SetAllShow"); + _fAllShow = fView; +} + +void CWorldSearch::SetSearchSquare(bool fSquareSearch) +{ + //ADDTOCALLSTACK_DEBUG("CWorldSearch::SetSearchSquare"); + _fSearchSquare = fSquareSearch; +} + +void CWorldSearch::RestartSearch() +{ + //ADDTOCALLSTACK_DEBUG("CWorldSearch::RestartSearch"); + _eSearchType = ws_search_e::None; + _pObj = nullptr; + _idxObj = _idxObjMax = 0; + _pSector = _pSectorBase; + // We could memset 0 _ppCurContObjs, but it isn't actually necessary... +} + +bool CWorldSearch::GetNextSector() +{ + //ADDTOCALLSTACK("CWorldSearch::GetNextSector"); + // Move search into nearby CSector(s) if necessary + + if (!_iDist) + return false; + + while (true) + { + _pSector = _rectSector.GetSector(_iSectorCur++); + if (_pSector == nullptr) + return false; // done searching. + if (_pSectorBase == _pSector) + continue; // same as base. + + _eSearchType = ws_search_e::None; + _pObj = nullptr; // start at head of next Sector. + _idxObj = _idxObjMax = 0; + + return true; + } +} + +CItem* CWorldSearch::GetItem() +{ + // This method is called very frequently, ADDTOCALLSTACK unneededly sucks cpu + //ADDTOCALLSTACK_DEBUG("CWorldSearch::GetItem"); + + while (true) + { + if (_pObj == nullptr) + { + ASSERT(_eSearchType == ws_search_e::None); + _eSearchType = ws_search_e::Items; + + const size_t sector_obj_num = _pSector->m_Items.size(); + if (0 != sector_obj_num) + { + if (_ppCurContObjs != nullptr) + { + if (_idxObjMax < sector_obj_num * kuiContainerScaleFactor) + { + delete[] _ppCurContObjs; + _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; + } + } + else + { + _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; + } + + memcpy(_ppCurContObjs, _pSector->m_Items.data(), sector_obj_num * sizeof(CSObjContRec*)); // I need this to be as fast as possible + } + + _idxObjMax = sector_obj_num; + _idxObj = 0; + } + else + { + ++_idxObj; + } + + ASSERT(_eSearchType == ws_search_e::Items); + _pObj = (_idxObj >= _idxObjMax) ? nullptr : static_cast (_ppCurContObjs[_idxObj]); + if (_pObj == nullptr) + { + if (GetNextSector()) + continue; + + return nullptr; + } + + const CPointMap& ptObj(_pObj->GetTopPoint()); + if (_fSearchSquare) + { + if (_fAllShow) + { + if (_pt.GetDistSightBase(ptObj) <= _iDist) + return static_cast (_pObj); + } + else + { + if (_pt.GetDistSight(ptObj) <= _iDist) + return static_cast (_pObj); + } + } + else + { + if (_fAllShow) + { + if (_pt.GetDistBase(ptObj) <= _iDist) + return static_cast (_pObj); + } + else + { + if (_pt.GetDist(ptObj) <= _iDist) + return static_cast (_pObj); + } + } + } +} + +CChar* CWorldSearch::GetChar() +{ + // This method is called very frequently, ADDTOCALLSTACK unneededly sucks cpu + //ADDTOCALLSTACK_DEBUG("CWorldSearch::GetChar"); + + while (true) + { + if (_pObj == nullptr) + { + ASSERT(_eSearchType == ws_search_e::None); + _eSearchType = ws_search_e::Chars; + _fInertToggle = false; + + const size_t sector_obj_num = _pSector->m_Chars_Active.size(); + if (0 != sector_obj_num) + { + if (_ppCurContObjs != nullptr) + { + if (_idxObjMax < sector_obj_num * kuiContainerScaleFactor) + { + delete[] _ppCurContObjs; + _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; + } + } + else + { + _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; + } + memcpy(_ppCurContObjs, _pSector->m_Chars_Active.data(), sector_obj_num * sizeof(CSObjContRec*)); // I need this to be as fast as possible + } + + _idxObjMax = sector_obj_num; + _idxObj = 0; + } + else + { + ++_idxObj; + } + + ASSERT(_eSearchType == ws_search_e::Chars); + _pObj = (_idxObj >= _idxObjMax) ? nullptr : static_cast (_ppCurContObjs[_idxObj]); + if (_pObj == nullptr) + { + if (!_fInertToggle && _fAllShow) + { + _fInertToggle = true; + + const size_t sector_obj_num = _pSector->m_Chars_Disconnect.size(); + if (0 != sector_obj_num) + { + if (_ppCurContObjs != nullptr) + { + if (_idxObjMax < sector_obj_num * kuiContainerScaleFactor) + { + delete[] _ppCurContObjs; + _ppCurContObjs = new CSObjContRec * [sector_obj_num * kuiContainerScaleFactor]; + } + } + else + { + _ppCurContObjs = new CSObjContRec * [sector_obj_num * 2]; + } + memcpy(_ppCurContObjs, _pSector->m_Chars_Disconnect.data(), sector_obj_num * sizeof(CSObjContRec*)); // I need this to be as fast as possible + } + + _idxObjMax = sector_obj_num; + _idxObj = 0; + + _pObj = (_idxObj >= _idxObjMax) ? nullptr : static_cast (_ppCurContObjs[_idxObj]); + if (_pObj != nullptr) + goto jumpover; + } + + if (GetNextSector()) + continue; + + return nullptr; + } + + jumpover: + const CPointMap& ptObj = _pObj->GetTopPoint(); + + if (_fSearchSquare) + { + if (_fAllShow) + { + if (_pt.GetDistSightBase(ptObj) <= _iDist) + return static_cast (_pObj); + } + else + { + if (_pt.GetDistSight(ptObj) <= _iDist) + return static_cast (_pObj); + } + } + else + { + if (_fAllShow) + { + if (_pt.GetDistBase(ptObj) <= _iDist) + return static_cast (_pObj); + } + else + { + if (_pt.GetDist(ptObj) <= _iDist) + return static_cast (_pObj); + } + } + } +} diff --git a/src/game/CWorldSearch.h b/src/game/CWorldSearch.h new file mode 100644 index 000000000..7a598c83b --- /dev/null +++ b/src/game/CWorldSearch.h @@ -0,0 +1,83 @@ +#ifndef _INC_CWORLDSEARCH_H +#define _INC_CWORLDSEARCH_H + +#include "../common/sphere_library/CSReferenceCount.h" +#include "../common/CRect.h" + +/* +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4244) +# pragma warning(disable : 4267) +# pragma warning(disable : 4324) +#endif + +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif +*/ + +class CSObjContRec; +class CObjBase; +class CSector; +class CChar; +class CItem; + + +// Search for dynamic objects on a specified zone +class CWorldSearch +{ + enum class ws_search_e + { + None = 0, + Items, + Chars + }; + + CPointMap _pt; // Base point of our search. + int _iDist; // How far from the point are we interested in + bool _fAllShow; // Include Even inert items. + bool _fSearchSquare; // Search in a square (uo-sight distance) rather than a circle (standard distance). + + ws_search_e _eSearchType; + bool _fInertToggle; // We are now doing the inert chars. + + CSObjContRec** _ppCurContObjs; // Array with a copy of the pointers to the objects inside the sector we are in searching right now. + CObjBase* _pObj; // The current object of interest. + size_t _idxObj, _idxObjMax; + + int _iSectorCur; // What is the current Sector index in m_rectSector + CSector* _pSectorBase; // Don't search the center sector 2 times. + CSector* _pSector; // current Sector + CRectMap _rectSector; // A rectangle containing our sectors we can search. + +public: + static const char* m_sClassName; + CWorldSearch(const CWorldSearch& copy) = delete; + CWorldSearch& operator=(const CWorldSearch& other) = delete; + + CWorldSearch() noexcept; + CWorldSearch(size_t uiPreallocateSize); + ~CWorldSearch() noexcept; + +public: + //static cdrc::rc_ptr GetInstance(const CPointMap& pt, int iDist = 0); + void Reset(const CPointMap& pt, int iDist = 0); + void SetAllShow(bool fView); + void SetSearchSquare(bool fSquareSearch); + void RestartSearch(); + CChar* GetChar(); + CItem* GetItem(); + +private: + bool GetNextSector(); +}; + +struct CWorldSearchHolder +{ + static CSReferenceCounted GetInstance(const CPointMap& pt, int iDist = 0); +}; + +#endif // _INC_CWORLDSEARCH_H diff --git a/src/game/CWorldTicker.cpp b/src/game/CWorldTicker.cpp index d3beb65a7..902db9ba6 100644 --- a/src/game/CWorldTicker.cpp +++ b/src/game/CWorldTicker.cpp @@ -8,6 +8,7 @@ #include "CWorldClock.h" #include "CWorldGameTime.h" #include "CWorldTicker.h" +#include CWorldTicker::CWorldTicker(CWorldClock *pClock) @@ -16,6 +17,10 @@ CWorldTicker::CWorldTicker(CWorldClock *pClock) _pWorldClock = pClock; _iLastTickDone = 0; + + _vecObjs.reserve(50); + _vecWorldObjsToEraseFromList.reserve(50); + _vecPeriodicCharsToEraseFromList.reserve(25); } @@ -37,52 +42,60 @@ void CWorldTicker::_InsertTimedObject(const int64 iTimeout, CTimedObject* pTimed #endif */ - std::unique_lock lock(_mWorldTickList.THREAD_CMUTEX); - TimedObjectsContainer& cont = _mWorldTickList[iTimeout]; - cont.emplace_back(pTimedObject); +#if MT_ENGINES + std::unique_lock lock(_mWorldTickList.MT_CMUTEX); +#endif + _mWorldTickList.emplace(iTimeout, pTimedObject); } void CWorldTicker::_RemoveTimedObject(const int64 iOldTimeout, CTimedObject* pTimedObject) { ASSERT(iOldTimeout != 0); - std::unique_lock lock(_mWorldTickList.THREAD_CMUTEX); - auto itList = _mWorldTickList.find(iOldTimeout); - if (itList == _mWorldTickList.end()) + //g_Log.EventDebug("Trying to erase TimedObject 0x%p with old timeout %ld.\n", pTimedObject, iOldTimeout); +#if MT_ENGINES + std::unique_lock lock(_mWorldTickList.MT_CMUTEX); +#endif + const auto itMap = _mWorldTickList.equal_range(iOldTimeout); + decltype(_mWorldTickList)::const_iterator itFound = itMap.second; // first element greater than the key we look for + for (auto it = itMap.first; it != itMap.second; ++it) { - // The object might have a timeout while being in a non-tickable state, so it isn't in the list. -/* -#ifdef _DEBUG - for (auto& elemList : _mWorldTickList) + // I have a pair of iterators for a range of the elements (all the elements with the same key) + if (it->second == pTimedObject) { - for (auto& elem : elemList.second) + if (itFound != itMap.second) { - ASSERT(elem != pTimedObject); + g_Log.EventDebug("The same TimedObject is inserted multiple times in mWorldTickList. This shouldn't happen. Removing only the first one.\n"); } + itFound = it; +#if !defined(_DEBUG) + break; +#endif } + } + if (itFound == itMap.second) + { + // Not found. The object might have a timeout while being in a non-tickable state, so it isn't in the list. +/* +#ifdef _DEBUG + g_Log.EventDebug("Requested erasure of TimedObject in mWorldTickList, but it wasn't found.\n"); #endif */ - return; - } - TimedObjectsContainer& cont = itList->second; // direct access to the container. - //TimedObjectsContainer& cont = itList.underlying->second; // direct access to the container. - cont.erase(std::remove(cont.begin(), cont.end(), pTimedObject), cont.end()); - if (cont.empty()) - { - _mWorldTickList.erase(itList); - } /* #ifdef _DEBUG - for (auto& elemList : _mWorldTickList) - { - for (auto& elem : elemList.second) + for (auto& elemList : _mWorldTickList) { - ASSERT(elem != pTimedObject); + for (auto& elem : elemList.second) + { + ASSERT(elem != pTimedObject); + } } - } #endif */ + return; + } + _mWorldTickList.erase(itFound); } void CWorldTicker::AddTimedObject(const int64 iTimeout, CTimedObject* pTimedObject, bool fForce) @@ -122,7 +135,7 @@ void CWorldTicker::AddTimedObject(const int64 iTimeout, CTimedObject* pTimedObje } } } - + if (fCanTick) { _InsertTimedObject(iTimeout, pTimedObject); @@ -152,31 +165,45 @@ void CWorldTicker::DelTimedObject(CTimedObject* pTimedObject) void CWorldTicker::_InsertCharTicking(const int64 iTickNext, CChar* pChar) { - std::unique_lock lock(_mCharTickList.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(_mCharTickList.MT_CMUTEX); +#endif - TimedCharsContainer& cont = _mCharTickList[iTickNext]; - cont.emplace_back(pChar); + _mCharTickList.emplace(iTickNext, pChar); pChar->_iTimePeriodicTick = iTickNext; } void CWorldTicker::_RemoveCharTicking(const int64 iOldTimeout, CChar* pChar) { - std::unique_lock lock(_mCharTickList.THREAD_CMUTEX); - auto itList = _mCharTickList.find(iOldTimeout); - if (itList == _mCharTickList.end()) + // I'm reasonably sure that the element i'm trying to remove is present in this container. +#if MT_ENGINES + std::unique_lock lock(_mCharTickList.MT_CMUTEX); +#endif + + const auto itMap = _mCharTickList.equal_range(iOldTimeout); + decltype(_mCharTickList)::const_iterator itFound = itMap.second; // first element greater than the key we look for + for (auto it = itMap.first; it != itMap.second; ++it) { - //ASSERT(0); // This shouldn't happen - return; + // I have a pair of iterators for a range of the elements (all the elements with the same key) + if (it->second == pChar) + { + if (itFound != itMap.second) + { + g_Log.EventDebug("The same CChar is inserted multiple times in mCharTickList. This shouldn't happen. Removing only the first one.\n"); + } + itFound = it; +#if !_DEBUG + break; +#endif + } } - TimedCharsContainer& cont = itList->second; // direct access to the container. - //TimedCharsContainer& cont = itList.underlying->second; // direct access to the container. - - cont.erase(std::remove(cont.begin(), cont.end(), pChar), cont.end()); - if (cont.empty()) + if (itFound == itMap.second) { - _mCharTickList.erase(itList); + return; } + _mCharTickList.erase(itFound); + pChar->_iTimePeriodicTick = 0; } @@ -190,7 +217,9 @@ void CWorldTicker::AddCharTicking(CChar* pChar, bool fNeedsLock) int64 iTickNext, iTickOld; if (fNeedsLock) { - std::unique_lock lock(pChar->THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(pChar->MT_CMUTEX); +#endif iTickNext = pChar->_iTimeNextRegen; iTickOld = pChar->_iTimePeriodicTick; } @@ -239,7 +268,9 @@ void CWorldTicker::DelCharTicking(CChar* pChar, bool fNeedsLock) int64 iTickOld; if (fNeedsLock) { - std::unique_lock lock(pChar->THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(pChar->MT_CMUTEX); +#endif iTickOld = pChar->_iTimePeriodicTick; } else @@ -261,7 +292,9 @@ void CWorldTicker::AddObjStatusUpdate(CObjBase* pObj, bool fNeedsLock) // static UnreferencedParameter(fNeedsLock); { - std::unique_lock lock(_ObjStatusUpdates.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(_ObjStatusUpdates.MT_CMUTEX); +#endif _ObjStatusUpdates.insert(pObj); } @@ -274,7 +307,9 @@ void CWorldTicker::DelObjStatusUpdate(CObjBase* pObj, bool fNeedsLock) // static UnreferencedParameter(fNeedsLock); { - std::unique_lock lock(_ObjStatusUpdates.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(_ObjStatusUpdates.MT_CMUTEX); +#endif _ObjStatusUpdates.erase(pObj); } @@ -288,8 +323,6 @@ void CWorldTicker::Tick() ADDTOCALLSTACK("CWorldTicker::Tick"); EXC_TRY("CWorldTicker::Tick"); - std::vector vecObjs; // Reuse the same container to avoid unnecessary reallocations - EXC_SET_BLOCK("Once per tick stuff"); // Do this once per tick. // Update status flags from objects, update current tick. @@ -302,35 +335,35 @@ void CWorldTicker::Tick() * called (whereas other items can receive the OnTickStatusUpdate() call via their normal * tick method). * note: ideally, a better solution to accomplish this should be found if possible - * TODO: implement a new class inheriting from CTimedObject to get rid of this code. + * TODO: implement a new class inheriting from CTimedObject to get rid of this code? */ { EXC_TRYSUB("StatusUpdates"); { EXC_SETSUB_BLOCK("Selection"); - std::unique_lock lock_su(_ObjStatusUpdates.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock_su(_ObjStatusUpdates.MT_CMUTEX); +#endif if (!_ObjStatusUpdates.empty()) { - // loop backwards? to avoid possible infinite loop if a status update is triggered - // as part of the status update (e.g. property changed under tooltip trigger) for (CObjBase* pObj : _ObjStatusUpdates) { - if (pObj != nullptr) - vecObjs.emplace_back(static_cast(pObj)); + if (pObj && !pObj->_IsBeingDeleted()) + _vecObjs.emplace_back(static_cast(pObj)); } _ObjStatusUpdates.clear(); } } EXC_SETSUB_BLOCK("Loop"); - for (void* pObjVoid : vecObjs) + for (void* pObjVoid : _vecObjs) { CObjBase* pObj = static_cast(pObjVoid); pObj->OnTickStatusUpdate(); } EXC_CATCHSUB(""); - vecObjs.clear(); + _vecObjs.clear(); } } @@ -346,65 +379,165 @@ void CWorldTicker::Tick() { // Need here a new, inner scope to get rid of EXC_TRYSUB variables and for the unique_lock EXC_TRYSUB("Timed Objects Selection"); - std::unique_lock lock(_mWorldTickList.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(_mWorldTickList.MT_CMUTEX); +#endif - WorldTickList::iterator itList = _mWorldTickList.begin(); - WorldTickList::iterator itListEnd = _mWorldTickList.end(); + WorldTickList::iterator itMap = _mWorldTickList.begin(); + WorldTickList::iterator itMapEnd = _mWorldTickList.end(); + size_t uiProgressive = 0; int64 iTime; - while ((itList != itListEnd) && (iCurTime > (iTime = itList->first))) + while ((itMap != itMapEnd) && (iCurTime > (iTime = itMap->first))) + { + CTimedObject* pTimedObj = itMap->second; + if (pTimedObj->_IsTimerSet() && pTimedObj->_CanTick()) + { + if (pTimedObj->_GetTimeoutRaw() <= iCurTime) + { + if (auto pObjBase = dynamic_cast(pTimedObj)) + { + if (pObjBase->_IsBeingDeleted()) + continue; + } + + _vecObjs.emplace_back(static_cast(pTimedObj)); + _vecWorldObjsToEraseFromList.emplace_back(uiProgressive); + + pTimedObj->_ClearTimeout(); + } + //else + //{ + // // This shouldn't happen... If it does, get rid of the entry on the list anyways, + // // it got desynchronized in some way and might be an invalid or even deleted and deallocated object! + //} + } + ++itMap; + ++uiProgressive; + } + + EXC_CATCHSUB("AddToSubLists"); + } + + { + EXC_TRYSUB("Timed Objects Delete from List"); + + // Erase in chunks, call erase the least times possible. + if (!_vecWorldObjsToEraseFromList.empty()) { - TimedObjectsContainer& cont = itList->second; - //TimedObjectsContainer& cont = itList.underlying->second; + /* + g_Log.EventDebug("-- Start WORLDTICK. I need to remove %lu items:\n", _vecWorldObjsToEraseFromList.size()); + std::stringstream ss; + for (size_t elem : _vecWorldObjsToEraseFromList) + { + ss << elem << ' '; + } + g_Log.EventDebug("%s\n", ss.str().c_str()); + */ - TimedObjectsContainer::iterator itContEnd = cont.end(); - for (auto it = cont.begin(); it != itContEnd;) + if (_vecWorldObjsToEraseFromList.size() > 1) { - CTimedObject* pTimedObj = *it; - - // FIXME / TODO: For now, since we don't have multithreading fully working, locking an unneeded mutex causes only useless slowdowns. - //std::unique_lock lockTimeObj(pTimedObj->THREAD_CMUTEX); - - if (pTimedObj->_IsTimerSet() && pTimedObj->_CanTick()) + size_t uiCurMapElemIdx = 0; + size_t uiCurVecElemIdx = 0; + //size_t uiSubRangeStartIdx = 0; + WorldTickList::iterator itSubRangeStart = _mWorldTickList.begin(); + WorldTickList::iterator itMap = _mWorldTickList.begin(); + bool fContiguous = true; + bool fFirstMatch = false; + while ((itMap != _mWorldTickList.end()) && + (uiCurMapElemIdx <= _vecWorldObjsToEraseFromList.back()) && + (uiCurVecElemIdx < _vecWorldObjsToEraseFromList.size())) { - if (pTimedObj->_GetTimeoutRaw() <= iCurTime) + if (!fFirstMatch) + { + if (uiCurMapElemIdx == _vecWorldObjsToEraseFromList[uiCurVecElemIdx]) + { + //uiSubRangeStartIdx = uiCurMapElemIdx; + itSubRangeStart = itMap; + fFirstMatch = true; + + ++uiCurVecElemIdx; + } + + ++itMap; + ++uiCurMapElemIdx; + continue; + } + + if (uiCurMapElemIdx == _vecWorldObjsToEraseFromList[uiCurVecElemIdx]) { - vecObjs.emplace_back(static_cast(pTimedObj)); - pTimedObj->_ClearTimeout(); + // Matches. I want to delete this. + if (uiCurMapElemIdx == _vecWorldObjsToEraseFromList[uiCurVecElemIdx - 1] + 1) + { + // I want to delete it and it's contiguous, go on + ASSERT(fContiguous); + } + else + { + // It isn't contiguous. Go below. + ASSERT(!fContiguous); + // This is the first one that matches after previous mismatches. We start this chunk from here. + //uiSubRangeStartIdx = uiCurMapElemIdx; + fContiguous = true; + } + + ++itMap; + ++uiCurMapElemIdx; + ++uiCurVecElemIdx; + continue; } - /* - else + + // Not contiguous to the next element to be erased (stored in the vector). + // What to do? + if (uiCurMapElemIdx != _vecWorldObjsToEraseFromList[uiCurVecElemIdx]) { - // This shouldn't happen... If it does, get rid of the entry on the list anyways, - // it got desynchronized in some way and might be an invalid or even deleted and deallocated object! + // I don't want to erase this. + if (!fContiguous) + { + // This is an element after the first one successive to the previous contiguous block (2nd, 3rd...) + // Ignore it. + //g_Log.EventDebug("Skip this %lu\n", uiCurMapElemIdx); + ++itMap; + } + else + { + // This is the first element after the previous contiguous block + // I want to erase until the previous one + // erase doesn't delete the last element in the range + itMap = _mWorldTickList.erase(itSubRangeStart, itMap); + //g_Log.EventDebug("Skip this %lu, not to be deleted, and...\n", uiCurMapElemIdx); + //g_Log.EventDebug("Erasing %lu items starting from pos %lu\n", (uiCurMapElemIdx - uiSubRangeStartIdx), uiSubRangeStartIdx); + + ++itMap; + itSubRangeStart = itMap; + //uiSubRangeStartIdx = uiCurMapElemIdx; // Not really needed + fContiguous = false; + } + ++uiCurMapElemIdx; + continue; } - */ - it = cont.erase(it); - itContEnd = cont.end(); + ASSERT(false); // Shouldn't really be here. } - else + if (fFirstMatch && fContiguous) { - ++it; + /*itMap =*/ _mWorldTickList.erase(itSubRangeStart, itMap); // last range to erase + //g_Log.EventDebug("(End cycle) Erasing %lu items starting from pos %lu\n", (uiCurMapElemIdx - uiSubRangeStartIdx), uiSubRangeStartIdx); } } - - if (cont.empty()) - { - itList = _mWorldTickList.erase(itList); - itListEnd = _mWorldTickList.end(); - } else { - ++itList; + _mWorldTickList.erase(std::next(_mWorldTickList.begin(), _vecWorldObjsToEraseFromList.front())); + //g_Log.EventDebug("Erasing 1 item.\n"); } } - EXC_CATCHSUB(""); + EXC_CATCHSUB("DeleteFromList"); + _vecWorldObjsToEraseFromList.clear(); } lpctstr ptcSubDesc; - for (void* pObjVoid : vecObjs) // Loop through all msecs stored, unless we passed the timestamp. + for (void* pObjVoid : _vecObjs) // Loop through all msecs stored, unless we passed the timestamp. { ptcSubDesc = "Generic"; @@ -413,8 +546,9 @@ void CWorldTicker::Tick() CTimedObject* pTimedObj = static_cast(pObjVoid); - // FIXME / TODO: For now, since we don't have multithreading fully working, locking an unneeded mutex causes only useless slowdowns. - //std::unique_lock lockTimeObj(pTimedObj->THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lockTimeObj(pTimedObj->MT_CMUTEX); +#endif const PROFILE_TYPE profile = pTimedObj->_GetProfileType(); const ProfileTask profileTask(profile); @@ -434,14 +568,14 @@ void CWorldTicker::Tick() ptcSubDesc = "ItemEquipped"; CObjBaseTemplate* pObjTop = pItem->GetTopLevelObj(); ASSERT(pObjTop); - + CChar* pChar = dynamic_cast(pObjTop); if (pChar) { fDelete = !pChar->OnTickEquip(pItem); break; } - + ptcSubDesc = "Item (fallback)"; g_Log.Event(LOGL_CRIT, "Item equipped, but not contained in a character? (UID: 0%" PRIx32 ")\n.", pItem->GetUID().GetObjUID()); } @@ -517,8 +651,9 @@ void CWorldTicker::Tick() } } - vecObjs.clear(); + _vecObjs.clear(); + // ---- /* Periodic, automatic ticking for every char */ @@ -527,65 +662,164 @@ void CWorldTicker::Tick() { // Need here a new, inner scope to get rid of EXC_TRYSUB variables, and for the unique_lock EXC_TRYSUB("Char Periodic Ticks Selection"); - std::unique_lock lock(_mCharTickList.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(_mCharTickList.MT_CMUTEX); +#endif - CharTickList::iterator itList = _mCharTickList.begin(); - CharTickList::iterator itListEnd = _mCharTickList.end(); + CharTickList::iterator itMap = _mCharTickList.begin(); + CharTickList::iterator itMapEnd = _mCharTickList.end(); + size_t uiProgressive = 0; int64 iTime; - while ((itList != itListEnd) && (iCurTime > (iTime = itList->first))) + while ((itMap != itMapEnd) && (iCurTime > (iTime = itMap->first))) { - TimedCharsContainer& cont = itList->second; - //TimedCharsContainer& cont = itList.underlying->second; + CChar* pChar = itMap->second; - TimedCharsContainer::iterator itContEnd = cont.end(); - for (auto it = cont.begin(); it != itContEnd;) + if ((pChar->_iTimePeriodicTick != 0) && pChar->_CanTick() && !pChar->_IsBeingDeleted()) { - CChar* pChar = *it; + if (pChar->_iTimePeriodicTick <= iCurTime) + { + _vecObjs.emplace_back(static_cast(pChar)); + _vecPeriodicCharsToEraseFromList.emplace_back(uiProgressive); - // FIXME / TODO: For now, since we don't have multithreading fully working, locking an unneeded mutex causes only useless slowdowns. - //std::unique_lock lockTimeObj(pTimedObj->THREAD_CMUTEX); + pChar->_iTimePeriodicTick = 0; + } + //else + //{ + // // This shouldn't happen... If it does, get rid of the entry on the list anyways, + // // it got desynchronized in some way and might be an invalid or even deleted and deallocated object! + //} - if ((pChar->_iTimePeriodicTick != 0) && pChar->_CanTick()) + } + ++itMap; + ++uiProgressive; + } + + EXC_CATCHSUB(""); + } + + { + EXC_TRYSUB("Periodic Chars Delete from List"); +//#if MT_ENGINES +// std::unique_lock lockTimeObj(pTimedObj->MT_CMUTEX); +//#endif + // Erase in chunks, call erase the least times possible. + if (!_vecPeriodicCharsToEraseFromList.empty()) + { + /* + g_Log.EventDebug("-- Start CHARPERIODICTICK. I need to remove %lu items:\n", _vecPeriodicCharsToEraseFromList.size()); + std::stringstream ss; + for (size_t elem : _vecPeriodicCharsToEraseFromList) + { + ss << elem << ' '; + } + g_Log.EventDebug("%s\n", ss.str().c_str()); + */ + + if (_vecPeriodicCharsToEraseFromList.size() > 1) { - if (pChar->_iTimePeriodicTick <= iCurTime) + size_t uiCurMapElemIdx = 0; + size_t uiCurVecElemIdx = 0; + // size_t uiSubRangeStartIdx = 0; + CharTickList::iterator itSubRangeStart = _mCharTickList.begin(); + CharTickList::iterator itMap = _mCharTickList.begin(); + bool fContiguous = true; + bool fFirstMatch = false; + while ((itMap != _mCharTickList.end()) && + (uiCurMapElemIdx <= _vecPeriodicCharsToEraseFromList.back()) && + (uiCurVecElemIdx < _vecPeriodicCharsToEraseFromList.size())) { - vecObjs.emplace_back(static_cast(pChar)); - pChar->_iTimePeriodicTick = 0; + if (!fFirstMatch) + { + if (uiCurMapElemIdx == _vecPeriodicCharsToEraseFromList[uiCurVecElemIdx]) + { + //uiSubRangeStartIdx = uiCurMapElemIdx; + itSubRangeStart = itMap; + fFirstMatch = true; + + ++uiCurVecElemIdx; + } + + ++itMap; + ++uiCurMapElemIdx; + continue; + } + + if (uiCurMapElemIdx == _vecPeriodicCharsToEraseFromList[uiCurVecElemIdx]) + { + // Matches. I want to delete this. + if (uiCurMapElemIdx == _vecPeriodicCharsToEraseFromList[uiCurVecElemIdx - 1] + 1) + { + // I want to delete it and it's contiguous, go on + ASSERT(fContiguous); + } + else + { + // It isn't contiguous. Go below. + ASSERT(!fContiguous); + // This is the first one that matches after previous mismatches. We start this chunk from here. + //uiSubRangeStartIdx = uiCurMapElemIdx; + fContiguous = true; + } + + ++itMap; + ++uiCurMapElemIdx; + ++uiCurVecElemIdx; + continue; + } + + // Not contiguous to the next element to be erased (stored in the vector). + // What to do? + if (uiCurMapElemIdx != _vecPeriodicCharsToEraseFromList[uiCurVecElemIdx]) + { + // I don't want to erase this. + if (!fContiguous) + { + // This is an element after the first one successive to the previous contiguous block (2nd, 3rd...) + // Ignore it. + //g_Log.EventDebug("Skip this %lu\n", uiCurMapElemIdx); + ++itMap; + } + else + { + // This is the first element after the previous contiguous block + // I want to erase until the previous one + // erase doesn't delete the last element in the range + //g_Log.EventDebug("Skip this %lu, not to be deleted, and...\n", uiCurMapElemIdx); + //g_Log.EventDebug("Erasing %lu items starting from pos %lu\n", (uiCurMapElemIdx - uiSubRangeStartIdx), uiSubRangeStartIdx); + + itMap = _mCharTickList.erase(itSubRangeStart, itMap); + ++itMap; + itSubRangeStart = itMap; + //uiSubRangeStartIdx = uiCurMapElemIdx; // Not really needed + fContiguous = false; + } + ++uiCurMapElemIdx; + continue; + } + + ASSERT(false); // Shouldn't really be here. } - /* - else + if (fFirstMatch && fContiguous) { - // This shouldn't happen... If it does, get rid of the entry on the list anyways, - // it got desynchronized in some way and might be an invalid or even deleted and deallocated object! + /*itMap =*/ _mCharTickList.erase(itSubRangeStart, itMap); // last range to erase + //g_Log.EventDebug("(End cycle) Erasing %lu items starting from pos %lu\n", (uiCurMapElemIdx - uiSubRangeStartIdx), uiSubRangeStartIdx); } - */ - it = cont.erase(it); - itContEnd = cont.end(); } else { - ++it; + _mCharTickList.erase(std::next(_mCharTickList.begin(), _vecPeriodicCharsToEraseFromList.front())); + //g_Log.EventDebug("Erasing 1 item.\n"); } } - if (cont.empty()) - { - itList = _mCharTickList.erase(itList); - itListEnd = _mCharTickList.end(); - } - else - { - ++itList; - } - } - - EXC_CATCHSUB(""); + EXC_CATCHSUB("DeleteFromList"); + _vecPeriodicCharsToEraseFromList.clear(); } { EXC_TRYSUB("Char Periodic Ticks Loop"); - for (void* pObjVoid : vecObjs) // Loop through all msecs stored, unless we passed the timestamp. + for (void* pObjVoid : _vecObjs) // Loop through all msecs stored, unless we passed the timestamp. { CChar* pChar = static_cast(pObjVoid); if (pChar->OnTickPeriodic()) @@ -598,6 +832,7 @@ void CWorldTicker::Tick() } } EXC_CATCHSUB(""); + _vecObjs.clear(); } EXC_CATCH; diff --git a/src/game/CWorldTicker.h b/src/game/CWorldTicker.h index 40b29489a..6a70df3f0 100644 --- a/src/game/CWorldTicker.h +++ b/src/game/CWorldTicker.h @@ -10,6 +10,10 @@ #include //#include +#ifdef ADDRESS_SANITIZER + #define MYASAN_ +#endif + #ifdef _WIN32 #undef SRWLOCK_INIT #endif @@ -17,11 +21,18 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshift-count-overflow" #endif + +// TODO: TEMPORARY !! +#undef ADDRESS_SANITIZER #include +#ifdef MYASAN_ + #define ADDRESS_SANITIZER +#endif + #ifdef __GNUC__ #pragma GCC diagnostic pop #endif -//#include +#include @@ -37,24 +48,20 @@ class CWorldTicker ~CWorldTicker() = default; private: - using TimedObjectsContainer = std::vector; - struct WorldTickList : public std::map - //struct WorldTickList : public fc::vector_map + struct WorldTickList : public phmap::btree_multimap { - THREAD_CMUTEX_DEF; + MT_CMUTEX_DEF; }; - using TimedCharsContainer = std::vector; - struct CharTickList : public std::map - //struct CharTickList : public fc::vector_map + struct CharTickList : public phmap::btree_multimap { - THREAD_CMUTEX_DEF; + MT_CMUTEX_DEF; }; struct StatusUpdatesList : public phmap::parallel_flat_hash_set //struct StatusUpdatesList : public std::unordered_set { - THREAD_CMUTEX_DEF; + MT_CMUTEX_DEF; }; WorldTickList _mWorldTickList; @@ -63,12 +70,21 @@ class CWorldTicker friend class CWorldTickingList; StatusUpdatesList _ObjStatusUpdates; // objects that need OnTickStatusUpdate called + // Reuse the same container (using void pointers statically casted) to avoid unnecessary reallocations. + std::vector _vecObjs; + // "Index" in the multimap + std::vector _vecWorldObjsToEraseFromList; + // "Index" in the multimap + std::vector _vecPeriodicCharsToEraseFromList; + + //---- + friend class CWorld; friend class CWorldTimedFunctions; CTimedFunctionHandler _TimedFunctions; // CTimedFunction Container/Wrapper CWorldClock* _pWorldClock; - int64 _iLastTickDone; + int64 _iLastTickDone; public: void Tick(); diff --git a/src/game/CWorldTickingList.cpp b/src/game/CWorldTickingList.cpp index 99ec12bcc..119259240 100644 --- a/src/game/CWorldTickingList.cpp +++ b/src/game/CWorldTickingList.cpp @@ -38,15 +38,21 @@ void CWorldTickingList::DelObjStatusUpdate(CObjBase* pObj, bool fNeedsLock) // s void CWorldTickingList::ClearTickingLists() // static { { - std::unique_lock lock(g_World._Ticker._mWorldTickList.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(g_World._Ticker._mWorldTickList.MT_CMUTEX); +#endif g_World._Ticker._mWorldTickList.clear(); } { - std::unique_lock lock(g_World._Ticker._mCharTickList.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(g_World._Ticker._mCharTickList.MT_CMUTEX); +#endif g_World._Ticker._mCharTickList.clear(); } { - std::unique_lock lock(g_World._Ticker._ObjStatusUpdates.THREAD_CMUTEX); +#if MT_ENGINES + std::unique_lock lock(g_World._Ticker._ObjStatusUpdates.MT_CMUTEX); +#endif g_World._Ticker._ObjStatusUpdates.clear(); } -} \ No newline at end of file +} diff --git a/src/game/chars/CChar.cpp b/src/game/chars/CChar.cpp index d2b5cca3d..60c3bba6d 100644 --- a/src/game/chars/CChar.cpp +++ b/src/game/chars/CChar.cpp @@ -294,6 +294,8 @@ CChar::CChar( CREID_TYPE baseID ) : _iRegenTickCount = 0; _iTimeLastCallGuards = 0; + m_fIgnoreNextPetCmd = 0; + m_zClimbHeight = 0; m_fClimbUpdated = false; _wPrev_Hue = HUE_DEFAULT; @@ -368,7 +370,9 @@ CChar::~CChar() void CChar::DeleteCleanup(bool fForce) { ADDTOCALLSTACK("CChar::DeleteCleanup"); - _fDeleting = true; + // Clean up CChar specific data. Not virtual method. + + //_uiInternalStateFlags |= SF_DELETING; // We don't want to have invalid pointers over there // Already called by CObjBase::DeletePrepare -> CObjBase::_GoSleep @@ -431,8 +435,8 @@ bool CChar::NotifyDelete(bool fForce) void CChar::DeletePrepare() { ADDTOCALLSTACK("CChar::DeletePrepare"); - CContainer::ContentDelete(true); // This object and its contents need to be deleted on the same tick - CObjBase::DeletePrepare(); + CContainer::ContentDelete(true); // This object and its contents need to be deleted on the same tick + CObjBase::DeletePrepare(); } bool CChar::Delete(bool fForce) @@ -450,6 +454,7 @@ bool CChar::Delete(bool fForce) pClient->GetNetState()->markReadClosed(); } + DeletePrepare(); DeleteCleanup(fForce); // not virtual if (m_pPlayer && fForce) @@ -501,7 +506,7 @@ void CChar::ClientAttach( CClient * pClient ) return; ASSERT(m_pPlayer); - m_pPlayer->_iTimeLastUsed = CWorldGameTime::GetCurrentTime().GetTimeRaw(); + m_pPlayer->_iTimeLastUsedMs = CWorldGameTime::GetCurrentTime().GetTimeRaw(); m_pClient = pClient; FixClimbHeight(); @@ -523,7 +528,7 @@ void CChar::SetDisconnected(CSector* pNewSector) if (m_pPlayer) { - m_pPlayer->_iTimeLastDisconnected = CWorldGameTime::GetCurrentTime().GetTimeRaw(); + m_pPlayer->_iTimeLastDisconnectedMs = CWorldGameTime::GetCurrentTime().GetTimeRaw(); } if (m_pParty) @@ -657,7 +662,7 @@ bool CChar::SetNPCBrain( NPCBRAIN_TYPE NPCBrain ) // RETURN: invalid code. int CChar::IsWeird() const { - ADDTOCALLSTACK_INTENSIVE("CChar::IsWeird"); + ADDTOCALLSTACK_DEBUG("CChar::IsWeird"); int iResultCode = CObjBase::IsWeird(); if ( iResultCode ) return iResultCode; @@ -786,11 +791,13 @@ bool CChar::_IsStatFlag(uint64 uiStatFlag) const noexcept return (_uiStatFlag & uiStatFlag); } */ +#if MT_ENGINES bool CChar::IsStatFlag( uint64 uiStatFlag) const noexcept { -// THREAD_SHARED_LOCK_SET; + MT_ENGINE_SHARED_LOCK_SET; return (_uiStatFlag & uiStatFlag); } +#endif /* void CChar::_StatFlag_Set( uint64 uiStatFlag) noexcept @@ -800,7 +807,7 @@ void CChar::_StatFlag_Set( uint64 uiStatFlag) noexcept */ void CChar::StatFlag_Set(uint64 uiStatFlag) noexcept { -// THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; _uiStatFlag |= uiStatFlag; } @@ -812,7 +819,7 @@ void CChar::_StatFlag_Clear(uint64 uiStatFlag) noexcept */ void CChar::StatFlag_Clear(uint64 uiStatFlag) noexcept { -// THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; _uiStatFlag &= ~uiStatFlag; } @@ -827,7 +834,7 @@ void CChar::_StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept */ void CChar::StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept { -// THREAD_UNIQUE_LOCK_SET; +// MT_ENGINE_UNIQUE_LOCK_SET; // _StatFlag_Mod(uiStatFlag, fMod); if (fMod) _uiStatFlag |= uiStatFlag; @@ -875,7 +882,7 @@ void CChar::SetVisualRange(byte newSight) { CClient* pClient; { - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; // max value is 18 on classic clients prior 7.0.55.27 version and 24 on enhanced clients and latest classic clients m_iVisualRange = minimum(newSight, g_Cfg.m_iMapViewSizeMax); pClient = GetClientActive(); @@ -1467,7 +1474,7 @@ bool CChar::SetName( lpctstr pszName ) height_t CChar::GetHeightMount( bool fEyeSubstract ) const { - ADDTOCALLSTACK_INTENSIVE("CChar::GetHeightMount"); + ADDTOCALLSTACK_DEBUG("CChar::GetHeightMount"); height_t height = GetHeight(); if ( IsStatFlag(STATF_ONHORSE|STATF_HOVERING) ) height += 4; @@ -1478,7 +1485,7 @@ height_t CChar::GetHeightMount( bool fEyeSubstract ) const height_t CChar::GetHeight() const { - ADDTOCALLSTACK_INTENSIVE("CChar::GetHeight"); + ADDTOCALLSTACK_DEBUG("CChar::GetHeight"); if ( m_height ) //set by a dynamic variable (On=@Create Height=10) return m_height; @@ -2241,7 +2248,7 @@ bool CChar::r_GetRef( lpctstr & ptcKey, CScriptObj * & pRef ) return ( CObjBase::r_GetRef( ptcKey, pRef )); } -enum CHC_TYPE +enum CHC_TYPE : int { #define ADD(a,b) CHC_##a, #include "../../tables/CChar_props.tbl" @@ -3326,6 +3333,18 @@ bool CChar::r_LoadVal( CScript & s ) EXC_SET_BLOCK("Keyword"); lpctstr ptcKey = s.GetKey(); + + if (!strnicmp("FOLLOWER", ptcKey, 8)) + { + if (ptcKey[8] == '.') + { + ptcKey = ptcKey + 4; + CUID ptcArg = CUID(s.GetArgDWVal()); + m_followers.emplace_back(ptcArg); + return true; + } + } + CHC_TYPE iKeyNum = (CHC_TYPE) FindTableHeadSorted( ptcKey, sm_szLoadKeys, ARRAY_COUNT( sm_szLoadKeys )-1 ); if ( iKeyNum < 0 ) { @@ -3698,7 +3717,7 @@ bool CChar::r_LoadVal( CScript & s ) if (g_Serv.IsLoading()) { // Don't set STATF_SAVEPARITY at server startup, otherwise the first worldsave will not save these chars - _uiStatFlag = s.GetArgLLVal() & ~STATF_SAVEPARITY; + _uiStatFlag = s.GetArgLLVal() & ~ (uint64)STATF_SAVEPARITY; break; } // Don't modify STATF_SAVEPARITY, STATF_PET, STATF_SPAWNED here @@ -3945,6 +3964,13 @@ void CChar::r_Write( CScript & s ) CObjBase::r_Write(s); + for (CUID uid : m_followers) { + dword dUID = (dword)uid; + char *pszTag = Str_GetTemp(); + snprintf(pszTag, Str_TempLength(), "FOLLOWER.%d", dUID); + s.WriteKeyHex(pszTag, dUID); + } + if (iValLastHit != 0) { pVarLastHit->SetValNum(iValLastHit); @@ -4009,7 +4035,8 @@ void CChar::r_Write( CScript & s ) The character action is one of the valid skill OR The character action is one of the NPC Action that uses ACTARG1/ACTARG2/ACTARG3 */ - if ((action > SKILL_NONE && action < SKILL_QTY) || action == NPCACT_FLEE || action == NPCACT_TALK || action == NPCACT_TALK_FOLLOW || action == NPCACT_RIDDEN) + if ((action > SKILL_NONE && action < SKILL_QTY) || + action == NPCACT_FLEE || action == NPCACT_TALK || action == NPCACT_TALK_FOLLOW || action == NPCACT_RIDDEN) { if (m_atUnk.m_dwArg1 != 0) s.WriteKeyHex("ACTARG1", m_atUnk.m_dwArg1); diff --git a/src/game/chars/CChar.h b/src/game/chars/CChar.h index bad507182..6a7a52fcd 100644 --- a/src/game/chars/CChar.h +++ b/src/game/chars/CChar.h @@ -41,13 +41,24 @@ enum NPCBRAIN_TYPE // General AI type. NPCBRAIN_QTY }; + +enum WAR_SWING_TYPE : int // m_Act_War_Swing_State +{ + WAR_SWING_INVALID = -1, + WAR_SWING_EQUIPPING = 0, // we are recoiling our weapon. + WAR_SWING_READY, // we can swing at any time. + WAR_SWING_SWINGING, // we are swinging our weapon. + //-- + WAR_SWING_EQUIPPING_NOWAIT = 10 // Special return value for CChar::Fight_Hit, DON'T USE IT IN SCRIPTS! +}; + class CChar : public CObjBase, public CContainer, public CTextConsole { // RES_WORLDCHAR friend class CWorldTicker; - // THREAD_CMUTEX_DEF; // It inherits from CObjBase which inherits CTimedObject, which already has a class mutex. + // MT_CMUTEX_DEF; // It inherits from CObjBase which inherits CTimedObject, which already has a class mutex. private: // Spell type effects. @@ -192,6 +203,8 @@ class CChar : public CObjBase, public CContainer, public CTextConsole CPointMap m_Act_p; // Moving to this location. or location of forge we are working on. int m_StepStealth; // Max steps allowed to walk invisible while using Stealth skill + std::vector m_followers; + // Args related to specific actions type (m_Act_SkillCurrent) union { @@ -328,8 +341,17 @@ class CChar : public CObjBase, public CContainer, public CTextConsole // Status and attributes ------------------------------------ virtual int IsWeird() const override; +#if MT_ENGINES //protected: bool _IsStatFlag(uint64 uiStatFlag) const noexcept; public: bool IsStatFlag(uint64 uiStatFlag) const noexcept; +#else +public: + // called very frequently, it's wise to inline it if we can + inline bool IsStatFlag(uint64 uiStatFlag) const noexcept + { + return (_uiStatFlag & uiStatFlag); + } +#endif //protected: void _StatFlag_Set(uint64 uiStatFlag) noexcept; public: void StatFlag_Set(uint64 uiStatFlag) noexcept; @@ -468,7 +490,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; bool MoveToRegion(CRegionWorld* pNewArea, bool fAllowReject); bool MoveToRegionReTest( dword dwType ); - bool MoveToChar(const CPointMap& pt, bool fStanding = true, bool fCheckLocation = true, bool fForceFix = false, bool fAllowReject = true); + bool MoveToChar(const CPointMap& pt, bool fStanding = true, bool fCheckLocationEffects = true, bool fForceFix = false, bool fAllowReject = true); virtual bool MoveTo(const CPointMap& pt, bool fForceFix = false) override; virtual void SetTopZ( char z ) override; virtual bool MoveNearObj( const CObjBaseTemplate *pObj, ushort iSteps = 0 ) override; @@ -480,7 +502,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; bool CanStandAt(CPointMap *ptDest, const CRegion* pArea, uint64 uiMyMovementFlags, height_t uiMyHeight, CServerMapBlockingState* blockingState, bool fPathfinding) const; CRegion * CanMoveWalkTo( CPointMap & pt, bool fCheckChars = true, bool fCheckOnly = false, DIR_TYPE dir = DIR_QTY, bool fPathFinding = false ); void CheckRevealOnMove(); - TRIGRET_TYPE CheckLocation( bool fStanding = false ); + TRIGRET_TYPE CheckLocationEffects(bool fStanding); public: // Client Player specific stuff. ------------------------- @@ -503,8 +525,8 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; bool IsNPC() const; bool SetNPCBrain( NPCBRAIN_TYPE NPCBrain ); NPCBRAIN_TYPE GetNPCBrain() const; - NPCBRAIN_TYPE GetNPCBrainGroup() const; // Return NPCBRAIN_ANIMAL for animals, _HUMAN for NPC human and PCs, >= _MONSTER for monsters - NPCBRAIN_TYPE GetNPCBrainAuto() const; // Guess default NPC brain + NPCBRAIN_TYPE GetNPCBrainGroup() const noexcept; // Return NPCBRAIN_ANIMAL for animals, _HUMAN for NPC human and PCs, >= _MONSTER for monsters + NPCBRAIN_TYPE GetNPCBrainAuto() const noexcept; // Guess default NPC brain void ClearNPC(); @@ -689,7 +711,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; * * @return true if I am. */ - bool Noto_IsMurderer() const; + bool Noto_IsMurderer() const noexcept; /** * @brief I'm evil? diff --git a/src/game/chars/CCharAct.cpp b/src/game/chars/CCharAct.cpp index 1d7ec23b7..71ae37737 100644 --- a/src/game/chars/CCharAct.cpp +++ b/src/game/chars/CCharAct.cpp @@ -17,6 +17,7 @@ #include "../CWorld.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../CWorldTickingList.h" #include "../spheresvr.h" #include "../triggers.h" @@ -40,7 +41,7 @@ bool CChar::TeleportToObj( int iType, tchar * pszArgs ) { if ( pszArgs[0] && iType == 1 ) dwUID = 0; - iArg = RES_GET_INDEX( Exp_GetVal( pszArgs )); + iArg = ResGetIndex( Exp_GetVal( pszArgs )); } while ( dwCount-- ) @@ -396,9 +397,10 @@ void CChar::LayerAdd( CItem * pItem, LAYER_TYPE layer ) void CChar::OnRemoveObj( CSObjContRec* pObRec ) // Override this = called when removed from list. { ADDTOCALLSTACK("CChar::OnRemoveObj"); + + ASSERT(pObRec); + ASSERT(dynamic_cast(pObRec)); CItem * pItem = static_cast (pObRec); - if ( !pItem ) - return; LAYER_TYPE layer = pItem->GetEquipLayer(); if (( IsTrigUsed(TRIGGER_UNEQUIP) ) || ( IsTrigUsed(TRIGGER_ITEMUNEQUIP) )) @@ -1307,14 +1309,14 @@ void CChar::UpdateMove( const CPointMap & ptOld, CClient * pExcludeClient, bool m_fStatusUpdate &= ~SU_UPDATE_MODE; EXC_TRY("UpdateMove"); - + // if skill is meditation, cancel it if we move if (g_Cfg._fMeditationMovementAbort && Skill_GetActive() == SKILL_MEDITATION) { //cancel meditation if we move Skill_Fail(true); } - + EXC_SET_BLOCK("FOR LOOP"); ClientIterator it; for ( CClient* pClient = it.next(); pClient != nullptr; pClient = it.next() ) @@ -1592,7 +1594,7 @@ void CChar::SoundChar( CRESND_TYPE type ) default: if (id < 0x4D6) // before the crane sound the sound IDs are ordered in a way... id += (SOUND_TYPE)type; - else if (id < 0x5D5) // starting with the crane and ending before absymal infernal there's another scheme + else if (id < 0x5D4) // starting with the crane and ending before absymal infernal there's another scheme { switch (type) { @@ -1843,10 +1845,10 @@ int CChar::ItemPickup(CItem * pItem, word amount) char iStackMaxZ = GetTopZ() + 16; CItem * pStack = nullptr; CPointMap ptNewPlace = pItem->GetTopPoint(); - CWorldSearch AreaItems(ptNewPlace); + auto AreaItems = CWorldSearchHolder::GetInstance(ptNewPlace); for (;;) { - pStack = AreaItems.GetItem(); + pStack = AreaItems->GetItem(); if ( pStack == nullptr ) break; if (( pStack->GetTopZ() <= pItem->GetTopZ()) || ( pStack->GetTopZ() > iStackMaxZ )) @@ -2040,15 +2042,15 @@ bool CChar::ItemDrop( CItem * pItem, const CPointMap & pt ) CPointMap ptStack = pt; const char iStackMaxZ = block.m_Top.m_z; //pt.m_z + 16; const CItem * pStack = nullptr; - CWorldSearch AreaItems(ptStack); - pStack = AreaItems.GetItem(); + auto AreaItems = CWorldSearchHolder::GetInstance(ptStack); + pStack = AreaItems->GetItem(); if (pStack != nullptr) //If there nothing on the ground, drop the item normally and flip it if it's possible { for (uint i = 0;; ++i) { if (i != 0) //on first iteration, pStack already contain the item on the ground. If you getitem again, you'll obtain nullptr { - pStack = AreaItems.GetItem(); + pStack = AreaItems->GetItem(); } if (pStack == nullptr) { @@ -2772,7 +2774,7 @@ bool CChar::Horse_Mount(CChar *pHorse) { CScriptTriggerArgs Args(pHorse); Args.m_iN1 = memoryId; - if ( OnTrigger(CTRIG_Mount, this, &Args) == TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_Mount, this, &Args) == TRIGRET_RET_TRUE ) return false; else memoryId = ITEMID_TYPE(Args.m_iN1);//(ITEMID_TYPE) Args.m_iN1; @@ -3296,11 +3298,11 @@ bool CChar::Death() // Remove the characters which I can't see as dead from the screen if (g_Cfg.m_fDeadCannotSeeLiving) { - CWorldSearch AreaChars(GetTopPoint(), g_Cfg.m_iMapViewSizeMax); - AreaChars.SetSearchSquare(true); + auto AreaChars = CWorldSearchHolder::GetInstance(GetTopPoint(), g_Cfg.m_iMapViewSizeMax); + AreaChars->SetSearchSquare(true); for (;;) { - CChar *pChar = AreaChars.GetChar(); + CChar *pChar = AreaChars->GetChar(); if (!pChar) break; if (!CanSeeAsDead(pChar)) @@ -3403,10 +3405,10 @@ bool CChar::ShoveCharAtPosition(CPointMap const& ptDst, ushort *uiStaminaRequire ushort uiLocalStamReq = 0; CItem *pPoly = LayerFind(LAYER_SPELL_Polymorph); - CWorldSearch AreaChars(ptDst); + auto AreaChars = CWorldSearchHolder::GetInstance(ptDst); for (;;) { - CChar *pChar = AreaChars.GetChar(); + CChar *pChar = AreaChars->GetChar(); if (!pChar) break; if (pChar->Can(CAN_C_STATUE)) @@ -3638,15 +3640,21 @@ void CChar::CheckRevealOnMove() } // We are at this location. What will happen? -// This function is called at every second on ALL chars +// This function is called at every second (or more) on ALL chars // (even walking or not), so avoid heavy codes here. // RETURN: // true = we can move there // false = we can't move there // default = we teleported -TRIGRET_TYPE CChar::CheckLocation( bool fStanding ) +TRIGRET_TYPE CChar::CheckLocationEffects(bool fStanding) { - ADDTOCALLSTACK("CChar::CheckLocation"); + ADDTOCALLSTACK("CChar::CheckLocationEffects"); + // This can also be called from char periodic ticks (not classic timer). + + static thread_local uint _uiRecursingStep = 0; + static thread_local uint _uiRecursingItemStep = 0; + static constexpr uint _kuiRecursingStepLimit = 20; + static constexpr uint _kuiRecursingItemStepLimit = 20; CClient *pClient = GetClientActive(); if ( pClient && pClient->m_pHouseDesign ) @@ -3658,146 +3666,180 @@ TRIGRET_TYPE CChar::CheckLocation( bool fStanding ) pClient->m_pHouseDesign->EndCustomize(true); } - if ( !fStanding ) - { - SKILL_TYPE iSkillActive = Skill_GetActive(); - if ( g_Cfg.IsSkillFlag(iSkillActive, SKF_IMMOBILE) ) - Skill_Fail(false); - else if ( g_Cfg.IsSkillFlag(iSkillActive, SKF_FIGHT) && g_Cfg.IsSkillFlag(iSkillActive, SKF_RANGED) && !IsSetCombatFlags(COMBAT_ARCHERYCANMOVE) && !IsStatFlag(STATF_ARCHERCANMOVE) ) - { - // Keep timer active holding the swing action until the char stops moving - m_atFight.m_iWarSwingState = WAR_SWING_EQUIPPING; - _SetTimeoutD(1); - } + if (!fStanding) + { + SKILL_TYPE iSkillActive = Skill_GetActive(); + if (g_Cfg.IsSkillFlag(iSkillActive, SKF_IMMOBILE)) + { + Skill_Fail(false); + } + else if (g_Cfg.IsSkillFlag(iSkillActive, SKF_FIGHT) && g_Cfg.IsSkillFlag(iSkillActive, SKF_RANGED) && !IsSetCombatFlags(COMBAT_ARCHERYCANMOVE) && !IsStatFlag(STATF_ARCHERCANMOVE)) + { + // Keep timer active holding the swing action until the char stops moving + m_atFight.m_iWarSwingState = WAR_SWING_EQUIPPING; + _SetTimeoutD(1); + } - // This could get REALLY EXPENSIVE ! - if ( m_pArea && IsTrigUsed(TRIGGER_STEP) ) //Check if m_pArea is exists because it may be invalid if it try to walk while multi removing? - { - if ( m_pArea->OnRegionTrigger( this, RTRIG_STEP ) == TRIGRET_RET_TRUE ) - return TRIGRET_RET_FALSE; + if (_uiRecursingStep >= _kuiRecursingStepLimit) + { + g_Log.EventError("Calling recursively @STEP for more than %u times. Skipping trigger call.\n", _kuiRecursingStepLimit); + } + else + { + if (m_pArea && IsTrigUsed(TRIGGER_STEP)) //Check if m_pArea is exists because it may be invalid if it try to walk while multi removing? + { + _uiRecursingStep += 1; + if (m_pArea->OnRegionTrigger(this, RTRIG_STEP) == TRIGRET_RET_TRUE) + { + _uiRecursingStep -= 1; + return TRIGRET_RET_FALSE; + } - CRegion *pRoom = GetTopPoint().GetRegion(REGION_TYPE_ROOM); - if ( pRoom && pRoom->OnRegionTrigger( this, RTRIG_STEP ) == TRIGRET_RET_TRUE ) - return TRIGRET_RET_FALSE; - } - } + CRegion *pRoom = GetTopPoint().GetRegion(REGION_TYPE_ROOM); + if (pRoom && pRoom->OnRegionTrigger(this, RTRIG_STEP) == TRIGRET_RET_TRUE) + { + _uiRecursingStep -= 1; + return TRIGRET_RET_FALSE; + } + _uiRecursingStep -= 1; + } + } + } - bool fStepCancel = false; - bool fSpellHit = false; - CWorldSearch AreaItems( GetTopPoint() ); - for (;;) - { - CItem *pItem = AreaItems.GetItem(); - if ( !pItem ) - break; + bool fStepCancel = false; + bool fSpellHit = false; + auto AreaItems = CWorldSearchHolder::GetInstance(GetTopPoint()); + for (;;) + { + CItem *pItem = AreaItems->GetItem(); + if (!pItem) + break; - int zdiff = pItem->GetTopZ() - GetTopZ(); - int height = pItem->Item_GetDef()->GetHeight(); - if ( height < 3 ) - height = 3; + int zdiff = pItem->GetTopZ() - GetTopZ(); + int height = pItem->Item_GetDef()->GetHeight(); + if (height < 3) + height = 3; - if ( (zdiff > height) || (zdiff < -3) ) - continue; - if ( IsTrigUsed(TRIGGER_STEP) || IsTrigUsed(TRIGGER_ITEMSTEP) ) - { - CScriptTriggerArgs Args(fStanding ? 1 : 0); - TRIGRET_TYPE iRet = pItem->OnTrigger(ITRIG_STEP, this, &Args); - if ( iRet == TRIGRET_RET_TRUE ) // block walk - { - fStepCancel = true; - continue; - } - if ( iRet == TRIGRET_RET_HALFBAKED ) // allow walk, skipping hardcoded checks below - continue; - } + if ((zdiff > height) || (zdiff < -3)) + continue; - switch ( pItem->GetType() ) - { - case IT_WEB: - if ( fStanding ) - continue; - if ( Use_Item_Web(pItem) ) // we got stuck in a spider web - return TRIGRET_RET_TRUE; - continue; - case IT_FIRE: - { - int iSkillLevel = pItem->m_itSpell.m_spelllevel; // heat level (0-1000) - iSkillLevel = g_Rand.GetVal2(iSkillLevel/2, iSkillLevel); - if ( IsStatFlag(STATF_FLY) ) - iSkillLevel /= 2; + if (IsTrigUsed(TRIGGER_STEP) || IsTrigUsed(TRIGGER_ITEMSTEP)) + { + if (_uiRecursingItemStep >= _kuiRecursingItemStepLimit) + { + g_Log.EventError("Calling recursively @ITEMSTEP for more than %u times. Skipping trigger call.\n", _kuiRecursingStepLimit); + } + else + { + _uiRecursingItemStep += 1; + CScriptTriggerArgs Args(fStanding ? 1 : 0); + TRIGRET_TYPE iRet = pItem->OnTrigger(ITRIG_STEP, this, &Args); + _uiRecursingItemStep -= 1; + if (iRet == TRIGRET_RET_TRUE) // block walk + { + fStepCancel = true; + continue; + } + if (iRet == TRIGRET_RET_HALFBAKED) // allow walk, skipping hardcoded checks below + continue; + } + } - int iDmg = OnTakeDamage( g_Cfg.GetSpellEffect(SPELL_Fire_Field, iSkillLevel), nullptr, DAMAGE_FIRE|DAMAGE_GENERAL, 0, 100, 0, 0, 0 ); - if (iDmg > 0) + switch (pItem->GetType()) + { + case IT_WEB: + if (fStanding) + continue; + if (Use_Item_Web(pItem)) // we got stuck in a spider web + return TRIGRET_RET_TRUE; + continue; + + case IT_FIRE: + { + int iSkillLevel = pItem->m_itSpell.m_spelllevel; // heat level (0-1000) + iSkillLevel = g_Rand.GetVal2(iSkillLevel / 2, iSkillLevel); + if (IsStatFlag(STATF_FLY)) + iSkillLevel /= 2; + + int iDmg = OnTakeDamage(g_Cfg.GetSpellEffect(SPELL_Fire_Field, iSkillLevel), nullptr, DAMAGE_FIRE | DAMAGE_GENERAL, 0, 100, 0, 0, 0); + if (iDmg > 0) + { + Sound(0x15f); // fire noise + if (m_pNPC && fStanding) { - Sound(0x15f); // fire noise - if ( m_pNPC && fStanding ) - { - m_Act_p.Move((DIR_TYPE)(g_Rand.GetVal(DIR_QTY))); - NPC_WalkToPoint(true); // run away from the threat - } + m_Act_p.Move((DIR_TYPE)(g_Rand.GetVal(DIR_QTY))); + NPC_WalkToPoint(true); // run away from the threat } - } - continue; - case IT_SPELL: - // Workaround: only hit 1 spell on each loop. If we hit all spells (eg: multiple field spells) - // it will allow weird exploits like cast many Fire Fields on the same spot to take more damage, - // or Paralyze Field + Fire Field to make the target get stuck forever being damaged with no way - // to get out of the field, since the damage won't allow cast any spell and the Paralyze Field - // will immediately paralyze again with 0ms delay at each damage tick. - // On OSI if the player cast multiple fields on the same tile, it will remove the previous field - // tile that got overlapped. But Sphere doesn't use this method, so this workaround is needed. - if (fSpellHit) + } + } + continue; + + case IT_SPELL: + // Workaround: only hit 1 spell on each loop. If we hit all spells (eg: multiple field spells) + // it will allow weird exploits like cast many Fire Fields on the same spot to take more damage, + // or Paralyze Field + Fire Field to make the target get stuck forever being damaged with no way + // to get out of the field, since the damage won't allow cast any spell and the Paralyze Field + // will immediately paralyze again with 0ms delay at each damage tick. + // On OSI if the player cast multiple fields on the same tile, it will remove the previous field + // tile that got overlapped. But Sphere doesn't use this method, so this workaround is needed. + if (fSpellHit) continue; - fSpellHit = OnSpellEffect((SPELL_TYPE)(RES_GET_INDEX(pItem->m_itSpell.m_spell)), - pItem->m_uidLink.CharFind(), pItem->m_itSpell.m_spelllevel, pItem); + fSpellHit = + OnSpellEffect((SPELL_TYPE)(ResGetIndex(pItem->m_itSpell.m_spell)), pItem->m_uidLink.CharFind(), pItem->m_itSpell.m_spelllevel, pItem); if (fSpellHit && m_pNPC && fStanding) { m_Act_p.Move((DIR_TYPE)(g_Rand.GetVal(DIR_QTY))); - NPC_WalkToPoint(true); // run away from the threat + NPC_WalkToPoint(true); // run away from the threat } - continue; - case IT_TRAP: - case IT_TRAP_ACTIVE: + continue; + + case IT_TRAP: + case IT_TRAP_ACTIVE: { - int iDmg = OnTakeDamage( pItem->Use_Trap(), nullptr, DAMAGE_HIT_BLUNT|DAMAGE_GENERAL ); - if ( (iDmg > 0) && m_pNPC && fStanding ) + int iDmg = OnTakeDamage(pItem->Use_Trap(), nullptr, DAMAGE_HIT_BLUNT | DAMAGE_GENERAL); + if ((iDmg > 0) && m_pNPC && fStanding) { m_Act_p.Move((DIR_TYPE)(g_Rand.GetVal(DIR_QTY))); - NPC_WalkToPoint(true); // run away from the threat + NPC_WalkToPoint(true); // run away from the threat } continue; } - case IT_SWITCH: - if ( pItem->m_itSwitch.m_wStep ) - Use_Item(pItem); - continue; - case IT_MOONGATE: - case IT_TELEPAD: - if ( fStanding ) - continue; - Use_MoonGate(pItem); - return TRIGRET_RET_DEFAULT; - case IT_SHIP_PLANK: - case IT_ROPE: - if ( !fStanding && !IsStatFlag(STATF_HOVERING) && !pItem->IsAttr(ATTR_STATIC) ) - { - // Check if we can go out of the ship (in the same direction of plank) + + case IT_SWITCH: + if (pItem->m_itSwitch.m_wStep) + Use_Item(pItem); + continue; + + case IT_MOONGATE: + case IT_TELEPAD: + if (fStanding) + continue; + Use_MoonGate(pItem); + return TRIGRET_RET_DEFAULT; + + case IT_SHIP_PLANK: + case IT_ROPE: + if (!fStanding && !IsStatFlag(STATF_HOVERING) && !pItem->IsAttr(ATTR_STATIC)) + { + // Check if we can go out of the ship (in the same direction of plank) //bool fFromShip = (nullptr != GetTopSector()->GetRegion(GetTopPoint(), REGION_TYPE_SHIP)); // always true - if ( MoveToValidSpot(m_dirFace, g_Cfg.m_iMaxShipPlankTeleport, 1, true) ) - { - //pItem->SetTimeoutS(5); // autoclose the plank behind us - return TRIGRET_RET_TRUE; - } - } - continue; - default: - continue; - } - } + if (MoveToValidSpot(m_dirFace, g_Cfg.m_iMaxShipPlankTeleport, 1, true)) + { + //pItem->SetTimeoutS(5); // autoclose the plank behind us + return TRIGRET_RET_TRUE; + } + } + continue; + + default: + continue; + } + } + + if (fStepCancel) + return TRIGRET_RET_FALSE; - if (fStepCancel) - return TRIGRET_RET_FALSE; if (fStanding) return TRIGRET_RET_TRUE; @@ -3819,18 +3861,19 @@ TRIGRET_TYPE CChar::CheckLocation( bool fStanding ) if ( m_pNPC->m_Brain == NPCBRAIN_GUARD ) { // Guards won't gate into unguarded areas. - const CRegionWorld *pArea = dynamic_cast(pTeleport->_ptDst.GetRegion(REGION_TYPE_MULTI|REGION_TYPE_AREA)); + auto pArea = dynamic_cast(pTeleport->_ptDst.GetRegion(REGION_TYPE_MULTI|REGION_TYPE_AREA)); if ( !pArea || (!pArea->IsGuarded() && !IsSetOF(OF_GuardOutsideGuardedArea)) ) return TRIGRET_RET_FALSE; } if ( Noto_IsCriminal() ) { // wont teleport to guarded areas. - const CRegionWorld *pArea = dynamic_cast(pTeleport->_ptDst.GetRegion(REGION_TYPE_MULTI|REGION_TYPE_AREA)); + auto pArea = dynamic_cast(pTeleport->_ptDst.GetRegion(REGION_TYPE_MULTI|REGION_TYPE_AREA)); if ( !pArea || pArea->IsGuarded() ) return TRIGRET_RET_FALSE; } } + Spell_Teleport(pTeleport->_ptDst, true, false, false); return TRIGRET_RET_DEFAULT; } @@ -4017,8 +4060,12 @@ bool CChar::MoveToRegionReTest( dword dwType ) // This could be us just taking a step or being teleported. // Low level: DOES NOT UPDATE DISPLAYS or container flags. (may be offline) // This does not check for gravity. -bool CChar::MoveToChar(const CPointMap& pt, bool fStanding, bool fCheckLocation, bool fForceFix, bool fAllowReject) +bool CChar::MoveToChar(const CPointMap& pt, bool fStanding, bool fCheckLocationEffects, bool fForceFix, bool fAllowReject) { + // WARNING: If you are using fCheckLocationEffects = true, be sure to NOT create situations where this call to CheckLocationEffects + // makes it recursively call itself (moving to something that moves again the char and so on). + // Using CheckLocationEffects here is often not necessary, since it's called at each char PeriodicTick. + ADDTOCALLSTACK("CChar::MoveToChar"); if ( !pt.IsValidPoint() ) @@ -4068,7 +4115,7 @@ bool CChar::MoveToChar(const CPointMap& pt, bool fStanding, bool fCheckLocation, } } - if (fCheckLocation && (CheckLocation(fStanding) == TRIGRET_RET_FALSE) && ptOld.IsValidPoint()) + if (fCheckLocationEffects && (CheckLocationEffects(fStanding) == TRIGRET_RET_FALSE) && ptOld.IsValidPoint()) { SetTopPoint(ptOld); return false; @@ -4078,8 +4125,10 @@ bool CChar::MoveToChar(const CPointMap& pt, bool fStanding, bool fCheckLocation, bool CChar::MoveTo(const CPointMap& pt, bool fForceFix) { + ADDTOCALLSTACK_DEBUG("CChar::MoveTo"); + m_fClimbUpdated = false; // update climb height - return MoveToChar(pt, true, true, fForceFix); + return MoveToChar(pt, true, false, fForceFix); } void CChar::SetTopZ( char z ) @@ -4432,7 +4481,7 @@ TRIGRET_TYPE CChar::OnTrigger( CTRIG_TYPE trigger, CTextConsole * pSrc, CScriptT // process m_fStatusUpdate flags void CChar::OnTickStatusUpdate() { - //ADDTOCALLSTACK_INTENSIVE("CChar::OnTickStatusUpdate"); + //ADDTOCALLSTACK_DEBUG("CChar::OnTickStatusUpdate"); EXC_TRYSUB("CChar::OnTickStatusUpdate"); if ( IsClientActive() ) @@ -4537,7 +4586,7 @@ void CChar::OnTickSkill() bool CChar::_CanTick(bool fParentGoingToSleep) const { - ADDTOCALLSTACK_INTENSIVE("CChar::_CanTick"); + ADDTOCALLSTACK_DEBUG("CChar::_CanTick"); EXC_TRY("Can tick?"); if (IsDisconnected() && (Skill_GetActive() != NPCACT_RIDDEN)) @@ -4595,7 +4644,7 @@ bool CChar::_OnTick() if (!_CanTick()) { ASSERT(!_IsSleeping()); - if (GetTopSector()->IsSleeping() && !g_Rand.GetVal(15)) + if (GetTopSector()->IsSleeping() && !g_Rand.Get16ValFast(15)) { _SetTimeout(1); //Make it tick after sector's awakening. _GoSleep(); @@ -4726,7 +4775,7 @@ bool CChar::OnTickPeriodic() { // Check location periodically for standing in fire fields, traps, etc. EXC_SET_BLOCK("check location"); - CheckLocation(true); + CheckLocationEffects(true); } EXC_SET_BLOCK("update stats"); diff --git a/src/game/chars/CCharAttacker.cpp b/src/game/chars/CCharAttacker.cpp index d582548d5..c43e8ecbd 100644 --- a/src/game/chars/CCharAttacker.cpp +++ b/src/game/chars/CCharAttacker.cpp @@ -116,7 +116,7 @@ int CChar::Attacker_GetHighestThreat() const { ADDTOCALLSTACK("CChar::Attacker_GetHighestThreat"); if (m_lastAttackers.empty()) - return -1; + return 0; int highThreat = 0; for (const LastAttackers & refAttacker : m_lastAttackers) diff --git a/src/game/chars/CCharFight.cpp b/src/game/chars/CCharFight.cpp index d267573d8..a26ad8a39 100644 --- a/src/game/chars/CCharFight.cpp +++ b/src/game/chars/CCharFight.cpp @@ -6,7 +6,7 @@ #include "../components/CCPropsChar.h" #include "../components/CCPropsItemWeapon.h" #include "../CWorldGameTime.h" -#include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CChar.h" #include "CCharNPC.h" @@ -101,10 +101,10 @@ bool CChar::CheckCrimeSeen( SKILL_TYPE SkillToSee, CChar * pCharMark, const CObj if (m_pNPC && m_pNPC->m_Brain == NPCBRAIN_GUARD) // guards only fight for justice, they can't commit a crime!!? return false; - CWorldSearch AreaChars( GetTopPoint(), g_Cfg.m_iMapViewSize ); + auto AreaChars = CWorldSearchHolder::GetInstance( GetTopPoint(), g_Cfg.m_iMapViewSize ); for (;;) { - CChar * pChar = AreaChars.GetChar(); + CChar * pChar = AreaChars->GetChar(); if ( pChar == nullptr ) break; if (this == pChar) // Ignore the player himself. @@ -183,8 +183,8 @@ void CChar::CallGuards() // We don't have any target yet, let's check everyone nearby CChar * pCriminal; - CWorldSearch AreaCrime(GetTopPoint(), g_Cfg.m_iMapViewSize); - while ((pCriminal = AreaCrime.GetChar()) != nullptr) + auto AreaCrime = CWorldSearchHolder::GetInstance(GetTopPoint(), g_Cfg.m_iMapViewSize); + while ((pCriminal = AreaCrime->GetChar()) != nullptr) { if (pCriminal == this) continue; @@ -232,9 +232,9 @@ bool CChar::CallGuards( CChar * pCriminal ) else { // Search for a free guards nearby - CWorldSearch AreaGuard(GetTopPoint(), UO_MAP_VIEW_SIGHT); + auto AreaGuard = CWorldSearchHolder::GetInstance(GetTopPoint(), UO_MAP_VIEW_SIGHT); CChar *pGuardFound = nullptr; - while ((pGuardFound = AreaGuard.GetChar()) != nullptr) + while ((pGuardFound = AreaGuard->GetChar()) != nullptr) { if (pGuardFound->m_pNPC && (pGuardFound->m_pNPC->m_Brain == NPCBRAIN_GUARD) && // Char found must be a guard (pGuardFound->m_Fight_Targ_UID == pCriminal->GetUID() || !pGuardFound->IsStatFlag(STATF_WAR))) // and will be eligible to fight this target if it's not already on a fight or if its already attacking this target (to avoid spamming docens of guards at the same target). @@ -298,7 +298,7 @@ void CChar::OnHarmedBy( CChar * pCharSrc ) // In war mode already if ( m_pPlayer ) return; - if ( g_Rand.GetVal( 10 )) + if ( g_Rand.Get16ValFast( 10 )) return; // NPC will Change targets. } @@ -720,10 +720,10 @@ int CChar::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uType, int iDmgPhys // pre-AOS armor rating (AR) int iArmorRating = pCharDef->m_defense + m_defense; - int iArMax = iArmorRating * g_Rand.GetVal2(7,35) / 100; + int iArMax = iArmorRating * g_Rand.Get16Val2Fast(7,35) / 100; int iArMin = iArMax / 2; - int iDef = g_Rand.GetVal2( iArMin, (iArMax - iArMin) + 1 ); + int iDef = g_Rand.GetVal2Fast( iArMin, (iArMax - iArMin) + 1 ); if ( uType & DAMAGE_MAGIC ) // magical damage halves effectiveness of defense iDef /= 2; @@ -734,7 +734,7 @@ int CChar::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uType, int iDmgPhys } CScriptTriggerArgs Args( iDmg, uType, (int64)(0) ); - Args.m_VarsLocal.SetNum("ItemDamageLayer", sm_ArmorDamageLayers[g_Rand.GetVal(ARRAY_COUNT(sm_ArmorDamageLayers))]); + Args.m_VarsLocal.SetNum("ItemDamageLayer", sm_ArmorDamageLayers[(size_t)g_Rand.Get16ValFast(ARRAY_COUNT(sm_ArmorDamageLayers))]); Args.m_VarsLocal.SetNum("ItemDamageChance", 25); Args.m_VarsLocal.SetNum("Spell", (int)spell); @@ -1025,13 +1025,13 @@ void CChar::OnTakeDamageInflictArea(int iDmg, CChar* pSrc, DAMAGE_TYPE uType, in if (IsAosFlagEnabled(FEATURE_AOS_DAMAGE)) iDistance=10; // 5 for ML and 10 for aos - CWorldSearch AreaChars(GetTopPoint(), iDistance); + auto AreaChars = CWorldSearchHolder::GetInstance(GetTopPoint(), iDistance); for (;;) //pSrc = Char make the attack //pChar = Char scanned on the loop iteration //this = Char get the initial hit { - CChar* pChar = AreaChars.GetChar(); + CChar* pChar = AreaChars->GetChar(); if (!pChar) break; if ((pChar == this) || (pChar == pSrc)) //This char already receive the base hit. Damage already done @@ -1079,7 +1079,7 @@ byte CChar::GetRangeH() const // What sort of weapon am i using? SKILL_TYPE CChar::Fight_GetWeaponSkill() const { - ADDTOCALLSTACK_INTENSIVE("CChar::Fight_GetWeaponSkill"); + ADDTOCALLSTACK_DEBUG("CChar::Fight_GetWeaponSkill"); const CItem * pWeapon = m_uidWeapon.ItemFind(); if ( pWeapon == nullptr ) return SKILL_WRESTLING; @@ -1088,7 +1088,7 @@ SKILL_TYPE CChar::Fight_GetWeaponSkill() const DAMAGE_TYPE CChar::Fight_GetWeaponDamType(const CItem* pWeapon) const { - ADDTOCALLSTACK_INTENSIVE("CChar::Fight_GetWeaponDamType"); + ADDTOCALLSTACK_DEBUG("CChar::Fight_GetWeaponDamType"); DAMAGE_TYPE iDmgType = DAMAGE_HIT_BLUNT; if ( pWeapon ) { @@ -1987,7 +1987,7 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if ( pAmmo && m_pPlayer ) { - if (40 >= g_Rand.GetVal(100)) + if (40 >= g_Rand.Get16ValFast(100)) { pAmmo->UnStackSplit(1); pAmmo->MoveToDecay(pCharTarg->GetTopPoint(), g_Cfg.m_iDecay_Item); @@ -2009,12 +2009,12 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if ( g_Cfg.IsSkillFlag(skill, SKF_RANGED) ) { static constexpr SOUND_TYPE sm_Snd_Miss_Ranged[] = { 0x233, 0x238 }; - iSound = sm_Snd_Miss_Ranged[g_Rand.GetVal(ARRAY_COUNT(sm_Snd_Miss_Ranged))]; + iSound = sm_Snd_Miss_Ranged[(size_t)g_Rand.Get16ValFast(ARRAY_COUNT(sm_Snd_Miss_Ranged))]; } else { static constexpr SOUND_TYPE sm_Snd_Miss[] = { 0x238, 0x239, 0x23a }; - iSound = sm_Snd_Miss[g_Rand.GetVal(ARRAY_COUNT(sm_Snd_Miss))]; + iSound = sm_Snd_Miss[(size_t)g_Rand.Get16ValFast(ARRAY_COUNT(sm_Snd_Miss))]; } } Sound(iSound); @@ -2155,7 +2155,7 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if ( pAmmo ) { - if ( pCharTarg->m_pNPC && (40 >= g_Rand.GetVal(100)) ) + if ( pCharTarg->m_pNPC && (40 >= g_Rand.Get16ValFast(100)) ) { pAmmo->UnStackSplit(1); pCharTarg->ItemBounce(pAmmo, false); @@ -2249,7 +2249,7 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if ( pPoly && pPoly->m_itSpell.m_spell == SPELL_Wraith_Form ) uiManaDrain += 5 + (15 * Skill_GetBase(SKILL_SPIRITSPEAK) / 1000); } - if ( GetPropNum(pCCPChar, PROPCH_HITMANADRAIN, pBaseCCPChar) > g_Rand.GetLLVal(100) ) + if ( GetPropNum(pCCPChar, PROPCH_HITMANADRAIN, pBaseCCPChar) > g_Rand.GetVal(100) ) uiManaDrain += (ushort)IMulDivLL(iDmg, 20, 100); // leech 20% of damage value ushort uiTargMana = pCharTarg->Stat_GetVal(STAT_INT); @@ -2269,40 +2269,40 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if (pWeapon) { - if (GetPropNum(pCCPChar, PROPCH_HITAREAPHYSICAL, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITAREAPHYSICAL, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnTakeDamageInflictArea(iDmg / 2, this, DAMAGE_HIT_BLUNT, 100, 0, 0, 0, 0, static_cast(0x32), static_cast(0x10E)); bool fElemental = IsSetCombatFlags(COMBAT_ELEMENTAL_ENGINE); if (fElemental) { - if (GetPropNum(pCCPChar, PROPCH_HITAREAFIRE, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITAREAFIRE, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnTakeDamageInflictArea(iDmg / 2, this, DAMAGE_FIRE, 0, 100, 0, 0, 0, static_cast(0x488), static_cast(0x11D)); - if (GetPropNum(pCCPChar, PROPCH_HITAREACOLD, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITAREACOLD, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnTakeDamageInflictArea(iDmg / 2, this, DAMAGE_COLD, 0, 0, 100, 0, 0, static_cast(0x834), static_cast(0xFC)); - if (GetPropNum(pCCPChar, PROPCH_HITAREAPOISON, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITAREAPOISON, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnTakeDamageInflictArea(iDmg / 2, this, DAMAGE_POISON, 0, 0, 0, 100, 0, static_cast(0x48E), static_cast(0x205)); - if (GetPropNum(pCCPChar, PROPCH_HITAREAENERGY, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITAREAENERGY, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnTakeDamageInflictArea(iDmg / 2, this, DAMAGE_ENERGY, 0, 0, 0, 0, 100, static_cast(0x78), static_cast(0x1F1)); } - if (GetPropNum(pCCPChar, PROPCH_HITDISPEL, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITDISPEL, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnSpellEffect(SPELL_Dispel, this, Skill_GetAdjusted(SKILL_MAGERY), pWeapon); - if (GetPropNum(pCCPChar, PROPCH_HITFIREBALL, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITFIREBALL, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnSpellEffect(SPELL_Fireball, this, Skill_GetAdjusted(SKILL_MAGERY), pWeapon); - if (GetPropNum(pCCPChar, PROPCH_HITHARM, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITHARM, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnSpellEffect(SPELL_Harm, this, Skill_GetAdjusted(SKILL_MAGERY), pWeapon); - if (GetPropNum(pCCPChar, PROPCH_HITLIGHTNING, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITLIGHTNING, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnSpellEffect(SPELL_Lightning, this, Skill_GetAdjusted(SKILL_MAGERY), pWeapon); - if (GetPropNum(pCCPChar, PROPCH_HITMAGICARROW, pBaseCCPChar) > g_Rand.GetLLVal(100)) + if (GetPropNum(pCCPChar, PROPCH_HITMAGICARROW, pBaseCCPChar) > g_Rand.GetVal(100)) pCharTarg->OnSpellEffect(SPELL_Magic_Arrow, this, Skill_GetAdjusted(SKILL_MAGERY), pWeapon); } @@ -2310,11 +2310,11 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if ( pCharTarg->_wBloodHue != (HUE_TYPE)(-1) ) { static constexpr ITEMID_TYPE sm_Blood[] = { ITEMID_BLOOD1, ITEMID_BLOOD2, ITEMID_BLOOD3, ITEMID_BLOOD4, ITEMID_BLOOD5, ITEMID_BLOOD6, ITEMID_BLOOD_SPLAT }; - const int iBloodQty = (g_Cfg.m_iFeatureSE & FEATURE_SE_UPDATE) ? g_Rand.GetVal2(4, 5) : g_Rand.GetVal2(1, 2); + const int iBloodQty = (g_Cfg.m_iFeatureSE & FEATURE_SE_UPDATE) ? g_Rand.Get16Val2Fast(4, 5) : g_Rand.Get16Val2Fast(1, 2); for ( int i = 0; i < iBloodQty; ++i ) { - const ITEMID_TYPE iBloodID = sm_Blood[g_Rand.GetVal(ARRAY_COUNT(sm_Blood))]; + const ITEMID_TYPE iBloodID = sm_Blood[(size_t)g_Rand.Get16ValFast(ARRAY_COUNT(sm_Blood))]; CItem *pBlood = CItem::CreateBase(iBloodID); ASSERT(pBlood); diff --git a/src/game/chars/CCharLOS.cpp b/src/game/chars/CCharLOS.cpp index beb9d8542..f49421f22 100644 --- a/src/game/chars/CCharLOS.cpp +++ b/src/game/chars/CCharLOS.cpp @@ -1,6 +1,7 @@ #include "../../common/CLog.h" #include "../uo_files/CUOTerrainInfo.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "CChar.h" #include @@ -424,10 +425,10 @@ bool CChar::CanSeeLOS_New( const CPointMap &ptDst, CPointMap *pptBlock, int iMax { if ( !((flags & LOS_NB_LOCAL_DYNAMIC) && (pSrcRegion == pNowRegion)) ) { - CWorldSearch AreaItems(ptNow, 0); + auto AreaItems = CWorldSearchHolder::GetInstance(ptNow, 0); for (;;) { - pItem = AreaItems.GetItem(); + pItem = AreaItems->GetItem(); if ( !pItem ) break; if ( pItem->GetUnkPoint().m_x != ptNow.m_x || pItem->GetUnkPoint().m_y != ptNow.m_y ) diff --git a/src/game/chars/CCharMemory.cpp b/src/game/chars/CCharMemory.cpp index 5e643b8f6..4b32b0ac7 100644 --- a/src/game/chars/CCharMemory.cpp +++ b/src/game/chars/CCharMemory.cpp @@ -172,8 +172,8 @@ void CChar::Memory_AddTypes( CItemMemory * pMemory, word MemTypes ) if ( pMemory ) { pMemory->SetMemoryTypes( pMemory->GetMemoryTypes() | MemTypes ); - pMemory->m_itEqMemory.m_pt = GetTopPoint(); // Where did the fight start ? - pMemory->SetTimeStamp(CWorldGameTime::GetCurrentTime().GetTimeRaw()); + pMemory->m_itEqMemory.m_pt = static_cast(GetTopPoint()); // Where did the fight start ? + pMemory->SetTimeStampS(CWorldGameTime::GetCurrentTime().GetTimeRaw()); Memory_UpdateFlags( pMemory ); } } @@ -432,7 +432,7 @@ bool CChar::Memory_Fight_OnTick( CItemMemory * pMemory ) return true; } - const int64 iTimeDiff = CWorldGameTime::GetCurrentTime().GetTimeDiff( pMemory->GetTimeStamp() ); + const int64 iTimeDiff = CWorldGameTime::GetCurrentTime().GetTimeDiff( pMemory->GetTimeStampS() ); // If am fully healthy then it's not much of a fight. if ( iTimeDiff > 60*60*MSECS_PER_SEC ) diff --git a/src/game/chars/CCharNPC.cpp b/src/game/chars/CCharNPC.cpp index 6b9a476c7..347111caf 100644 --- a/src/game/chars/CCharNPC.cpp +++ b/src/game/chars/CCharNPC.cpp @@ -282,22 +282,21 @@ void CChar::NPC_LoadScript( bool fRestock ) CCharBase * pCharDef = Char_GetDef(); - // 1) CHARDEF trigger - if ( m_pPlayer == nullptr ) // CHARDEF triggers (based on body type) - { - CChar * pChar = this->GetChar(); - if ( pChar != nullptr ) - { - CUID uidOldAct = pChar->m_Act_UID; - pChar->m_Act_UID = GetUID(); - pChar->ReadScriptReducedTrig(pCharDef, CTRIG_Create); - pChar->m_Act_UID = uidOldAct; - } - } - //This remains untouched but moved after the chardef's section - if ( fRestock && IsTrigUsed(TRIGGER_NPCRESTOCK) ) - ReadScriptReducedTrig(pCharDef, CTRIG_NPCRestock); - + CChar * pChar = this->GetChar(); + if (pChar != nullptr) + { + // 1) CHARDEF trigger + if ( m_pPlayer == nullptr ) // CHARDEF triggers (based on body type) + { + CUID uidOldAct = pChar->m_Act_UID; + pChar->m_Act_UID = GetUID(); + pChar->ReadScriptReducedTrig(pCharDef, CTRIG_Create); + pChar->m_Act_UID = uidOldAct; + } + //This remains untouched but moved after the chardef's section + if ( fRestock && IsTrigUsed(TRIGGER_NPCRESTOCK) ) + pChar->ReadScriptReducedTrig(pCharDef, CTRIG_NPCRestock); + } CreateNewCharCheck(); //This one is giving stats, etc to the char, so we can read/set them in the next triggers. } diff --git a/src/game/chars/CCharNPC.h b/src/game/chars/CCharNPC.h index 9b8811ee4..94ce26bb2 100644 --- a/src/game/chars/CCharNPC.h +++ b/src/game/chars/CCharNPC.h @@ -26,23 +26,22 @@ class CCharNPC // Stuff that is specific to an NPC character instance (not an NPC type see CCharBase for that). // Any NPC AI stuff will go here. static lpctstr const sm_szVerbKeys[]; + static lpctstr const sm_szLoadKeys[]; NPCBRAIN_TYPE m_Brain; // For NPCs: Number of the assigned basic AI block word m_Home_Dist_Wander; // Distance to allow to "wander". byte m_Act_Motivation; // 0-100 (100=very greatly) how bad do i want to do the current action. bool m_bonded; // Bonded pet - CResourceRefArray m_Speech; // Speech fragment list (other stuff we know): We respond to what we hear with this. - - CResourceQty m_Need; // What items might i need/Desire ? (coded as resource scripts) ex "10 gold,20 logs" etc. + int64 m_timeRestock; // when last restock happened in sell/buy container - static lpctstr const sm_szLoadKeys[]; + CResourceRefArray m_Speech; // Speech fragment list (other stuff we know): We respond to what we hear with this. + CResourceQty m_Need; // What items might i need/Desire ? (coded as resource scripts) ex "10 gold,20 logs" etc. short m_nextX[MAX_NPC_PATH_STORAGE_SIZE]; // array of X coords of the next step short m_nextY[MAX_NPC_PATH_STORAGE_SIZE]; // array of Y coords of the next step CPointMap m_nextPt; // where the array(^^) wants to go, if changed, recount the path - int64 m_timeRestock; // when last restock happened in sell/buy container struct Spells { SPELL_TYPE id; @@ -63,13 +62,13 @@ class CCharNPC bool IsVendor() const; int GetNpcAiFlags( const CChar *pChar ) const; + public: CCharNPC( CChar * pChar, NPCBRAIN_TYPE NPCBrain ); ~CCharNPC(); -private: - CCharNPC(const CCharNPC& copy); - CCharNPC& operator=(const CCharNPC& other); + CCharNPC(const CCharNPC& copy) = delete; + CCharNPC& operator=(const CCharNPC& other) = delete; }; diff --git a/src/game/chars/CCharNPCAct.cpp b/src/game/chars/CCharNPCAct.cpp index d22904b2d..eb79497c2 100644 --- a/src/game/chars/CCharNPCAct.cpp +++ b/src/game/chars/CCharNPCAct.cpp @@ -7,6 +7,7 @@ #include "../clients/CClient.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CCharNPC.h" @@ -502,10 +503,10 @@ int CChar::NPC_WalkToPoint( bool fRun ) else point = ptFirstTry; // Scan point for items that could be moved by me and move them to my position - CWorldSearch AreaItems(point); + auto AreaItems = CWorldSearchHolder::GetInstance(point); for (;;) { - CItem *pItem = AreaItems.GetItem(); + CItem *pItem = AreaItems->GetItem(); if ( !pItem ) break; else if ( abs(pItem->GetTopZ() - pMe.m_z) > 3 ) continue; // item is too high else if ( !pItem->Can(CAN_I_BLOCK) ) continue; // this item not blocking me @@ -571,7 +572,8 @@ int CChar::NPC_WalkToPoint( bool fRun ) CheckRevealOnMove(); EXC_SET_BLOCK("MoveToChar"); - if (!MoveToChar(pMe, false, true)) + //if (!MoveToChar(pMe, false, true)) + if (!MoveToChar(pMe, false, false)) return 2; EXC_SET_BLOCK("Move Update"); @@ -1107,10 +1109,10 @@ bool CChar::NPC_LookAround( bool fForceCheckItems ) // Any interesting chars here ? int iDist = 0; CChar *pChar = nullptr; - CWorldSearch AreaChars(ptTop, iRange); + auto AreaChars = CWorldSearchHolder::GetInstance(ptTop, iRange); for (;;) { - pChar = AreaChars.GetChar(); + pChar = AreaChars->GetChar(); if ( !pChar ) break; if ( pChar == this ) // just myself. @@ -1136,10 +1138,10 @@ bool CChar::NPC_LookAround( bool fForceCheckItems ) if ( fForceCheckItems ) { CItem *pItem = nullptr; - CWorldSearch AreaItems(ptTop, iRange); + auto AreaItems = CWorldSearchHolder::GetInstance(ptTop, iRange); for (;;) { - pItem = AreaItems.GetItem(); + pItem = AreaItems->GetItem(); if ( !pItem ) break; @@ -1278,8 +1280,10 @@ bool CChar::NPC_Act_Follow(bool fFlee, int maxDistance, bool fMoveAway) //If the NPC action is following somebody, directly assign the character from the m_Act_UID value. if (Skill_GetActive() == NPCACT_FOLLOW_TARG) pChar = m_Act_UID.CharFind(); - else + else if (Fight_IsActive()) pChar = m_Fight_Targ_UID.IsValidUID() ? m_Fight_Targ_UID.CharFind() : m_Act_UID.CharFind(); + else + pChar = m_Act_UID.CharFind(); if (pChar == nullptr) { // free to do as i wish ! @@ -1730,10 +1734,10 @@ bool CChar::NPC_Act_Food() // Search for food nearby iSearchDistance = (UO_MAP_VIEW_SIGHT * ( 100 - iFoodLevel ) ) / 100; - CWorldSearch AreaItems(GetTopPoint(), minimum(iSearchDistance,m_pNPC->m_Home_Dist_Wander)); + auto AreaItems = CWorldSearchHolder::GetInstance(GetTopPoint(), minimum(iSearchDistance,m_pNPC->m_Home_Dist_Wander)); for (;;) { - CItem * pItem = AreaItems.GetItem(); + CItem * pItem = AreaItems->GetItem(); if ( !pItem ) break; if ( !CanSee(pItem) ) @@ -2450,10 +2454,10 @@ void CChar::NPC_Food() // Search for food nearby EXC_SET_BLOCK("searching nearby"); iSearchDistance = (UO_MAP_VIEW_SIGHT * ( 100 - iFoodLevel ) ) / 100; - CWorldSearch AreaItems(ptMe, minimum(iSearchDistance, m_pNPC->m_Home_Dist_Wander)); + auto AreaItems = CWorldSearchHolder::GetInstance(ptMe, minimum(iSearchDistance, m_pNPC->m_Home_Dist_Wander)); for (;;) { - CItem *pItem = AreaItems.GetItem(); + CItem *pItem = AreaItems->GetItem(); if ( !pItem ) break; if ( !CanSee(pItem) || pItem->IsAttr(ATTR_MOVE_NEVER|ATTR_STATIC|ATTR_LOCKEDDOWN|ATTR_SECURE) ) diff --git a/src/game/chars/CCharNPCAct_Fight.cpp b/src/game/chars/CCharNPCAct_Fight.cpp index 881b99487..3b9133dc2 100644 --- a/src/game/chars/CCharNPCAct_Fight.cpp +++ b/src/game/chars/CCharNPCAct_Fight.cpp @@ -230,10 +230,10 @@ void CChar::NPC_Act_Fight() break; case (TRIGRET_TYPE)(2) : { - SKILL_TYPE iSkillforced = (SKILL_TYPE)RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("skill")); + SKILL_TYPE iSkillforced = (SKILL_TYPE)ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("skill")); if (iSkillforced) { - SPELL_TYPE iSpellforced = (SPELL_TYPE)RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("spell")); + SPELL_TYPE iSpellforced = (SPELL_TYPE)ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("spell")); if (g_Cfg.IsSkillFlag(iSkillforced, SKF_MAGIC)) { m_atMagery.m_iSpell = iSpellforced; diff --git a/src/game/chars/CCharNPCAct_Magic.cpp b/src/game/chars/CCharNPCAct_Magic.cpp index aecb71817..d963de302 100644 --- a/src/game/chars/CCharNPCAct_Magic.cpp +++ b/src/game/chars/CCharNPCAct_Magic.cpp @@ -1,9 +1,10 @@ // Actions specific to an NPC. -#include "../CWorldMap.h" -#include "CCharNPC.h" #include "../../common/CScriptTriggerArgs.h" #include "../triggers.h" +#include "../CWorldMap.h" +#include "../CWorldSearch.h" +#include "CCharNPC.h" // Retrieves all the spells this character has to spells[x] list int CCharNPC::Spells_GetCount() @@ -314,12 +315,12 @@ bool CChar::NPC_FightCast(CObjBase * &pTarg, CObjBase * pSrc, SPELL_TYPE &spell, pFriend[1] = pFriend[2] = pFriend[3] = nullptr; iFriendIndex = 1; - if (NPC_GetAiFlags()&NPC_AI_COMBAT && !bIgnoreAITargetChoice) + if (HAS_FLAGS_STRICT(NPC_GetAiFlags(), NPC_AI_COMBAT) && !bIgnoreAITargetChoice) { - CWorldSearch AreaChars(GetTopPoint(), UO_MAP_VIEW_SIGHT); + auto AreaChars = CWorldSearchHolder::GetInstance(GetTopPoint(), UO_MAP_VIEW_SIGHT); for (;;) { - pTarget = AreaChars.GetChar(); + pTarget = AreaChars->GetChar(); if (!pTarget) break; diff --git a/src/game/chars/CCharNPCAct_Vendor.cpp b/src/game/chars/CCharNPCAct_Vendor.cpp index 38a6478c9..e5e83ad24 100644 --- a/src/game/chars/CCharNPCAct_Vendor.cpp +++ b/src/game/chars/CCharNPCAct_Vendor.cpp @@ -42,7 +42,7 @@ bool CChar::NPC_Vendor_Restock(bool bForce, bool bFillStock) bool bRestockNow = false; int64 iRestockDelay = 10 * 60 * MSECS_PER_SEC; // 10 Minutes delay - + if ( !bForce && (CWorldGameTime::GetCurrentTime().GetTimeDiff(m_pNPC->m_timeRestock) >= 0)) { bRestockNow = true; // restock timeout has expired, make it restock again (unless it's declared to do not restock in the bellow lines). @@ -59,7 +59,7 @@ bool CChar::NPC_Vendor_Restock(bool bForce, bool bFillStock) bRestockNow = false; } int64 iNextRestock = CWorldGameTime::GetCurrentTime().GetTimeRaw() + iRestockDelay; - + // At restock the containers are actually emptied if ( bRestockNow ) { @@ -69,7 +69,7 @@ bool CChar::NPC_Vendor_Restock(bool bForce, bool bFillStock) if ( !pCont ) return false; - pCont->ClearContainer(); + pCont->ClearContainer(false); } bFillStock = true; // force the vendor to restock. } @@ -337,7 +337,7 @@ bool CChar::NPC_TrainSkill( CChar * pCharSrc, SKILL_TYPE skill, ushort uiAmountT ushort iTrain = uiAmountToTrain; if ( (pCharSrc->Skill_GetSum() + uiAmountToTrain) > pCharSrc->Skill_GetSumMax() ) - { + { for ( uint i = 0; i < g_Cfg.m_iMaxSkill; ++i ) { if ( !g_Cfg.m_SkillIndexDefs.valid_index((SKILL_TYPE)i) ) diff --git a/src/game/chars/CCharNPCStatus.cpp b/src/game/chars/CCharNPCStatus.cpp index 5315787af..bda2802d7 100644 --- a/src/game/chars/CCharNPCStatus.cpp +++ b/src/game/chars/CCharNPCStatus.cpp @@ -4,6 +4,7 @@ #include "../items/CItemVendable.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../spheresvr.h" #include "CChar.h" #include "CCharNPC.h" @@ -567,10 +568,10 @@ bool CChar::NPC_CheckWalkHere( const CPointMap & pt, const CRegion * pArea ) con } // Is there a nasty object here that will hurt us ? - CWorldSearch AreaItems(pt); + auto AreaItems = CWorldSearchHolder::GetInstance(pt); for (;;) { - CItem * pItem = AreaItems.GetItem(); + CItem * pItem = AreaItems->GetItem(); if ( pItem == nullptr ) break; diff --git a/src/game/chars/CCharNotoriety.cpp b/src/game/chars/CCharNotoriety.cpp index 3dbb70c29..4c2e3f6af 100644 --- a/src/game/chars/CCharNotoriety.cpp +++ b/src/game/chars/CCharNotoriety.cpp @@ -5,9 +5,8 @@ #include "CCharNPC.h" // I'm a murderer? -bool CChar::Noto_IsMurderer() const +bool CChar::Noto_IsMurderer() const noexcept { - ADDTOCALLSTACK("CChar::Noto_IsMurderer"); return ( m_pPlayer && (m_pPlayer->m_wMurders > g_Cfg.m_iMurderMinCount) ); } @@ -705,17 +704,20 @@ int64 CChar::NotoSave_GetTime( int id ) void CChar::NotoSave_Clear() { - ADDTOCALLSTACK("CChar::NotoSave_Clear"); if ( !m_notoSaves.empty() ) m_notoSaves.clear(); } void CChar::NotoSave_Update() { - ADDTOCALLSTACK("CChar::NotoSave_Update"); - NotoSave_Clear(); + //ADDTOCALLSTACK_DEBUG("CChar::NotoSave_Update"); + EXC_TRY("NotoSave_Update"); + + NotoSave_Clear(); UpdateMode(); UpdatePropertyFlag(); + + EXC_CATCH; } void CChar::NotoSave_CheckTimeout() diff --git a/src/game/chars/CCharPlayer.cpp b/src/game/chars/CCharPlayer.cpp index debef2773..3aea3915d 100644 --- a/src/game/chars/CCharPlayer.cpp +++ b/src/game/chars/CCharPlayer.cpp @@ -26,7 +26,7 @@ CCharPlayer::CCharPlayer(CChar *pChar, CAccount *pAccount) : m_SkillLock{}, m_StatLock{}, m_pAccount(pAccount) { - _iTimeLastUsed = _iTimeLastDisconnected = 0; + _iTimeLastUsedMs = _iTimeLastDisconnectedMs = 0; m_SpeechHue = m_EmoteHue = 0; m_wDeaths = m_wMurders = 0; @@ -48,8 +48,8 @@ CCharPlayer::~CCharPlayer() m_Speech.clear(); CMultiStorage* pOldStorage = _pMultiStorage; - _pMultiStorage = nullptr; - delete pOldStorage; // I need _pMultiStorage to be nullptr before CMultiStorage destructor is called + _pMultiStorage = nullptr; // I need _pMultiStorage to be nullptr before CMultiStorage destructor is called ! + delete pOldStorage; } CAccount * CCharPlayer::GetAccount() const @@ -239,10 +239,12 @@ bool CCharPlayer::r_WriteVal( CChar * pChar, lpctstr ptcKey, CSString & sVal ) sVal = m_Speech.ContainsResourceName(RES_SPEECH, ptcKey) ? "1" : "0"; return true; case CPC_LASTDISCONNECTED: - sVal.FormatLLVal(CWorldGameTime::GetCurrentTime().GetTimeDiff(_iTimeLastDisconnected) / MSECS_PER_SEC); //seconds + // Stored as ms, but printed as seconds + sVal.FormatLLVal(CWorldGameTime::GetCurrentTime().GetTimeDiff(_iTimeLastDisconnectedMs) / MSECS_PER_SEC); return true; case CPC_LASTUSED: - sVal.FormatLLVal( CWorldGameTime::GetCurrentTime().GetTimeDiff( _iTimeLastUsed ) / MSECS_PER_SEC ); //seconds + // Stored as ms, but printed as seconds + sVal.FormatLLVal( CWorldGameTime::GetCurrentTime().GetTimeDiff( _iTimeLastUsedMs ) / MSECS_PER_SEC ); return true; case CPC_LIGHT: sVal.FormatHex(m_LocalLight); @@ -460,12 +462,13 @@ bool CCharPlayer::r_LoadVal( CChar * pChar, CScript &s ) if ( pChar->IsClientActive() ) pChar->GetClientActive()->addKRToolbar( m_fKrToolbarEnabled ); return true; - // FIXME !!! OVERFLOW!!! NO NEED TO MULTIPLY EACH TIME !!! case CPC_LASTDISCONNECTED: - _iTimeLastDisconnected = s.GetArgLLVal() * MSECS_PER_SEC; + // Accepts seconds but stores as milliseconds. + _iTimeLastDisconnectedMs = s.GetArgLLVal() * MSECS_PER_SEC; return true; case CPC_LASTUSED: - _iTimeLastUsed = s.GetArgLLVal() * MSECS_PER_SEC; + // Accepts seconds but stores as milliseconds. + _iTimeLastUsedMs = s.GetArgLLVal() * MSECS_PER_SEC; return true; case CPC_PFLAG: { @@ -537,10 +540,10 @@ void CCharPlayer::r_WriteChar( CChar * pChar, CScript & s ) s.WriteKeyStr("ACCOUNT", pAccount->GetName()); - if (_iTimeLastUsed > 0) - s.WriteKeyVal("LASTUSED", _iTimeLastUsed); - if (_iTimeLastDisconnected > 0) - s.WriteKeyVal("LASTDISCONNECTED", _iTimeLastDisconnected); + if (_iTimeLastUsedMs > 0) + s.WriteKeyVal("LASTUSED", _iTimeLastUsedMs / MSECS_PER_SEC); + if (_iTimeLastDisconnectedMs > 0) + s.WriteKeyVal("LASTDISCONNECTED", _iTimeLastDisconnectedMs / MSECS_PER_SEC); if ( m_wDeaths ) s.WriteKeyVal( "DEATHS", m_wDeaths ); diff --git a/src/game/chars/CCharPlayer.h b/src/game/chars/CCharPlayer.h index 4178a2767..19a0eb08f 100644 --- a/src/game/chars/CCharPlayer.h +++ b/src/game/chars/CCharPlayer.h @@ -39,8 +39,8 @@ struct CCharPlayer static const char *m_sClassName; CAccount * m_pAccount; // The account index. (for idle players mostly) - int64 _iTimeLastUsed; // Time the player char was last used (connected). - int64 _iTimeLastDisconnected; + int64 _iTimeLastUsedMs; // Time the player char was last used (connected). + int64 _iTimeLastDisconnectedMs; CSString m_sProfile; // limited to SCRIPT_MAX_LINE_LEN-16 HUE_TYPE m_SpeechHue; // speech hue used (sent by client) diff --git a/src/game/chars/CCharSkill.cpp b/src/game/chars/CCharSkill.cpp index b633a98ad..158333da9 100644 --- a/src/game/chars/CCharSkill.cpp +++ b/src/game/chars/CCharSkill.cpp @@ -9,6 +9,7 @@ #include "../triggers.h" #include "../CServer.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "CChar.h" #include "CCharNPC.h" @@ -1030,7 +1031,7 @@ CItem * CChar::Skill_NaturalResource_Create( CItem * pResBit, SKILL_TYPE skill ) return nullptr; //Creating the 'id' variable with the local given through->by the trigger(s) instead on top of method - ITEMID_TYPE id = (ITEMID_TYPE)(RES_GET_INDEX( Args.m_VarsLocal.GetKeyNum("ResourceID"))); + ITEMID_TYPE id = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("ResourceID"))); wAmount = pResBit->ConsumeAmount( (word)(Args.m_iN1) ); // amount i used up. if ( wAmount <= 0 ) @@ -1123,7 +1124,7 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) if ( pOreDef->IsType( IT_ORE )) { - ITEMID_TYPE idIngot = (ITEMID_TYPE)(RES_GET_INDEX( pOreDef->m_ttOre.m_idIngot)); + ITEMID_TYPE idIngot = (ITEMID_TYPE)(ResGetIndex( pOreDef->m_ttOre.m_idIngot)); const CItemBase* pBaseDef = CItemBase::FindItemBase(idIngot); //Usually a lingot, but could be a a gem also. if (!pBaseDef) { @@ -1177,7 +1178,7 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) { tchar* pszTmp = Str_GetTemp(); snprintf(pszTmp, Str_TempLength(), "resource.%u.ID", (int)i); - const CItemBase* pBaseDef = CItemBase::FindItemBase((ITEMID_TYPE)(RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum(pszTmp)))); + const CItemBase* pBaseDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum(pszTmp)))); //We have finished the ore or the item being smelted. if (iOreQty <= 0) @@ -1703,11 +1704,11 @@ int CChar::Skill_DetectHidden( SKTRIG_TYPE stage ) else iRadius = iSkill / 100; //Default Sphere Detecting Hidden Radius. - CWorldSearch Area(GetTopPoint(), iRadius); - bool bFound = false; + auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), iRadius); + bool fFound = false; for (;;) { - CChar *pChar = Area.GetChar(); + CChar *pChar = Area->GetChar(); if ( pChar == nullptr ) break; if ( pChar == this || !pChar->IsStatFlag(STATF_INVISIBLE|STATF_HIDDEN) ) @@ -1721,10 +1722,10 @@ int CChar::Skill_DetectHidden( SKTRIG_TYPE stage ) pChar->Reveal(); SysMessagef(g_Cfg.GetDefaultMsg(DEFMSG_DETECTHIDDEN_SUCC), pChar->GetName()); - bFound = true; + fFound = true; } - if ( !bFound ) + if ( !fFound ) return -SKTRIG_FAIL; return 0; @@ -1813,10 +1814,10 @@ int CChar::Skill_Peacemaking( SKTRIG_TYPE stage ) { int peace = Skill_GetAdjusted(SKILL_PEACEMAKING); int iRadius = ( peace / 100 ) + 2; // 2..12 - CWorldSearch Area(GetTopPoint(), iRadius); + auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), iRadius); for (;;) { - CChar *pChar = Area.GetChar(); + CChar *pChar = Area->GetChar(); if ( pChar == nullptr ) return -SKTRIG_FAIL; if (( pChar == this ) || !CanSee(pChar) ) @@ -2112,7 +2113,7 @@ int CChar::Skill_Poisoning( SKTRIG_TYPE stage ) if ( stage == SKTRIG_FAIL ) return 0; // lose the poison sometimes ? - if ( RES_GET_INDEX(pPoison->m_itPotion.m_Type) != SPELL_Poison ) + if ( ResGetIndex(pPoison->m_itPotion.m_Type) != SPELL_Poison ) return -SKTRIG_ABORT; CItem * pItem = m_Act_Prv_UID.ItemFind(); @@ -3557,7 +3558,9 @@ int CChar::Skill_Stroke() int CChar::Skill_Stage( SKTRIG_TYPE stage ) { - ADDTOCALLSTACK("CChar::Skill_Stage"); + ADDTOCALLSTACK_DEBUG("CChar::Skill_Stage"); + EXC_TRY("Skill_Stage"); + SKILL_TYPE skill = Skill_GetActive(); if (g_Cfg.IsSkillFlag(skill, SKF_SCRIPTED)) return Skill_Scripted(stage); @@ -3677,6 +3680,9 @@ int CChar::Skill_Stage( SKTRIG_TYPE stage ) } SysMessageDefault(DEFMSG_SKILL_NOSKILL); + + EXC_CATCH; + return -SKTRIG_QTY; } @@ -3975,7 +3981,7 @@ bool CChar::Skill_Snoop_Check(const CItemContainer * pItem) case IT_SHIP_HOLD: // Must be on board a ship to open the hatch. ASSERT(m_pArea); - if (m_pArea->GetResourceID() != pItem->m_uidLink) + if (m_pArea->GetResourceID().GetObjUID() != pItem->m_uidLink.GetObjUID()) { SysMessage(g_Cfg.GetDefaultMsg(DEFMSG_ITEMUSE_HATCH_FAIL)); return true; diff --git a/src/game/chars/CCharSpell.cpp b/src/game/chars/CCharSpell.cpp index 580e71eae..c97448fc3 100644 --- a/src/game/chars/CCharSpell.cpp +++ b/src/game/chars/CCharSpell.cpp @@ -7,6 +7,7 @@ #include "../CServer.h" #include "../CWorld.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CChar.h" #include "CCharNPC.h" @@ -109,7 +110,7 @@ bool CChar::Spell_Teleport( CPointMap ptNew, bool fTakePets, bool fCheckAntiMagi return false; ptNew.m_z = GetFixZ(ptNew); - + if (!IsPriv(PRIV_GM)) { if ( g_Cfg.m_iMountHeight ) @@ -191,15 +192,15 @@ bool CChar::Spell_Teleport( CPointMap ptNew, bool fTakePets, bool fCheckAntiMagi } } - CPointMap ptOld = GetTopPoint(); + CPointMap ptOld(GetTopPoint()); if ( ptOld.IsValidPoint() ) // guards might have just been created { if ( fTakePets ) // look for any creatures that might be following me near by { - CWorldSearch Area(ptOld, UO_MAP_VIEW_SIGHT); + auto Area = CWorldSearchHolder::GetInstance(ptOld, UO_MAP_VIEW_SIGHT); for (;;) { - CChar * pChar = Area.GetChar(); + CChar * pChar = Area->GetChar(); if ( pChar == nullptr ) break; if ( pChar == this ) @@ -214,7 +215,7 @@ bool CChar::Spell_Teleport( CPointMap ptNew, bool fTakePets, bool fCheckAntiMagi } } - MoveToChar(ptNew, true, true); // move character + MoveToChar(ptNew, true, false); // move character CClient *pClient = GetClientActive(); CClient *pClientIgnore = nullptr; @@ -312,7 +313,7 @@ bool CChar::Spell_CreateGate(CPointMap ptDest, bool fCheckAntiMagic) pGateOrig->SetAttr(ATTR_MOVE_NEVER); pGateOrig->m_itNormal.m_more1 = (dword)GetUID(); pGateOrig->m_itTelepad.m_ptMark = ptDest; - + CItem *pGateDest = CItem::CreateBase(idDest); ASSERT(pGateDest); pGateDest->SetType(IT_TELEPAD); @@ -499,7 +500,7 @@ bool CChar::Spell_Resurrection(CItemCorpse * pCorpse, CChar * pCharSrc, bool fNo Effect(EFFECT_OBJ, pSpellDef->m_idEffect, this, 10, 16); Sound(pSpellDef->m_sound); } - + if (IsClientActive()) { CClient *pClient = GetClientActive(); @@ -528,7 +529,7 @@ void CChar::Spell_Effect_Remove(CItem * pSpell) if ( !pSpell || !pSpell->IsTypeSpellable() || pSpell->IsType(IT_WAND) ) return; - SPELL_TYPE spell = (SPELL_TYPE)(RES_GET_INDEX(pSpell->m_itSpell.m_spell)); + SPELL_TYPE spell = (SPELL_TYPE)(ResGetIndex(pSpell->m_itSpell.m_spell)); const CSpellDef *pSpellDef = g_Cfg.GetSpellDef(spell); if ( !spell || !pSpellDef ) return; @@ -950,7 +951,7 @@ void CChar::Spell_Effect_Add( CItem * pSpell ) if ( !pSpell || !pSpell->IsTypeSpellable() || pSpell->IsType(IT_WAND) ) return; - SPELL_TYPE spell = (SPELL_TYPE)(RES_GET_INDEX(pSpell->m_itSpell.m_spell)); + SPELL_TYPE spell = (SPELL_TYPE)(ResGetIndex(pSpell->m_itSpell.m_spell)); const CSpellDef *pSpellDef = g_Cfg.GetSpellDef(spell); if ( !spell || !pSpellDef ) return; @@ -1319,7 +1320,7 @@ void CChar::Spell_Effect_Add( CItem * pSpell ) CCPropsChar* pCCPChar = GetComponentProps(); CCPropsChar* pBaseCCPChar = Base_GetDef()->GetComponentProps(); - + ModPropNum(pCCPChar, PROPCH_RESFIRE, - pSpell->m_itSpell.m_PolyDex, pBaseCCPChar); ModPropNum(pCCPChar, PROPCH_RESPOISON, - pSpell->m_itSpell.m_PolyDex, pBaseCCPChar); ModPropNum(pCCPChar, PROPCH_RESCOLD, + pSpell->m_itSpell.m_PolyStr, pBaseCCPChar); @@ -1648,7 +1649,7 @@ void CChar::Spell_Effect_Add( CItem * pSpell ) ushort uiMyMagicResistance = Skill_GetBase(SKILL_MAGICRESISTANCE), uiMyInscription = Skill_GetBase(SKILL_INSCRIPTION); wStatEffectRef = (uiCasterEvalInt + uiCasterMeditation + uiCasterInscription) / 40; wStatEffectRef = minimum(75, wStatEffectRef); - + iPhysicalResist = 15 - (uiCasterInscription / 200); int iPhysicalResistMin = minimum(INT16_MAX, iPhysicalResist); pSpell->m_itSpell.m_PolyStr = (short)(maximum(-INT16_MAX, iPhysicalResistMin )); @@ -1703,7 +1704,7 @@ void CChar::Spell_Effect_Add( CItem * pSpell ) Skill_AddBase( SKILL_MEDITATION, + wStatEffectRef ); return; } - + /*case SPELL_Chameleon: // 106 // makes your skin match the colors of whatever is behind you. case SPELL_BeastForm: // 107 // polymorphs you into an animal for a while. @@ -1722,7 +1723,7 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) ASSERT(pItem); - SPELL_TYPE spell = (SPELL_TYPE)(RES_GET_INDEX(pItem->m_itSpell.m_spell)); + SPELL_TYPE spell = (SPELL_TYPE)(ResGetIndex(pItem->m_itSpell.m_spell)); const CSpellDef* pSpellDef = g_Cfg.GetSpellDef(spell); if (!pSpellDef) return false; @@ -1731,7 +1732,7 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) int iEffect = 0; DAMAGE_TYPE iDmgType = 0; int64 iSecondsDelay = 5; //default value for custom spells, can be overriden by Sphere spells below. - + switch ( spell ) { case SPELL_Ale: // 90 = drunkeness ? @@ -1770,7 +1771,7 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) if (iCharges <=0 || iLevel <= 0) return false; iSecondsDelay = g_Rand.GetLLVal2(15, 30); - + if (IsClientActive()) { static const SOUND_TYPE sm_sounds[] = { 0x243, 0x244 }; @@ -1855,7 +1856,7 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) pItem->m_itSpell.m_spelllevel -= 50; // gets weaker too. Only on old formulas iEffect = IMulDiv(Stat_GetMaxAdjusted(STAT_STR), iLevel * 2, 100); iSecondsDelay = (5 + g_Rand.GetLLVal(4)); - + static lpctstr const sm_Poison_Message[] = { g_Cfg.GetDefaultMsg(DEFMSG_SPELL_POISON_1), @@ -1873,11 +1874,11 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) Emote(pszMsg, GetClientActive()); SysMessagef(g_Cfg.GetDefaultMsg(DEFMSG_SPELL_YOUFEEL), sm_Poison_Message[iLevel]); } - + static const int sm_iPoisonMax[] = { 2, 4, 6, 8, 10 }; iEffect = maximum(sm_iPoisonMax[iLevel], iEffect); iDmgType = DAMAGE_MAGIC | DAMAGE_POISON | DAMAGE_NODISTURB | DAMAGE_NOREVEAL; - + // We will have this effect again. if (IsSetOF(OF_Buffs) && IsClientActive()) { @@ -1945,7 +1946,7 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) Args.m_VarsLocal.SetNum("Delay", iSecondsDelay); Args.m_VarsLocal.SetNum("DamageType", iDmgType); Args.m_VarsLocal.SetNum("Effect", iEffect); - + if (IsTrigUsed(TRIGGER_SPELLEFFECTTICK)) { switch (OnTrigger(CTRIG_SpellEffectTick, this, &Args)) @@ -1972,7 +1973,7 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) if (pSpellDef->IsSpellType(SPELLFLAG_HARM)) { - iDmgType = (DAMAGE_TYPE)(RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("DamageType"))); + iDmgType = (DAMAGE_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("DamageType"))); if (iDmgType > 0 && iEffect > 0) // This is necessary if we have a spell that is harmful but does no damage periodically. { OnTakeDamage(iEffect, pItem->m_uidLink.CharFind(), iDmgType, @@ -2041,7 +2042,7 @@ CItem * CChar::Spell_Effect_Create( SPELL_TYPE spell, LAYER_TYPE layer, int iEff switch ( layer ) { case LAYER_FLAG_Criminal: pSpell->SetName("Criminal Timer"); break; - case LAYER_FLAG_Potion: pSpell->SetName("Potion Cooldown"); break; + case LAYER_FLAG_PotionUsed: pSpell->SetName("Potion Cooldown"); break; case LAYER_FLAG_Drunk: pSpell->SetName("Drunk Effect"); break; case LAYER_FLAG_Hallucination: pSpell->SetName("Hallucination Effect"); break; case LAYER_FLAG_Murders: pSpell->SetName("Murder Decay"); break; @@ -2078,26 +2079,28 @@ void CChar::Spell_Area( CPointMap pntTarg, int iDist, int iSkillLevel, int64 iDu if ( pSpellDef == nullptr ) return; - CWorldSearch AreaChar( pntTarg, iDist ); - for (;;) - { - CChar * pChar = AreaChar.GetChar(); - if ( pChar == nullptr ) - break; - if ( pChar == this ) - { - if ( pSpellDef->IsSpellType(SPELLFLAG_HARM) && !IsSetMagicFlags(MAGICF_CANHARMSELF) ) - continue; - } - pChar->OnSpellEffect( spelltype, this, iSkillLevel, nullptr, iDuration); - } + { + auto AreaChar = CWorldSearchHolder::GetInstance(pntTarg, iDist); + for (;;) + { + CChar * pChar = AreaChar->GetChar(); + if (pChar == nullptr) + break; + if (pChar == this) + { + if (pSpellDef->IsSpellType(SPELLFLAG_HARM) && !IsSetMagicFlags(MAGICF_CANHARMSELF)) + continue; + } + pChar->OnSpellEffect(spelltype, this, iSkillLevel, nullptr, iDuration); + } + } if ( !pSpellDef->IsSpellType( SPELLFLAG_DAMAGE )) // prevent damage nearby items on ground { - CWorldSearch AreaItem( pntTarg, iDist ); + auto AreaItem = CWorldSearchHolder::GetInstance( pntTarg, iDist ); for (;;) { - CItem * pItem = AreaItem.GetItem(); + CItem * pItem = AreaItem->GetItem(); if ( pItem == nullptr ) break; pItem->OnSpellEffect( spelltype, this, iSkillLevel, nullptr ); @@ -2188,7 +2191,7 @@ void CChar::Spell_Field(CPointMap pntTarg, ITEMID_TYPE idEW, ITEMID_TYPE idNS, u bool fGoodLoc = true; // Where is this ? - CPointMap ptg = pntTarg; + CPointMap ptg(pntTarg); if ( dx > dy ) { ptg.m_y += (short)(ix); @@ -2201,10 +2204,10 @@ void CChar::Spell_Field(CPointMap pntTarg, ITEMID_TYPE idEW, ITEMID_TYPE idNS, u } // Check for direct cast on a creature. - CWorldSearch AreaChar( ptg ); + auto Area = CWorldSearchHolder::GetInstance( ptg ); for (;;) { - CChar * pChar = AreaChar.GetChar(); + CChar * pChar = Area->GetChar(); if ( pChar == nullptr ) break; @@ -2237,10 +2240,10 @@ void CChar::Spell_Field(CPointMap pntTarg, ITEMID_TYPE idEW, ITEMID_TYPE idNS, u continue; // Check for direct cast on an item. - CWorldSearch AreaItem( ptg ); + Area->RestartSearch(); for (;;) { - CItem * pItem = AreaItem.GetItem(); + CItem * pItem = Area->GetItem(); if ( pItem == nullptr ) break; if ( pItem->IsType(IT_SPELL) && IsSetMagicFlags(MAGICF_OVERRIDEFIELDS) ) @@ -2443,7 +2446,7 @@ bool CChar::Spell_CanCast( SPELL_TYPE &spellRef, bool fTest, CObjBase * pSrc, bo SysMessagef( g_Cfg.GetDefaultMsg( DEFMSG_SPELL_TRY_NOREGS ), pReagDef ? pReagDef->GetName() : g_Cfg.GetDefaultMsg( DEFMSG_SPELL_TRY_THEREG ) ); } return false; - } + } // Check for Tithing CVarDefContNum* pVarTithing = GetDefKeyNum("Tithing", false); @@ -2502,51 +2505,51 @@ CChar * CChar::Spell_Summon_Try(SPELL_TYPE spell, CPointMap ptTarg, CREID_TYPE i { switch (spell) { - /*The creature ID for the Summon Spell is already stored in m_atMagery.m_iSummonID when the creature + /*The creature ID for the Summon Spell is already stored in m_atMagery.m_iSummonID when the creature is chosen from the summoning menu.*/ - case SPELL_Summon: - break; + case SPELL_Summon: + break; case SPELL_Blade_Spirit: - m_atMagery.m_iSummonID = CREID_BLADE_SPIRIT; + m_atMagery.m_iSummonID = CREID_BLADE_SPIRIT; break; - case SPELL_Vortex: - m_atMagery.m_iSummonID = CREID_ENERGY_VORTEX; + case SPELL_Vortex: + m_atMagery.m_iSummonID = CREID_ENERGY_VORTEX; break; - case SPELL_Air_Elem: - m_atMagery.m_iSummonID = CREID_AIR_ELEM; + case SPELL_Air_Elem: + m_atMagery.m_iSummonID = CREID_AIR_ELEM; break; - case SPELL_Daemon: - m_atMagery.m_iSummonID = CREID_DEMON; + case SPELL_Daemon: + m_atMagery.m_iSummonID = CREID_DEMON; break; - case SPELL_Earth_Elem: - m_atMagery.m_iSummonID = CREID_EARTH_ELEM; + case SPELL_Earth_Elem: + m_atMagery.m_iSummonID = CREID_EARTH_ELEM; break; - case SPELL_Fire_Elem: - m_atMagery.m_iSummonID = CREID_FIRE_ELEM; + case SPELL_Fire_Elem: + m_atMagery.m_iSummonID = CREID_FIRE_ELEM; break; - case SPELL_Water_Elem: - m_atMagery.m_iSummonID = CREID_WATER_ELEM; + case SPELL_Water_Elem: + m_atMagery.m_iSummonID = CREID_WATER_ELEM; break; - case SPELL_Vengeful_Spirit: - m_atMagery.m_iSummonID = CREID_REVENANT; + case SPELL_Vengeful_Spirit: + m_atMagery.m_iSummonID = CREID_REVENANT; break; case SPELL_Rising_Colossus: - m_atMagery.m_iSummonID = CREID_RISING_COLOSSUS; + m_atMagery.m_iSummonID = CREID_RISING_COLOSSUS; break; case SPELL_Summon_Undead: //Sphere custom spell. switch (g_Rand.GetVal(15)) { - case 1: - m_atMagery.m_iSummonID = CREID_LICH; + case 1: + m_atMagery.m_iSummonID = CREID_LICH; break; case 3: case 5: case 7: - case 9: - m_atMagery.m_iSummonID = CREID_SKELETON; + case 9: + m_atMagery.m_iSummonID = CREID_SKELETON; break; - default: - m_atMagery.m_iSummonID = CREID_ZOMBIE; + default: + m_atMagery.m_iSummonID = CREID_ZOMBIE; break; } break; @@ -2576,7 +2579,7 @@ CChar * CChar::Spell_Summon_Try(SPELL_TYPE spell, CPointMap ptTarg, CREID_TYPE i } break; } - default: + default: m_atMagery.m_iSummonID = CREID_INVALID; break; } @@ -2746,7 +2749,7 @@ bool CChar::Spell_TargCheck() return false; } } - + // Is it a valid teleport location that allows this ? CRegion* pArea = CheckValidMove(m_Act_p, nullptr, DIR_QTY, nullptr); if (!pArea) @@ -2908,8 +2911,8 @@ bool CChar::Spell_CastDone() if (fIsSpellField) { //Setting new IDs as another variables to pass as different arguments to the field function. - it1test = (ITEMID_TYPE)(RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("CreateObject1"))); - it2test = (ITEMID_TYPE)(RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("CreateObject2"))); + it1test = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject1"))); + it2test = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject2"))); fieldWidth = (uint)Args.m_VarsLocal.GetKeyNum("fieldWidth"); fieldGauge = (uint)Args.m_VarsLocal.GetKeyNum("fieldGauge"); } @@ -3278,8 +3281,8 @@ void CChar::Spell_CastFail(bool fAbort) HUE_TYPE iColor = (HUE_TYPE)(Args.m_VarsLocal.GetKeyNum("EffectColor")); dword dwRender = (dword)Args.m_VarsLocal.GetKeyNum("EffectRender"); - - iT1 = (ITEMID_TYPE)(RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("CreateObject1"))); + + iT1 = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject1"))); if (iT1) Effect(EFFECT_OBJ, iT1, this, 1, 30, false, iColor, dwRender); Sound( SOUND_SPELL_FIZZLE ); @@ -3533,11 +3536,13 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, return false; if ( spell == SPELL_Poison_Field && IsStatFlag(STATF_POISONED) ) return false; + if (IsStatFlag(STATF_RIDDEN) && (pSpellDef->IsSpellType(SPELLFLAG_FIELD) || pSpellDef->IsSpellType(SPELLFLAG_AREA))) + return false; iSkillLevel = (iSkillLevel / 2) + g_Rand.GetVal(iSkillLevel / 2); // randomize the potency int iEffect = g_Cfg.GetSpellEffect(spell, iSkillLevel); - if (pSpellDef->m_idLayer && !iDuration) //By using SPELLEFFECT command (and the spell has a layer where to store properties) we need to calculate the duration. + if (pSpellDef->m_idLayer && !iDuration) //By using SPELLEFFECT command (and the spell has a layer where to store properties) we need to calculate the duration. iDuration = GetSpellDuration(spell, iSkillLevel, pCharSrc); // tenths of second SOUND_TYPE iSound = pSpellDef->m_sound; @@ -3636,8 +3641,8 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, spell = (SPELL_TYPE)(Args.m_iN1); iSkillLevel = (int)(Args.m_iN2); // remember that effect/duration is calculated before triggers - DAMAGE_TYPE iDmgType = (DAMAGE_TYPE)(RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("DamageType"))); - ITEMID_TYPE iEffectID = (ITEMID_TYPE)(RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum("CreateObject1"))); + DAMAGE_TYPE iDmgType = (DAMAGE_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("DamageType"))); + ITEMID_TYPE iEffectID = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject1"))); fExplode = Args.m_VarsLocal.GetKeyNum("EffectExplode") > 0 ? true : false; iSound = (SOUND_TYPE)(Args.m_VarsLocal.GetKeyNum("Sound")); iEffect = (int)(Args.m_VarsLocal.GetKeyNum("Effect")); @@ -3837,7 +3842,7 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, } } break; - + case SPELL_Protection: case SPELL_Arch_Prot: @@ -3919,7 +3924,7 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, case SPELL_Hallucination: { - CItem * pItem = Spell_Effect_Create( spell, LAYER_FLAG_Hallucination, iEffect, 100*MSECS_PER_TENTH, pCharSrc ); + CItem * pItem = Spell_Effect_Create( spell, LAYER_FLAG_Hallucination, iEffect, iDuration, pCharSrc ); ASSERT(pItem); pItem->m_itSpell.m_spellcharges = g_Rand.GetVal(30); } diff --git a/src/game/chars/CCharStat.cpp b/src/game/chars/CCharStat.cpp index cd3df1bfb..27358095f 100644 --- a/src/game/chars/CCharStat.cpp +++ b/src/game/chars/CCharStat.cpp @@ -44,12 +44,12 @@ void CChar::Stat_SetMod( STAT_TYPE i, int iVal ) } const int iPrevVal = iVal; - if (iVal > UINT16_MAX) - iVal = UINT16_MAX; - else if (iVal < -UINT16_MAX) - iVal = -UINT16_MAX; + iVal = std::clamp(iVal, int(-UINT16_MAX), int(UINT16_MAX)); if (iVal != iPrevVal) - g_Log.EventWarn("Trying to set MOD%s to invalid value=%d. Defaulting it to %d.\n", g_Cfg.GetStatName(i), iPrevVal, iVal); + { + g_Log.EventError("Trying to set MOD%s to invalid value=%d. Defaulting it to %d.\n", + g_Cfg.GetStatName(i), iPrevVal, iVal); + } m_Stat[i].m_mod = iVal; @@ -71,8 +71,14 @@ void CChar::Stat_SetMod( STAT_TYPE i, int iVal ) int CChar::Stat_GetMod( STAT_TYPE i ) const { - ADDTOCALLSTACK("CChar::Stat_GetMod"); - ASSERT(i >= 0 && i < STAT_QTY); + [[unlikely]] + if (i < 0 || i >= STAT_QTY) + { + ADDTOCALLSTACK("CChar::Stat_GetMod"); + ASSERT(i >= 0 && i < STAT_QTY); + } + + [[likely]] return m_Stat[i].m_mod; } @@ -98,12 +104,12 @@ void CChar::Stat_SetMaxMod( STAT_TYPE i, int iVal ) } const int iPrevVal = iVal; - if (iVal > UINT16_MAX) - iVal = UINT16_MAX; - else if (iVal < -UINT16_MAX) - iVal = -UINT16_MAX; + iVal = std::clamp(iVal, int(-UINT16_MAX), int(UINT16_MAX)); if (iVal != iPrevVal) - g_Log.EventWarn("Trying to set MODMAX%s to invalid value=%d. Defaulting it to %d.\n", g_Cfg.GetStatName(i), iPrevVal, iVal); + { + g_Log.EventError("Trying to set MODMAX%s to invalid value=%d. Defaulting it to %d.\n", + g_Cfg.GetStatName(i), iPrevVal, iVal); + } m_Stat[i].m_maxMod = iVal; @@ -125,12 +131,12 @@ void CChar::Stat_AddMaxMod( STAT_TYPE i, int iVal ) return; const int iPrevVal = iVal; - if (iVal > UINT16_MAX) - iVal = UINT16_MAX; - else if (iVal < -UINT16_MAX) - iVal = -UINT16_MAX; + iVal = std::clamp(iVal, int(-UINT16_MAX), int(UINT16_MAX)); if (iVal != iPrevVal) - g_Log.EventWarn("Trying to add MODMAX%s to invalid value=%d. Defaulting it to %d.\n", g_Cfg.GetStatName(i), iPrevVal, iVal); + { + g_Log.EventError("Trying to add to MODMAX%s an invalid value=%d. Defaulting the addend to %d.\n", + g_Cfg.GetStatName(i), iPrevVal, iVal); + } m_Stat[i].m_maxMod += iVal; @@ -182,8 +188,23 @@ void CChar::Stat_AddVal( STAT_TYPE i, int iVal ) } ASSERT((i >= 0) && (i < STAT_QTY)); // allow for food - iVal = m_Stat[i].m_val + iVal; - m_Stat[i].m_val = (ushort)(maximum(0, iVal)); + + int iPrevVal = iVal; + iVal = std::clamp(iVal, int(-UINT16_MAX), int(UINT16_MAX)); + if (iVal != iPrevVal) + { + g_Log.EventError("Trying to add to %s an invalid value=%d. Defaulting the addend to %d.\n", + g_Cfg.GetStatName(i), iPrevVal, iVal); + } + + iPrevVal = m_Stat[i].m_val + iVal; + iVal = maximum(0, iPrevVal); + if (iVal != iPrevVal) + { + g_Log.EventError("Trying to set %s to invalid value=%d. Defaulting it to %d.\n", + g_Cfg.GetStatName(i), iPrevVal, iVal); + } + m_Stat[i].m_val = (ushort)iVal; if ((i == STAT_STR) && (iVal <= 0)) { // Ensure this char will tick and die @@ -294,15 +315,20 @@ uint CChar::Stat_GetSum() const ushort CChar::Stat_GetAdjusted( STAT_TYPE i ) const { - ADDTOCALLSTACK("CChar::Stat_GetAdjusted"); + ADDTOCALLSTACK_DEBUG("CChar::Stat_GetAdjusted"); return ushort(Stat_GetBase(i) + Stat_GetMod(i)); } ushort CChar::Stat_GetBase( STAT_TYPE i ) const { - ADDTOCALLSTACK("CChar::Stat_GetBase"); - ASSERT(i >= 0 && i < STAT_QTY); + [[unlikely]] + if (i < 0 || i >= STAT_QTY) + { + ADDTOCALLSTACK("CChar::Stat_GetBase"); + ASSERT(i >= 0 && i < STAT_QTY); + } + [[likely]] return m_Stat[i].m_base; } @@ -332,18 +358,37 @@ void CChar::Stat_SetBase( STAT_TYPE i, ushort uiVal ) args.m_iN3 = uiVal; if (OnTrigger(CTRIG_StatChange, this, &args) == TRIGRET_RET_TRUE) return; + // do not restore argn1 to i, bad things will happen! leave i untouched. (matex) - uiVal = (ushort)(args.m_iN3); - if (i != STAT_FOOD && m_Stat[i].m_max < 1) // MaxFood cannot depend on something, otherwise if the Stat depends on STR, INT, DEX, fire MaxHits, MaxMana, MaxStam + int64 iPrevVal = args.m_iN3; + int64 iVal = std::clamp(iPrevVal, int64(-UINT16_MAX), int64(UINT16_MAX)); + if (iVal != iPrevVal) + { + g_Log.EventError("Trying to set %s to invalid value=%" PRId64 ". Defaulting it to %" PRId64 ".\n", + g_Cfg.GetStatName(i), iPrevVal, iVal); + } + uiVal = (ushort)iVal; + + // MaxFood cannot depend on something, otherwise if the Stat depends on STR, INT, DEX, fire MaxHits, MaxMana, MaxStam + if (i != STAT_FOOD && m_Stat[i].m_max < 1) { args.m_iN1 = i + 4LL; // Shift by 4 to indicate MaxHits, MaxMana, MaxStam args.m_iN2 = uiStatVal; args.m_iN3 = uiVal; if (OnTrigger(CTRIG_StatChange, this, &args) == TRIGRET_RET_TRUE) return; + // do not restore argn1 to i, bad things will happen! leave i untouched. (matex) - uiVal = (ushort)(args.m_iN3); + + iPrevVal = args.m_iN3; + iVal = std::clamp(iPrevVal, int64(-UINT16_MAX), int64(UINT16_MAX)); + if (iVal != iPrevVal) + { + g_Log.EventError("Trying to set MAX%s to invalid value=%" PRId64 ". Defaulting it to %" PRId64 ".\n", + g_Cfg.GetStatName(i), iPrevVal, iVal); + } + uiVal = (ushort)iVal; } } } @@ -437,13 +482,16 @@ uint CChar::Stat_GetSumLimit() const ADDTOCALLSTACK("CChar::Stat_GetSumLimit"); // The return value is uint, but the value supported by the packets is a word (which is smaller) const CVarDefCont* pTagStorage = GetKey("OVERRIDE.STATSUM", true); + if (pTagStorage) + return (uint)pTagStorage->GetValNum(); + if ( m_pPlayer ) { const CSkillClassDef * pSkillClass = m_pPlayer->GetSkillClass(); ASSERT(pSkillClass); - return pTagStorage ? ((uint)(pTagStorage->GetValNum())) : (uint)(pSkillClass->m_StatSumMax); + return (uint)(pSkillClass->m_StatSumMax); } - return pTagStorage ? (uint)(pTagStorage->GetValNum()) : 300; + return 300; } bool CChar::Stats_Regen() @@ -608,12 +656,11 @@ void CChar::Stat_SetLock(STAT_TYPE stat, SKILLLOCK_TYPE state) short CChar::GetKarma() const { - return (short)(maximum(g_Cfg.m_iMinKarma, minimum(g_Cfg.m_iMaxKarma, m_iKarma))); + return (short)std::clamp((int)m_iKarma, g_Cfg.m_iMinKarma, g_Cfg.m_iMaxKarma); } void CChar::SetKarma(short iNewKarma, CChar* pNPC) { - /* Issue: 1118 https://github.com/Sphereserver/Source-X/issues/1118 diff --git a/src/game/chars/CCharStatus.cpp b/src/game/chars/CCharStatus.cpp index 6aa803a04..8d25c9fb7 100644 --- a/src/game/chars/CCharStatus.cpp +++ b/src/game/chars/CCharStatus.cpp @@ -531,9 +531,9 @@ NPCBRAIN_TYPE CChar::GetNPCBrain() const return m_pNPC->m_Brain; } -NPCBRAIN_TYPE CChar::GetNPCBrainGroup() const +NPCBRAIN_TYPE CChar::GetNPCBrainGroup() const noexcept { - ADDTOCALLSTACK("CChar::GetNPCBrainGroup"); + //ADDTOCALLSTACK("CChar::GetNPCBrainGroup"); // Return NPCBRAIN_ANIMAL for animals, _HUMAN for NPC human and PCs, >= _MONSTER for monsters // (can return also _BERSERK and _DRAGON) // For tracking and other purposes. @@ -553,9 +553,9 @@ NPCBRAIN_TYPE CChar::GetNPCBrainGroup() const return NPCBRAIN_NONE; } -NPCBRAIN_TYPE CChar::GetNPCBrainAuto() const +NPCBRAIN_TYPE CChar::GetNPCBrainAuto() const noexcept { - ADDTOCALLSTACK("CChar::GetNPCBrainAuto"); + //ADDTOCALLSTACK("CChar::GetNPCBrainAuto"); // Auto-detect the brain const CREID_TYPE id = GetDispID(); @@ -1029,7 +1029,7 @@ lpctstr CChar::GetTradeTitle() const // Paperdoll title for character p (2) bool CChar::CanDisturb( const CChar *pChar ) const { - ADDTOCALLSTACK_INTENSIVE("CChar::CanDisturb"); + ADDTOCALLSTACK_DEBUG("CChar::CanDisturb"); // I can see/disturb only players with priv same/less than me. if ( !pChar ) return false; diff --git a/src/game/chars/CCharUse.cpp b/src/game/chars/CCharUse.cpp index e21aa373a..8bee39424 100644 --- a/src/game/chars/CCharUse.cpp +++ b/src/game/chars/CCharUse.cpp @@ -7,12 +7,14 @@ #include "../components/CCSpawn.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../CWorldTickingList.h" #include "../triggers.h" #include "CChar.h" #include "CCharNPC.h" -static const int MASK_RETURN_FOLLOW_LINKS = 0x02; +static constexpr int MASK_RETURN_FOLLOW_LINKS = 0x02; + bool CChar::Use_MultiLockDown( CItem * pItemTarg ) { @@ -114,7 +116,7 @@ void CChar::Use_CarveCorpse( CItemCorpse * pCorpse, CItem * pItemCarving ) tchar* pszTmp = Str_GetTemp(); snprintf(pszTmp, Str_TempLength(), "resource.%u.ID", (int)i); - ITEMID_TYPE id = (ITEMID_TYPE)RES_GET_INDEX(Args.m_VarsLocal.GetKeyNum(pszTmp)); + ITEMID_TYPE id = (ITEMID_TYPE)ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum(pszTmp)); if (id == ITEMID_NOTHING) break; @@ -1059,7 +1061,7 @@ void CChar::Use_Drink( CItem * pItem ) if ( iEnhance ) iSkillQuality += IMulDiv(iSkillQuality, iEnhance, 100); - OnSpellEffect((SPELL_TYPE)(RES_GET_INDEX(pItem->m_itPotion.m_Type)), this, iSkillQuality, pItem); + OnSpellEffect((SPELL_TYPE)(ResGetIndex(pItem->m_itPotion.m_Type)), this, iSkillQuality, pItem); // Give me the marker that i've used a potion. Spell_Effect_Create(SPELL_NONE, LAYER_FLAG_PotionUsed, g_Cfg.GetSpellEffect(SPELL_NONE, iSkillQuality), (int64)dwDelay, this); @@ -1428,10 +1430,10 @@ bool CChar::Use_Seed( CItem * pSeed, CPointMap * pPoint ) } // Already a plant here ? - CWorldSearch AreaItems(pt); + auto AreaItems = CWorldSearchHolder::GetInstance(pt); for (;;) { - CItem *pItem = AreaItems.GetItem(); + CItem *pItem = AreaItems->GetItem(); if ( !pItem ) break; if ( pItem->IsType(IT_TREE) || pItem->IsType(IT_FOLIAGE) ) // there's already a tree here diff --git a/src/game/clients/CAccount.cpp b/src/game/clients/CAccount.cpp index 15bc226b1..6c1b536a4 100644 --- a/src/game/clients/CAccount.cpp +++ b/src/game/clients/CAccount.cpp @@ -1519,7 +1519,7 @@ bool CAccount::r_LoadVal( CScript & s ) void CAccount::r_Write(CScript &s) { - ADDTOCALLSTACK_INTENSIVE("CAccount::r_Write"); + ADDTOCALLSTACK_DEBUG("CAccount::r_Write"); if ( GetPrivLevel() >= PLEVEL_QTY ) return; diff --git a/src/game/clients/CClientEvent.cpp b/src/game/clients/CClientEvent.cpp index 6e391d50d..1397e3da0 100644 --- a/src/game/clients/CClientEvent.cpp +++ b/src/game/clients/CClientEvent.cpp @@ -11,8 +11,9 @@ #include "../CSector.h" #include "../CServer.h" #include "../CWorld.h" -#include "../CWorldMap.h" #include "../CWorldGameTime.h" +#include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../spheresvr.h" #include "../triggers.h" #include "CClient.h" @@ -887,7 +888,7 @@ bool CClient::Event_Walk( byte rawdir, byte sequence ) // Player moves } // Check if I stepped on any item/teleport - TRIGRET_TYPE iRet = m_pChar->CheckLocation(false); + TRIGRET_TYPE iRet = m_pChar->CheckLocationEffects(false); if (iRet == TRIGRET_RET_FALSE) { m_pChar->SetUnkPoint(ptOld); // we already moved, so move back to previous location @@ -1041,7 +1042,8 @@ bool CClient::Event_Command(lpctstr pszCommand, TALKMODE_TYPE mode) return true; // should not be said if ( Str_Check(pszCommand) ) return true; // should not be said - if (((m_pChar->GetDispID() == CREID_EQUIP_GM_ROBE) && (pszCommand[0] == '=')) || (pszCommand[0] == g_Cfg.m_cCommandPrefix)) //Should be dispid, or it's bugged when you change character's dispid to c_man_gm. + if ( ((m_pChar->GetDispID() == CREID_EQUIP_GM_ROBE) && (pszCommand[0] == '=')) // WTF? In any case, keep using dispid, or it's bugged when you change character's dispid to c_man_gm. + || (pszCommand[0] == g_Cfg.m_cCommandPrefix)) { // Lazy :P } @@ -1054,22 +1056,22 @@ bool CClient::Event_Command(lpctstr pszCommand, TALKMODE_TYPE mode) return true; } - bool m_fAllowCommand = true; - bool m_fAllowSay = true; + bool fAllowCommand = true; + bool fAllowSay = true; pszCommand += 1; GETNONWHITESPACE(pszCommand); - m_fAllowCommand = g_Cfg.CanUsePrivVerb(this, pszCommand, this); + fAllowCommand = g_Cfg.CanUsePrivVerb(this, pszCommand, this); - if ( !m_fAllowCommand ) - m_fAllowSay = ( GetPrivLevel() <= PLEVEL_Player ); + if ( !fAllowCommand ) + fAllowSay = ( GetPrivLevel() <= PLEVEL_Player ); // filter on commands is active - so trigger it if ( !g_Cfg.m_sCommandTrigger.IsEmpty() ) { CScriptTriggerArgs Args(pszCommand); - Args.m_iN1 = m_fAllowCommand; - Args.m_iN2 = m_fAllowSay; + Args.m_iN1 = fAllowCommand; + Args.m_iN2 = fAllowSay; enum TRIGRET_TYPE tr; // Call the filtering function @@ -1077,16 +1079,16 @@ bool CClient::Event_Command(lpctstr pszCommand, TALKMODE_TYPE mode) if ( tr == TRIGRET_RET_TRUE ) return (Args.m_iN2 != 0); - m_fAllowCommand = ( Args.m_iN1 != 0 ); - m_fAllowSay = ( Args.m_iN2 != 0 ); + fAllowCommand = ( Args.m_iN1 != 0 ); + fAllowSay = ( Args.m_iN2 != 0 ); } - if ( !m_fAllowCommand && !m_fAllowSay ) + if ( !fAllowCommand && !fAllowSay ) SysMessage(g_Cfg.GetDefaultMsg(DEFMSG_MSG_ACC_PRIV)); - if ( m_fAllowCommand ) + if ( fAllowCommand ) { - m_fAllowSay = false; + fAllowSay = false; // Assume you don't mean yourself ! if ( FindTableHeadSorted( pszCommand, sm_szCmd_Redirect, ARRAY_COUNT(sm_szCmd_Redirect)) >= 0 ) @@ -1103,9 +1105,9 @@ bool CClient::Event_Command(lpctstr pszCommand, TALKMODE_TYPE mode) } if ( GetPrivLevel() >= g_Cfg.m_iCommandLog ) - g_Log.Event(LOGM_GM_CMDS, "%x:'%s' commands '%s'=%d\n", GetSocketID(), GetName(), pszCommand, m_fAllowCommand); + g_Log.Event(LOGM_GM_CMDS, "%x:'%s' commands '%s'=%d\n", GetSocketID(), GetName(), pszCommand, fAllowCommand); - return !m_fAllowSay; + return !fAllowSay; } void CClient::Event_Attack( CUID uid ) @@ -1885,11 +1887,10 @@ void CClient::Event_Talk_Common(lpctstr pszText) // PC speech //Reduce NPC hear distance for non pets int iAltDist = iFullDist; - CWorldSearch AreaChars(m_pChar->GetTopPoint(), iFullDist); // Search for the iFullDist, as it can be overriden in sphere.ini - + auto AreaChars = CWorldSearchHolder::GetInstance(m_pChar->GetTopPoint(), iFullDist); // Search for the iFullDist, as it can be overriden in sphere. for (;;) { - pChar = AreaChars.GetChar(); + pChar = AreaChars->GetChar(); //No more Chars to check if ( !pChar ) @@ -1901,7 +1902,9 @@ void CClient::Event_Talk_Common(lpctstr pszText) // PC speech for (CSObjContRec* pObjRec : pChar->GetIterationSafeCont()) { CItem* pItem = static_cast(pObjRec); - pItem->OnHear(pszText, m_pChar); + if (pItem->CanHear()) { + pItem->OnHear(pszText, m_pChar); + } } } @@ -3062,10 +3065,10 @@ void CClient::Event_ExtCmd( EXTCMD_TYPE type, tchar *pszName ) char iCharZ = pt.m_z; pt.Move(m_pChar->m_dirFace); - CWorldSearch Area(pt, 1); + auto Area = CWorldSearchHolder::GetInstance(pt, 1); for (;;) { - CItem *pItem = Area.GetItem(); + CItem *pItem = Area->GetItem(); if ( !pItem ) return; diff --git a/src/game/clients/CClientMsg.cpp b/src/game/clients/CClientMsg.cpp index 214f92585..0576c5753 100644 --- a/src/game/clients/CClientMsg.cpp +++ b/src/game/clients/CClientMsg.cpp @@ -15,6 +15,7 @@ #include "../CWorld.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../CWorldTickingList.h" #include "../spheresvr.h" #include "../triggers.h" @@ -65,7 +66,7 @@ void CClient::resendBuffs() const wStatEffect = pItem->m_itSpell.m_spelllevel; int64 iTimerEffectSigned = pItem->GetTimerSAdjusted(); wTimerEffect = (word)(maximum(iTimerEffectSigned, 0)); - SPELL_TYPE spell = (SPELL_TYPE)(RES_GET_INDEX(pItem->m_itSpell.m_spell)); + SPELL_TYPE spell = (SPELL_TYPE)(ResGetIndex(pItem->m_itSpell.m_spell)); const CSpellDef* pSpellDef = g_Cfg.GetSpellDef(spell); switch (spell) @@ -309,11 +310,11 @@ void CClient::addRemoveAll( bool fItems, bool fChars ) if ( fItems ) { // Remove any multi objects first ! or client will hang - CWorldSearch AreaItems(GetChar()->GetTopPoint(), g_Cfg.m_iMapViewRadar); - AreaItems.SetSearchSquare(true); + auto AreaItems = CWorldSearchHolder::GetInstance(GetChar()->GetTopPoint(), g_Cfg.m_iMapViewRadar); + AreaItems->SetSearchSquare(true); for (;;) { - CItem * pItem = AreaItems.GetItem(); + CItem * pItem = AreaItems->GetItem(); if ( pItem == nullptr ) break; addObjectRemove(pItem); @@ -322,12 +323,12 @@ void CClient::addRemoveAll( bool fItems, bool fChars ) if ( fChars ) { CChar * pCharSrc = GetChar(); - CWorldSearch AreaChars(GetChar()->GetTopPoint(), GetChar()->GetVisualRange()); - AreaChars.SetAllShow(IsPriv(PRIV_ALLSHOW)); - AreaChars.SetSearchSquare(true); + auto AreaChars = CWorldSearchHolder::GetInstance(GetChar()->GetTopPoint(), GetChar()->GetVisualRange()); + AreaChars->SetAllShow(IsPriv(PRIV_ALLSHOW)); + AreaChars->SetSearchSquare(true); for (;;) { - CChar * pChar = AreaChars.GetChar(); + CChar * pChar = AreaChars->GetChar(); if ( pChar == nullptr ) break; if ( pChar == pCharSrc ) @@ -1787,7 +1788,7 @@ void CClient::addTargetDeed( const CItem * pDeed ) // Place an item from a deed. preview all the stuff ASSERT( m_Targ_UID == pDeed->GetUID()); - ITEMID_TYPE iddef = (ITEMID_TYPE)(RES_GET_INDEX(pDeed->m_itDeed.m_Type)); + ITEMID_TYPE iddef = (ITEMID_TYPE)(ResGetIndex(pDeed->m_itDeed.m_Type)); m_tmUseItem.m_pParent = pDeed->GetParent(); // Cheat Verify. addTargetItems( CLIMODE_TARG_USE_ITEM, iddef, pDeed->GetHue() ); } @@ -1929,15 +1930,17 @@ void CClient::addPlayerSee( const CPointMap & ptOld ) uint iSeeMax = g_Cfg.m_iMaxItemComplexity * 30; std::vector vecMultis; + vecMultis.reserve(5); std::vector vecItems; + vecItems.reserve(15); // ptOld: the point from where i moved (i can call this method when i'm moving to a new position), // If ptOld is an invalid point, just send every object i can see. - CWorldSearch AreaItems(ptCharThis, g_Cfg.m_iMapViewRadar * 2); // *2 to catch big multis - AreaItems.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(ptCharThis, g_Cfg.m_iMapViewRadar * 2); // *2 to catch big multis + Area->SetSearchSquare(true); for (;;) { - CItem* pItem = AreaItems.GetItem(); + CItem* pItem = Area->GetItem(); if ( !pItem ) break; @@ -2023,12 +2026,12 @@ void CClient::addPlayerSee( const CPointMap & ptOld ) iSeeCurrent = 0; iSeeMax = g_Cfg.m_iMaxCharComplexity * 5; - CWorldSearch AreaChars(pCharThis->GetTopPoint(), iViewDist); - AreaChars.SetAllShow(IsPriv(PRIV_ALLSHOW)); - AreaChars.SetSearchSquare(true); + Area->Reset(pCharThis->GetTopPoint(), iViewDist); + Area->SetAllShow(IsPriv(PRIV_ALLSHOW)); + Area->SetSearchSquare(true); for (;;) { - CChar* pChar = AreaChars.GetChar(); + CChar* pChar = Area->GetChar(); if ( !pChar || iSeeCurrent > iSeeMax ) break; if ( pCharThis == pChar || !CanSee(pChar) ) @@ -2851,9 +2854,9 @@ byte CClient::Setup_Start( CChar * pChar ) // Send character startup stuff to pl // The timeout is stored as server time (not real world time) in milliseconds. // When a char logs out, the logout server time is stored. // When the char logs in again, move forward its timers by the time it spent offline. - if (m_pChar->m_pPlayer->_iTimeLastDisconnected > 0) + if (m_pChar->m_pPlayer->_iTimeLastDisconnectedMs > 0) { - const int64 iDelta = CWorldGameTime::GetCurrentTime().GetTimeRaw() - m_pChar->m_pPlayer->_iTimeLastDisconnected; + const int64 iDelta = CWorldGameTime::GetCurrentTime().GetTimeRaw() - m_pChar->m_pPlayer->_iTimeLastDisconnectedMs; if (iDelta < 0) { g_Log.EventWarn("World Time was manually changed. The TIMERs belonging to the char '%s' (UID=0%x) couldn't be frozen during its logout.\n", m_pChar->GetName(), m_pChar->GetUID().GetObjUID()); diff --git a/src/game/clients/CClientMsg_AOSTooltip.cpp b/src/game/clients/CClientMsg_AOSTooltip.cpp index d49c6f7f6..ee75de721 100644 --- a/src/game/clients/CClientMsg_AOSTooltip.cpp +++ b/src/game/clients/CClientMsg_AOSTooltip.cpp @@ -12,11 +12,12 @@ // Simple string hashing algorithm function by D. J. Bernstein // Original code found at: http://www.cse.yorku.ca/~oz/hash.html -uint HashString(lpctstr str, size_t length) +NO_SANITIZE_UNDEFINED +static uint HashString(lpctstr str, const size_t length) // integer overflow is expected { uint hash = 5381; for (size_t i = 0; i < length; ++i) - hash = ((hash << 5) + hash) + *(str++); + hash = ((hash << 5) + hash) + (uint)(*(str++)); return hash; } @@ -111,7 +112,7 @@ bool CClient::addAOSTooltip(CObjBase * pObj, bool fRequested, bool fShop) pObj->AddPropsTooltipData(pObj); } - + if (IsTrigUsed(TRIGGER_CLIENTTOOLTIP_AFTERDEFAULT) || (pItem && IsTrigUsed(TRIGGER_ITEMCLIENTTOOLTIP_AFTERDEFAULT)) || (pChar && IsTrigUsed(TRIGGER_CHARCLIENTTOOLTIP_AFTERDEFAULT))) { CScriptTriggerArgs args(pObj); @@ -165,7 +166,7 @@ bool CClient::addAOSTooltip(CObjBase * pObj, bool fRequested, bool fShop) pObj->SetPropertyList(propertyList); } } - + if (propertyList->isEmpty() == false) { switch (g_Cfg.m_iTooltipMode) @@ -661,7 +662,7 @@ void CClient::AOSTooltip_addDefaultItemData(CItem * pItem) t->FormatArgs("Time range\t%hu min / %hu max", pSpawn->GetTimeLo(), pSpawn->GetTimeHi()); PUSH_BACK_TOOLTIP(pItem, t = new CClientTooltip(1060660)); // ~1_val~: ~2_val~ t->FormatArgs("Time until next spawn\t%" PRId64 " sec", pItem->GetTimerSAdjusted()); - + } break; case IT_SPAWN_ITEM: diff --git a/src/game/clients/CClientTarg.cpp b/src/game/clients/CClientTarg.cpp index cb91231d7..3ea9bb43e 100644 --- a/src/game/clients/CClientTarg.cpp +++ b/src/game/clients/CClientTarg.cpp @@ -8,6 +8,7 @@ #include "../items/CItemVendable.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CClient.h" @@ -493,11 +494,11 @@ int CClient::Cmd_Extract( CScript * pScript, const CRectMap &rect, int & zlowest int rx = 1 + abs( rect.m_right - rect.m_left ) / 2; int ry = 1 + abs( rect.m_bottom - rect.m_top ) / 2; - CWorldSearch AreaItem( ptCtr, maximum( rx, ry )); - AreaItem.SetSearchSquare( true ); + auto AreaItem = CWorldSearchHolder::GetInstance( ptCtr, maximum( rx, ry )); + AreaItem->SetSearchSquare( true ); for (;;) { - CItem * pItem = AreaItem.GetItem(); + CItem * pItem = AreaItem->GetItem(); if ( pItem == nullptr ) break; if ( ! rect.IsInside2d( pItem->GetTopPoint())) @@ -554,7 +555,7 @@ bool CClient::OnTarg_Tile( CObjBase * pObj, const CPointMap & pt ) CRectMap rect; rect.SetRect( m_tmTile.m_ptFirst.m_x, m_tmTile.m_ptFirst.m_y, pt.m_x, pt.m_y, pt.m_map); - CPointMap ptCtr = rect.GetCenter(); + CPointMap ptCtr(rect.GetCenter()); ptCtr.m_map = pt.m_map; int rx = 1 + abs( rect.m_right - rect.m_left ) / 2; @@ -602,12 +603,12 @@ bool CClient::OnTarg_Tile( CObjBase * pObj, const CPointMap & pt ) CPointMap ptNudge((word)(piArgs[0]),(word)(piArgs[1]),(char)(piArgs[2]) ); - CWorldSearch AreaItem( ptCtr, iRadius ); - AreaItem.SetAllShow( IsPriv( PRIV_ALLSHOW )); - AreaItem.SetSearchSquare( true ); + auto Area = CWorldSearchHolder::GetInstance( ptCtr, iRadius ); + Area->SetAllShow( IsPriv( PRIV_ALLSHOW )); + Area->SetSearchSquare( true ); for (;;) { - CItem * pItem = AreaItem.GetItem(); + CItem * pItem = Area->GetItem(); if ( pItem == nullptr ) break; if ( ! rect.IsInside2d( pItem->GetTopPoint())) @@ -618,12 +619,10 @@ bool CClient::OnTarg_Tile( CObjBase * pObj, const CPointMap & pt ) ++iCount; } - CWorldSearch AreaChar( ptCtr, iRadius ); - AreaChar.SetAllShow( IsPriv( PRIV_ALLSHOW )); - AreaChar.SetSearchSquare( true ); - for (;;) + Area->RestartSearch(); + for (;;) { - CChar* pChar = AreaChar.GetChar(); + CChar* pChar = Area->GetChar(); if ( pChar == nullptr ) break; if ( ! rect.IsInside2d( pChar->GetTopPoint())) @@ -640,12 +639,12 @@ bool CClient::OnTarg_Tile( CObjBase * pObj, const CPointMap & pt ) case CV_NUKE: // NUKE all items in the region. { - CWorldSearch AreaItem( ptCtr, iRadius ); - AreaItem.SetAllShow( IsPriv( PRIV_ALLSHOW )); - AreaItem.SetSearchSquare( true ); + auto AreaItem = CWorldSearchHolder::GetInstance( ptCtr, iRadius ); + AreaItem->SetAllShow( IsPriv( PRIV_ALLSHOW )); + AreaItem->SetSearchSquare( true ); for (;;) { - CItem * pItem = AreaItem.GetItem(); + CItem * pItem = AreaItem->GetItem(); if ( pItem == nullptr ) break; if ( ! rect.IsInside2d( pItem->GetTopPoint())) @@ -669,12 +668,12 @@ bool CClient::OnTarg_Tile( CObjBase * pObj, const CPointMap & pt ) case CV_NUKECHAR: { - CWorldSearch AreaChar( ptCtr, iRadius ); - AreaChar.SetAllShow( IsPriv( PRIV_ALLSHOW )); - AreaChar.SetSearchSquare( true ); + auto AreaChar = CWorldSearchHolder::GetInstance( ptCtr, iRadius ); + AreaChar->SetAllShow( IsPriv( PRIV_ALLSHOW )); + AreaChar->SetSearchSquare( true ); for (;;) { - CChar* pChar = AreaChar.GetChar(); + CChar* pChar = AreaChar->GetChar(); if ( pChar == nullptr ) break; if ( ! rect.IsInside2d( pChar->GetTopPoint())) @@ -713,7 +712,7 @@ bool CClient::OnTarg_Tile( CObjBase * pObj, const CPointMap & pt ) { if ( ++iArg >= iArgQty ) iArg = 1; - CItem * pItem = CItem::CreateTemplate((ITEMID_TYPE)(RES_GET_INDEX(piArgs[iArg])), nullptr, m_pChar); + CItem *pItem = CItem::CreateTemplate((ITEMID_TYPE)(ResGetIndex((dword)piArgs[iArg])), nullptr, m_pChar); if (!pItem) continue; pItem->SetAttr( ATTR_MOVE_NEVER ); @@ -1193,9 +1192,12 @@ int CClient::OnSkill_Forensics( CUID uid, int iSkillLevel, bool fTest ) Str_CopyLimitNull( pszTemp + len, g_Cfg.GetDefaultMsg(DEFMSG_FORENSICS_FAILNAME), Str_TempLength() - len); } - else if ( pCorpse->GetTimeStamp() > 0 ) + else if ( pCorpse->GetTimeStampS() > 0 ) { - int len = snprintf( pszTemp, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_FORENSICS_TIMER), pCorpse->GetName(), CWorldGameTime::GetCurrentTime().GetTimeDiff(pCorpse->GetTimeStamp()) / MSECS_PER_SEC); + int len = snprintf( pszTemp, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_FORENSICS_TIMER), + pCorpse->GetName(), + (CWorldGameTime::GetCurrentTime().GetTimeDiff(pCorpse->GetTimeStampS() * MSECS_PER_SEC) / MSECS_PER_SEC)); + if ( pName ) snprintf( pszTemp + len, Str_TempLength() - len, g_Cfg.GetDefaultMsg(DEFMSG_FORENSICS_NAME), pName ); else @@ -1237,7 +1239,7 @@ int CClient::OnSkill_TasteID( CUID uid, int iSkillLevel, bool fTest ) switch ( pItem->GetType()) { case IT_POTION: - if ( RES_GET_INDEX(pItem->m_itPotion.m_Type) == SPELL_Poison ) + if ( ResGetIndex(pItem->m_itPotion.m_Type) == SPELL_Poison ) { iPoisonLevel = pItem->m_itPotion.m_dwSkillQuality; } @@ -1522,10 +1524,10 @@ bool CClient::OnTarg_Pet_Command( CObjBase * pObj, const CPointMap & pt ) // All the pets that could hear me. bool fGhostSpeak = m_pChar->IsSpeakAsGhost(); - CWorldSearch AreaChars( m_pChar->GetTopPoint(), UO_MAP_VIEW_SIGHT ); + auto AreaChars = CWorldSearchHolder::GetInstance( m_pChar->GetTopPoint(), UO_MAP_VIEW_SIGHT ); for (;;) { - CChar * pCharPet = AreaChars.GetChar(); + CChar * pCharPet = AreaChars->GetChar(); if ( pCharPet == nullptr ) break; if ( pCharPet == m_pChar ) @@ -1629,7 +1631,7 @@ bool CClient::OnTarg_Use_Deed( CItem * pDeed, CPointMap & pt ) return false; } - const CItemBase * pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(RES_GET_INDEX(pDeed->m_itDeed.m_Type))); + const CItemBase * pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex(pDeed->m_itDeed.m_Type))); if (!OnTarg_Use_Multi(pItemDef, pt, pDeed)) { return false; @@ -1726,7 +1728,7 @@ bool CClient::OnTarg_Use_Item( CObjBase * pObjTarg, CPointMap & pt, ITEMID_TYPE case IT_POTION: // Use a potion on something else. - if ( RES_GET_INDEX(pItemUse->m_itPotion.m_Type) == SPELL_Explosion ) + if ( ResGetIndex(pItemUse->m_itPotion.m_Type) == SPELL_Explosion ) { // Throw explosion potion if ( !pItemUse->IsItemEquipped() || pItemUse->GetEquipLayer() != LAYER_DRAGGING ) @@ -2120,7 +2122,7 @@ bool CClient::OnTarg_Use_Item( CObjBase * pObjTarg, CPointMap & pt, ITEMID_TYPE case IT_HIDE: // IT_LEATHER // Cut up the hides and create strips of leather - iOutID = (ITEMID_TYPE)(RES_GET_INDEX(pItemTarg->Item_GetDef()->m_ttNormal.m_tData1)); + iOutID = (ITEMID_TYPE)(ResGetIndex(pItemTarg->Item_GetDef()->m_ttNormal.m_tData1)); if ( ! iOutID ) iOutID = ITEMID_LEATHER_1; iOutQty = pItemTarg->GetAmount(); diff --git a/src/game/clients/CClientTooltip.h b/src/game/clients/CClientTooltip.h index 70f526984..350e02ada 100644 --- a/src/game/clients/CClientTooltip.h +++ b/src/game/clients/CClientTooltip.h @@ -24,9 +24,8 @@ class CClientTooltip CClientTooltip(dword dwClilocID, lpctstr ptcArgs); CClientTooltip(dword dwClilocID, int64 iArgs); -private: - CClientTooltip(const CClientTooltip& copy); - CClientTooltip& operator=(const CClientTooltip& other); + CClientTooltip(const CClientTooltip& copy) = delete; + CClientTooltip& operator=(const CClientTooltip& other) = delete; public: void __cdecl FormatArgs(lpctstr format, ...) __printfargs(2,3); diff --git a/src/game/clients/CClientUse.cpp b/src/game/clients/CClientUse.cpp index 6d51d4d4e..e7c230956 100644 --- a/src/game/clients/CClientUse.cpp +++ b/src/game/clients/CClientUse.cpp @@ -7,6 +7,7 @@ #include "../items/CItemShip.h" #include "../components/CCSpawn.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CClient.h" @@ -271,7 +272,7 @@ bool CClient::Cmd_Use_Item( CItem *pItem, bool fTestTouch, bool fScript ) SysMessageDefault(DEFMSG_ITEMUSE_POTION_FAIL); return false; } - if ( RES_GET_INDEX(pItem->m_itPotion.m_Type) == SPELL_Explosion ) + if ( ResGetIndex(pItem->m_itPotion.m_Type) == SPELL_Explosion ) { // Throw explosion potion if ( m_pChar->ItemPickup(pItem, 1) == -1 ) // put the potion in our hand @@ -346,7 +347,7 @@ bool CClient::Cmd_Use_Item( CItem *pItem, bool fTestTouch, bool fScript ) case IT_WAND: case IT_SCROLL: { - const SPELL_TYPE spell = (SPELL_TYPE)(RES_GET_INDEX(pItem->m_itWeapon.m_spell)); + const SPELL_TYPE spell = (SPELL_TYPE)(ResGetIndex(pItem->m_itWeapon.m_spell)); const CSpellDef *pSpellDef = g_Cfg.GetSpellDef(spell); if ( !pSpellDef ) return false; @@ -1156,10 +1157,10 @@ bool CClient::Cmd_Skill_Tracking( uint track_sel, bool fExec ) iSkillLevel = maximum(iSkillLevel, 200); // humans always have a 20.0 minimum skill (racial traits) m_pChar->m_atTracking.m_dwDistMax = (dword)(iSkillLevel / 10 + 10); } - CWorldSearch AreaChars(m_pChar->GetTopPoint(), m_pChar->m_atTracking.m_dwDistMax); + auto AreaChars = CWorldSearchHolder::GetInstance(m_pChar->GetTopPoint(), m_pChar->m_atTracking.m_dwDistMax); for (;;) { - CChar *pChar = AreaChars.GetChar(); + CChar *pChar = AreaChars->GetChar(); if ( !pChar ) break; if ( m_pChar == pChar ) diff --git a/src/game/clients/CGMPage.cpp b/src/game/clients/CGMPage.cpp index 660b83de9..984ce8518 100644 --- a/src/game/clients/CGMPage.cpp +++ b/src/game/clients/CGMPage.cpp @@ -47,7 +47,7 @@ void CGMPage::ClearHandler() void CGMPage::r_Write(CScript& s) const { - ADDTOCALLSTACK_INTENSIVE("CGMPage::r_Write"); + ADDTOCALLSTACK_DEBUG("CGMPage::r_Write"); s.WriteSection("GMPAGE %s", GetName()); s.WriteKeyHex("CHARUID", m_uidChar.GetObjUID()); s.WriteKeyStr("P", m_pt.WriteUsed()); diff --git a/src/game/components/CCFaction.cpp b/src/game/components/CCFaction.cpp index 8a75438c1..8e9954832 100644 --- a/src/game/components/CCFaction.cpp +++ b/src/game/components/CCFaction.cpp @@ -119,7 +119,7 @@ bool CCFaction::IsOppositeLesserSlayer(const CCFaction *target) const else if ((myFaction & FACTION_OPHIDIAN) && (targFaction & FACTION_OPHIDIAN)) return true; else if ((myFaction & FACTION_SNAKE) && (targFaction & FACTION_SNAKE)) - return true; + return true; else if ((myFaction & FACTION_LIZARDMAN) && (targFaction & FACTION_LIZARDMAN)) return true; @@ -146,7 +146,7 @@ bool CCFaction::IsOppositeLesserSlayer(const CCFaction *target) const return false; } -enum CHF_TYPE +enum CHF_TYPE : int { CHF_FACTION, CHF_SLAYER, @@ -166,12 +166,12 @@ lpctstr const CCFaction::sm_szLoadKeys[CHF_QTY + 1] = CCFaction::CCFaction() : CFactionDef(), CComponent(COMP_FACTION) { - //ADDTOCALLSTACK_INTENSIVE("CCFaction::CCFaction(FACTION_TYPE)"); + //ADDTOCALLSTACK_DEBUG("CCFaction::CCFaction(FACTION_TYPE)"); } CCFaction::CCFaction(CCFaction *copy) : CFactionDef(), CComponent(COMP_FACTION) { - //ADDTOCALLSTACK_INTENSIVE("CCFaction::CCFaction(CCFaction*)"); + //ADDTOCALLSTACK_DEBUG("CCFaction::CCFaction(CCFaction*)"); Copy(copy); } @@ -195,7 +195,7 @@ bool CCFaction::r_LoadVal(CScript & s) case CHF_FACTION: case CHF_SLAYER: { - SetFactionID(static_cast(s.GetArgULLVal())); + SetFactionID(static_cast(s.GetArgLLVal())); return true; } } @@ -294,62 +294,63 @@ NPC_GROUP CCFaction::GetGroupID() const NPC_FACTION CCFaction::GetFactionID() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::GetFactionID"); + ADDTOCALLSTACK_DEBUG("CCFaction::GetFactionID"); return _iFaction; } void CCFaction::SetFactionID(NPC_FACTION faction) { - ADDTOCALLSTACK_INTENSIVE("CCFaction::SetFactionID"); + ADDTOCALLSTACK_DEBUG("CCFaction::SetFactionID"); ASSERT(faction < FACTION_QTY); + ASSERT(faction >= 0); _iFaction = faction; } bool CCFaction::IsGroupElemental() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsGroupElemental"); + //ADDTOCALLSTACK_DEBUG("CCFaction::IsGroupElemental"); return ((_iFaction >= FACTION_ELEMENTAL) && (_iFaction < FACTION_ELEMENTAL_QTY)); } bool CCFaction::IsGroupFey() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsGroupFey"); + //ADDTOCALLSTACK_DEBUG("CCFaction::IsGroupFey"); return (_iFaction == FACTION_FEY); } bool CCFaction::IsGroupAbyss() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsGroupAbyss"); + //ADDTOCALLSTACK_DEBUG("CCFaction::IsGroupAbyss"); return ((_iFaction >= FACTION_DEMON) && (_iFaction < FACTION_ABYSS_QTY)); } bool CCFaction::IsGroupHumanoid() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsGroupHumanoid"); + //ADDTOCALLSTACK_DEBUG("CCFaction::IsGroupHumanoid"); return ((_iFaction >= FACTION_REPOND) && (_iFaction < FACTION_HUMANOID_QTY)); } bool CCFaction::IsGroupUndead() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsGroupUndead"); + //ADDTOCALLSTACK_DEBUG("CCFaction::IsGroupUndead"); return ((_iFaction >= FACTION_UNDEAD) && (_iFaction < FACTION_UNDEAD_QTY)); } bool CCFaction::IsGroupArachnid() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsGroupArachnid"); + //ADDTOCALLSTACK_DEBUG("CCFaction::IsGroupArachnid"); return ((_iFaction >= FACTION_ARACHNID) && (_iFaction < FACTION_ARACHNID_QTY)); } bool CCFaction::IsGroupReptilian() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsGroupReptilian"); + //ADDTOCALLSTACK_DEBUG("CCFaction::IsGroupReptilian"); return ((_iFaction >= FACTION_REPTILE) && (_iFaction < FACTION_REPTILIAN_QTY)); } bool CCFaction::IsSuperSlayer() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsSuperSlayer"); + ADDTOCALLSTACK_DEBUG("CCFaction::IsSuperSlayer"); switch (_iFaction) { case FACTION_FEY: @@ -366,7 +367,7 @@ bool CCFaction::IsSuperSlayer() const bool CCFaction::IsLesserSlayer() const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::IsLesserSlayer"); + ADDTOCALLSTACK_DEBUG("CCFaction::IsLesserSlayer"); if ((_iFaction > FACTION_NONE) && (_iFaction < FACTION_QTY) && (!IsSuperSlayer())) return true; return false; @@ -374,7 +375,7 @@ bool CCFaction::IsLesserSlayer() const int CCFaction::GetSlayerDamageBonus(const CCFaction *target) const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::GetSlayerDamageBonus"); + ADDTOCALLSTACK_DEBUG("CCFaction::GetSlayerDamageBonus"); if (IsOppositeLesserSlayer(target)) return DAMAGE_SLAYER_LESSER; else if (IsOppositeSuperSlayer(target)) @@ -384,7 +385,7 @@ int CCFaction::GetSlayerDamageBonus(const CCFaction *target) const int CCFaction::GetSlayerDamagePenalty(const CCFaction * target) const { - ADDTOCALLSTACK_INTENSIVE("CCFaction::GetSlayerDamagePenalty"); + ADDTOCALLSTACK_DEBUG("CCFaction::GetSlayerDamagePenalty"); if (IsOppositeGroup(target)) return DAMAGE_SLAYER_OPPOSITE; return 1; diff --git a/src/game/components/CCItemDamageable.cpp b/src/game/components/CCItemDamageable.cpp index d638f7263..c42e996e4 100644 --- a/src/game/components/CCItemDamageable.cpp +++ b/src/game/components/CCItemDamageable.cpp @@ -3,7 +3,7 @@ #include "../CServer.h" #include "../CObjBase.h" #include "../CWorldGameTime.h" -#include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../CWorldTickingList.h" #include "../chars/CChar.h" #include "../clients/CClient.h" @@ -70,12 +70,12 @@ void CCItemDamageable::OnTickStatsUpdate() _iTimeLastUpdate = iCurtime; CItem *pItem = static_cast(GetLink()); - CWorldSearch AreaChars(pItem->GetTopPoint(), g_Cfg.m_iMapViewSize); - AreaChars.SetSearchSquare(true); + auto AreaChars = CWorldSearchHolder::GetInstance(pItem->GetTopPoint(), g_Cfg.m_iMapViewSize); + AreaChars->SetSearchSquare(true); CChar *pChar = nullptr; for (;;) { - pChar = AreaChars.GetChar(); + pChar = AreaChars->GetChar(); if (!pChar) { break; diff --git a/src/game/components/CCMultiMovable.cpp b/src/game/components/CCMultiMovable.cpp index 85f1e5a77..ddc069b1e 100644 --- a/src/game/components/CCMultiMovable.cpp +++ b/src/game/components/CCMultiMovable.cpp @@ -8,6 +8,7 @@ #include "../CObjBase.h" #include "../CServer.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CCMultiMovable.h" @@ -153,12 +154,13 @@ uint CCMultiMovable::ListObjs(CObjBase ** ppObjList) } // add chars to the list - CWorldSearch AreaChar(pItemThis->GetTopPoint(), iMaxDist); - AreaChar.SetAllShow(true); - AreaChar.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(pItemThis->GetTopPoint(), iMaxDist); + + Area->SetAllShow(true); + Area->SetSearchSquare(true); while (uiCount < MAX_MULTI_LIST_OBJS) { - CChar *pChar = AreaChar.GetChar(); + CChar *pChar = Area->GetChar(); if (pChar == nullptr) break; if (!pMulti->GetRegion()->IsInside2d(pChar->GetTopPoint())) @@ -174,11 +176,11 @@ uint CCMultiMovable::ListObjs(CObjBase ** ppObjList) } // last, add the rest of the items - CWorldSearch AreaItem(pItemThis->GetTopPoint(), iMaxDist); - AreaItem.SetSearchSquare(true); + Area->Reset(pItemThis->GetTopPoint(), iMaxDist); + Area->SetSearchSquare(true); while (uiCount < MAX_MULTI_LIST_OBJS) { - CItem *pItem = AreaItem.GetItem(); + CItem *pItem = Area->GetItem(); if (pItem == nullptr) break; if (pItem == pItemThis) // already listed. @@ -1260,7 +1262,7 @@ bool CCMultiMovable::r_Verb(CScript & s, CTextConsole * pSrc) // Execute command return true; } -enum CML_TYPE +enum CML_TYPE : int { CML_ANCHOR, CML_DIRFACE, diff --git a/src/game/components/CCSpawn.cpp b/src/game/components/CCSpawn.cpp index 812ffbbff..af37ae458 100644 --- a/src/game/components/CCSpawn.cpp +++ b/src/game/components/CCSpawn.cpp @@ -23,7 +23,7 @@ void CCSpawn::AddBadSpawn() { return; } - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; if (std::find(_vBadSpawns.cbegin(), _vBadSpawns.cend(), this) == _vBadSpawns.cend()) { _vBadSpawns.emplace_back(this); //only if it's not inserted already. @@ -34,7 +34,7 @@ void CCSpawn::AddBadSpawn() void CCSpawn::DelBadSpawn() { ADDTOCALLSTACK("CCSpawn::DelBadSpawn"); - THREAD_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET; if (!_vBadSpawns.empty()) { _vBadSpawns.erase(std::find(_vBadSpawns.begin(), _vBadSpawns.end(), this)); @@ -59,7 +59,7 @@ CCSpawn *CCSpawn::GetBadSpawn(int index) CCSpawn::CCSpawn(CItem *pLink, bool fIsChampion) : CComponent(COMP_SPAWN), _fIsChampion(fIsChampion) { - //ADDTOCALLSTACK_INTENSIVE("CCSpawn::CCSpawn"); + //ADDTOCALLSTACK_DEBUG("CCSpawn::CCSpawn"); _pLink = pLink; _iAmount = 1; _iPile = 1; @@ -656,7 +656,7 @@ void CCSpawn::AddObj(const CUID& uid) if (GetCurrentSpawned() >= GetAmount()) { - pSpawnItem->m_CanMask &= ~CAN_O_NOSLEEP; + pSpawnItem->m_CanMask &= ~ (uint64)CAN_O_NOSLEEP; if (pSpawnItem->GetTopSector()->IsSleeping()) pSpawnItem->_GoSleep(); @@ -720,20 +720,40 @@ void CCSpawn::KillChildren() for (std::vector::iterator it = _uidList.begin(), itEnd = _uidList.end(); it != itEnd; ++it) { CObjBase* pObj = it->ObjFind(); + if (!pObj) + { + // Unregistered UID? + continue; + } + CChar *pChar = dynamic_cast(pObj); if (pChar) { +#ifdef _DEBUG + auto parent = pChar->GetParent(); + const auto sector = pChar->GetTopSector(); + DEBUG_ASSERT(parent == §or->m_Chars_Active || parent == §or->m_Chars_Disconnect); +#endif pChar->SetSpawn(nullptr); // Just to prevent CObjBase to call DelObj. pChar->Delete(); + continue; } + CItem *pItem = dynamic_cast(pObj); if (pItem) { +#ifdef _DEBUG + auto parent = pItem->GetParent(); + const auto sector = pItem->GetTopSector(); + DEBUG_ASSERT(parent == §or->m_Items); +#endif pItem->SetSpawn(nullptr); // Just to prevent CObjBase to call DelObj. pItem->Delete(); + continue; } + ASSERT(false); } _uidList.clear(); _fKillingChildren = false; diff --git a/src/game/components/CCSpawn.h b/src/game/components/CCSpawn.h index 77185002e..bea92a9c1 100644 --- a/src/game/components/CCSpawn.h +++ b/src/game/components/CCSpawn.h @@ -21,7 +21,6 @@ class CUID; class CCSpawn : public CComponent { - THREAD_CMUTEX_DEF; CItem* _pLink; static lpctstr const sm_szLoadKeys[]; static lpctstr const sm_szVerbKeys[]; diff --git a/src/game/game_enums.h b/src/game/game_enums.h index 1f91e3cd8..637174639 100644 --- a/src/game/game_enums.h +++ b/src/game/game_enums.h @@ -1,6 +1,6 @@ /** * @file game_enums.h -* @brief Enums commonly used in the "game" folder. +* @brief Enums commonly used in the "game" folder. */ #ifndef _INC_GAME_ENUMS_H @@ -13,7 +13,7 @@ enum ePayGold PAYGOLD_HIRE }; -enum RESDISPLAY_VERSION +enum RESDISPLAY_VERSION : char { RDS_PRET2A, RDS_T2A, @@ -102,7 +102,7 @@ enum CLIMODE_TYPE // What mode is the client to server connection in ? (waiting CLIMODE_PROMPT_NAME_RUNE, CLIMODE_PROMPT_NAME_SHIP, CLIMODE_PROMPT_NAME_SIGN, // naming a house sign - + CLIMODE_PROMPT_GM_PAGE_TEXT, // allowed to enter text for GM page CLIMODE_PROMPT_VENDOR_PRICE, // what would you like the price to be? CLIMODE_PROMPT_TARG_VERB, // send message to another player diff --git a/src/game/items/CItem.cpp b/src/game/items/CItem.cpp index 25305a935..d011d9744 100644 --- a/src/game/items/CItem.cpp +++ b/src/game/items/CItem.cpp @@ -21,6 +21,7 @@ #include "../CWorldTickingList.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CItem.h" #include "CItemCommCrystal.h" @@ -163,29 +164,32 @@ CItem::CItem( ITEMID_TYPE id, CItemBase * pItemDef ) : g_World.m_uidLastNewItem = GetUID(); // for script access. ASSERT( IsDisconnected() ); - // Manual CComponents addition - - /* CCItemDamageable is also added from CObjBase::r_LoadVal(OC_CANMASK) for manual override of can flags - * but it's required to add it also on item's creation depending on it's CItemBase can flags. - */ - if (CCItemDamageable::CanSubscribe(this)) - { - SubscribeComponent(new CCItemDamageable(this)); - } - if (CCFaction::CanSubscribe(this)) + // If it's a memory, those won't be needed, and we can avoid dynamically allocating useless stuff. + if (id != ITEMID_MEMORY) { - SubscribeComponent(new CCFaction(pItemDef->GetFaction())); // Adding it only to equippable items + /* CCItemDamageable is also added from CObjBase::r_LoadVal(OC_CANMASK) for manual override of can flags + * but it's required to add it also on item's creation depending on it's CItemBase can flags. + */ + if (CCItemDamageable::CanSubscribe(this)) + { + SubscribeComponent(new CCItemDamageable(this)); + } + if (CCFaction::CanSubscribe(this)) + { + SubscribeComponent(new CCFaction(pItemDef->GetFaction())); // Adding it only to equippable items + } + + TrySubscribeComponentProps(); + TrySubscribeComponentProps(); } - TrySubscribeComponentProps(); - TrySubscribeComponentProps(); } void CItem::DeleteCleanup(bool fForce) { ADDTOCALLSTACK("CItem::DeleteCleanup"); - _fDeleting = true; + _uiInternalStateFlags |= SF_DELETING; // We don't want to have invalid pointers over there // Already called by CObjBase::DeletePrepare -> CObjBase::_GoSleep @@ -211,7 +215,7 @@ void CItem::DeleteCleanup(bool fForce) if ( pHorse && pHorse->IsDisconnected() && ! pHorse->m_pPlayer ) { pHorse->m_atRidden.m_uidFigurine.InitUID(); - pHorse->Delete(fForce); + pHorse->Delete(fForce); } } break; @@ -255,7 +259,7 @@ bool CItem::Delete(bool fForce) if (( NotifyDelete() == false ) && !fForce) return false; - DeletePrepare(); // Virtual -> Must remove early because virtuals will fail in child destructor. + DeletePrepare(); // Virtual -> Must call it early because virtuals will fail in child destructors. DeleteCleanup(fForce); return CObjBase::Delete(fForce); @@ -758,7 +762,7 @@ byte CItem::GetRangeH() const int CItem::IsWeird() const { - ADDTOCALLSTACK_INTENSIVE("CItem::IsWeird"); + ADDTOCALLSTACK_DEBUG("CItem::IsWeird"); // Does item i have a "link to reality"? // (Is the container it is in still there) // RETURN: 0 = success ok @@ -1645,38 +1649,42 @@ bool CItem::MoveToCheck( const CPointMap & pt, CChar * pCharMover ) return true; // Check if there's too many items on the same spot - uint iItemCount = 0; - const CItem * pItem = nullptr; - CWorldSearch AreaItems(ptNewPlace); - for (;;) - { - pItem = AreaItems.GetItem(); - if ( pItem == nullptr ) - break; - - ++iItemCount; - if ( iItemCount > g_Cfg.m_iMaxItemComplexity ) - { - Speak(g_Cfg.GetDefaultMsg(DEFMSG_TOO_MANY_ITEMS)); - iDecayTime = 60 * MSECS_PER_SEC; // force decay (even when REGION_FLAG_NODECAY is set) - break; - } - } - /* // From 56b - // Too many items on the same spot! - if ( iItemCount > g_Cfg.m_iMaxItemComplexity ) + //if (g_Cfg.m_iMaxItemComplexity > 0) // It might be wise to keep this always on. + { + uint iItemCount = 0; + const CItem * pItem = nullptr; + auto AreaItems = CWorldSearchHolder::GetInstance(ptNewPlace); + for (;;) { - Speak("Too many items here!"); - if ( iItemCount > g_Cfg.m_iMaxItemComplexity + g_Cfg.m_iMaxItemComplexity/2 ) + pItem = AreaItems->GetItem(); + if (pItem == nullptr) + break; + + ++iItemCount; + if (iItemCount > g_Cfg.m_iMaxItemComplexity) { - Speak("The ground collapses!"); - Delete(); + Speak(g_Cfg.GetDefaultMsg(DEFMSG_TOO_MANY_ITEMS)); + iDecayTime = 60 * MSECS_PER_SEC; // force decay (even when REGION_FLAG_NODECAY is set) + break; } - // attempt to reject the move. - return false; } - */ + + /* // From 56b + // Too many items on the same spot! + if ( iItemCount > g_Cfg.m_iMaxItemComplexity ) + { + Speak("Too many items here!"); + if ( iItemCount > g_Cfg.m_iMaxItemComplexity + g_Cfg.m_iMaxItemComplexity/2 ) + { + Speak("The ground collapses!"); + Delete(); + } + // attempt to reject the move. + return false; + } + */ + } SetDecayTime(iDecayTime); Sound(GetDropSound(nullptr)); @@ -1723,7 +1731,7 @@ lpctstr CItem::GetName() const { if ( IsType( IT_SCROLL ) || IsType( IT_POTION ) ) { - if ( RES_GET_INDEX(m_itPotion.m_Type) != SPELL_Explosion ) + if ( ResGetIndex(m_itPotion.m_Type) != SPELL_Explosion ) { const CSpellDef * pSpell = g_Cfg.GetSpellDef((SPELL_TYPE)(m_itSpell.m_spell)); if (pSpell != nullptr) @@ -1867,7 +1875,7 @@ lpctstr CItem::GetNameFull( bool fIdentified ) const if ( fIdentified && IsAttr(ATTR_MAGIC) && IsTypeArmorWeapon()) // wand is also a weapon. { - SPELL_TYPE spell = (SPELL_TYPE)(RES_GET_INDEX(m_itWeapon.m_spell)); + SPELL_TYPE spell = (SPELL_TYPE)(ResGetIndex(m_itWeapon.m_spell)); if ( spell ) { const CSpellDef * pSpellDef = g_Cfg.GetSpellDef( spell ); @@ -1920,7 +1928,7 @@ lpctstr CItem::GetNameFull( bool fIdentified ) const case IT_BONE: if ( fIdentified ) { - CREID_TYPE id = static_cast(RES_GET_INDEX(m_itSkin.m_creid)); + CREID_TYPE id = static_cast(ResGetIndex(m_itSkin.m_creid)); if ( id) { const CCharBase * pCharDef = CCharBase::FindCharBase( id ); @@ -2103,12 +2111,19 @@ bool CItem::SetBaseID( ITEMID_TYPE id ) void CItem::OnHear( lpctstr pszCmd, CChar * pSrc ) { + ADDTOCALLSTACK("CItem::OnHear"); // This should never be called directly. Normal items cannot hear. IT_SHIP and IT_COMM_CRYSTAL UnreferencedParameter(pszCmd); UnreferencedParameter(pSrc); ASSERT(false); } +bool CItem::CanHear() const +{ + //ADDTOCALLSTACK("CItem::CanHear"); + return IsType(IT_SHIP) || IsType(IT_COMM_CRYSTAL); +} + CItemBase * CItem::Item_GetDef() const { return static_cast ( Base_GetDef() ); @@ -2303,16 +2318,16 @@ void CItem::r_WriteMore1(CSString & sVal) case IT_LOOM: case IT_ARCHERY_BUTTE: case IT_ITEM_STONE: - sVal = ResourceGetName(CResourceID(RES_ITEMDEF, RES_GET_INDEX(m_itNormal.m_more1))); + sVal = ResourceGetName(CResourceID(RES_ITEMDEF, ResGetIndex(m_itNormal.m_more1))); return; case IT_FIGURINE: case IT_EQ_HORSE: - sVal = ResourceGetName(CResourceID(RES_CHARDEF, RES_GET_INDEX(m_itNormal.m_more1))); + sVal = ResourceGetName(CResourceID(RES_CHARDEF, ResGetIndex(m_itNormal.m_more1))); return; case IT_POTION: - sVal = ResourceGetName(CResourceID(RES_SPELL, RES_GET_INDEX(m_itPotion.m_Type))); + sVal = ResourceGetName(CResourceID(RES_SPELL, ResGetIndex(m_itPotion.m_Type))); return; default: @@ -2326,7 +2341,7 @@ void CItem::r_WriteMore1(CSString & sVal) void CItem::r_WriteMore2( CSString & sVal ) { - ADDTOCALLSTACK_INTENSIVE("CItem::r_WriteMore2"); + ADDTOCALLSTACK_DEBUG("CItem::r_WriteMore2"); // do special processing to represent this. switch ( GetType()) @@ -2372,7 +2387,7 @@ void CItem::r_WriteMore2( CSString & sVal ) void CItem::r_Write( CScript & s ) { - ADDTOCALLSTACK_INTENSIVE("CItem::r_Write"); + ADDTOCALLSTACK_DEBUG("CItem::r_Write"); const CItemBase *pItemDef = Item_GetDef(); if ( !pItemDef ) return; @@ -2896,11 +2911,11 @@ bool CItem::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc, bo void CItem::r_LoadMore1(dword dwVal) { - ADDTOCALLSTACK_INTENSIVE("CItem::r_LoadMore1"); + ADDTOCALLSTACK_DEBUG("CItem::r_LoadMore1"); // Ensure that (when needed) the dwVal is stored as a CResourceIDBase, // plus, do some extra checks for spawns - const int iIndex = RES_GET_INDEX(dwVal); + const int iIndex = ResGetIndex(dwVal); switch (GetType()) { case IT_TREE: @@ -2955,10 +2970,10 @@ void CItem::r_LoadMore1(dword dwVal) void CItem::r_LoadMore2(dword dwVal) { - ADDTOCALLSTACK_INTENSIVE("CItem::r_LoadMore2"); + ADDTOCALLSTACK_DEBUG("CItem::r_LoadMore2"); // Ensure that (when needed) the dwVal is stored as a CResourceIDBase - const int iIndex = RES_GET_INDEX(dwVal); + const int iIndex = ResGetIndex(dwVal); switch (GetType()) { case IT_CROPS: @@ -3162,7 +3177,7 @@ bool CItem::r_LoadVal( CScript & s ) // Load an item Script for ( addCircle = atoi(ppVal[0]); addCircle; --addCircle ) { for ( short i = 1; i < 9; ++i ) - AddSpellbookSpell((SPELL_TYPE)(RES_GET_INDEX(((addCircle - 1) * 8) + i)), false); + AddSpellbookSpell((SPELL_TYPE)(ResGetIndex(((addCircle - 1) * 8) + i)), false); if ( includeLower == false ) break; @@ -3172,7 +3187,7 @@ bool CItem::r_LoadVal( CScript & s ) // Load an item Script case IC_ADDSPELL: // Add this spell to the i_spellbook. { - SPELL_TYPE spell = (SPELL_TYPE)(RES_GET_INDEX(s.GetArgVal())); + SPELL_TYPE spell = (SPELL_TYPE)(ResGetIndex(s.GetArgVal())); if (AddSpellbookSpell(spell, false)) return false; break; @@ -3322,7 +3337,7 @@ bool CItem::r_LoadVal( CScript & s ) // Load an item Script break; case IC_FRUIT: // m_more2 - m_itCrop.m_ridFruitOverride = CResourceIDBase(RES_ITEMDEF, RES_GET_INDEX(s.GetArgDWVal())); + m_itCrop.m_ridFruitOverride = CResourceIDBase(RES_ITEMDEF, ResGetIndex(s.GetArgDWVal())); break; case IC_MAXHITS: m_itNormal.m_more1 = MAKEDWORD(LOWORD(m_itNormal.m_more1), s.GetArgVal()); @@ -5118,14 +5133,15 @@ lpctstr CItem::Use_SpyGlass( CChar * pUser ) const } // Check for interesting items, like boats, carpets, etc.., ignore our stuff + auto Area = CWorldSearchHolder::GetInstance(ptCoords, iVisibility); + CItem * pItemSighted = nullptr; CItem * pBoatSighted = nullptr; int iItemSighted = 0; int iBoatSighted = 0; - CWorldSearch ItemsArea( ptCoords, iVisibility ); for (;;) { - CItem * pItem = ItemsArea.GetItem(); + CItem * pItem = Area->GetItem(); if ( pItem == nullptr ) break; if ( pItem == this ) @@ -5196,12 +5212,13 @@ lpctstr CItem::Use_SpyGlass( CChar * pUser ) const } // Check for creatures + Area->RestartSearch(); + CChar * pCharSighted = nullptr; int iCharSighted = 0; - CWorldSearch AreaChar( ptCoords, iVisibility ); for (;;) { - CChar * pChar = AreaChar.GetChar(); + CChar * pChar = Area->GetChar(); if ( pChar == nullptr ) break; if ( pChar == pUser ) @@ -5240,7 +5257,7 @@ lpctstr CItem::Use_Sextant( CPointMap pntCoords ) const bool CItem::IsBookWritable() const { - return ( (m_itBook.m_ResID.GetPrivateUID() == 0) && (GetTimeStamp() == 0) ); + return ( (m_itBook.m_ResID.GetPrivateUID() == 0) && (GetTimeStampS() == 0) ); } bool CItem::IsBookSystem() const // stored in RES_BOOK @@ -5545,7 +5562,7 @@ bool CItem::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, if ( IsType(IT_WAND) ) // try to recharge the wand. { - if ( !m_itWeapon.m_spell || RES_GET_INDEX(m_itWeapon.m_spell) == (word)spell ) + if ( !m_itWeapon.m_spell || ResGetIndex(m_itWeapon.m_spell) == (word)spell ) { SetAttr(ATTR_MAGIC); if ( !m_itWeapon.m_spell || ( pCharSrc && pCharSrc->IsPriv(PRIV_GM) ) ) @@ -5766,7 +5783,7 @@ int CItem::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uType ) return INT32_MAX; case IT_POTION: - if ( RES_GET_INDEX(m_itPotion.m_Type) == SPELL_Explosion ) + if ( ResGetIndex(m_itPotion.m_Type) == SPELL_Explosion ) { CSpellDef *pSpell = g_Cfg.GetSpellDef(SPELL_Explosion); if (!pSpell) @@ -5917,10 +5934,10 @@ void CItem::OnExplosion() iDmgPhysical = 100; CChar * pSrc = m_uidLink.CharFind(); - CWorldSearch AreaChars( GetTopPoint(), m_itExplode.m_iDist ); + auto AreaChars = CWorldSearchHolder::GetInstance( GetTopPoint(), m_itExplode.m_iDist ); for (;;) { - CChar * pChar = AreaChars.GetChar(); + CChar * pChar = AreaChars->GetChar(); if ( pChar == nullptr ) break; if ( pChar->CanSeeLOS(this) ) @@ -6045,7 +6062,7 @@ bool CItem::_CanHoldTimer() const return false; } - if (HAS_FLAG(g_Cfg.m_uiItemTimers, ITEM_CANTIMER_IN_CONTAINER) || Can(CAN_I_TIMER_CONTAINED)) + if (HAS_FLAGS_STRICT(g_Cfg.m_uiItemTimers, ITEM_CANTIMER_IN_CONTAINER) || Can(CAN_I_TIMER_CONTAINED)) { return true; } @@ -6069,11 +6086,11 @@ bool CItem::_CanHoldTimer() const bool CItem::_CanTick(bool fParentGoingToSleep) const { - ADDTOCALLSTACK_INTENSIVE("CItem::_CanTick"); + ADDTOCALLSTACK_DEBUG("CItem::_CanTick"); EXC_TRY("Can tick?"); const CObjBase* pCont = GetContainer(); - const bool fIgnoreCont = (HAS_FLAG(g_Cfg.m_uiItemTimers, ITEM_CANTIMER_IN_CONTAINER) || Can(CAN_I_TIMER_CONTAINED)); + const bool fIgnoreCont = (HAS_FLAGS_STRICT(g_Cfg.m_uiItemTimers, ITEM_CANTIMER_IN_CONTAINER) || Can(CAN_I_TIMER_CONTAINED)); // ATTR_DECAY ignores/overrides fParentGoingToSleep if (fIgnoreCont || (IsAttr(ATTR_DECAY) && !pCont)) { @@ -6255,7 +6272,7 @@ bool CItem::_OnTick() { EXC_SET_BLOCK("default behaviour::IT_POTION"); // This is a explosion potion? - if ( (RES_GET_INDEX(m_itPotion.m_Type) == SPELL_Explosion) && m_itPotion.m_ignited ) + if ( (ResGetIndex(m_itPotion.m_Type) == SPELL_Explosion) && m_itPotion.m_ignited ) { if ( m_itPotion.m_tick <= 1 ) { diff --git a/src/game/items/CItem.h b/src/game/items/CItem.h index 5151fe16a..a30c15352 100644 --- a/src/game/items/CItem.h +++ b/src/game/items/CItem.h @@ -25,20 +25,69 @@ class CWorldTicker; class CCSpawn; -enum ITC_TYPE // Item Template commands + +/** +* @enum MEMORY_TYPE +* +* @brief Values that represent memory types ( IT_EQ_MEMORY_OBJ ). +* +* Types of memory a CChar has about a game object. (m_wHue) +*/ +enum MEMORY_TYPE +{ + MEMORY_NONE = 0, + MEMORY_SAWCRIME = 0x0001, // I saw them commit a crime or i was attacked criminally. I can call the guards on them. the crime may not have been against me. + MEMORY_IPET = 0x0002, // I am a pet. (this link is my master) (never time out) + MEMORY_FIGHT = 0x0004, // Active fight going on now. may not have done any damage. and they may not know they are fighting me. + MEMORY_IAGGRESSOR = 0x0008, // I was the agressor here. (good or evil) + MEMORY_HARMEDBY = 0x0010, // I was harmed by them. (but they may have been retaliating) + MEMORY_IRRITATEDBY = 0x0020, // I saw them snoop from me or someone, or i have been attacked, or someone used Provocation on me. + MEMORY_SPEAK = 0x0040, // We spoke about something at some point. (or was tamed) (NPC_MEM_ACT_TYPE) + MEMORY_AGGREIVED = 0x0080, // I was attacked and was the inocent party here ! + MEMORY_GUARD = 0x0100, // Guard this item (never time out) + MEMORY_LEGACY_ISPAWNED = 0x0200, // UNUSED (but keep this for backwards compatibility). I am spawned from this item. (never time out) + MEMORY_GUILD = 0x0400, // This is my guild stone. (never time out) only have 1 + MEMORY_TOWN = 0x0800, // This is my town stone. (never time out) only have 1 + MEMORY_UNUSED = 0x1000, // UNUSED!!!! I am following this Object (never time out) + MEMORY_UNUSED2 = 0x2000, // UNUSED!!!! (MEMORY_WAR_TARG) This is one of my current war targets. + MEMORY_FRIEND = 0x4000, // They can command me but not release me. (not primary blame) + MEMORY_UNUSED3 = 0x8000 // UNUSED!!!! Gump record memory (More1 = Context, More2 = Uid) +}; + +enum NPC_MEM_ACT_TYPE // A simgle primary memory about the object. +{ + NPC_MEM_ACT_NONE = 0, // we spoke about something non-specific, + NPC_MEM_ACT_SPEAK_TRAIN, // I am speaking about training. Waiting for money + NPC_MEM_ACT_SPEAK_HIRE, // I am speaking about being hired. Waiting for money + NPC_MEM_ACT_FIRSTSPEAK, // I attempted (or could have) to speak to player. but have had no response. + NPC_MEM_ACT_TAMED, // I was tamed by this person previously. + NPC_MEM_ACT_IGNORE // I looted or looked at and discarded this item (ignore it) +}; + +enum STONEALIGN_TYPE // Types of Guild/Town stones +{ + STONEALIGN_STANDARD = 0, + STONEALIGN_ORDER, + STONEALIGN_CHAOS +}; + + +// Item Template commands +enum ITC_TYPE { - ITC_BREAK, - ITC_BUY, - ITC_CONTAINER, - ITC_FULLINTERP, - ITC_FUNC, - ITC_ITEM, - ITC_ITEMNEWBIE, - ITC_NEWBIESWAP, - ITC_SELL, - ITC_QTY + ITC_BREAK, + ITC_BUY, + ITC_CONTAINER, + ITC_FULLINTERP, + ITC_FUNC, + ITC_ITEM, + ITC_ITEMNEWBIE, + ITC_NEWBIESWAP, + ITC_SELL, + ITC_QTY }; + class CItem : public CObjBase { // RES_WORLDITEM @@ -54,21 +103,17 @@ class CItem : public CObjBase static lpctstr const sm_szTrigName[ITRIG_QTY+1]; static lpctstr const sm_szTemplateTable[ITC_QTY+1]; -private: +protected: ITEMID_TYPE m_dwDispIndex; // The current display type. ITEMID_TYPE word m_wAmount; // Amount of items in pile. 64K max (or corpse type) + word m_weight; IT_TYPE m_type; // What does this item do when dclicked ? defines dynamic_cast type - uchar m_containedGridIndex; // Which grid have i been placed in ? (when in a container) uint64 m_CanUse; // Base attribute flags. can_u_all/male/female.. - word m_weight; + uint64 m_Attr; + uchar m_containedGridIndex; // Which grid have i been placed in ? (when in a container) public: - CUID GetComponentOfMulti() const; - CUID GetLockDownOfMulti() const; - void SetComponentOfMulti(const CUID& uidMulti); - void SetLockDownOfMulti(const CUID& uidMulti); - - byte m_speed; + byte m_speed; // Attribute flags. #define ATTR_IDENTIFIED 0x0001 // This is the identified name. ??? @@ -114,13 +159,11 @@ class CItem : public CObjBase #define ATTR_VVVITEM 0x100000000000000 // ? Vice vs Virtue Item (Has CliLoc) - uint64 m_Attr; - // NOTE: If this link is set but not valid -> then delete the whole object ! CUID m_uidLink; // Linked to this other object in the world. (owned, key, etc) // Type specific info. IT_TYPE - union // 4(more1) + 4(more2) + 6(morep: (2 morex) (2 morey) (1 morez) (1 morem) ) = 14 bytes + union // 4(more1) + 4(more2) + 6(morep: (2 morex) (2 morey) (1 morez) (1 morem) ) = 14 bytes (+ padding?) { // IT_NORMAL struct // used only to save and restore all this junk. @@ -549,11 +592,10 @@ class CItem : public CObjBase CItem( ITEMID_TYPE id, CItemBase * pItemDef ); // only created via CreateBase() bool SetBase(CItemBase* pItemDef); public: - virtual ~CItem(); + virtual ~CItem() override; -private: - CItem(const CItem& copy); - CItem& operator=(const CItem& other); + CItem(const CItem& copy) = delete; + CItem& operator=(const CItem& other) = delete; protected: virtual int FixWeirdness() override; @@ -577,6 +619,12 @@ class CItem : public CObjBase bool _CanHoldTimer() const; public: + CUID GetComponentOfMulti() const; + CUID GetLockDownOfMulti() const; + void SetComponentOfMulti(const CUID& uidMulti); + void SetLockDownOfMulti(const CUID& uidMulti); + + bool CanHear() const; virtual void OnHear( lpctstr pszCmd, CChar * pSrc ); CItemBase * Item_GetDef() const; @@ -635,6 +683,10 @@ class CItem : public CObjBase { m_Attr &= ~uiAttr; } + uint64 GetAttrRaw() const noexcept + { + return m_Attr; + } bool IsAttr(uint64 uiAttr) const noexcept { // true even if only one flag among those passed is present @@ -776,10 +828,10 @@ protected: virtual void _SetTimeout(int64 iMsecs) override final; TRIGRET_TYPE OnTrigger( ITRIG_TYPE trigger, CTextConsole * pSrc, CScriptTriggerArgs * pArgs = nullptr ); // Item type specific stuff. - inline bool IsType(IT_TYPE type) const { + inline bool IsType(IT_TYPE type) const noexcept { return ( m_type == type ); } - inline IT_TYPE GetType() const { + inline IT_TYPE GetType() const noexcept { return m_type; } bool SetType( IT_TYPE type, bool fPreCheck = true ); diff --git a/src/game/items/CItemBase.cpp b/src/game/items/CItemBase.cpp index 4d519b64c..c9e723cc4 100644 --- a/src/game/items/CItemBase.cpp +++ b/src/game/items/CItemBase.cpp @@ -480,8 +480,11 @@ int CItemBase::IsID_Door( ITEMID_TYPE id ) noexcept // static for ( uint i = 0; i < ARRAY_COUNT(sm_Item_DoorBase); ++i) { - const int did = id - sm_Item_DoorBase[i]; - if ( did >= 0 && did <= 15 ) + if (id < sm_Item_DoorBase[i]) + continue; + + const uint did = id - sm_Item_DoorBase[i]; + if ( did <= 15 ) return ( did+1 ); } return 0; @@ -637,22 +640,22 @@ bool CItemBase::IsID_Chair( ITEMID_TYPE id ) noexcept // static bool CItemBase::GetItemData( ITEMID_TYPE id, CUOItemTypeRec_HS * pData, bool fNameNotNeeded) // static { - ADDTOCALLSTACK("CItemBase::GetItemData"); + //ADDTOCALLSTACK_DEBUG("CItemBase::GetItemData"); // Read from g_Install.m_fTileData // Get an Item tiledata def data. // Invalid object id ? // NOTE: This data should already be read into the m_ItemBase table ??? - - if (id >= g_Install.m_tiledata.GetItemMaxIndex()) + ASSERT(pData); + try { - g_Log.EventError("ITEMDEF has invalid ID=%d (0%x) (value is greater than the tiledata maximum index).\n", id, id); - return false; - } - if (!IsValidDispID(id)) - return false; + if (id >= g_Install.m_tiledata.GetItemMaxIndex()) + { + g_Log.EventError("ITEMDEF has invalid ID=%d (0%x) (value is greater than the tiledata maximum index).\n", id, id); + return false; + } + if (!IsValidDispID(id)) + return false; - try - { *pData = CUOItemInfo(id, fNameNotNeeded); } catch (const std::exception& e) @@ -703,17 +706,17 @@ void CItemBase::GetItemSpecificFlags( const CUOItemTypeRec_HS & tiledata, uint64 ADDTOCALLSTACK("CItemBase::GetItemSpecificFlags"); if ( type == IT_DOOR ) { - *uiBlockFlags &= ~CAN_I_BLOCK; + *uiBlockFlags &= ~ (uint64)CAN_I_BLOCK; if ( IsID_DoorOpen(id)) - *uiBlockFlags &= ~CAN_I_DOOR; + *uiBlockFlags &= ~ (uint64)CAN_I_DOOR; else *uiBlockFlags |= CAN_I_DOOR; } - if ( tiledata.m_flags & UFLAG3_LIGHT ) // this may actually be a moon gate or fire ? + if ( tiledata.m_flags & (uint64)UFLAG3_LIGHT ) // this may actually be a moon gate or fire ? *uiBlockFlags |= CAN_I_LIGHT; // normally of type IT_LIGHT_LIT; - if ( (tiledata.m_flags & UFLAG2_STACKABLE) || type == IT_REAGENT || id == ITEMID_EMPTY_BOTTLE ) + if ( (tiledata.m_flags & (uint64)UFLAG2_STACKABLE) || type == IT_REAGENT || id == ITEMID_EMPTY_BOTTLE ) *uiBlockFlags |= CAN_I_PILE; } @@ -791,7 +794,7 @@ height_t CItemBase::GetItemHeightFlags( const CUOItemTypeRec_HS & tiledata, uint height_t CItemBase::GetItemHeight( ITEMID_TYPE id, uint64 *uiBlockFlags ) // static { - ADDTOCALLSTACK_INTENSIVE("CItemBase::GetItemHeight"); + ADDTOCALLSTACK_DEBUG("CItemBase::GetItemHeight"); // Get just the height and the blocking flags for the item by id. // used for walk block checking. @@ -810,7 +813,7 @@ height_t CItemBase::GetItemHeight( ITEMID_TYPE id, uint64 *uiBlockFlags ) // sta } // Not already loaded. - CUOItemTypeRec_HS tiledata = {}; + CUOItemTypeRec_HS tiledata {}; if ( ! GetItemData( id, &tiledata, true )) { *uiBlockFlags = CAN_I_MOVEMASK; @@ -1013,7 +1016,7 @@ int CItemBase::GetMakeValue( int iQualityLevel ) // ARGS: // iQualityLevel = 0-100 - CValueRangeDef values = m_values; + CValueRangeDef values(m_values); if ( m_values.m_iLo == INT64_MIN || m_values.m_iHi == INT64_MIN ) { @@ -1024,8 +1027,8 @@ int CItemBase::GetMakeValue( int iQualityLevel ) } else { - values.m_iLo = llabs(values.m_iLo); - values.m_iHi = llabs(values.m_iHi); + values.m_iLo = abs(values.m_iLo); + values.m_iHi = abs(values.m_iHi); } return values.GetLinear(iQualityLevel*10); @@ -1524,6 +1527,7 @@ bool CItemBase::r_LoadVal( CScript &s ) if ( iArgQty <= 0 ) return false; m_flip_id.clear(); + m_flip_id.reserve(iArgQty); for ( int i = 0; i < iArgQty; ++i ) { ITEMID_TYPE id = (ITEMID_TYPE)(g_Cfg.ResourceGetIndexType( RES_ITEMDEF, ppArgs[i] )); @@ -1533,6 +1537,7 @@ bool CItemBase::r_LoadVal( CScript &s ) continue; m_flip_id.emplace_back(id); } + m_flip_id.shrink_to_fit(); } break; case IBC_DYE: @@ -1641,7 +1646,7 @@ bool CItemBase::r_LoadVal( CScript &s ) g_Log.EventError( "Setting new ID for base type %s not allowed\n", GetResourceName()); return false; } - + ITEMID_TYPE id = (ITEMID_TYPE)(g_Cfg.ResourceGetIndexType( RES_ITEMDEF, s.GetArgStr())); CItemBase * pItemDef = FindItemBase( id ); // make sure the base is loaded. if ( ! pItemDef ) @@ -1649,7 +1654,7 @@ bool CItemBase::r_LoadVal( CScript &s ) g_Log.EventError( "Setting unknown base ID=0%x for base type %s\n", id, GetResourceName()); return false; } - + /* * I add Is Duped Item check to check if item is from DUPELIST of base item, and ID won't change to baseid for unnecessarily. * I made this change to fix issue #512 (https://github.com/Sphereserver/Source-X/issues/512) @@ -1711,16 +1716,16 @@ bool CItemBase::r_LoadVal( CScript &s ) break; case IBC_TDATA1: - m_ttNormal.m_tData1 = s.GetArgVal(); + m_ttNormal.m_tData1 = s.GetArgDWVal(); break; case IBC_TDATA2: - m_ttNormal.m_tData2 = s.GetArgVal(); + m_ttNormal.m_tData2 = s.GetArgDWVal(); break; case IBC_TDATA3: - m_ttNormal.m_tData3 = s.GetArgVal(); + m_ttNormal.m_tData3 = s.GetArgDWVal(); break; case IBC_TDATA4: - m_ttNormal.m_tData4 = s.GetArgVal(); + m_ttNormal.m_tData4 = s.GetArgDWVal(); break; case IBC_TWOHANDS: @@ -1930,7 +1935,7 @@ bool CItemBaseMulti::AddComponent( tchar * pArgs ) size_t iQty = Str_ParseCmds( pArgs, piArgs, ARRAY_COUNT(piArgs)); if ( iQty <= 1 ) return false; - return AddComponent((ITEMID_TYPE)(RES_GET_INDEX(piArgs[0])), (short)piArgs[1], (short)piArgs[2], (char)piArgs[3] ); + return AddComponent((ITEMID_TYPE)(ResGetIndex((dword)piArgs[0])), (short)piArgs[1], (short)piArgs[2], (char)piArgs[3] ); } int CItemBaseMulti::GetDistanceMax() const @@ -2212,7 +2217,7 @@ bool CItemBaseMulti::r_WriteVal(lpctstr ptcKey, CSString & sVal, CTextConsole * CItemBase * CItemBase::FindItemBase( ITEMID_TYPE id ) // static { - ADDTOCALLSTACK_INTENSIVE("CItemBase::FindItemBase"); + ADDTOCALLSTACK_DEBUG("CItemBase::FindItemBase"); // CItemBase is a like item already loaded. if ( id <= ITEMID_NOTHING ) return nullptr; diff --git a/src/game/items/CItemCommCrystal.cpp b/src/game/items/CItemCommCrystal.cpp index 22bdd9266..43d4b578b 100644 --- a/src/game/items/CItemCommCrystal.cpp +++ b/src/game/items/CItemCommCrystal.cpp @@ -82,7 +82,7 @@ void CItemCommCrystal::OnHear(lpctstr pszCmd, CChar *pSrc) void CItemCommCrystal::r_Write(CScript & s) { - ADDTOCALLSTACK_INTENSIVE("CItemCommCrystal::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemCommCrystal::r_Write"); CItemVendable::r_Write(s); m_Speech.r_Write(s, "SPEECH"); } diff --git a/src/game/items/CItemContainer.cpp b/src/game/items/CItemContainer.cpp index f44450f35..e329591aa 100644 --- a/src/game/items/CItemContainer.cpp +++ b/src/game/items/CItemContainer.cpp @@ -22,7 +22,7 @@ CItemContainer::CItemContainer( ITEMID_TYPE id, CItemBase *pItemDef ) : CItemContainer::~CItemContainer() { CItemContainer::DeletePrepare(); - CContainer::ClearContainer(); // get rid of my contents first to protect against weight calc errors. + CContainer::ClearContainer(false); // get rid of my contents first to protect against weight calc errors. CItemMulti *pMulti = nullptr; if (_uidMultiSecured.IsValidUID()) @@ -60,9 +60,10 @@ bool CItemContainer::NotifyDelete() void CItemContainer::DeletePrepare() { ADDTOCALLSTACK("CItemContainer::DeletePrepare"); + if ( IsType( IT_EQ_TRADE_WINDOW )) Trade_Delete(); - + CContainer::ContentDelete(false); // This object and its contents need to be deleted on the same tick CItem::DeletePrepare(); } @@ -346,7 +347,7 @@ void CItemContainer::OnWeightChange( int iChange ) // Some containers do not add weight to you. if ( !IsWeighed() ) return; - + // Propagate the weight change up the stack if there is one. CContainer *pCont = dynamic_cast(GetParent()); if ( !pCont ) @@ -537,7 +538,7 @@ void CItemContainer::ContentAdd( CItem *pItem, CPointMap pt, bool bForceNoStack, // delete all it's pieces. CItemContainer *pCont = dynamic_cast(pItem); ASSERT(pCont); - pCont->ClearContainer(); + pCont->ClearContainer(false); break; } default: @@ -1001,7 +1002,7 @@ void CItemContainer::Restock() case LAYER_VENDOR_EXTRA: // clear all this junk periodically. // sell it back for cash value ? - ClearContainer(); + ClearContainer(false); break; case LAYER_VENDOR_BUYS: @@ -1294,7 +1295,7 @@ bool CItemContainer::r_Verb( CScript &s, CTextConsole *pSrc ) return false; case ICV_EMPTY: { - ClearContainer(); + ClearContainer(false); return true; } case ICV_FIXWEIGHT: @@ -1351,7 +1352,7 @@ bool CItemContainer::_OnTick() { case IT_TRASH_CAN: // Empty it ! - ClearContainer(); + ClearContainer(false); return true; case IT_CONTAINER: if ( IsAttr(ATTR_MAGIC) ) diff --git a/src/game/items/CItemCorpse.cpp b/src/game/items/CItemCorpse.cpp index 243ccec40..6494a4578 100644 --- a/src/game/items/CItemCorpse.cpp +++ b/src/game/items/CItemCorpse.cpp @@ -5,6 +5,7 @@ #include "../chars/CCharNPC.h" #include "../CWorldGameTime.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "CItem.h" #include "CItemCorpse.h" @@ -96,7 +97,7 @@ CChar *CItemCorpse::IsCorpseSleeping() const } CChar *pCharCorpse = m_uidLink.CharFind(); - if ( pCharCorpse && pCharCorpse->IsStatFlag(STATF_SLEEPING) && !GetTimeStamp() ) + if ( pCharCorpse && pCharCorpse->IsStatFlag(STATF_SLEEPING) && !GetTimeStampS() ) return pCharCorpse; return nullptr; @@ -141,10 +142,10 @@ CItemCorpse *CChar::FindMyCorpse( bool ignoreLOS, int iRadius ) const { ADDTOCALLSTACK("CChar::FindMyCorpse"); // If they are standing on their own corpse then res the corpse ! - CWorldSearch Area(GetTopPoint(), iRadius); + auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), iRadius); for (;;) { - CItem *pItem = Area.GetItem(); + CItem *pItem = Area->GetItem(); if ( !pItem ) break; if ( !pItem->IsType(IT_CORPSE) ) @@ -198,7 +199,7 @@ CItemCorpse * CChar::MakeCorpse( bool fFrontFall ) if (IsStatFlag(STATF_DEAD)) { iDecayTimer = (m_pPlayer) ? g_Cfg.m_iDecay_CorpsePlayer : g_Cfg.m_iDecay_CorpseNPC; - pCorpse->SetTimeStamp(CWorldGameTime::GetCurrentTime().GetTimeRaw()); // death time + pCorpse->SetTimeStampS(CWorldGameTime::GetCurrentTime().GetTimeRaw()); // death time if (Attacker_GetLast()) pCorpse->m_itCorpse.m_uidKiller = Attacker_GetLast()->GetUID(); else @@ -206,7 +207,7 @@ CItemCorpse * CChar::MakeCorpse( bool fFrontFall ) } else // sleeping (not dead) { - pCorpse->SetTimeStamp(0); + pCorpse->SetTimeStampS(0); pCorpse->m_itCorpse.m_uidKiller = GetUID(); } @@ -265,4 +266,4 @@ bool CChar::RaiseCorpse( CItemCorpse * pCorpse ) UpdateAnimate((pCorpse->m_itCorpse.m_facing_dir & DIR_MASK_RUNNING) ? ANIM_DIE_FORWARD : ANIM_DIE_BACK, true, true); pCorpse->Delete(); return true; -} \ No newline at end of file +} diff --git a/src/game/items/CItemMap.cpp b/src/game/items/CItemMap.cpp index 38b64460c..bffacdd28 100644 --- a/src/game/items/CItemMap.cpp +++ b/src/game/items/CItemMap.cpp @@ -91,7 +91,7 @@ bool CItemMap::r_WriteVal(lpctstr ptcKey, CSString &sVal, CTextConsole *pSrc, bo void CItemMap::r_Write(CScript & s) { - ADDTOCALLSTACK_INTENSIVE("CItemMap::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemMap::r_Write"); CItemVendable::r_Write(s); for ( size_t i = 0; i < m_Pins.size(); i++ ) s.WriteKeyFormat("PIN", "%i,%i", m_Pins[i].m_x, m_Pins[i].m_y); diff --git a/src/game/items/CItemMemory.cpp b/src/game/items/CItemMemory.cpp index e90cfc8d5..5ef7ea9a5 100644 --- a/src/game/items/CItemMemory.cpp +++ b/src/game/items/CItemMemory.cpp @@ -11,10 +11,6 @@ CItemMemory::CItemMemory( ITEMID_TYPE id, CItemBase * pItemDef ) : UnreferencedParameter(id); } -CItemMemory::~CItemMemory() -{ -} - word CItemMemory::SetMemoryTypes( word wType ) // For memory type objects. { SetHueQuick( wType ); diff --git a/src/game/items/CItemMemory.h b/src/game/items/CItemMemory.h index 502d3ed7b..ebc2294db 100644 --- a/src/game/items/CItemMemory.h +++ b/src/game/items/CItemMemory.h @@ -17,13 +17,11 @@ class CItemMemory : public CItem // Allow extra tags for the memory public: static const char *m_sClassName; - CItemMemory( ITEMID_TYPE id, CItemBase * pItemDef ); + CItemMemory(ITEMID_TYPE id, CItemBase * pItemDef); + virtual ~CItemMemory() override = default; - virtual ~CItemMemory(); - -private: - CItemMemory(const CItemMemory& copy); - CItemMemory& operator=(const CItemMemory& other); + CItemMemory(const CItemMemory& copy) = delete; + CItemMemory& operator=(const CItemMemory& other) = delete; public: word SetMemoryTypes( word wType ); diff --git a/src/game/items/CItemMessage.cpp b/src/game/items/CItemMessage.cpp index 454425649..a068827e1 100644 --- a/src/game/items/CItemMessage.cpp +++ b/src/game/items/CItemMessage.cpp @@ -26,7 +26,7 @@ lpctstr const CItemMessage::sm_szLoadKeys[CIC_QTY+1] = void CItemMessage::r_Write(CScript & s) { - ADDTOCALLSTACK_INTENSIVE("CItemMessage::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemMessage::r_Write"); CItemVendable::r_Write(s); s.WriteKeyStr("AUTHOR", m_sAuthor.GetBuffer()); diff --git a/src/game/items/CItemMulti.cpp b/src/game/items/CItemMulti.cpp index d32ec8e16..10da80797 100644 --- a/src/game/items/CItemMulti.cpp +++ b/src/game/items/CItemMulti.cpp @@ -6,6 +6,7 @@ #include "../CServer.h" #include "../CWorld.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CItemMulti.h" #include "CItemShip.h" @@ -246,11 +247,11 @@ bool CItemMulti::MultiRealizeRegion() //We have to update the Characters if not moving around like Player Vendors. //Otherwise, when you reboot server, the region.name of the characters returns as Region name instead of multis. - CWorldSearch Area(m_pRegion->m_pt, Multi_GetDistanceMax()); - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(m_pRegion->m_pt, Multi_GetDistanceMax()); + Area->SetSearchSquare(true); for (;;) { - CChar* pChar = Area.GetChar(); + CChar* pChar = Area->GetChar(); if (pChar == nullptr) //Invalid char? Ignore. { break; @@ -277,11 +278,11 @@ void CItemMulti::MultiUnRealizeRegion() m_pRegion->UnRealizeRegion(); // find all creatures in the region and remove this from them. - CWorldSearch Area(m_pRegion->m_pt, Multi_GetDistanceMax()); - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(m_pRegion->m_pt, Multi_GetDistanceMax()); + Area->SetSearchSquare(true); for (;;) { - CChar * pChar = Area.GetChar(); + CChar * pChar = Area->GetChar(); if (pChar == nullptr) { break; @@ -463,11 +464,11 @@ CItem * CItemMulti::Multi_FindItemType(IT_TYPE type) const return nullptr; } - CWorldSearch Area(GetTopPoint(), Multi_GetDistanceMax()); - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), Multi_GetDistanceMax()); + Area->SetSearchSquare(true); for (;;) { - CItem * pItem = Area.GetItem(); + CItem * pItem = Area->GetItem(); if (pItem == nullptr) { return nullptr; @@ -1042,12 +1043,12 @@ void CItemMulti::Eject(const CUID& uidChar) void CItemMulti::EjectAll(CUID uidCharNoTp) { - CWorldSearch Area(m_pRegion->m_pt, Multi_GetDistanceMax()); - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(m_pRegion->m_pt, Multi_GetDistanceMax()); + Area->SetSearchSquare(true); CChar *pCharNoTp = uidCharNoTp.CharFind(); for (;;) { - CChar * pChar = Area.GetChar(); + CChar * pChar = Area->GetChar(); if (pChar == nullptr) { break; @@ -1339,11 +1340,12 @@ void CItemMulti::TransferAllItemsToMovingCrate(TRANSFER_TYPE iType) { ptArea = m_pRegion->m_pt; } - CWorldSearch Area(ptArea, Multi_GetDistanceMax()); // largest area. - Area.SetSearchSquare(true); + + auto Area = CWorldSearchHolder::GetInstance(ptArea, Multi_GetDistanceMax()); // largest area. + Area->SetSearchSquare(true); for (;;) { - CItem * pItem = Area.GetItem(); + CItem * pItem = Area->GetItem(); if (pItem == nullptr) { break; @@ -1379,7 +1381,7 @@ void CItemMulti::TransferAllItemsToMovingCrate(TRANSFER_TYPE iType) if (fTransferAddons) // Shall be transfered, but addons needs an special transfer code by redeeding. { static_cast(pItem)->Redeed(false, false); - Area.RestartSearch(); // we removed an item and this will mess the search loop, so restart to fix it. + Area->RestartSearch(); // we removed an item and this will mess the search loop, so restart to fix it. continue; } else @@ -2516,7 +2518,7 @@ const lpctstr CItemMulti::sm_szLoadKeys[SHL_QTY + 1] = void CItemMulti::r_Write(CScript & s) { - ADDTOCALLSTACK_INTENSIVE("CItemMulti::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemMulti::r_Write"); CItem::r_Write(s); if (m_pRegion) { @@ -3272,11 +3274,11 @@ CItem *CItemMulti::Multi_Create(CChar *pChar, const CItemBase * pItemDef, CPoint CPointMap ptn = pt; // A copy to work on. // Check for chars in the way, just search for any char in the house area, no extra tiles, it's enough for them to do not be inside the house. - CWorldSearch Area(pt, std::max(rect.GetWidth(), rect.GetHeight())); - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(pt, std::max(rect.GetWidth(), rect.GetHeight())); + Area->SetSearchSquare(true); for (;;) { - CChar * pCharSearch = Area.GetChar(); + CChar * pCharSearch = Area->GetChar(); if (pCharSearch == nullptr) { break; @@ -3395,7 +3397,7 @@ CItem *CItemMulti::Multi_Create(CChar *pChar, const CItemBase * pItemDef, CPoint return nullptr; } - pItemNew->SetAttr(ATTR_MOVE_NEVER | (pDeed->m_Attr & (ATTR_MAGIC | ATTR_INVIS))); + pItemNew->SetAttr(ATTR_MOVE_NEVER | (pDeed->GetAttrRaw() & (ATTR_MAGIC | ATTR_INVIS))); pItemNew->SetHue(pDeed->GetHue()); pItemNew->MoveToUpdate(pt); diff --git a/src/game/items/CItemMultiCustom.cpp b/src/game/items/CItemMultiCustom.cpp index 1de47c1bf..34ff19621 100644 --- a/src/game/items/CItemMultiCustom.cpp +++ b/src/game/items/CItemMultiCustom.cpp @@ -11,6 +11,7 @@ #include "../clients/CClient.h" #include "../CServer.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CItemMultiCustom.h" @@ -173,11 +174,11 @@ void CItemMultiCustom::BeginCustomize(CClient* pClientSrc, bool continueCustomiz pChar->UpdateMove(ptOld); // hide all dynamic items inside the house - CWorldSearch Area(GetTopPoint(), GetDesignArea().GetWidth() / 2); - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), GetDesignArea().GetWidth() / 2); + Area->SetSearchSquare(true); for (;;) { - CItem * pItem = Area.GetItem(); + CItem * pItem = Area->GetItem(); if (pItem == nullptr) break; if (pItem != this) @@ -338,12 +339,12 @@ void CItemMultiCustom::CommitChanges(CClient * pClientSrc) return; // remove all existing dynamic item fixtures - CWorldSearch Area(ptMe, GetDesignArea().GetWidth()); - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(ptMe, GetDesignArea().GetWidth()); + Area->SetSearchSquare(true); CItem * pItem; for (;;) { - pItem = Area.GetItem(); + pItem = Area->GetItem(); if (pItem == nullptr) break; @@ -1407,11 +1408,11 @@ void CItemMultiCustom::ClearFloor(char iFloor) } } - CWorldSearch Area(m_pRegion->m_pt, Multi_GetDistanceMax()); // largest area. - Area.SetSearchSquare(true); + auto Area = CWorldSearchHolder::GetInstance(m_pRegion->m_pt, Multi_GetDistanceMax()); // largest area. + Area->SetSearchSquare(true); for (;;) { - CItem * pItem = Area.GetItem(); + CItem * pItem = Area->GetItem(); if (pItem == nullptr) { break; @@ -1435,7 +1436,7 @@ void CItemMultiCustom::ClearFloor(char iFloor) if (pItem->IsType(IT_MULTI_ADDON) || pItem->IsType(IT_MULTI)) // If the item is a house Addon, redeed it. { static_cast(pItem)->Redeed(false, false); - Area.RestartSearch(); // we removed an item and this will mess the search loop, so restart to fix it. + Area->RestartSearch(); // we removed an item and this will mess the search loop, so restart to fix it. continue; } pItem->RemoveFromView(); @@ -1694,7 +1695,7 @@ bool CItemMultiCustom::r_Verb(CScript & s, CTextConsole * pSrc) // Execute comma void CItemMultiCustom::r_Write(CScript & s) { - ADDTOCALLSTACK_INTENSIVE("CItemMultiCustom::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemMultiCustom::r_Write"); CItemMulti::r_Write(s); CMultiComponent * comp; diff --git a/src/game/items/CItemPlant.cpp b/src/game/items/CItemPlant.cpp index e5182a59f..7378d6672 100644 --- a/src/game/items/CItemPlant.cpp +++ b/src/game/items/CItemPlant.cpp @@ -2,7 +2,7 @@ #include "CItem.h" #include "../chars/CChar.h" #include "../triggers.h" -#include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../../common/CScriptTriggerArgs.h" #include "../../common/resource/CResourceID.h" #include "../CServer.h" @@ -39,9 +39,9 @@ bool CItem::Plant_Use(CChar *pChar) { CScriptTriggerArgs args(iGrowID, iFruitID, iFruitIDOverride); TRIGRET_TYPE iRet = OnTrigger(ITRIG_ResourceTest, pChar, &args); - iGrowID = (ITEMID_TYPE)(RES_GET_INDEX(args.m_iN1)); - iFruitID = (ITEMID_TYPE)(RES_GET_INDEX(args.m_iN2)); - iFruitIDOverride = (ITEMID_TYPE)(RES_GET_INDEX(args.m_iN3)); + iGrowID = (ITEMID_TYPE)(ResGetIndex((dword)args.m_iN1)); + iFruitID = (ITEMID_TYPE)(ResGetIndex((dword)args.m_iN2)); + iFruitIDOverride = (ITEMID_TYPE)(ResGetIndex((dword)args.m_iN3)); if (iRet == TRIGRET_RET_TRUE) return true; } @@ -131,10 +131,10 @@ bool CItem::Plant_OnTick() if (iFruitID != ITEMID_NOTHING) { // Put a fruit on the ground if not already here. - CWorldSearch AreaItems(GetTopPoint()); + auto AreaItems = CWorldSearchHolder::GetInstance(GetTopPoint()); for (;;) { - CItem *pItem = AreaItems.GetItem(); + CItem *pItem = AreaItems->GetItem(); if ( !pItem ) { CItem *pItemFruit = CItem::CreateScript(iFruitID); diff --git a/src/game/items/CItemShip.cpp b/src/game/items/CItemShip.cpp index d26ee898a..67bdffee5 100644 --- a/src/game/items/CItemShip.cpp +++ b/src/game/items/CItemShip.cpp @@ -6,6 +6,7 @@ #include "../chars/CChar.h" #include "../CServer.h" #include "../CWorldMap.h" +#include "../CWorldSearch.h" #include "../triggers.h" #include "CItemContainer.h" #include "CItemShip.h" @@ -55,7 +56,7 @@ bool CItem::Ship_Plank(bool fOpen) m_itShipPlank.m_wSideType = (word)oldType; if ( !IsTimerSet() ) { - SetTimeoutS(5); // autoclose the plank + SetTimeoutS(5); // autoclose the plank SetAttr(ATTR_DECAY); // For preventing Decay Warning on the console. } } @@ -112,7 +113,7 @@ bool CItemShip::r_Verb(CScript & s, CTextConsole * pSrc) // Execute command from void CItemShip::r_Write(CScript & s) { - ADDTOCALLSTACK_INTENSIVE("CItemShip::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemShip::r_Write"); CItemMulti::r_Write(s); if (m_uidHold.IsValidUID()) s.WriteKeyHex("HATCH", m_uidHold.GetObjUID()); @@ -125,7 +126,7 @@ void CItemShip::r_Write(CScript & s) } } -enum IMCS_TYPE +enum IMCS_TYPE : int { IMCS_HATCH, IMCS_PLANK, @@ -196,7 +197,7 @@ bool CItemShip::r_LoadVal(CScript & s) EXC_TRY("LoadVal"); lpctstr ptcKey = s.GetKey(); IMCS_TYPE index = (IMCS_TYPE)FindTableHeadSorted(ptcKey, sm_szLoadKeys, ARRAY_COUNT(sm_szLoadKeys) - 1); - if (g_Serv.IsLoading()) + if (index >= 0 && g_Serv.IsLoading()) { switch (index) { @@ -305,10 +306,10 @@ CItem * CItemShip::GetShipPlank(size_t index) // Find plank(s) if the list is empty if (m_uidPlanks.empty()) { - CWorldSearch Area(GetTopPoint(), Multi_GetDistanceMax()); + auto Area = CWorldSearchHolder::GetInstance(GetTopPoint(), Multi_GetDistanceMax()); for (;;) { - const CItem * pItem = Area.GetItem(); + const CItem * pItem = Area->GetItem(); if (pItem == nullptr) break; diff --git a/src/game/items/CItemStone.cpp b/src/game/items/CItemStone.cpp index a7d3ba906..8b993995b 100644 --- a/src/game/items/CItemStone.cpp +++ b/src/game/items/CItemStone.cpp @@ -119,7 +119,7 @@ lpctstr CItemStone::GetTypeName() const void CItemStone::r_Write( CScript & s ) { - ADDTOCALLSTACK_INTENSIVE("CItemStone::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemStone::r_Write"); CItem::r_Write( s ); s.WriteKeyVal( "ALIGN", GetAlignType()); if ( ! m_sAbbrev.IsEmpty()) diff --git a/src/game/items/CItemVendable.cpp b/src/game/items/CItemVendable.cpp index 9a995a711..147d7390f 100644 --- a/src/game/items/CItemVendable.cpp +++ b/src/game/items/CItemVendable.cpp @@ -94,7 +94,7 @@ bool CItemVendable::r_LoadVal(CScript &s) void CItemVendable::r_Write(CScript &s) { - ADDTOCALLSTACK_INTENSIVE("CItemVendable::r_Write"); + ADDTOCALLSTACK_DEBUG("CItemVendable::r_Write"); CItem::r_Write(s); if ( GetQuality() != 0 ) s.WriteKeyVal( "QUALITY", GetQuality()); @@ -201,7 +201,7 @@ dword CItemVendable::GetVendorPrice( int iConvertFactor , bool forselling ) if ( IsType(IT_DEED) ) { // Deeds just represent the item they are deeding. - pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(RES_GET_INDEX(m_itDeed.m_Type))); + pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex(m_itDeed.m_Type))); if ( !pItemDef ) return 1; } diff --git a/src/game/items/CItemVendable.h b/src/game/items/CItemVendable.h index 727623b09..5fbe8b471 100644 --- a/src/game/items/CItemVendable.h +++ b/src/game/items/CItemVendable.h @@ -22,12 +22,10 @@ class CItemVendable : public CItem CItemVendable( ITEMID_TYPE id, CItemBase * pItemDef ); virtual ~CItemVendable(); -private: - CItemVendable(const CItemVendable& copy); - CItemVendable& operator=(const CItemVendable& other); + CItemVendable(const CItemVendable& copy) = delete; + CItemVendable& operator=(const CItemVendable& other) = delete; public: - word GetQuality() const; void SetQuality( word quality = 0 ); diff --git a/src/game/spheresvr.cpp b/src/game/spheresvr.cpp index d570a9a24..0a3361304 100644 --- a/src/game/spheresvr.cpp +++ b/src/game/spheresvr.cpp @@ -200,7 +200,7 @@ void MainThread::tick() Sphere_OnTick(); } -bool MainThread::shouldExit() +bool MainThread::shouldExit() noexcept { if (g_Serv.GetExitFlag() != 0) return true; @@ -249,10 +249,11 @@ int Sphere_InitServer( int argc, char *argv[] ) { constexpr const char *m_sClassName = "SphereInit"; EXC_TRY("Init Server"); - EXC_SET_BLOCK("loading ini and scripts"); + GlobalInitializer::InitRuntimeDefaultValues(); + + EXC_SET_BLOCK("loading ini and scripts"); if ( !g_Serv.Load() ) return -3; - GlobalInitializer::InitRuntimeDefaultValues(); if ( argc > 1 ) { @@ -778,20 +779,29 @@ int _cdecl main( int argc, char * argv[] ) static constexpr lpctstr m_sClassName = "main"; EXC_TRY("MAIN"); -#ifndef _WIN32 - IThread::setThreadName("T_SphereStartup"); - g_UnixTerminal.start(); - // We need to find out the log files folder... look it up in the .ini file (on Windows it's done in WinMain function). - g_Cfg.LoadIni(false); -#endif - - const int atexit_handler_result = std::atexit(atexit_handler); // Handler will be called + const int atexit_handler_result = std::atexit(atexit_handler); // Handler will be called if (atexit_handler_result != 0) { g_Log.Event(LOGL_CRIT, "atexit handler registration failed.\n"); goto exit_server; } + { + // Ensure i have this to have context for ADDTOCALLSTACK and other operations. + const IThread* curthread = ThreadHolder::get().current(); + ASSERT(curthread != nullptr); + ASSERT(dynamic_cast(curthread)); + } + +#ifndef _WIN32 + IThread::setThreadName("T_SphereStartup"); + + g_UnixTerminal.start(); + + // We need to find out the log files folder... look it up in the .ini file (on Windows it's done in WinMain function). + g_Serv.SetServerMode(SERVMODE_PreLoadingINI); + g_Cfg.LoadIni(false); +#endif g_Serv.SetServerMode(SERVMODE_Loading); g_Serv.SetExitFlag( Sphere_InitServer( argc, argv )); @@ -812,8 +822,8 @@ int _cdecl main( int argc, char * argv[] ) // an instance of CNetworkInput nad CNetworkOutput, which support working in a multi threaded way (declarations and definitions in network_multithreaded.h/.cpp) g_NetworkManager.start(); - const bool shouldRunInThread = ( g_Cfg.m_iFreezeRestartTime > 0 ); - if (shouldRunInThread) + const bool fShouldCoreRunInSeparateThread = ( g_Cfg.m_iFreezeRestartTime > 0 ); + if (fShouldCoreRunInSeparateThread) { g_Main.start(); // Starts another thread to do all the work (it does Sphere_OnTick()) IThread::setThreadName("T_Monitor"); diff --git a/src/game/spheresvr.h b/src/game/spheresvr.h index 9cd7de0fc..1633b2561 100644 --- a/src/game/spheresvr.h +++ b/src/game/spheresvr.h @@ -34,11 +34,11 @@ class MainThread : public AbstractSphereThread // we increase the access level from protected to public in order to allow manual execution when // configuration disables using threads // TODO: in the future, such simulated functionality should lie in AbstractThread inself instead of hacks - virtual void tick(); + virtual void tick() override; protected: - virtual void onStart(); - virtual bool shouldExit(); + virtual void onStart() override; + virtual bool shouldExit() noexcept override; }; ////////////////////////////////////////////////////////////// diff --git a/src/game/triggers.cpp b/src/game/triggers.cpp index 430cd2029..8958d1114 100644 --- a/src/game/triggers.cpp +++ b/src/game/triggers.cpp @@ -1,50 +1,66 @@ -#include #include "../common/CLog.h" #include "CServer.h" #include "triggers.h" //Trigger function start -struct T_TRIGGERS +static constexpr lpctstr kOrderedTrigsNames[] = { + #define ADD(_a_) \ + "@" #_a_ , + + #include "../tables/triggers.tbl" + #undef ADD +}; + +struct TRIGGER_T_ID { char m_name[TRIGGER_NAME_MAX_LEN]; int m_used; }; -std::vector g_triggers; +//struct TRIGGER_T_str +//{ +// int m_used; +//}; +std::vector g_triggers_id; + bool IsTrigUsed(E_TRIGGERS id) { if ( g_Serv.IsLoading() == true) return false; - return (( (uint)id < g_triggers.size() ) && g_triggers[id].m_used ); + + return (( (uint)id < g_triggers_id.size() ) && g_triggers_id[id].m_used ); } bool IsTrigUsed(const char *name) { if ( g_Serv.IsLoading() == true) return false; - for ( auto it = g_triggers.cbegin(), end = g_triggers.cend(); it != end; ++it ) - { - if ( !strcmpi(it->m_name, name) ) - return (it->m_used != 0); // Returns true or false for known triggers - } - return true; //Must return true for custom triggers + + int index = FindTableSorted(name, kOrderedTrigsNames, ARRAY_COUNT(kOrderedTrigsNames)); + if (index >= 0) + return IsTrigUsed((E_TRIGGERS)index); + return false; } void TriglistInit() { - T_TRIGGERS trig; - g_triggers.clear(); + TRIGGER_T_ID trig{}; + g_triggers_id.clear(); + +#define ADD(_a_) \ + snprintf(trig.m_name, TRIGGER_NAME_MAX_LEN, "@%s", #_a_); \ + trig.m_used = 0; \ + g_triggers_id.push_back(trig); -#define ADD(_a_) snprintf(trig.m_name, TRIGGER_NAME_MAX_LEN, "@%s", #_a_); trig.m_used = 0; g_triggers.emplace_back(trig); #include "../tables/triggers.tbl" #undef ADD } void TriglistClear() { - for ( auto it = g_triggers.begin(), end = g_triggers.end(); it != end; ++it ) + for ( auto it = g_triggers_id.begin(), end = g_triggers_id.end(); it != end; ++it ) { it->m_used = 0; } @@ -52,13 +68,13 @@ void TriglistClear() void TriglistAdd(E_TRIGGERS id) { - if ( g_triggers.size() ) - ++ g_triggers[id].m_used; + if (g_triggers_id.size() ) + ++ g_triggers_id[id].m_used; } void TriglistAdd(const char *name) { - for ( auto it = g_triggers.begin(), end = g_triggers.end(); it != end; ++it ) + for ( auto it = g_triggers_id.begin(), end = g_triggers_id.end(); it != end; ++it ) { if ( !strcmpi(it->m_name, name) ) { @@ -71,7 +87,7 @@ void TriglistAdd(const char *name) void Triglist(int &total, int &used) { total = used = 0; - for ( auto it = g_triggers.cbegin(), end = g_triggers.cend(); it != end; ++it ) + for ( auto it = g_triggers_id.cbegin(), end = g_triggers_id.cend(); it != end; ++it ) { ++total; if ( it->m_used ) @@ -81,7 +97,7 @@ void Triglist(int &total, int &used) void TriglistPrint() { - for ( auto it = g_triggers.cbegin(), end = g_triggers.cend(); it != end; ++it ) + for ( auto it = g_triggers_id.cbegin(), end = g_triggers_id.cend(); it != end; ++it ) { if (it->m_used) { diff --git a/src/game/uo_files/CUOItemInfo.cpp b/src/game/uo_files/CUOItemInfo.cpp index 9372a13f4..18e4d74fd 100644 --- a/src/game/uo_files/CUOItemInfo.cpp +++ b/src/game/uo_files/CUOItemInfo.cpp @@ -24,6 +24,10 @@ CUOItemInfo::CUOItemInfo( ITEMID_TYPE id, bool fNameNotNeeded) { strcpy(m_name, CItemBase::IsID_Ship(id) ? "ship" : "structure"); } + else + { + m_name[0] = '\0'; + } return; } @@ -40,4 +44,8 @@ CUOItemInfo::CUOItemInfo( ITEMID_TYPE id, bool fNameNotNeeded) { Str_CopyLimitNull(m_name, cachedEntry->m_name, ARRAY_COUNT(m_name)); } + else + { + m_name[0] = '\0'; + } } diff --git a/src/game/uo_files/CUOMapList.cpp b/src/game/uo_files/CUOMapList.cpp index 4f2cc5273..fd0f08c01 100644 --- a/src/game/uo_files/CUOMapList.cpp +++ b/src/game/uo_files/CUOMapList.cpp @@ -156,6 +156,7 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, // #5 - map5.mul (ter mur, 1280x4096, 16056320 bytes) // +#define SECTORSIZE_DEFAULT 64 /* 8 x 8 */ MapGeoData& map_data = m_mapGeoData.maps[map]; switch (index) { @@ -181,7 +182,7 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, } if (map_data.sectorsize <= 0) - map_data.sectorsize = 64; + map_data.sectorsize = SECTORSIZE_DEFAULT; break; case 2: // map2.mul @@ -190,7 +191,7 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, if (map_data.sizey <= 0) map_data.sizey = 1600; if (map_data.sectorsize <= 0) - map_data.sectorsize = 64; + map_data.sectorsize = SECTORSIZE_DEFAULT; break; case 3: // map3.mul @@ -199,7 +200,7 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, if (map_data.sizey <= 0) map_data.sizey = 2048; if (map_data.sectorsize <= 0) - map_data.sectorsize = 64; + map_data.sectorsize = SECTORSIZE_DEFAULT; break; case 4: // map4.mul @@ -208,7 +209,7 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, if (map_data.sizey <= 0) map_data.sizey = 1448; if (map_data.sectorsize <= 0) - map_data.sectorsize = 64; + map_data.sectorsize = SECTORSIZE_DEFAULT; break; case 5: // map5.mul @@ -217,7 +218,7 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, if (map_data.sizey <= 0) map_data.sizey = 4096; if (map_data.sectorsize <= 0) - map_data.sectorsize = 64; + map_data.sectorsize = SECTORSIZE_DEFAULT; break; default: diff --git a/src/game/uo_files/CUOMapMeter.h b/src/game/uo_files/CUOMapMeter.h index e403f0868..eaac0fd12 100644 --- a/src/game/uo_files/CUOMapMeter.h +++ b/src/game/uo_files/CUOMapMeter.h @@ -26,6 +26,7 @@ struct CUOMapMeter { word m_wTerrainIndex; // TERRAIN_TYPE index to Radarcol and CUOTerrainTypeRec/CUOTerrainTypeRec_HS char m_z; + static bool IsTerrainNull( word wTerrainIndex ); } PACK_NEEDED; diff --git a/src/game/uo_files/uofiles_enums.h b/src/game/uo_files/uofiles_enums.h index de3493853..72f73988e 100644 --- a/src/game/uo_files/uofiles_enums.h +++ b/src/game/uo_files/uofiles_enums.h @@ -286,7 +286,7 @@ enum CRESND_TYPE // Placeholders (not real sound IDs): the SoundChar method choo CRESND_RAND = -1, // pick up randomly CRESND_IDLE or CRESND_NOTICE CRESND_IDLE = 0, // just random noise. or default "no" response CRESND_NOTICE, // just random noise. or default "yes" response - + CRESND_HIT, CRESND_GETHIT, CRESND_DIE @@ -450,7 +450,7 @@ enum SKILL_TYPE // List of skill numbers (things that can be done at a given tim }; -enum LAYER_TYPE // defined by UO. Only one item can be in a slot. +enum LAYER_TYPE : unsigned char // defined by UO. Only one item can be in a slot. { LAYER_NONE = 0, // spells that are layed on the CChar ? LAYER_HAND1, // 1 = spellbook or weapon. diff --git a/src/network/CNetworkOutput.cpp b/src/network/CNetworkOutput.cpp index 269411253..b2168ec91 100644 --- a/src/network/CNetworkOutput.cpp +++ b/src/network/CNetworkOutput.cpp @@ -67,10 +67,10 @@ bool CNetworkOutput::processOutput() ASSERT(!m_thread->isActive() || m_thread->isCurrentThread()); const ProfileTask networkTask(PROFILE_NETWORK_TX); + static thread_local uchar tick = 0; - static uchar tick = 0; EXC_TRY("CNetworkOutput"); - ++tick; + tick = (tick == 16) ? 0 : (tick + 1); // decide which queues need to be processed bool toProcess[PacketSend::PRI_QTY]; diff --git a/src/network/PingServer.cpp b/src/network/PingServer.cpp index 9f14d504d..d84b3aeca 100644 --- a/src/network/PingServer.cpp +++ b/src/network/PingServer.cpp @@ -54,7 +54,7 @@ void PingServer::tick() socklen_t addr_len = sizeof(addr); const ProfileTask receiveTask(PROFILE_NETWORK_RX); - + // receive data from someone int length = recvfrom(m_socket.GetSocket(), buffer, sizeof(buffer), 0, reinterpret_cast(&addr), &addr_len); if ( length <= 0 ) @@ -71,7 +71,7 @@ void PingServer::tick() GetCurrentProfileData().Count(PROFILE_DATA_TX, sent); } -bool PingServer::shouldExit() +bool PingServer::shouldExit() noexcept { if (m_socket.IsOpen() == false) return true; diff --git a/src/network/PingServer.h b/src/network/PingServer.h index 2f83fa155..9be1d819a 100644 --- a/src/network/PingServer.h +++ b/src/network/PingServer.h @@ -28,10 +28,10 @@ class PingServer : public AbstractSphereThread PingServer& operator=(const PingServer& other); public: - virtual void onStart(); - virtual void tick(); - virtual bool shouldExit(); - virtual void waitForClose(); + virtual void onStart() override; + virtual void tick() override; + virtual bool shouldExit() noexcept override; + virtual void waitForClose() override; }; diff --git a/src/network/packet.cpp b/src/network/packet.cpp index e95b2d91c..fd6a48f3e 100644 --- a/src/network/packet.cpp +++ b/src/network/packet.cpp @@ -184,7 +184,7 @@ void Packet::resize(uint newsize) memcpy(buffer, m_buffer, m_bufferSize); delete[] m_buffer; } - + m_buffer = buffer; m_bufferSize = newsize; m_length = m_bufferSize; @@ -205,9 +205,13 @@ void Packet::skip(int count) { // ensure we can't go lower than 0 if (count < 0 && (uint)SphereAbs(count) > m_position) + { m_position = 0; - else - m_position += (uint)count; + return; + } + + ASSERT((int64)m_position + count < UINT32_MAX); + m_position += (uint)count; } byte &Packet::operator[](uint index) @@ -235,7 +239,7 @@ void Packet::writeCharASCII(const char value) { if ((m_position + sizeof(char)) > m_bufferSize) expand(sizeof(char)); - + ASSERT((m_position + sizeof(char)) <= m_bufferSize); m_buffer[m_position++] = (byte)(value); } @@ -244,7 +248,7 @@ void Packet::writeCharUTF16(const wchar value) { if ((m_position + sizeof(wchar)) > m_bufferSize) expand(sizeof(wchar)); - + ASSERT((m_position + sizeof(wchar)) <= m_bufferSize); m_buffer[m_position++] = (byte)(value); m_buffer[m_position++] = (byte)(value >> 8); @@ -254,7 +258,7 @@ void Packet::writeCharNETUTF16(const wchar value) { if ((m_position + sizeof(wchar)) > m_bufferSize) expand(sizeof(wchar)); - + ASSERT((m_position + sizeof(wchar)) <= m_bufferSize); // Big endian m_buffer[m_position++] = (byte)(value >> 8); @@ -265,7 +269,7 @@ void Packet::writeByte(const byte value) { if ((m_position + sizeof(byte)) > m_bufferSize) expand(sizeof(byte)); - + ASSERT((m_position + sizeof(byte)) <= m_bufferSize); m_buffer[m_position++] = value; } @@ -274,7 +278,7 @@ void Packet::writeData(const byte* buffer, uint size) { if ((m_position + (sizeof(byte) * size)) > m_bufferSize) expand((sizeof(byte) * size)); - + ASSERT((m_position + (sizeof(byte) * size)) <= m_bufferSize); memcpy(&m_buffer[m_position], buffer, sizeof(byte) * size); m_position += size; @@ -284,7 +288,7 @@ void Packet::writeInt16(const word value) { if ((m_position + sizeof(word)) > m_bufferSize) expand(sizeof(word)); - + ASSERT((m_position + sizeof(word)) <= m_bufferSize); m_buffer[m_position++] = (byte)(value >> 8); m_buffer[m_position++] = (byte)(value); @@ -294,7 +298,7 @@ void Packet::writeInt32(const dword value) { if ((m_position + sizeof(dword)) > m_bufferSize) expand(sizeof(dword)); - + ASSERT((m_position + sizeof(dword)) <= m_bufferSize); m_buffer[m_position++] = (byte)(value >> 24); m_buffer[m_position++] = (byte)(value >> 16); @@ -306,7 +310,7 @@ void Packet::writeInt64(const int64 value) { if ((m_position + sizeof(int64)) > m_bufferSize) expand(sizeof(int64)); - + ASSERT((m_position + sizeof(int64)) <= m_bufferSize); m_buffer[m_position++] = (byte)(value >> 56); m_buffer[m_position++] = (byte)(value >> 48); @@ -322,7 +326,7 @@ void Packet::writeInt64(const dword hi, const dword lo) { if ((m_position + sizeof(int64)) > m_bufferSize) expand(sizeof(int64)); - + ASSERT((m_position + sizeof(int64)) <= m_bufferSize); m_buffer[m_position++] = (byte)(hi); m_buffer[m_position++] = (byte)(hi >> 8); @@ -378,7 +382,7 @@ void Packet::writeStringASCII(const wchar* value, bool terminate) value++; } delete[] buffer; - + if (terminate) writeCharASCII('\0'); #else @@ -436,7 +440,7 @@ void Packet::writeStringFixedASCII(const wchar* value, uint size, bool terminate reinterpret_cast(buffer)[i] = reinterpret_cast(value)[i]; reinterpret_cast(buffer)[i] = '\0'; } - + CvtNETUTF16ToSystem(buffer, THREAD_STRING_LENGTH, reinterpret_cast(buffer), THREAD_STRING_LENGTH); writeStringFixedASCII(buffer, size, terminate); @@ -458,12 +462,12 @@ void Packet::writeStringUTF16(const char* value, bool terminate) if (terminate) writeCharUTF16('\0'); #else - + ASSERT(value != nullptr); wchar * buffer = reinterpret_cast(Str_GetTemp()); CvtSystemToNETUTF16(reinterpret_cast(buffer), THREAD_STRING_LENGTH / sizeof(wchar), value, (int)(strlen(value))); - + writeStringNETUTF16(buffer, terminate); #endif } @@ -490,12 +494,12 @@ void Packet::writeStringFixedUTF16(const char* value, uint size, bool terminate) } } #else - + ASSERT(value != nullptr); wchar * buffer = reinterpret_cast(Str_GetTemp()); CvtSystemToNETUTF16(reinterpret_cast(buffer), THREAD_STRING_LENGTH / sizeof(wchar), value, (int)(strlen(value))); - + writeStringFixedNETUTF16(buffer, size, terminate); #endif } @@ -577,7 +581,7 @@ void Packet::writeStringNETUTF16(const char* value, bool terminate) wchar* buffer = reinterpret_cast(Str_GetTemp()); CvtSystemToNETUTF16(reinterpret_cast(buffer), THREAD_STRING_LENGTH / sizeof(wchar), value, (int)(strlen(value))); - + writeStringUTF16(buffer, terminate); #endif } @@ -606,10 +610,10 @@ void Packet::writeStringFixedNETUTF16(const char* value, uint size, bool termina #else ASSERT(value != nullptr); - + wchar* buffer = reinterpret_cast(Str_GetTemp()); CvtSystemToNETUTF16(reinterpret_cast(buffer), THREAD_STRING_LENGTH / sizeof(wchar), value, (int)(strlen(value))); - + writeStringFixedUTF16(buffer, size, terminate); #endif } @@ -744,8 +748,8 @@ word Packet::readInt16(void) if ((m_position + sizeof(word)) > m_length) return 0; - word w =(( m_buffer[m_position] << 8 ) | - ( m_buffer[m_position + 1])); + word w =(((word)m_buffer[m_position] << 8u) | + ((word)m_buffer[m_position + 1u])); m_position += 2; return w; @@ -756,10 +760,10 @@ dword Packet::readInt32(void) if ((m_position + sizeof(dword)) > m_length) return 0; - dword dw = ((m_buffer[m_position] << 24) | - (m_buffer[m_position + 1] << 16) | - (m_buffer[m_position + 2] << 8) | - (m_buffer[m_position + 3])); + dword dw = (((dword)m_buffer[m_position] << 24u) | + ((dword)m_buffer[m_position + 1u] << 16u) | + ((dword)m_buffer[m_position + 2u] << 8u) | + ((dword)m_buffer[m_position + 3u])); m_position += 4; return dw; @@ -819,7 +823,7 @@ void Packet::readStringASCII(wchar* buffer, uint length, bool includeNull) #endif delete[] bufferReal; #else - + char* bufferReal = new char[(size_t)length + 1](); readStringASCII(bufferReal, length, includeNull); CvtSystemToNETUTF16(reinterpret_cast(buffer), (int)(length), bufferReal, (int)(length)); @@ -1253,7 +1257,7 @@ void PacketSend::send(const CClient *client, bool appendTransaction) fixLength(); if (client != nullptr) target(client); - + // check target is set and can receive this packet if (m_target == nullptr || canSendTo(m_target) == false) return; diff --git a/src/network/receive.cpp b/src/network/receive.cpp index f4d27ebd4..b62668f57 100644 --- a/src/network/receive.cpp +++ b/src/network/receive.cpp @@ -1230,7 +1230,7 @@ bool PacketBulletinBoardReq::onReceive(CNetState* net) CSTime datetime = CSTime::GetCurrentTime(); newMessage->SetAttr(ATTR_MOVE_NEVER); newMessage->SetName(str); - newMessage->SetTimeStamp(datetime.GetTime()); + newMessage->SetTimeStampS(datetime.GetTime()); newMessage->m_sAuthor = character->GetName(); newMessage->m_uidLink = character->GetUID(); @@ -2199,7 +2199,7 @@ bool PacketGumpDialogRet::onReceive(CNetState* net) #ifdef _DEBUG if (g_Cfg.m_iDebugFlags & DEBUGF_SCRIPTS) { - const CResourceDef* resource = g_Cfg.ResourceGetDef(CResourceID(RES_DIALOG, RES_GET_INDEX(context))); + const CResourceDef* resource = g_Cfg.ResourceGetDef(CResourceID(RES_DIALOG, ResGetIndex(context))); if (resource == nullptr) g_Log.Event(LOGM_DEBUG|LOGL_EVENT|LOGM_NOCONTEXT, "[DEBUG_SCRIPTS] Gump context: %x (%s), UID: 0x%x, Button: %u.\n", context, "undefined resource", (dword)serial, button); else @@ -2254,7 +2254,7 @@ bool PacketGumpDialogRet::onReceive(CNetState* net) context = g_Cfg.GetKRDialogMap(context); CResourceID ridContext; - if ((RES_TYPE)RES_GET_TYPE(context) == RES_DIALOG) + if ((RES_TYPE)ResGetType(context) == RES_DIALOG) { ridContext = CResourceID(context, 0); } diff --git a/src/network/send.cpp b/src/network/send.cpp index 5343115b4..a4813b3f7 100644 --- a/src/network/send.cpp +++ b/src/network/send.cpp @@ -1751,7 +1751,7 @@ PacketAddTarget::PacketAddTarget(const CClient* target, PacketAddTarget::TargetT { ADDTOCALLSTACK("PacketAddTarget::PacketAddTarget(2)"); - //CItemBase *pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(RES_GET_INDEX(id))); + //CItemBase *pItemDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex(id))); CItemBase *pItemDef = CItemBase::FindItemBase(id); if ( !pItemDef ) return; @@ -2171,7 +2171,7 @@ PacketBulletinBoard::PacketBulletinBoard(const CClient* target, BBOARDF_TYPE act writeStringFixedASCII(message->GetName(), (uint)lenstr); // message time - CSTime datetime(message->GetTimeStamp()); + CSTime datetime(message->GetTimeStampS()); snprintf(tempstr, Str_TempLength(), "%s", datetime.Format("%b %d, %Y")); lenstr = strlen(tempstr) + 1; @@ -2679,7 +2679,7 @@ PacketCorpseEquipment::PacketCorpseEquipment(CClient* target, const CItemContain if (item->IsAttr(ATTR_INVIS) && viewer->CanSee(item) == false) continue; - layer = static_cast(item->GetContainedLayer()); + layer = (LAYER_TYPE)(item->GetContainedLayer()); ASSERT(layer < LAYER_HORSE); switch (layer) // don't put these on a corpse. { diff --git a/src/sphere.ini b/src/sphere.ini index c380b9adc..737c8a22d 100644 --- a/src/sphere.ini +++ b/src/sphere.ini @@ -79,7 +79,7 @@ Log=logs/ //MapX=MaxX,MaxY,SectorSize,MapReadID,MapSendID //MapX = Sets X to the given number, so .map X will send work over these settings //MaxX = Sets Maximum X value for this map's size. Same for MaxY. -//SectorSize = Sets the size of sectors, in each server's tick all sectors for all maps are read in a loop, this should be taken in consideration for performance along with SectorSleep. +//SectorSize = Sets the size of sectors, in each server's tick all sectors for all maps are read in a loop, this should be taken in consideration for performance along with SectorSleep. Default: 64 (8 x 8 tiles). //MapReadID = Sets the mapX file to read from muls folder (This means which file will server read for walkchecks). //MapSendID = Sets the mapX file to read for the client (this can be different than MapReadID but, obviously, not recomended for normal usage). @@ -818,7 +818,7 @@ Secure=1 // EF_Item_Strict_Comparison 000040 // Don't consider log/board and leather/hide as the same resource type // EF_FollowerList 000080 // Save the followers to the list and enable CURFOLLOWER.n.UID, CURFOLLOWER.ADD/DEL and CURFOLLOWER.CLEAR commands. // EF_AllowTelnetPacketFilter 000200 // Enable packet filtering for telnet connections as well -// EF_Script_Profiler 000400 // Record all functions/triggers execution time statistics (it can be viewed pressing P on console) +// EF_Script_Profiler 000400 // Record all functions/triggers execution time statistics (it can be viewed pressing P on console). WARNING: CPU intensive! // EF_DamageTools 002000 // Damage tools (and fire @damage on them) while mining or lumberjacking // EF_UsePingServer 008000 // Enable the experimental Ping Server (for showing pings on the server list, uses UDP port 12000) // EF_FixCanSeeInClosedConts 020000 // Change CANSEE to return 0 for items inside containers that a client hasn't opened diff --git a/src/sphere/ProfileData.cpp b/src/sphere/ProfileData.cpp index 27159a802..281cb247e 100644 --- a/src/sphere/ProfileData.cpp +++ b/src/sphere/ProfileData.cpp @@ -22,6 +22,7 @@ ProfileData::ProfileData() noexcept m_CurrentTime = llTicksStart; m_CurrentTask = PROFILE_IDLE; m_TimeTotal = 0; + m_iMapTaskCounter = 0; } int ProfileData::GetActiveWindow() const noexcept @@ -103,6 +104,11 @@ void ProfileData::Start(PROFILE_TYPE id) else { ASSERT(llDiff >= 0); } + + //if (m_CurrentTask == PROFILE_MAP) { + // llDiff *= 10; + //} + m_TimeTotal += llDiff; ASSERT(m_TimeTotal >= 0); m_CurrentTimes[m_CurrentTask].m_Time += llDiff; diff --git a/src/sphere/ProfileData.h b/src/sphere/ProfileData.h index 9e029def7..3ea4dd03e 100644 --- a/src/sphere/ProfileData.h +++ b/src/sphere/ProfileData.h @@ -60,6 +60,9 @@ class ProfileData PROFILE_TYPE m_CurrentTask; // What task are we currently processing ? llong m_CurrentTime; // in milliseconds + friend class ProfileTask; + uint m_iMapTaskCounter; + public: ProfileData() noexcept; diff --git a/src/sphere/ProfileTask.cpp b/src/sphere/ProfileTask.cpp index 630602c43..500a837cf 100644 --- a/src/sphere/ProfileTask.cpp +++ b/src/sphere/ProfileTask.cpp @@ -1,8 +1,10 @@ #include "ProfileTask.h" +#include "../common/CException.h" +#include "../game/CServerConfig.h" #include "threads.h" -ProfileData& GetCurrentProfileData() noexcept +ProfileData& GetCurrentProfileData() { auto cur_thread = static_cast(ThreadHolder::get().current()); ASSERT(cur_thread); @@ -12,6 +14,9 @@ ProfileData& GetCurrentProfileData() noexcept ProfileTask::ProfileTask(PROFILE_TYPE id) : m_context(nullptr), m_previousTask(PROFILE_OVERHEAD) { + if (!IsSetEF(EF_Script_Profiler)) + return; + auto& th = ThreadHolder::get(); if (th.closing()) return; @@ -26,13 +31,33 @@ ProfileTask::ProfileTask(PROFILE_TYPE id) : m_context = static_cast(icontext); if (m_context != nullptr && !m_context->closing()) { - m_previousTask = m_context->m_profile.GetCurrentTask(); - m_context->m_profile.Start(id); + ProfileData& pdata = m_context->m_profile; + const PROFILE_TYPE task = pdata.GetCurrentTask(); + /* + if (task == PROFILE_MAP) + { + // Just track 1 out of 10 map actions, since those are *really* frequent + // and activating a task requests an accurate system timer, which is a slow operation. + if (pdata.m_iMapTaskCounter < 10) + { + ++ pdata.m_iMapTaskCounter; + return; + } + pdata.m_iMapTaskCounter = 0; + } + */ + + m_previousTask = task; + pdata.Start(id); } } -ProfileTask::~ProfileTask(void) +ProfileTask::~ProfileTask(void) noexcept { + EXC_TRY("destroy profiletask"); + if (m_context != nullptr && !m_context->closing()) m_context->m_profile.Start(m_previousTask); + + EXC_CATCH; } diff --git a/src/sphere/ProfileTask.h b/src/sphere/ProfileTask.h index f488c90bd..dc840ad02 100644 --- a/src/sphere/ProfileTask.h +++ b/src/sphere/ProfileTask.h @@ -10,7 +10,7 @@ class AbstractSphereThread; -ProfileData& GetCurrentProfileData() noexcept; +ProfileData& GetCurrentProfileData(); class ProfileTask @@ -20,8 +20,10 @@ class ProfileTask PROFILE_TYPE m_previousTask; public: + static const char *m_sClassName; + explicit ProfileTask(PROFILE_TYPE id); - ~ProfileTask(void); + ~ProfileTask(void) noexcept; ProfileTask(const ProfileTask& copy) = delete; ProfileTask& operator=(const ProfileTask& other) = delete; diff --git a/src/sphere/UnixTerminal.cpp b/src/sphere/UnixTerminal.cpp index d6bfced1d..4673e7641 100644 --- a/src/sphere/UnixTerminal.cpp +++ b/src/sphere/UnixTerminal.cpp @@ -88,7 +88,7 @@ bool UnixTerminal::isReady() // echo to console fputc(c, stdout); - fflush(stdout); + //fflush(stdout); m_nextChar = static_cast(c); return m_nextChar != '\0'; @@ -138,6 +138,7 @@ void UnixTerminal::print(lpctstr message) wrefresh(m_window); #else fputs(message, stdout); + //fflush(stdout); #endif } @@ -168,7 +169,7 @@ void UnixTerminal::prepare() // set new terminal attributes termios term_caps = m_original; - term_caps.c_lflag &= ~(ICANON | ECHO); + term_caps.c_lflag &= ~ unsigned(ICANON | ECHO); term_caps.c_cc[VMIN] = 1; if (tcsetattr(STDIN_FILENO, TCSANOW, &term_caps) < 0) diff --git a/src/sphere/ntwindow.cpp b/src/sphere/ntwindow.cpp index c0ec33fad..166d98243 100644 --- a/src/sphere/ntwindow.cpp +++ b/src/sphere/ntwindow.cpp @@ -199,7 +199,7 @@ void CNTWindow::terminate(bool ended) AbstractSphereThread::terminate(ended); } -bool CNTWindow::shouldExit() +bool CNTWindow::shouldExit() noexcept { return AbstractThread::shouldExit(); } @@ -308,11 +308,12 @@ void CNTWindow::List_AddSingle(COLORREF color, LPCTSTR ptcText) void CNTWindow::List_AddGroup(std::deque>&& msgs) { + std::deque> moved_msgs(std::move(msgs)); const int iMaxTextLen = (64 * 1024); // Erase the old text to make space for all the message queue at once int iTotalTextLen = 0; - for (std::unique_ptr const& co : msgs) + for (std::unique_ptr const& co : moved_msgs) { iTotalTextLen += co->GetTextString().GetLength(); } @@ -329,7 +330,7 @@ void CNTWindow::List_AddGroup(std::deque>&& msgs) } // Append all the messages at once - for (std::unique_ptr const& co : msgs) + for (std::unique_ptr const& co : moved_msgs) { const COLORREF color = (COLORREF)CTColToRGB(co->GetTextColor()); const lpctstr ptcText = co->GetTextString().GetBuffer(); diff --git a/src/sphere/ntwindow.h b/src/sphere/ntwindow.h index d3b00bdf3..42d7730e0 100644 --- a/src/sphere/ntwindow.h +++ b/src/sphere/ntwindow.h @@ -25,7 +25,7 @@ extern struct CNTWindow : public AbstractSphereThread, public CSWindow, public C virtual void onStart(); virtual void terminate(bool ended); - virtual bool shouldExit(); + virtual bool shouldExit() noexcept override; virtual void tick(); bool NTWindow_Init(HINSTANCE hInstance, LPTSTR lpCmdLinel, int nCmdShow); diff --git a/src/sphere/threads.cpp b/src/sphere/threads.cpp index 516bd1b34..bd69b5e74 100644 --- a/src/sphere/threads.cpp +++ b/src/sphere/threads.cpp @@ -13,11 +13,9 @@ #if defined(_WIN32) #include #include -#elif !defined(_BSD) -#ifndef __APPLE__ +#elif !defined(_BSD) && !defined(__APPLE__) #include #endif -#endif // number of exceptions after which we restart thread and think that the thread have gone in exceptioning loops @@ -66,7 +64,7 @@ typedef struct tagTHREADNAME_INFO } THREADNAME_INFO; #pragma pack(pop) -constexpr DWORD MS_VC_EXCEPTION = 0x406D1388; +static constexpr DWORD MS_VC_EXCEPTION = 0x406D1388; #endif void IThread::setThreadName(const char* name) @@ -79,22 +77,22 @@ void IThread::setThreadName(const char* name) Str_CopyLimitNull(name_trimmed, name, m_nameMaxLength); #if defined(_WIN32) -#if defined(_MSC_VER) // TODO: support thread naming when compiling with compilers other than Microsoft's - // Windows uses THREADNAME_INFO structure to set thread name - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = name_trimmed; - info.dwThreadID = (DWORD)(-1); - info.dwFlags = 0; - - __try - { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } -#endif + #if defined(_MSC_VER) // TODO: support thread naming when compiling with compilers other than Microsoft's + // Windows uses THREADNAME_INFO structure to set thread name + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name_trimmed; + info.dwThreadID = (DWORD)(-1); + info.dwFlags = 0; + + __try + { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } + #endif #elif defined(__APPLE__) // Mac pthread_setname_np(name_trimmed); #elif !defined(_BSD) // Linux @@ -116,15 +114,9 @@ void IThread::setThreadName(const char* name) **/ ThreadHolder::ThreadHolder() noexcept : - m_threadCount(0), m_inited(false), m_closing(false) + m_threadCount(-1), m_closing(false) {} -void ThreadHolder::init() -{ - ASSERT(!m_inited); - m_inited = true; -} - ThreadHolder& ThreadHolder::get() noexcept { static ThreadHolder instance; @@ -133,45 +125,78 @@ ThreadHolder& ThreadHolder::get() noexcept bool ThreadHolder::closing() noexcept { + std::shared_lock lock(m_mutex); return m_closing; } -IThread* ThreadHolder::current() noexcept +IThread* ThreadHolder::current() { - if (!m_inited) - { - EXC_TRY("Uninitialized"); + // Do not use ASSERTs here, would cause recursion. - init(); + std::shared_lock lock(m_mutex); - EXC_CATCH; - } + if (m_closing) + return nullptr; - IThread * thread = m_currentThread; - if (thread == nullptr) - return DummySphereThread::getInstance(); + const threadid_t tid = IThread::getCurrentThreadSystemId(); - if (!thread || m_closing) - return nullptr; + if (m_spherethreadpairs_systemid_ptr.empty()) { + auto thread = static_cast(DummySphereThread::getInstance()); + thread->m_threadSystemId = tid; + lock.unlock(); + push(thread); + return thread; + } - SphereThreadData* tdata = &(m_threads[thread->m_threadHolderId]); + spherethreadpair_t *found = nullptr; + for (auto &elem : m_spherethreadpairs_systemid_ptr) { + if (elem.first == tid) { + found = &elem; + break; + } + } + if (!found) { + throw CSError(LOGL_FATAL, 0, "Thread handle not found in vector?"); + } + + auto thread = static_cast(found->second); + + ASSERT(thread->m_threadHolderId != -1); + SphereThreadData *tdata = &(m_threads[thread->m_threadHolderId]); if (tdata->m_closed) return nullptr; // Uncomment it only for testing purposes, since this method is called very often and we don't need the additional overhead - //ASSERT( thread->isSameThread(thread->getId()) ); + //DEBUG_ASSERT( thread->isSameThread(thread->getId()) ); return thread; } void ThreadHolder::push(IThread *thread) { - if (!m_inited) - init(); + auto sphere_thread = dynamic_cast(thread); + if (!sphere_thread) + throw CSError(LOGL_FATAL, 0, "IThread not being an AbstractSphereThread?"); + + std::unique_lock lock(m_mutex); + + ASSERT(thread->m_threadSystemId != 0); + ASSERT(thread->m_threadHolderId == -1); - SimpleThreadLock lock(m_mutex); m_threads.emplace_back( SphereThreadData{ thread, false }); - thread->m_threadHolderId = m_threadCount; + thread->m_threadHolderId = m_threadCount + 1; + +#ifdef _DEBUG + auto it_thread = std::find_if( + m_spherethreadpairs_systemid_ptr.begin(), + m_spherethreadpairs_systemid_ptr.end(), + [sphere_thread](spherethreadpair_t const &elem) noexcept -> bool { return elem.second == sphere_thread; }); + + // I don't want duplicates. + DEBUG_ASSERT(it_thread == m_spherethreadpairs_systemid_ptr.end()); +#endif + m_spherethreadpairs_systemid_ptr.emplace_back(sphere_thread->m_threadSystemId, sphere_thread); + ++m_threadCount; } @@ -193,24 +218,36 @@ SphereThreadData* ThreadHolder::findThreadData(IThread* thread) noexcept void ThreadHolder::remove(IThread *thread) { - if (!m_inited) - init(); + if (!thread) + throw CSError(LOGL_FATAL, 0, "thread == nullptr"); - ASSERT(m_threadCount > 0); // Trying to dequeue thread while no threads are active? + std::unique_lock lock(m_mutex); - SimpleThreadLock lock(m_mutex); - auto it = std::find_if(m_threads.begin(), m_threads.end(), + ASSERT(m_threadCount > 0); // Trying to de-queue thread while no threads are active? + + auto sphere_thread = static_cast(thread); + auto it_thread = std::find_if( + m_spherethreadpairs_systemid_ptr.begin(), + m_spherethreadpairs_systemid_ptr.end(), + [sphere_thread](spherethreadpair_t const &elem) noexcept -> bool { return elem.second == sphere_thread; }); + ASSERT(it_thread != m_spherethreadpairs_systemid_ptr.end()); + + auto it_data = std::find_if(m_threads.begin(), m_threads.end(), [thread](SphereThreadData const& elem) { return elem.m_ptr == thread; }); - - ASSERT(it != m_threads.end()); // Ensure that the thread to dequeue is registered + ASSERT(it_data != m_threads.end()); // Ensure that the thread to de-queue is registered + --m_threadCount; - m_threads.erase(it); + + m_threads.erase(it_data); + m_spherethreadpairs_systemid_ptr.erase(it_thread); } void ThreadHolder::markThreadsClosing() { + std::unique_lock lock(m_mutex); + m_closing = true; for (auto& thread_data : m_threads) { @@ -223,10 +260,11 @@ void ThreadHolder::markThreadsClosing() IThread * ThreadHolder::getThreadAt(size_t at) { + std::shared_lock lock(m_mutex); + if ( at > getActiveThreads() ) return nullptr; - SimpleThreadLock lock(m_mutex); for ( spherethreadlist_t::const_iterator it = m_threads.begin(), end = m_threads.end(); it != end; ++it ) { if ( at == 0 ) @@ -257,7 +295,7 @@ AbstractThread::AbstractThread(const char *name, IThread::Priority priority) #endif ++AbstractThread::m_threadsAvailable; } - m_id = 0; + m_threadSystemId = 0; Str_CopyLimitNull(m_name, name, sizeof(m_name)); m_handle = 0; m_hangCheck = 0; @@ -307,7 +345,6 @@ void AbstractThread::start() #endif m_terminateEvent.reset(); - ThreadHolder::get().push(this); } void AbstractThread::terminate(bool ended) @@ -333,7 +370,7 @@ void AbstractThread::terminate(bool ended) // Common things ThreadHolder::get().remove(this); - m_id = 0; + m_threadSystemId = 0; m_handle = 0; // let everyone know we have been terminated @@ -493,7 +530,7 @@ void AbstractThread::awaken() m_sleepEvent.signal(); } -bool AbstractThread::isCurrentThread() const +bool AbstractThread::isCurrentThread() const noexcept { #ifdef _WIN32 return (getId() == ::GetCurrentThreadId()); @@ -544,7 +581,9 @@ void AbstractThread::onStart() // we set the id here to ensure it is available before the first tick, otherwise there's // a small delay when setting it from AbstractThread::start and it's possible for the id // to not be set fast enough (particular when using pthreads) - m_id = getCurrentThreadId(); + m_threadSystemId = getCurrentThreadSystemId(); + + ThreadHolder::get().push(this); if (isActive()) // This thread has actually been spawned and the code is executing on a different thread setThreadName(getName()); @@ -579,10 +618,10 @@ void AbstractThread::setPriority(IThread::Priority pri) case IThread::Disabled: m_tickPeriod = AutoResetEvent::_kiInfinite; break; - } + }\ } -bool AbstractThread::shouldExit() +bool AbstractThread::shouldExit() noexcept { return m_terminateRequested || _thread_selfTerminateAfterThisTick; } @@ -594,7 +633,7 @@ AbstractSphereThread::AbstractSphereThread(const char *name, Priority priority) : AbstractThread(name, priority), _fIsClosing(false) { #ifdef THREAD_TRACK_CALLSTACK - m_stackPos = 0; + m_stackPos = -1; memset(m_stackInfo, 0, sizeof(m_stackInfo)); m_freezeCallStack = false; m_exceptionStackUnwinding = false; @@ -680,7 +719,7 @@ void AbstractSphereThread::getStringBuffer(TemporaryString &string) noexcept string.init(store->m_buffer, &store->m_state); } -bool AbstractSphereThread::shouldExit() +bool AbstractSphereThread::shouldExit() noexcept { if ( g_Serv.GetExitFlag() != 0 ) return true; @@ -698,14 +737,32 @@ void AbstractSphereThread::exceptionCaught() #endif } +#include +#include "../game/CServerConfig.h" + #ifdef THREAD_TRACK_CALLSTACK -void AbstractSphereThread::pushStackCall(const char *name) noexcept +static thread_local ssize_t _stackpos = -1; +void AbstractSphereThread::pushStackCall(const char *name) NOEXCEPT_NODEBUG { - if (m_freezeCallStack == false) - { - m_stackInfo[m_stackPos].functionName = name; - ++m_stackPos; - } + if (m_freezeCallStack == true) + return; + + DEBUG_ASSERT(m_stackPos >= -1); + DEBUG_ASSERT(m_stackPos < (ssize_t)sizeof(m_stackInfo)); + ++m_stackPos; + _stackpos = m_stackPos; + m_stackInfo[m_stackPos].functionName = name; +} + +void AbstractSphereThread::popStackCall() NOEXCEPT_NODEBUG +{ + if (m_freezeCallStack == true) + return; + + --m_stackPos; + _stackpos = m_stackPos; + ASSERT(m_stackPos >= -1); + } void AbstractSphereThread::exceptionNotifyStackUnwinding() noexcept @@ -726,9 +783,11 @@ void AbstractSphereThread::printStackTrace() noexcept const uint64_t threadId = static_cast(getId()); const lpctstr threadName = getName(); + //EXC_NOTIFY_DEBUGGER; + g_Log.EventDebug("Printing STACK TRACE for debugging purposes.\n"); g_Log.EventDebug(" _______ thread (id) name _______ | # | _____________ function _____________ |\n"); - for ( size_t i = 0; i < ARRAY_COUNT(m_stackInfo); ++i ) + for ( ssize_t i = 0; i < (ssize_t)ARRAY_COUNT(m_stackInfo); ++i ) { if( m_stackInfo[i].functionName == nullptr ) break; @@ -805,6 +864,7 @@ StackDebugInformation::StackDebugInformation(const char *name) noexcept if (m_context != nullptr && !m_context->closing()) { m_context->pushStackCall(name); + //printf("- - - - Pushing stack call: %s.\n", name); } } diff --git a/src/sphere/threads.h b/src/sphere/threads.h index dd4278a1e..87478d071 100644 --- a/src/sphere/threads.h +++ b/src/sphere/threads.h @@ -47,13 +47,6 @@ #endif class IThread; -struct SphereThreadData -{ - IThread* m_ptr; - bool m_closed; -}; -using spherethreadlist_t = std::vector; - // stores a value unique to each thread, intended to hold // a pointer (e.g. the current IThread instance) @@ -72,9 +65,8 @@ class TlsValue TlsValue(); ~TlsValue(); -private: - TlsValue(const TlsValue& copy); - TlsValue& operator=(const TlsValue& other); + TlsValue(const TlsValue& copy) = delete; + TlsValue& operator=(const TlsValue& other) = delete; public: // allows assignment to set the current value @@ -83,7 +75,7 @@ class TlsValue set(value); return *this; } - + // allows a cast to get current value operator T() const { return get(); } @@ -104,7 +96,7 @@ TlsValue::TlsValue() _ready = (pthread_key_create(&_key, nullptr) == 0); #endif } - + template TlsValue::~TlsValue() { @@ -145,6 +137,10 @@ T TlsValue::get() const // Interface for threads. Almost always should be used instead of any implementing classes class IThread { +public: // TODO: lazy + threadid_t m_threadSystemId; + int m_threadHolderId; + public: enum Priority { @@ -170,7 +166,7 @@ class IThread virtual void setPriority(Priority) = 0; virtual Priority getPriority() const = 0; - static inline threadid_t getCurrentThreadId() noexcept + static inline threadid_t getCurrentThreadSystemId() noexcept { #if defined(_WIN32) return ::GetCurrentThreadId(); @@ -194,64 +190,21 @@ class IThread inline bool isSameThread(threadid_t otherThreadId) const noexcept { - return isSameThreadId(getCurrentThreadId(), otherThreadId); + return isSameThreadId(getCurrentThreadSystemId(), otherThreadId); } static constexpr uint m_nameMaxLength = 16; // Unix support a max 16 bytes thread name. static void setThreadName(const char* name); - size_t m_threadHolderId; - protected: - virtual bool shouldExit() = 0; + virtual bool shouldExit() noexcept = 0; public: - IThread() noexcept : m_threadHolderId(0) { }; + IThread() noexcept : m_threadSystemId(0), m_threadHolderId(-1) { }; virtual ~IThread() = default; }; -// Singleton utility class for working with threads. Holds all running threads inside. -class ThreadHolder -{ - spherethreadlist_t m_threads; - size_t m_threadCount; - std::atomic_bool m_inited; - std::atomic_bool m_closing; - SimpleMutex m_mutex; - - - ThreadHolder() noexcept; - void init(); - - friend void atexit_handler(void); - friend void Sphere_ExitServer(void); - void markThreadsClosing(); - - //SphereThreadData* findThreadData(IThread* thread) noexcept; - - friend class AbstractThread; - TlsValue m_currentThread; - -public: - static constexpr lpctstr m_sClassName = "ThreadHolder"; - - static ThreadHolder& get() noexcept; - - bool closing() noexcept; - // returns current working thread or DummySphereThread * if no IThread threads are running - IThread *current() noexcept; - // records a thread to the list. Sould NOT be called, internal usage - void push(IThread *thread); - // removes a thread from the list. Sould NOT be called, internal usage - void remove(IThread *thread); - // returns thread at i pos - IThread * getThreadAt(size_t at); - - // returns number of running threads. Sould NOT be called, unit tests usage - inline size_t getActiveThreads() noexcept { return m_threadCount; } -}; - // Thread implementation. See IThread for list of available methods. class AbstractThread : public IThread { @@ -261,7 +214,6 @@ class AbstractThread : public IThread bool _thread_selfTerminateAfterThisTick; private: - threadid_t m_id; char m_name[30]; static int m_threadsAvailable; spherethread_t m_handle; @@ -270,7 +222,7 @@ class AbstractThread : public IThread uint m_tickPeriod; AutoResetEvent m_sleepEvent; - bool m_terminateRequested; + volatile std::atomic_bool m_terminateRequested; ManualResetEvent m_terminateEvent; public: @@ -281,29 +233,28 @@ class AbstractThread : public IThread AbstractThread& operator=(const AbstractThread& other) = delete; public: - threadid_t getId() const noexcept { return m_id; } - const char *getName() const noexcept { return m_name; } + virtual threadid_t getId() const noexcept override { return m_threadSystemId; } + virtual const char *getName() const noexcept override { return m_name; } - void overwriteInternalThreadName(const char* name) noexcept; - - bool isActive() const; - bool isCurrentThread() const; - bool checkStuck(); + virtual bool isActive() const override; + virtual bool checkStuck() override; - virtual void start(); - virtual void terminate(bool ended); - virtual void waitForClose(); - virtual void awaken(); + virtual void start() override; + virtual void terminate(bool ended) override; + virtual void waitForClose() override; + void awaken(); - void setPriority(Priority pri); - Priority getPriority() const { return m_priority; } + virtual void setPriority(Priority pri) override; + virtual Priority getPriority() const override { return m_priority; } + void overwriteInternalThreadName(const char* name) noexcept; + bool isCurrentThread() const noexcept; protected: virtual void tick() = 0; // NOTE: this should not be too long-lasted function, so no world loading, etc here!!! virtual void onStart(); - virtual bool shouldExit(); + virtual bool shouldExit() noexcept override; private: void run(); @@ -324,7 +275,7 @@ class AbstractSphereThread : public AbstractThread }; STACK_INFO_REC m_stackInfo[0x1000]; - size_t m_stackPos; + ssize_t m_stackPos; bool m_freezeCallStack; bool m_exceptionStackUnwinding; #endif @@ -354,12 +305,8 @@ class AbstractSphereThread : public AbstractThread m_freezeCallStack = freeze; } - void pushStackCall(const char *name) noexcept; - inline void popStackCall(void) noexcept - { - if (m_freezeCallStack == false) - --m_stackPos; - } + void pushStackCall(const char *name) NOEXCEPT_NODEBUG; + void popStackCall() NOEXCEPT_NODEBUG; void exceptionNotifyStackUnwinding() noexcept; void printStackTrace() noexcept; @@ -368,10 +315,10 @@ class AbstractSphereThread : public AbstractThread ProfileData m_profile; // the current active statistical profile. protected: - virtual bool shouldExit(); + virtual bool shouldExit() noexcept; }; -// Dummy thread for context when no thread really exists +// Dummy thread for context when no thread really exists. To be called only once, at startup. class DummySphereThread : public AbstractSphereThread { private: @@ -387,7 +334,55 @@ class DummySphereThread : public AbstractSphereThread }; -// used to hold debug information for stack +// Singleton utility class for working with threads. Holds all running threads inside. +class ThreadHolder +{ + friend class AbstractThread; + + struct SphereThreadData { + IThread *m_ptr; + bool m_closed; + }; + using spherethreadlist_t = std::vector; + spherethreadlist_t m_threads; + + using spherethreadpair_t = std::pair; + std::vector m_spherethreadpairs_systemid_ptr; + + int m_threadCount; + volatile std::atomic_bool m_closing; + mutable std::shared_mutex m_mutex; + + ThreadHolder() noexcept; + ~ThreadHolder() noexcept = default; + + friend void atexit_handler(void); + friend void Sphere_ExitServer(void); + void markThreadsClosing(); + + //SphereThreadData* findThreadData(IThread* thread) noexcept; + +public: + static constexpr lpctstr m_sClassName = "ThreadHolder"; + + static ThreadHolder& get() noexcept; + + bool closing() noexcept; + // returns current working thread or DummySphereThread * if no IThread threads are running + IThread *current(); + // records a thread to the list. Sould NOT be called, internal usage + void push(IThread *thread); + // removes a thread from the list. Sould NOT be called, internal usage + void remove(IThread *thread); + // returns thread at i pos + IThread * getThreadAt(size_t at); + + // returns number of running threads. Sould NOT be called, unit tests usage + inline size_t getActiveThreads() noexcept { return m_threadCount; } +}; + + +// used to hold debug information for the function call stack #ifdef THREAD_TRACK_CALLSTACK class StackDebugInformation { @@ -412,16 +407,16 @@ class StackDebugInformation // Add to the call stack these functions only in debug mode, to have the most precise call stack // even if these functions are thought to be very safe and (nearly) exception-free. #ifdef _DEBUG - #define ADDTOCALLSTACK_INTENSIVE(_function_) ADDTOCALLSTACK(_function_) + #define ADDTOCALLSTACK_DEBUG(_function_) ADDTOCALLSTACK(_function_) #else - #define ADDTOCALLSTACK_INTENSIVE(_function_) (void)0 + #define ADDTOCALLSTACK_DEBUG(_function_) (void)0 #endif #else // THREAD_TRACK_CALLSTACK #define ADDTOCALLSTACK(_function_) (void)0 -#define ADDTOCALLSTACK_INTENSIVE(_function_) (void)0 +#define ADDTOCALLSTACK_DEBUG(_function_) (void)0 #endif // THREAD_TRACK_CALLSTACK diff --git a/src/tables/classnames.tbl b/src/tables/classnames.tbl index c350dbcc8..a26551248 100644 --- a/src/tables/classnames.tbl +++ b/src/tables/classnames.tbl @@ -37,6 +37,8 @@ #include "../network/CNetworkOutput.h" #include "../network/CNetworkThread.h" +#include "../sphere/ProfileTask.h" + #define ADD(a,b) const char * a::m_sClassName = b @@ -113,6 +115,7 @@ ADD(CNetworkInput, "CNetworkInput"); ADD(CNetworkOutput, "CNetworkOutput"); ADD(CNetworkThread, "CNetworkThread"); ADD(CNetworkManager, "CNetworkManager"); +ADD(ProfileTask, "ProfileTask"); #ifdef _WIN32 ADD(CNTWindow, "CNTWindow"); #endif diff --git a/utilities/configure_asan.bat b/utilities/configure_asan.bat index d7c0472cf..2f91dc276 100644 --- a/utilities/configure_asan.bat +++ b/utilities/configure_asan.bat @@ -9,4 +9,5 @@ SET ASAN_OPTIONS=%ASAN_OPTIONS%:sleep_before_dying=3:print_stats=true:stack_trac SET LSAN_OPTIONS=%_SAN_COMMON_FLAGS% SET MSAN_OPTIONS=%_SAN_COMMON_FLAGS% SET TSAN_OPTIONS=%_SAN_COMMON_FLAGS% +SET UBSAN_OPTIONS="${_SAN_COMMON_FLAGS}:print_stacktrack=true" SET ASAN_SYMBOLIZER_PATH="C:\LLVM\bin\llvm-symbolizer.exe" diff --git a/utilities/configure_asan.sh b/utilities/configure_asan.sh index 529f53f29..ece552bf6 100644 --- a/utilities/configure_asan.sh +++ b/utilities/configure_asan.sh @@ -1,14 +1,13 @@ #!/bin/sh -function export_flags { - local _SAN_COMMON_FLAGS=handle_abort=true:abort_on_error=false - #ASAN_MORE=check_initialization_order=1 - export ASAN_OPTIONS='${_SAN_COMMON_FLAGS}:strict_init_order=true:detect_stack_use_after_return=true' - export ASAN_OPTIONS='${ASAN_OPTIONS}:sleep_before_dying=3:print_stats=true:stack_trace_format="[frame=%n, function=%f, location=%S]"' - export LSAN_OPTIONS='${_SAN_COMMON_FLAGS}' - export MSAN_OPTIONS='${_SAN_COMMON_FLAGS}' - export TSAN_OPTIONS='${_SAN_COMMON_FLAGS}' -} -export ASAN_SYMBOLIZER_PATH='/usr/bin/addr2line' -export_flags +_SAN_COMMON_FLAGS=handle_abort=true:abort_on_error=false +#ASAN_MORE=check_initialization_order=1 +export ASAN_OPTIONS="${_SAN_COMMON_FLAGS}" +export ASAN_OPTIONS="${ASAN_OPTIONS}:strict_init_order=true:detect_stack_use_after_return=true" +export ASAN_OPTIONS="${ASAN_OPTIONS}:sleep_before_dying=3:print_stats=true:stack_trace_format=\"[frame=%n, function=%f, location=%S]\"" +export LSAN_OPTIONS="${_SAN_COMMON_FLAGS}" +export MSAN_OPTIONS="${_SAN_COMMON_FLAGS}" +export TSAN_OPTIONS="${_SAN_COMMON_FLAGS}" +export UBSAN_OPTIONS="${_SAN_COMMON_FLAGS}:print_stacktrack=true" +export ASAN_SYMBOLIZER_PATH='/usr/bin/addr2line'