diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000000..f9375a3ba5 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,50 @@ +name: CMake + +on: + pull_request: + branches: [ master ] + +env: + BUILD_TYPE: RelWithDebInfo + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Add OpenMW PPA Dependancies + run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update + + - name: Install Building Dependancies + run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + + - name: Prime ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} + max-size: 1000M + + - name: Configure + run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build + run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 + + - name: Install + shell: bash + run: cmake --install . + + - name: Create Artifact + shell: bash + working-directory: install + run: | + ls -laR + 7z a ../build_artifact.7z . + + - name: Upload Artifact + uses: actions/upload-artifact@v1 + with: + path: ./build_artifact.7z + name: build_artifact.7z diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8d82d6ed79..c9b8cf9341 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -223,6 +223,18 @@ variables: &tests-targets - choco install ninja -y - choco install python -y - refreshenv + - | + function Make-SafeFileName { + param( + [Parameter(Mandatory=$true)] + [String] + $FileName + ) + [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { + $FileName = $FileName.Replace($_, '_') + } + return $FileName + } stage: build script: - $time = (Get-Date -Format "HH:mm:ss") @@ -237,10 +249,10 @@ variables: &tests-targets - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log @@ -326,6 +338,18 @@ Windows_Ninja_Tests_RelWithDebInfo: - choco install vswhere -y - choco install python -y - refreshenv + - | + function Make-SafeFileName { + param( + [Parameter(Mandatory=$true)] + [String] + $FileName + ) + [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { + $FileName = $FileName.Replace($_, '_') + } + return $FileName + } stage: build script: - $time = (Get-Date -Format "HH:mm:ss") @@ -339,10 +363,10 @@ Windows_Ninja_Tests_RelWithDebInfo: - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log diff --git a/AUTHORS.md b/AUTHORS.md index beb2b490f2..62121a797b 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -44,6 +44,7 @@ Programmers Austin Salgat (Salgat) Ben Shealy (bentsherman) Berulacks + Bo Svensson Britt Mathis (galdor557) Capostrophic Carl Maxwell @@ -99,6 +100,7 @@ Programmers James Stephens (james-h-stephens) Jan-Peter Nilsson (peppe) Jan Borsodi (am0s) + JanuarySnow Jason Hooks (jhooks) jeaye jefetienne diff --git a/CHANGELOG.md b/CHANGELOG.md index ef07fe4eee..0554668ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI + Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed Bug #4752: UpdateCellCommand doesn't undo properly @@ -36,6 +37,7 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop + Bug #6273: Respawning NPCs rotation is inconsistent Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map @@ -47,7 +49,11 @@ Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering - Editor: Preserve the "blocked" record flag for referenceable objects. + Feature #6251: OpenMW-CS: Set instance movement based on camera zoom + Feature #6288: Preserve the "blocked" record flag for referenceable objects. + Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings + Task #6264: Remove the old classes in animation.cpp + 0.47.0 ------ diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 2687946f41..bc0eb0013d 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -14,6 +14,12 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then BUILD_BENCHMARKS=ON fi +CXX_FLAGS='-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy' + +if [[ "${CXX}" == 'clang++' ]]; then + CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" +fi + declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" @@ -24,6 +30,8 @@ declare -a CMAKE_CONF_OPTS=( -DBUILD_SHARED_LIBS=OFF -DUSE_SYSTEM_TINYXML=ON -DCMAKE_INSTALL_PREFIX=install + -DCMAKE_C_FLAGS='-Werror' + -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" ) if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index c733c22ff5..1b8350a04b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,11 @@ if(POLICY CMP0083) cmake_policy(SET CMP0083 NEW) endif() +# to link with freetype library +if(POLICY CMP0079) + cmake_policy(SET CMP0079 NEW) +endif() + option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) @@ -202,8 +207,6 @@ if (USE_QT) find_package(Qt5Widgets REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5OpenGL REQUIRED) - # Instruct CMake to run moc automatically when needed. - #set(CMAKE_AUTOMOC ON) endif() set(USED_OSG_COMPONENTS @@ -512,7 +515,7 @@ endif() if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long") + set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long ${CMAKE_CXX_FLAGS}") add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON ) if (APPLE) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index e9ec906e6a..7e8bd67e94 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -36,23 +36,6 @@ set(LAUNCHER_HEADER ) # Headers that must be pre-processed -set(LAUNCHER_HEADER_MOC - datafilespage.hpp - graphicspage.hpp - maindialog.hpp - playpage.hpp - textslotmsgbox.hpp - settingspage.hpp - advancedpage.hpp - - utils/cellnameloader.hpp - utils/textinputdialog.hpp - utils/profilescombobox.hpp - utils/lineedit.hpp - utils/openalutil.hpp - -) - set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui @@ -74,7 +57,6 @@ if(WIN32) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) -QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -109,4 +91,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-launcher gcov) endif() +if(USE_QT) + set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 2e81885c75..cb6205ef5c 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -52,11 +52,8 @@ void readVFS(VFS::Archive* anArchive,std::string archivePath = "") myManager.addArchive(anArchive); myManager.buildIndex(); - std::map files=myManager.getIndex(); - for(auto it=files.begin(); it!=files.end(); ++it) + for(const auto& name : myManager.getRecursiveDirectoryIterator("")) { - std::string name = it->first; - try{ if(isNIF(name)) { diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 5435de07e2..0ffa3da559 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -8,11 +8,11 @@ opencs_units (model/doc document operation saving documentmanager loader runner operationholder ) -opencs_units_noqt (model/doc +opencs_units (model/doc stage savingstate savingstages blacklist messages ) -opencs_hdrs_noqt (model/doc +opencs_hdrs (model/doc state ) @@ -23,14 +23,14 @@ opencs_units (model/world ) -opencs_units_noqt (model/world +opencs_units (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro ) -opencs_hdrs_noqt (model/world +opencs_hdrs (model/world columnimp idcollection collection info subcellcollection ) @@ -39,14 +39,14 @@ opencs_units (model/tools tools reportmodel mergeoperation ) -opencs_units_noqt (model/tools +opencs_units (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck ) -opencs_hdrs_noqt (model/tools +opencs_hdrs (model/tools mergestate ) @@ -57,11 +57,11 @@ opencs_units (view/doc ) -opencs_units_noqt (view/doc +opencs_units (view/doc subviewfactory ) -opencs_hdrs_noqt (view/doc +opencs_hdrs (view/doc subviewfactoryimp ) @@ -74,7 +74,7 @@ opencs_units (view/world bodypartcreator landtexturecreator landcreator ) -opencs_units_noqt (view/world +opencs_units (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate scripthighlighter idvalidator dialoguecreator idcompletiondelegate colordelegate dragdroputils @@ -92,12 +92,12 @@ opencs_units (view/render cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands ) -opencs_units_noqt (view/render +opencs_units (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase cellarrow cellmarker cellborder pathgrid ) -opencs_hdrs_noqt (view/render +opencs_hdrs (view/render mask ) @@ -106,7 +106,7 @@ opencs_units (view/tools reportsubview reporttable searchsubview searchbox merge ) -opencs_units_noqt (view/tools +opencs_units (view/tools subviews ) @@ -119,11 +119,11 @@ opencs_units (model/prefs shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting ) -opencs_units_noqt (model/prefs +opencs_units (model/prefs category ) -opencs_units_noqt (model/filter +opencs_units (model/filter node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode ) @@ -150,7 +150,6 @@ if(WIN32) endif(WIN32) qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) -qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) # for compiled .ui files @@ -232,31 +231,6 @@ target_link_libraries(openmw-cs components_qt ) -if(OSG_STATIC) - unset(_osg_plugins_static_files) - add_library(openmw_cs_osg_plugins INTERFACE) - foreach(_plugin ${USED_OSG_PLUGINS}) - string(TOUPPER ${_plugin} _plugin_uc) - if(OPENMW_USE_SYSTEM_OSG) - list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) - else() - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_cs_osg_plugins INTERFACE $) - add_dependencies(openmw_cs_osg_plugins ${${_plugin_uc}_LIBRARY}) - endif() - endforeach() - # We use --whole-archive because OSG plugins use registration. - get_whole_archive_options(_opts ${_osg_plugins_static_files}) - target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts}) - target_link_libraries(openmw-cs openmw_cs_osg_plugins) - - if(OPENMW_USE_SYSTEM_OSG) - # OSG plugin pkgconfig files are missing these dependencies. - # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(openmw freetype jpeg png) - endif() -endif(OSG_STATIC) - target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) if (WIN32) @@ -284,3 +258,7 @@ endif (MSVC) if(APPLE) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle) endif() + +if(USE_QT) + set_property(TARGET openmw-cs PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 8558aa9bc9..7e7f926384 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -9,6 +9,9 @@ #include "data.hpp" +#include +#include + namespace CSMWorld { const std::string& ActorAdapter::RaceData::getId() const @@ -121,7 +124,7 @@ namespace CSMWorld return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); } - const std::string ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + std::string_view ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { auto it = mParts.find(index); if (it == mParts.end()) @@ -131,7 +134,7 @@ namespace CSMWorld if (mFemale) { // Note: we should use male parts for females as fallback - const std::string femalePart = mRaceData->getFemalePart(index); + const std::string& femalePart = mRaceData->getFemalePart(index); if (!femalePart.empty()) return femalePart; } @@ -139,11 +142,10 @@ namespace CSMWorld return mRaceData->getMalePart(index); } - return ""; + return {}; } - const std::string& partName = it->second.first; - return partName; + return it->second.first; } bool ActorAdapter::ActorData::hasDependency(const std::string& id) const diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 912a6bcb38..826e3b9179 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -93,7 +95,7 @@ namespace CSMWorld /// Returns the skeleton the actor should use for attaching parts to std::string getSkeleton() const; /// Retrieves the associated actor part - const std::string getPart(ESM::PartReferenceType index) const; + std::string_view getPart(ESM::PartReferenceType index) const; /// Checks if the actor has a data dependency bool hasDependency(const std::string& id) const; diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 9d9c69a5da..6ab9d7ff9d 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -153,7 +154,7 @@ namespace CSMWorld ///< Change the state of a record from base to modified, if it is not already. /// \return True if the record was changed. - int searchId (const std::string& id) const override; + int searchId(std::string_view id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) @@ -476,7 +477,7 @@ namespace CSMWorld } template - int Collection::searchId (const std::string& id) const + int Collection::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase(id); diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index 13471b9886..be6131ee52 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "universalid.hpp" #include "columns.hpp" @@ -61,7 +62,7 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None) = 0; ///< \param type Will be ignored, unless the collection supports multiple record types - virtual int searchId (const std::string& id) const = 0; + virtual int searchId(std::string_view id) const = 0; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index fc39fa8f7d..6eefdb6e21 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -392,7 +392,7 @@ int CSMWorld::Columns::getId (const std::string& name) std::string name2 = Misc::StringUtils::lowerCase (name); for (int i=0; sNames[i].mName; ++i) - if (Misc::StringUtils::ciEqual(sNames[i].mName, name2)) + if (Misc::StringUtils::ciEqual(std::string_view(sNames[i].mName), name2)) return sNames[i].mId; return -1; diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 978ce3595d..d58a8327f2 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -97,7 +97,7 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) } } -int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::string& topic) const +int CSMWorld::InfoCollection::getInfoIndex(std::string_view id, std::string_view topic) const { // find the topic first std::unordered_map > >::const_iterator iter @@ -345,12 +345,12 @@ void CSMWorld::InfoCollection::appendBlankRecord (const std::string& id, Univer insertRecord(std::move(record2), getInsertIndex(id, type, nullptr), type); // call InfoCollection::insertRecord() } -int CSMWorld::InfoCollection::searchId (const std::string& id) const +int CSMWorld::InfoCollection::searchId(std::string_view id) const { std::string::size_type separator = id.find_last_of('#'); if (separator == std::string::npos) - throw std::runtime_error("invalid info ID: " + id); + throw std::runtime_error("invalid info ID: " + std::string(id)); return getInfoIndex(id.substr(separator+1), id.substr(0, separator)); } diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 3e8455c399..96061fb03c 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_INFOCOLLECTION_H #include +#include #include "collection.hpp" #include "info.hpp" @@ -43,7 +44,7 @@ namespace CSMWorld void load (const Info& record, bool base); - int getInfoIndex (const std::string& id, const std::string& topic) const; + int getInfoIndex(std::string_view id, std::string_view topic) const; ///< Return index for record \a id or -1 (if not present; deleted records are considered) /// /// \param id info ID without topic prefix @@ -79,7 +80,7 @@ namespace CSMWorld void appendBlankRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None) override; - int searchId (const std::string& id) const override; + int searchId(std::string_view id) const override; void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) override; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 4782bde6b9..4f56bbb463 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -7,6 +7,8 @@ #include "universalid.hpp" #include "record.hpp" +#include + namespace CSMWorld { template<> @@ -261,7 +263,7 @@ void CSMWorld::RefCollection::cloneRecord (const std::string& origin, insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord() } -int CSMWorld::RefCollection::searchId (const std::string& id) const +int CSMWorld::RefCollection::searchId(std::string_view id) const { return searchId(extractIdNum(id)); } diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index e0e88d721f..34e258c11b 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_REFCOLLECTION_H #include +#include #include "../doc/stage.hpp" @@ -56,7 +57,7 @@ namespace CSMWorld const std::string& destination, const UniversalId::Type type); - virtual int searchId (const std::string& id) const; + virtual int searchId(std::string_view id) const; virtual void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 6ffb7969a8..928c7284ad 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -790,7 +791,7 @@ void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, Univer mData.appendRecord (type, id, false); } -int CSMWorld::RefIdCollection::searchId (const std::string& id) const +int CSMWorld::RefIdCollection::searchId(std::string_view id) const { RefIdData::LocalIndex localIndex = mData.searchId (id); diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index dc722055ff..ee17bb3214 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "columnbase.hpp" #include "collectionbase.hpp" @@ -85,7 +86,7 @@ namespace CSMWorld void appendBlankRecord (const std::string& id, UniversalId::Type type) override; ///< \param type Will be ignored, unless the collection supports multiple record types - int searchId (const std::string& id) const override; + int searchId(std::string_view id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index df8e06c251..3bd8bfd5fc 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -2,6 +2,7 @@ #include #include +#include CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} @@ -74,8 +75,7 @@ int CSMWorld::RefIdData::localToGlobalIndex (const LocalIndex& index) return globalIndex; } -CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId ( - const std::string& id) const +CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase (id); diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 175aaef410..b9dee80638 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -277,7 +278,7 @@ namespace CSMWorld int localToGlobalIndex (const LocalIndex& index) const; - LocalIndex searchId (const std::string& id) const; + LocalIndex searchId(std::string_view id) const; void erase (int index, int count); diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index c3eb9762e7..cd9f58e848 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -22,10 +23,8 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const * size_t baseSize = mBaseDirectory.size(); - const std::map& index = vfs->getIndex(); - for (std::map::const_iterator it = index.begin(); it != index.end(); ++it) + for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) { - std::string filepath = it->first; if (filepath.size() #include #include +#include #include "universalid.hpp" @@ -35,7 +36,7 @@ namespace CSMWorld int getIndex (const std::string& id) const; - int searchId (const std::string& id) const; + int searchId(std::string_view id) const; UniversalId::Type getType() const; }; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index d6077a65a5..271ca2365a 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -96,7 +96,7 @@ namespace CSVRender for (int i = 0; i < ESM::PRT_Count; ++i) { auto type = (ESM::PartReferenceType) i; - std::string partId = mActorData->getPart(type); + const std::string_view partId = mActorData->getPart(type); attachBodyPart(type, getBodyPartMesh(partId)); } } @@ -115,7 +115,7 @@ namespace CSVRender } } - std::string Actor::getBodyPartMesh(const std::string& bodyPartId) + std::string Actor::getBodyPartMesh(std::string_view bodyPartId) { const auto& bodyParts = mData.getBodyParts(); diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 2f19454f78..8172e6fff7 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -2,6 +2,7 @@ #define OPENCS_VIEW_RENDER_ACTOR_H #include +#include #include @@ -54,7 +55,7 @@ namespace CSVRender void loadBodyParts(); void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); - std::string getBodyPartMesh(const std::string& bodyPartId); + std::string getBodyPartMesh(std::string_view bodyPartId); static const std::string MeshPrefix; diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 99ddce7f7d..ebb7f46fa5 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -297,6 +297,8 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) return false; } + mObjectsAtDragStart.clear(); + for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) { @@ -305,6 +307,12 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) if (mSubModeId == "move") { objectTag->mObject->setEdited (Object::Override_Position); + float x = objectTag->mObject->getPosition().pos[0]; + float y = objectTag->mObject->getPosition().pos[1]; + float z = objectTag->mObject->getPosition().pos[2]; + osg::Vec3f thisPoint(x, y, z); + mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move; } else if (mSubModeId == "rotate") @@ -392,29 +400,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); - if (mDragMode == DragMode_Move) - { - osg::Vec3f eye, centre, up; - getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); - - if (diffY) - { - offset += up * diffY * speedFactor; - } - if (diffX) - { - offset += ((centre-eye) ^ up) * diffX * speedFactor; - } - - if (mDragAxis!=-1) - { - for (int i=0; i<3; ++i) - { - if (i!=mDragAxis) - offset[i] = 0; - } - } - } + if (mDragMode == DragMode_Move) {} else if (mDragMode == DragMode_Rotate) { osg::Vec3f eye, centre, up; @@ -514,17 +500,32 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou return; } + int i = 0; + // Apply - for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) + for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter, i++) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { if (mDragMode == DragMode_Move) { ESM::Position position = objectTag->mObject->getPosition(); - for (int i=0; i<3; ++i) + osg::Vec3f mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); + float addToX = mousePos.x() - mDragStart.x(); + float addToY = mousePos.y() - mDragStart.y(); + float addToZ = mousePos.z() - mDragStart.z(); + position.pos[0] = mObjectsAtDragStart[i].x() + addToX; + position.pos[1] = mObjectsAtDragStart[i].y() + addToY; + position.pos[2] = mObjectsAtDragStart[i].z() + addToZ; + + // XYZ-locking + if (mDragAxis != -1) { - position.pos[i] += offset[i]; + for (int j = 0; j < 3; ++j) + { + if (j != mDragAxis) + position.pos[j] = mObjectsAtDragStart[i][j]; + } } objectTag->mObject->setPosition(position.pos); @@ -608,6 +609,7 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) } } + mObjectsAtDragStart.clear(); mDragMode = DragMode_None; } @@ -634,8 +636,10 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); + int j = 0; + for (std::vector >::iterator iter (selection.begin()); - iter!=selection.end(); ++iter) + iter!=selection.end(); ++iter, j++) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { @@ -643,6 +647,9 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) for (int i=0; i<3; ++i) position.pos[i] += offset[i]; objectTag->mObject->setPosition (position.pos); + osg::Vec3f thisPoint(position.pos[0], position.pos[1], position.pos[2]); + mDragStart = getMousePlaneCoords(getWorldspaceWidget().mapFromGlobal(QCursor::pos()), getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart[j] = thisPoint; } } } diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 73b7fff12a..4ece934e93 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -45,6 +45,8 @@ namespace CSVRender bool mLocked; float mUnitScaleDist; osg::ref_ptr mParentNode; + osg::Vec3f mDragStart; + std::vector mObjectsAtDragStart; int getSubModeFromId (const std::string& id) const; diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp index d394a431ed..c7c4585979 100644 --- a/apps/opencs/view/tools/merge.hpp +++ b/apps/opencs/view/tools/merge.hpp @@ -1,5 +1,5 @@ -#ifndef CSV_TOOLS_REPORTTABLE_H -#define CSV_TOOLS_REPORTTABLE_H +#ifndef CSV_TOOLS_MERGE_H +#define CSV_TOOLS_MERGE_H #include diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 07ba7907e7..d687cbeb3f 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -36,7 +36,7 @@ void CSVTools::SearchSubView::replace (bool selection) // in a single string. for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) { - CSMWorld::UniversalId id = model.getUniversalId (*iter); + const CSMWorld::UniversalId& id = model.getUniversalId (*iter); CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType (id.getType()); diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 1a2f2bbaa3..6bc0126b3e 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -22,7 +22,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd std::vector types = CSMWorld::UniversalId::listReferenceableTypes(); mType = new QComboBox (this); - + mType->setMaxVisibleItems(20); + for (std::vector::const_iterator iter (types.begin()); iter!=types.end(); ++iter) { @@ -31,7 +32,9 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), static_cast (id2.getType())); } - + + mType->model()->sort(0); + insertBeforeButtons (mType, false); connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 53e35f3310..5605ff229e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -155,31 +155,6 @@ target_link_libraries(openmw ${LUA_LIBRARIES} ) -if(OSG_STATIC) - unset(_osg_plugins_static_files) - add_library(openmw_osg_plugins INTERFACE) - foreach(_plugin ${USED_OSG_PLUGINS}) - string(TOUPPER ${_plugin} _plugin_uc) - if(OPENMW_USE_SYSTEM_OSG) - list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) - else() - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_osg_plugins INTERFACE $) - add_dependencies(openmw_osg_plugins ${${_plugin_uc}_LIBRARY}) - endif() - endforeach() - # We use --whole-archive because OSG plugins use registration. - get_whole_archive_options(_opts ${_osg_plugins_static_files}) - target_link_options(openmw_osg_plugins INTERFACE ${_opts}) - target_link_libraries(openmw openmw_osg_plugins) - - if(OPENMW_USE_SYSTEM_OSG) - # OSG plugin pkgconfig files are missing these dependencies. - # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(openmw freetype jpeg png) - endif() -endif(OSG_STATIC) - if (ANDROID) target_link_libraries(openmw EGL android log z) endif (ANDROID) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f968bbfac7..772af3759e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -521,7 +521,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); // load user settings if they exist - const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 50dafba39b..54623e6699 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -846,6 +846,7 @@ namespace MWClass // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 41cf3beed5..718b4d972d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1397,6 +1397,7 @@ namespace MWClass // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 2bfec8105b..874d1d866b 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -491,7 +491,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter { add_partial_text(); stream.consume (); - mLine = nullptr, mRun = nullptr; + mLine = nullptr; + mRun = nullptr; continue; } @@ -551,7 +552,9 @@ struct TypesetBookImpl::Typesetter : BookTypesetter if (left + space_width + word_width > mPageWidth) { - mLine = nullptr, mRun = nullptr, left = 0; + mLine = nullptr; + mRun = nullptr; + left = 0; } else { diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index ee5fe59399..1732bc24ea 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -517,6 +517,7 @@ namespace MWGui std::vector CreateClassDialog::getMajorSkills() const { std::vector v; + v.reserve(5); for(int i = 0; i < 5; i++) { v.push_back(mMajorSkill[i]->getSkillId()); @@ -527,6 +528,7 @@ namespace MWGui std::vector CreateClassDialog::getMinorSkills() const { std::vector v; + v.reserve(5); for(int i=0; i < 5; i++) { v.push_back(mMinorSkill[i]->getSkillId()); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index eead3919e0..f53ba21f9f 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -704,7 +704,7 @@ namespace MWGui if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) return; // make sure the object is of a type that can be picked up - std::string type = object.getTypeName(); + const std::string& type = object.getTypeName(); if ( (type != typeid(ESM::Apparatus).name()) && (type != typeid(ESM::Armor).name()) && (type != typeid(ESM::Book).name()) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 61fcacca46..ef8eea0104 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -66,35 +67,15 @@ namespace MWGui void LoadingScreen::findSplashScreens() { - const std::map& index = mResourceSystem->getVFS()->getIndex(); - std::string pattern = "Splash/"; - mResourceSystem->getVFS()->normalizeFilename(pattern); + auto isSupportedExtension = [](const std::string_view& ext) { + static const std::array supported_extensions{ {"tga", "dds", "ktx", "png", "bmp", "jpeg", "jpg"} }; + return !ext.empty() && std::find(supported_extensions.begin(), supported_extensions.end(), ext) != supported_extensions.end(); + }; - /* priority given to the left */ - const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; - - auto found = index.lower_bound(pattern); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) { - const std::string& name = found->first; - if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos) - { - for(auto const& extension: supported_extensions) - { - if (name.compare(pos, name.size() - pos, extension) == 0) - { - mSplashScreens.push_back(found->first); - break; /* based on priority */ - } - } - } - } - else - break; - ++found; + if (isSupportedExtension(Misc::getFileExtension(name))) + mSplashScreens.push_back(name); } if (mSplashScreens.empty()) Log(Debug::Warning) << "Warning: no splash screens found!"; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index e42808776c..388bbc7d48 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -988,7 +988,8 @@ namespace MWGui if (mInterior) { auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); - x = pos.x(), y = pos.y(); + x = pos.x(); + y = pos.y(); } setGlobalMapPlayerPosition(x, y); @@ -1160,7 +1161,8 @@ namespace MWGui void MapWindow::worldPosToGlobalMapImageSpace(float x, float y, float& imageX, float& imageY) const { mGlobalMapRender->worldPosToImageSpace(x, y, imageX, imageY); - imageX *= mGlobalMapZoom, imageY *= mGlobalMapZoom; + imageX *= mGlobalMapZoom; + imageY *= mGlobalMapZoom; } void MapWindow::updateCustomMarkers() diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 4907a247ff..ed6633c983 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -376,7 +376,9 @@ namespace MWGui { for (const std::string& keyword : keywords) { - if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), button->getCaption())) + if (Misc::StringUtils::ciEqual( + MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}").asUTF8(), + button->getCaption().asUTF8())) { return button; } diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 500ad98490..1f75760f7c 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -71,7 +71,7 @@ namespace MWLua else { const std::string& recordId = std::get(item); - if (old_it != store.end() && *old_it->getCellRef().getRefIdPtr() == recordId) + if (old_it != store.end() && old_it->getCellRef().getRefIdRef() == recordId) return true; // already equipped itemPtr = store.search(recordId); if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index d358979aec..1e009081ff 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -284,7 +284,7 @@ namespace MWLua std::shared_ptr scripts; // When loading a game, it can be called before LuaManager::setPlayer, // so we can't just check ptr == mPlayer here. - if (*ptr.getCellRef().getRefIdPtr() == "player") + if (ptr.getCellRef().getRefIdRef() == "player") { scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts->addPackage("openmw.ui", mUserInterfacePackage); diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 696179d003..d94ce1f49d 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -51,13 +51,13 @@ namespace MWLua bool isMarker(const MWWorld::Ptr& ptr) { - std::string_view id = *ptr.getCellRef().getRefIdPtr(); + std::string_view id = ptr.getCellRef().getRefIdRef(); return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; } std::string_view getMWClassName(const MWWorld::Ptr& ptr) { - if (*ptr.getCellRef().getRefIdPtr() == "player") + if (ptr.getCellRef().getRefIdRef() == "player") return "Player"; if (isMarker(ptr)) return "Marker"; @@ -71,7 +71,7 @@ namespace MWLua res.append(" ("); res.append(getMWClassName(ptr)); res.append(", "); - res.append(*ptr.getCellRef().getRefIdPtr()); + res.append(ptr.getCellRef().getRefIdRef()); res.append(")"); return res; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 62c130b36e..ed26e2b9a4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1653,7 +1653,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) MWRender::Animation::BlendMask_All, false, weapSpeed, startKey, stopKey, 0.0f, 0); - if(mAnimation->isPlaying(mCurrentWeapon)) + if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) mUpperBodyState = UpperCharState_StartToMinAttack; } } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index c61b64f411..7e63190edb 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -206,9 +206,6 @@ namespace MWMechanics endPointInLocalCoords, startNode); - if (!endNode.second) - return; - // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); @@ -279,7 +276,8 @@ namespace MWMechanics // unreachable pathgrid point. // // The AI routines will have to deal with such situations. - *out++ = endPoint; + if (endNode.second) + *out++ = endPoint; } float PathFinder::getZAngleToNext(float x, float y) const diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b50962be9..b3c4ef6168 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -181,7 +181,7 @@ namespace MWPhysics void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); + ObjectMap::iterator found = mObjects.find(ptr.mRef); if (found == mObjects.end()) return; @@ -198,7 +198,7 @@ namespace MWPhysics if (obj.isEmpty()) return true; // assume standing on terrain (which is a non-object, so not collision tracked) - ObjectMap::const_iterator foundObj = mObjects.find(obj); + ObjectMap::const_iterator foundObj = mObjects.find(obj.mRef); if (foundObj == mObjects.end()) return false; @@ -374,8 +374,8 @@ namespace MWPhysics bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const { - const auto it1 = mActors.find(actor1); - const auto it2 = mActors.find(actor2); + const auto it1 = mActors.find(actor1.mRef); + const auto it2 = mActors.find(actor2.mRef); if (it1 == mActors.end() || it2 == mActors.end()) return false; @@ -441,7 +441,7 @@ namespace MWPhysics { btCollisionObject* me = nullptr; - auto found = mObjects.find(ptr); + auto found = mObjects.find(ptr.mRef); if (found != mObjects.end()) me = found->second->getCollisionObject(); else @@ -464,7 +464,7 @@ namespace MWPhysics osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); @@ -504,7 +504,7 @@ namespace MWPhysics assert(!getObject(ptr)); auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); - mObjects.emplace(ptr, obj); + mObjects.emplace(ptr.mRef, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); @@ -512,8 +512,7 @@ namespace MWPhysics void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { if (mUnrefQueue.get()) mUnrefQueue->push(found->second->getShapeInstance()); @@ -522,11 +521,9 @@ namespace MWPhysics mObjects.erase(found); } - - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - mActors.erase(foundActor); + mActors.erase(found); } } @@ -539,22 +536,10 @@ namespace MWPhysics void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { - ObjectMap::iterator found = mObjects.find(old); - if (found != mObjects.end()) - { - auto obj = found->second; - obj->updatePtr(updated); - mObjects.erase(found); - mObjects.emplace(updated, std::move(obj)); - } - - auto actorNode = mActors.extract(old); - if (!actorNode.empty()) - { - actorNode.key() = updated; - actorNode.mapped()->updatePtr(updated); - mActors.insert(std::move(actorNode)); - } + if (auto found = mObjects.find(old.mRef); found != mObjects.end()) + found->second->updatePtr(updated); + else if (auto found = mActors.find(old.mRef); found != mActors.end()) + found->second->updatePtr(updated); for (auto& [_, actor] : mActors) { @@ -572,7 +557,7 @@ namespace MWPhysics Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; @@ -580,7 +565,7 @@ namespace MWPhysics const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const { - ActorMap::const_iterator found = mActors.find(ptr); + ActorMap::const_iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; @@ -588,7 +573,7 @@ namespace MWPhysics const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const { - ObjectMap::const_iterator found = mObjects.find(ptr); + ObjectMap::const_iterator found = mObjects.find(ptr.mRef); if (found != mObjects.end()) return found->second.get(); return nullptr; @@ -604,20 +589,16 @@ namespace MWPhysics void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { float scale = ptr.getCellRef().getScale(); found->second->setScale(scale); mTaskScheduler->updateSingleAabb(found->second); - return; } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - foundActor->second->updateScale(); - mTaskScheduler->updateSingleAabb(foundActor->second); - return; + found->second->updateScale(); + mTaskScheduler->updateSingleAabb(found->second); } } @@ -650,40 +631,32 @@ namespace MWPhysics void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { found->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(found->second); - return; } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - if (!foundActor->second->isRotationallyInvariant()) + if (!found->second->isRotationallyInvariant()) { - foundActor->second->setRotation(rotate); - mTaskScheduler->updateSingleAabb(foundActor->second); + found->second->setRotation(rotate); + mTaskScheduler->updateSingleAabb(found->second); } - return; } } void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { found->second->updatePosition(); mTaskScheduler->updateSingleAabb(found->second); - return; } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - foundActor->second->updatePosition(); - mTaskScheduler->updateSingleAabb(foundActor->second, true); - return; + found->second->updatePosition(); + mTaskScheduler->updateSingleAabb(found->second, true); } } @@ -710,7 +683,7 @@ namespace MWPhysics auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); - mActors.emplace(ptr, std::move(actor)); + mActors.emplace(ptr.mRef, std::move(actor)); } int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater) @@ -738,7 +711,7 @@ namespace MWPhysics bool PhysicsSystem::toggleCollisionMode() { - ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); + ActorMap::iterator found = mActors.find(MWMechanics::getPlayer().mRef); if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); @@ -753,7 +726,7 @@ namespace MWPhysics void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) found->second->setVelocity(velocity); } @@ -770,13 +743,13 @@ namespace MWPhysics framedata.first.reserve(mActors.size()); framedata.second.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); - for (const auto& [actor, physicActor] : mActors) + for (const auto& [ref, physicActor] : mActors) { auto ptr = physicActor->getPtr(); - if (!actor.getClass().isMobile(ptr)) + if (!ptr.getClass().isMobile(ptr)) continue; float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = actor.getCell(); + const MWWorld::CellStore *cell = ptr.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); @@ -786,7 +759,7 @@ namespace MWPhysics bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (physicActor->getCollisionMode() || !world->isUnderwater(actor.getCell(), actor.getRefData().getPosition().asVec3())) + if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), ptr.getRefData().getPosition().asVec3())) waterCollision = true; } @@ -813,7 +786,7 @@ namespace MWPhysics { if (animatedObject->animateCollisionShapes()) { - auto obj = mObjects.find(animatedObject->getPtr()); + auto obj = mObjects.find(animatedObject->getPtr().mRef); assert(obj != mObjects.end()); mTaskScheduler->updateSingleAabb(obj->second); } @@ -840,18 +813,26 @@ namespace MWPhysics { auto* player = getActor(MWMechanics::getPlayer()); auto* world = MWBase::Environment::get().getWorld(); - for (auto& [ptr, physicActor] : mActors) + + // copy new ptr position in temporary vector. player is handled separately as its movement might change active cell. + std::vector> newPositions; + newPositions.reserve(mActors.size() - 1); + for (const auto& [ptr, physicActor] : mActors) { if (physicActor.get() == player) continue; - world->moveObject(physicActor->getPtr(), physicActor->getSimulationPosition(), false, false); + newPositions.emplace_back(physicActor->getPtr(), physicActor->getSimulationPosition()); } + + for (auto& [ptr, pos] : newPositions) + world->moveObject(ptr, pos, false, false); + world->moveObject(player->getPtr(), player->getSimulationPosition(), false, false); } void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object) { - ObjectMap::iterator found = mObjects.find(object); + ObjectMap::iterator found = mObjects.find(object.mRef); if (found != mObjects.end()) if (found->second->animateCollisionShapes()) mTaskScheduler->updateSingleAabb(found->second); @@ -865,7 +846,7 @@ namespace MWPhysics bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { - const auto physActor = mActors.find(actor); + const auto physActor = mActors.find(actor.mRef); if (physActor != mActors.end()) return physActor->second->getStandingOnPtr() == object; return false; @@ -943,7 +924,7 @@ namespace MWPhysics bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const { btCollisionObject* object = nullptr; - const auto it = mActors.find(ignore); + const auto it = mActors.find(ignore.mRef); if (it != mActors.end()) object = it->second->getCollisionObject(); const auto bulletPosition = Misc::Convert::toBullet(position); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b20c8f88e1..78569bb0c3 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -56,7 +57,7 @@ namespace MWPhysics class PhysicsTaskScheduler; class Projectile; - using ActorMap = std::map>; + using ActorMap = std::unordered_map>; struct ContactPoint { @@ -272,7 +273,7 @@ namespace MWPhysics std::unique_ptr mShapeManager; Resource::ResourceSystem* mResourceSystem; - using ObjectMap = std::map>; + using ObjectMap = std::unordered_map>; ObjectMap mObjects; std::set mAnimatedObjects; // stores pointers to elements in mObjects diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 4109d61e8c..f556e6891a 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -271,7 +271,7 @@ bool ActorAnimation::useShieldAnimations() const return false; } -osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) +osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) const { if (!mObjectRoot) return nullptr; diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index e6bba48d56..61ad1ca235 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -41,7 +41,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener bool updateCarriedLeftVisible(const int weaptype) const override; protected: - osg::Group* getBoneByName(const std::string& boneName); + osg::Group* getBoneByName(const std::string& boneName) const; virtual void updateHolsteredWeapon(bool showHolsteredWeapons); virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 591e666d01..1dc42db47c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -196,32 +197,6 @@ namespace return 0.0f; } - /// @brief Base class for visitors that remove nodes from a scene graph. - /// Subclasses need to fill the mToRemove vector. - /// To use, node->accept(removeVisitor); removeVisitor.remove(); - class RemoveVisitor : public osg::NodeVisitor - { - public: - RemoveVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { - } - - void remove() - { - for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) - { - if (!it->second->removeChild(it->first)) - Log(Debug::Error) << "Error removing " << it->first->getName(); - } - } - - protected: - // - typedef std::vector > RemoveVec; - std::vector > mToRemove; - }; - class GetExtendedBonesVisitor : public osg::NodeVisitor { public: @@ -244,7 +219,7 @@ namespace std::vector > mFoundBones; }; - class RemoveFinishedCallbackVisitor : public RemoveVisitor + class RemoveFinishedCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; @@ -289,7 +264,7 @@ namespace } }; - class RemoveCallbackVisitor : public RemoveVisitor + class RemoveCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; @@ -397,90 +372,6 @@ namespace private: int mEffectId; }; - - // Removes all drawables from a graph. - class CleanObjectRootVisitor : public RemoveVisitor - { - public: - void apply(osg::Drawable& drw) override - { - applyDrawable(drw); - } - - void apply(osg::Group& node) override - { - applyNode(node); - } - void apply(osg::MatrixTransform& node) override - { - applyNode(node); - } - void apply(osg::Node& node) override - { - applyNode(node); - } - - void applyNode(osg::Node& node) - { - if (node.getStateSet()) - node.setStateSet(nullptr); - - if (node.getNodeMask() == 0x1 && node.getNumParents() == 1) - mToRemove.emplace_back(&node, node.getParent(0)); - else - traverse(node); - } - void applyDrawable(osg::Node& node) - { - osg::NodePath::iterator parent = getNodePath().end()-2; - // We know that the parent is a Group because only Groups can have children. - osg::Group* parentGroup = static_cast(*parent); - - // Try to prune nodes that would be empty after the removal - if (parent != getNodePath().begin()) - { - // This could be extended to remove the parent's parent, and so on if they are empty as well. - // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance. - osg::Group* parentParent = static_cast(*(parent - 1)); - if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC) - { - mToRemove.emplace_back(parentGroup, parentParent); - return; - } - } - - mToRemove.emplace_back(&node, parentGroup); - } - }; - - class RemoveTriBipVisitor : public RemoveVisitor - { - public: - void apply(osg::Drawable& drw) override - { - applyImpl(drw); - } - - void apply(osg::Group& node) override - { - traverse(node); - } - void apply(osg::MatrixTransform& node) override - { - traverse(node); - } - - void applyImpl(osg::Node& node) - { - const std::string toFind = "tri bip"; - if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) - { - osg::Group* parent = static_cast(*(getNodePath().end()-2)); - // Not safe to remove in apply(), since the visitor is still iterating the child list - mToRemove.emplace_back(&node, parent); - } - } - }; } namespace MWRender @@ -701,8 +592,6 @@ namespace MWRender void Animation::loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel) { - const std::map& index = mResourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { @@ -710,21 +599,10 @@ namespace MWRender } animationPath.replace(animationPath.size()-3, 3, "/"); - mResourceSystem->getVFS()->normalizeFilename(animationPath); - - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".kf") == 0) - addSingleAnimSource(name, baseModel); - } - else - break; - ++found; + if (Misc::getFileExtension(name) == "kf") + addSingleAnimSource(name, baseModel); } } @@ -1405,8 +1283,6 @@ namespace MWRender if (model.empty()) return; - const std::map& index = resourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { @@ -1414,21 +1290,10 @@ namespace MWRender } animationPath.replace(animationPath.size()-4, 4, "/"); - resourceSystem->getVFS()->normalizeFilename(animationPath); - - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0) - loadBonesFromFile(node, name, resourceSystem); - } - else - break; - ++found; + if (Misc::getFileExtension(name) == "nif") + loadBonesFromFile(node, name, resourceSystem); } } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 2987111621..f1d28b0634 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -158,7 +158,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) try { - osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(itemModel); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(itemModel); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 947bdad202..a199201d41 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,6 +1,7 @@ #include "groundcover.hpp" #include +#include #include #include @@ -66,18 +67,6 @@ namespace MWRender { } - void apply(osg::Node& node) override - { - osg::ref_ptr ss = node.getStateSet(); - if (ss != nullptr) - { - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - } - - traverse(node); - } - void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) @@ -110,32 +99,14 @@ namespace MWRender // Display lists do not support instancing in OSG 3.4 geom.setUseDisplayList(false); + geom.setUseVertexBufferObjects(true); geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); - - osg::ref_ptr ss = geom.getOrCreateStateSet(); - ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); - ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); - - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - - traverse(geom); } private: std::vector mInstances; osg::Vec3f mChunkPosition; - - void removeAlpha(osg::StateSet* stateset) - { - // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties - stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); - stateset->removeMode(GL_ALPHA_TEST); - stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); - stateset->removeMode(GL_BLEND); - stateset->setRenderBinToInherit(); - } }; class DensityCalculator @@ -180,11 +151,11 @@ namespace MWRender osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { - ChunkId id = std::make_tuple(center, size, activeGrid); + GroundcoverChunkId id = std::make_tuple(center, size); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { InstanceMap instances; @@ -196,10 +167,19 @@ namespace MWRender } Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) - : GenericResourceManager(nullptr) + : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) + , mStateset(new osg::StateSet) { + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); + mStateset->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + mStateset->setAttributeAndModes(new osg::BlendFunc, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); + mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); } void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) @@ -255,27 +235,23 @@ namespace MWRender for (auto& pair : instances) { const osg::Node* temp = mSceneManager->getTemplate(pair.first); - osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES|osg::CopyOp::DEEP_COPY_USERDATA|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES)); // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); - mSceneManager->reinstateRemovedState(node); - InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); } - // Force a unified alpha handling instead of data from meshes - osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); - group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); - group->getBound(); + group->setStateSet(mStateset); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", false, true); - + mSceneManager->shareState(group); + group->getBound(); return group; } diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index b92ab97c95..4874bea899 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -29,8 +29,8 @@ namespace MWRender osg::Vec3f mPlayerPos; }; - typedef std::tuple ChunkId; // Center, Size, ActiveGrid - class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + typedef std::tuple GroundcoverChunkId; // Center, Size + class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: Groundcover(Resource::SceneManager* sceneManager, float density); @@ -56,6 +56,7 @@ namespace MWRender private: Resource::SceneManager* mSceneManager; float mDensity; + osg::ref_ptr mStateset; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d142996f40..7fc488020c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -714,14 +714,14 @@ void NpcAnimation::updateParts() PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { - osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model); + osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); - osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second); + osg::ref_ptr attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index b23f079888..907631436f 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -77,7 +77,7 @@ namespace MWRender osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 381a2e5f62..56d3ba4a43 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -373,7 +373,9 @@ namespace MWRender mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); - if (Settings::Manager::getBool("distant terrain", "Terrain")) + bool groundcover = Settings::Manager::getBool("enabled", "Groundcover"); + bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + if (distantTerrain || groundcover) { const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); @@ -398,41 +400,27 @@ namespace MWRender mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); - if (Settings::Manager::getBool("enabled", "Groundcover")) - { - osg::ref_ptr groundcoverRoot = new osg::Group; - groundcoverRoot->setNodeMask(Mask_Groundcover); - groundcoverRoot->setName("Groundcover Root"); - sceneRoot->addChild(groundcoverRoot); - - mGroundcoverUpdater = new GroundcoverUpdater; - groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); - - float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); - if (chunkSize >= 1.0f) - chunkSize = 1.0f; - else if (chunkSize >= 0.5f) - chunkSize = 0.5f; - else if (chunkSize >= 0.25f) - chunkSize = 0.25f; - else if (chunkSize != 0.125f) - chunkSize = 0.125f; + osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; + if (groundcover) + { float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); + mGroundcoverUpdater = new GroundcoverUpdater; + composite->addController(mGroundcoverUpdater); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); - static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); + static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); - // Groundcover it is handled in the same way indifferently from if it is from active grid or from distant cell. - // Use a stub grid to avoid splitting between chunks for active grid and chunks for distant cells. - mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); + float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); + mGroundcover->setViewDistance(groundcoverDistance); } mStateUpdater = new StateUpdater; - sceneRoot->addUpdateCallback(mStateUpdater); + composite->addController(mStateUpdater); + sceneRoot->addUpdateCallback(composite); mSharedUniformStateUpdater = new SharedUniformStateUpdater; rootNode->addUpdateCallback(mSharedUniformStateUpdater); @@ -693,8 +681,6 @@ namespace MWRender if (store->getCell()->isExterior()) { mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - if (mGroundcoverWorld) - mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } } void RenderingManager::removeCell(const MWWorld::CellStore *store) @@ -706,8 +692,6 @@ namespace MWRender if (store->getCell()->isExterior()) { mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - if (mGroundcoverWorld) - mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } mWater->removeCell(store); @@ -718,8 +702,6 @@ namespace MWRender if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); - if (mGroundcoverWorld) - mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -1179,12 +1161,6 @@ namespace MWRender fov = std::min(mFieldOfView, 140.f); float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); - - if (mGroundcoverWorld) - { - float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); - mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); - } } void RenderingManager::updateTextureFiltering() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d6d7e74634..b991b08efa 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -262,8 +262,6 @@ namespace MWRender osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mGroundcoverUpdater; - osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; @@ -278,10 +276,10 @@ namespace MWRender std::unique_ptr mObjects; std::unique_ptr mWater; std::unique_ptr mTerrain; - std::unique_ptr mGroundcoverWorld; std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; std::unique_ptr mGroundcover; + osg::ref_ptr mGroundcoverUpdater; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index b9ea5daacf..5a047a1566 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -191,6 +191,7 @@ namespace MWRender screenshotH = screenshotW; // use square resolution for planet mapping std::vector> images; + images.reserve(6); for (int i = 0; i < 6; ++i) images.push_back(new osg::Image); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 528ce70ea3..879a2ef68b 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -33,7 +33,10 @@ namespace MWRender void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { - minX = 0, minY = 0, maxX = 0, maxY = 0; + minX = 0; + minY = 0; + maxX = 0; + maxY = 0; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index cb71cb56d2..e64e89d775 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -131,7 +131,7 @@ namespace MWSound max = std::max(min, max); Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); - mVfs->normalizeFilename(sfx.mResourceName); + sfx.mResourceName = mVfs->normalizeFilename(sfx.mResourceName); mBufferNameMap.emplace(soundId, &sfx); return &sfx; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index fc9735d576..d2422870d7 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -294,20 +294,9 @@ namespace MWSound if (mMusicFiles.find(playlist) == mMusicFiles.end()) { std::vector filelist; - const std::map& index = mVFS->getIndex(); - std::string pattern = "Music/" + playlist; - mVFS->normalizeFilename(pattern); - - std::map::const_iterator found = index.lower_bound(pattern); - while (found != index.end()) - { - if (found->first.size() >= pattern.size() && found->first.substr(0, pattern.size()) == pattern) - filelist.push_back(found->first); - else - break; - ++found; - } + for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist)) + filelist.push_back(name); mMusicFiles[playlist] = filelist; } @@ -327,13 +316,11 @@ namespace MWSound if (mMusicFiles.find("Title") == mMusicFiles.end()) { std::vector filelist; - const std::map& index = mVFS->getIndex(); // Is there an ini setting for this filename or something? std::string filename = "music/special/morrowind title.mp3"; - auto found = index.find(filename); - if (found != index.end()) + if (mVFS->exists(filename)) { - filelist.emplace_back(found->first); + filelist.emplace_back(filename); mMusicFiles["Title"] = filelist; } else @@ -355,10 +342,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; @@ -389,10 +373,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index a2167c562f..f2f2ebc5a6 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -14,12 +14,36 @@ #include #include #include +#include #include "../mwrender/landmanager.hpp" #include "cellstore.hpp" #include "class.hpp" +namespace +{ + template + bool contains(const std::vector& container, + const Contained& contained, float tolerance=1.f) + { + for (const auto& pos : contained) + { + bool found = false; + for (const auto& pos2 : container) + { + if ((pos.first-pos2.first).length2() < tolerance*tolerance && pos.second == pos2.second) + { + found = true; + break; + } + } + if (!found) return false; + } + return true; + } +} + namespace MWWorld { @@ -94,7 +118,6 @@ namespace MWWorld { mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); - bool animated = false; size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) { @@ -106,17 +129,11 @@ namespace MWWorld { kfname.replace(kfname.size()-4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) - { mPreloadedObjects.insert(mKeyframeManager->get(kfname)); - animated = true; - } } } } - if (mPreloadInstances && animated) - mPreloadedObjects.insert(mSceneManager->cacheInstance(mesh)); - else - mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); + mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); if (mPreloadInstances) mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); else @@ -157,8 +174,6 @@ namespace MWWorld public: TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) : mAbort(false) - , mProgress(views.size()) - , mProgressRange(0) , mTerrainViews(views) , mWorld(world) , mPreloadPositions(preloadPositions) @@ -178,8 +193,9 @@ namespace MWWorld for (unsigned int i=0; ireset(); - mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mProgress[i], mProgressRange); + mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mLoadingReporter); } + mLoadingReporter.complete(); } void abort() override @@ -187,16 +203,17 @@ namespace MWWorld mAbort = true; } - int getProgress() const { return !mProgress.empty() ? mProgress[0].load() : 0; } - int getProgressRange() const { return !mProgress.empty() && mProgress[0].load() ? mProgressRange : 0; } + void wait(Loading::Listener& listener) const + { + mLoadingReporter.wait(listener); + } private: std::atomic mAbort; - std::vector> mProgress; - int mProgressRange; std::vector > mTerrainViews; Terrain::World* mWorld; std::vector mPreloadPositions; + Loading::Reporter mLoadingReporter; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. @@ -230,6 +247,7 @@ namespace MWWorld , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) , mStoreViewsFailCount(0) + , mLoadedTerrainTimestamp(0.0) { } @@ -375,7 +393,11 @@ namespace MWWorld setTerrainPreloadPositions(std::vector()); } else + { mStoreViewsFailCount = 0; + mLoadedTerrainPositions = mTerrainPreloadPositions; + mLoadedTerrainTimestamp = timestamp; + } mTerrainPreloadItem = nullptr; } } @@ -415,7 +437,7 @@ namespace MWWorld mUnrefQueue = unrefQueue; } - bool CellPreloader::syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp) + bool CellPreloader::syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener) { if (!mTerrainPreloadItem) return true; @@ -435,18 +457,15 @@ namespace MWWorld } else { - progress = mTerrainPreloadItem->getProgress(); - progressRange = mTerrainPreloadItem->getProgressRange(); - return false; + mTerrainPreloadItem->wait(listener); + return true; } } void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) { - const float resetThreshold = ESM::Land::REAL_SIZE; - for (const auto& pos : mTerrainPreloadPositions) - if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) - return; + if (exceptPos && contains(mTerrainPreloadPositions, std::array {*exceptPos}, ESM::Land::REAL_SIZE)) + return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) { mTerrainPreloadItem->abort(); @@ -455,28 +474,13 @@ namespace MWWorld setTerrainPreloadPositions(std::vector()); } - bool contains(const std::vector& container, const std::vector& contained) - { - for (const auto& pos : contained) - { - bool found = false; - for (const auto& pos2 : container) - { - if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) - { - found = true; - break; - } - } - if (!found) return false; - } - return true; - } - void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) { if (positions.empty()) + { mTerrainPreloadPositions.clear(); + mLoadedTerrainPositions.clear(); + } else if (contains(mTerrainPreloadPositions, positions)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) @@ -503,5 +507,10 @@ namespace MWWorld } } } + + bool CellPreloader::isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const + { + return mLoadedTerrainTimestamp + mResourceSystem->getSceneManager()->getExpiryDelay() > referenceTime && contains(mLoadedTerrainPositions, std::array {position}, ESM::Land::REAL_SIZE); + } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index e719f2e606..39436dc5ad 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -29,6 +29,11 @@ namespace MWRender class LandManager; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { class CellStore; @@ -72,8 +77,9 @@ namespace MWWorld typedef std::pair PositionCellGrid; void setTerrainPreloadPositions(const std::vector& positions); - bool syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp); + bool syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener); void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); + bool isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const; private: Resource::ResourceSystem* mResourceSystem; @@ -114,6 +120,9 @@ namespace MWWorld std::vector mTerrainPreloadPositions; osg::ref_ptr mTerrainPreloadItem; osg::ref_ptr mUpdateCacheItem; + + std::vector mLoadedTerrainPositions; + double mLoadedTerrainTimestamp; }; } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 5e91ddcc67..0b16964043 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -48,11 +48,6 @@ namespace MWWorld return mCellRef.mRefID; } - const std::string* CellRef::getRefIdPtr() const - { - return &mCellRef.mRefID; - } - bool CellRef::getTeleport() const { return mCellRef.mTeleport; diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 6a6ac69c57..78170a766f 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -38,8 +38,8 @@ namespace MWWorld // Id of object being referenced std::string getRefId() const; - // Pointer to ID of the object being referenced - const std::string* getRefIdPtr() const; + // Reference to ID of the object being referenced + const std::string& getRefIdRef() const { return mCellRef.mRefID; } // For doors - true if this door teleports to somewhere else, false // if it should open through animation. diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index ad4dfcfb90..d020eace45 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -36,7 +36,7 @@ namespace } bool cont = cell.second.forEach([&] (MWWorld::Ptr ptr) { - if(*ptr.getCellRef().getRefIdPtr() == id) + if (ptr.getCellRef().getRefIdRef() == id) { return visitor(ptr); } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 37c4e178ad..3d82a30c29 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -181,7 +181,7 @@ namespace { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) - if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID) + if (iter->mRef.getRefNum()==state.mRef.mRefNum && iter->mRef.getRefIdRef() == state.mRef.mRefID) { // overwrite existing reference float oldscale = iter->mRef.getScale(); @@ -417,7 +417,7 @@ namespace MWWorld const std::string *mIdToFind; bool operator()(const PtrType& ptr) { - if (*ptr.getCellRef().getRefIdPtr() == *mIdToFind) + if (ptr.getCellRef().getRefIdRef() == *mIdToFind) { mFound = ptr; return false; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index cced17688d..b02c2bb407 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -184,7 +184,7 @@ int MWWorld::ContainerStore::count(const std::string &id) const { int total=0; for (const auto&& iter : *this) - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefIdRef(), id)) total += iter.getRefData().getCount(); return total; } @@ -249,7 +249,7 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); - if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefId(), ptr2.getCellRef().getRefId())) + if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefIdRef(), ptr2.getCellRef().getRefIdRef())) return false; // If it has an enchantment, don't stack when some of the charge is already used @@ -364,7 +364,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { - if (Misc::StringUtils::ciEqual((*iter).getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefIdRef(), MWWorld::ContainerStore::sGoldId)) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); flagAsModified(); @@ -465,7 +465,7 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefIdRef(), itemId)) toRemove -= remove(*iter, toRemove, actor, equipReplacement, resolveFirst); flagAsModified(); @@ -740,7 +740,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) for (auto&& iter : *this) { int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefIdRef(), id)) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d96447f87d..0b8af4463c 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -143,7 +143,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() && actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) { - std::string type = itemPtr.getTypeName(); + const std::string& type = itemPtr.getTypeName(); if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) autoEquip(actorPtr); } @@ -748,7 +748,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc() && !actor.getClass().getNpcStats(actor).isWerewolf()) { - std::string type = item.getTypeName(); + const std::string& type = item.getTypeName(); if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) autoEquip(actor); } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 5fa9be1be5..89349d329a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -628,7 +628,10 @@ namespace MWWorld osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); - preloadTerrain(pos, true); + if (mRendering.pagingUnlockCache()) + mPreloader->abortTerrainPreloadExcept(nullptr); + if (!mPreloader->isTerrainLoaded(std::make_pair(pos, newGrid), mRendering.getReferenceTime())) + preloadTerrain(pos, true); mPagedRefs.clear(); mRendering.getPagedRefnums(newGrid, mPagedRefs); @@ -742,14 +745,12 @@ namespace MWWorld { loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); - CellStoreCollection::iterator iter = mActiveCells.begin(); - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); loadInactiveCell (cell, loadingListener, true); activateCell (cell, loadingListener, false, true); - iter = mActiveCells.begin(); - while (iter != mActiveCells.end()) + auto iter = mInactiveCells.begin(); + while (iter != mInactiveCells.end()) { if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) @@ -796,8 +797,8 @@ namespace MWWorld loadInactiveCell (cell, loadingListener, true); activateCell (cell, loadingListener, false, true); - CellStoreCollection::iterator iter = mActiveCells.begin(); - while (iter != mActiveCells.end()) + auto iter = mInactiveCells.begin(); + while (iter != mInactiveCells.end()) { assert (!(*iter)->getCell()->isExterior()); @@ -1243,32 +1244,16 @@ namespace MWWorld { std::vector vec; vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); - if (sync && mRendering.pagingUnlockCache()) - mPreloader->abortTerrainPreloadExcept(nullptr); - else - mPreloader->abortTerrainPreloadExcept(&vec[0]); + mPreloader->abortTerrainPreloadExcept(&vec[0]); mPreloader->setTerrainPreloadPositions(vec); if (!sync) return; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - int progress = 0, initialProgress = -1, progressRange = 0; - while (!mPreloader->syncTerrainLoad(vec, progress, progressRange, mRendering.getReferenceTime())) - { - if (initialProgress == -1) - { - loadingListener->setLabel("#{sLoadingMessage4}"); - initialProgress = progress; - } - if (progress) - { - loadingListener->setProgressRange(std::max(0, progressRange-initialProgress)); - loadingListener->setProgress(progress-initialProgress); - } - else - loadingListener->setProgress(0); - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } + + loadingListener->setLabel("#{sLoadingMessage4}"); + + while (!mPreloader->syncTerrainLoad(vec, mRendering.getReferenceTime(), *loadingListener)) {} } void Scene::reloadTerrain() diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index f6de1d485b..718cebd790 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -520,7 +520,8 @@ namespace MWWorld const ESM::Cell *Store::search(int x, int y) const { ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; + cell.mData.mX = x; + cell.mData.mY = y; std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); @@ -538,7 +539,8 @@ namespace MWWorld const ESM::Cell *Store::searchStatic(int x, int y) const { ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; + cell.mData.mX = x; + cell.mData.mY = y; std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 88edb71ddf..4b1d648703 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -428,7 +428,7 @@ namespace MWWorld const ESM::WeaponType *search(const int id) const; const ESM::WeaponType *find(const int id) const; - RecordId load(ESM::ESMReader &esm) override { return RecordId(nullptr, false); } + RecordId load(ESM::ESMReader &esm) override { return RecordId({}, false); } ESM::WeaponType* insert(const ESM::WeaponType &weaponType); diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 69a326060c..9405dba4b2 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -77,6 +77,14 @@ return { EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 45); } + TEST_F(LuaStateTest, ToString) + { + EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), 3.14)), "3.14"); + EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), true)), "true"); + EXPECT_EQ(LuaUtil::toString(sol::nil), "nil"); + EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), "something")), "\"something\""); + } + TEST_F(LuaStateTest, ErrorHandling) { EXPECT_ERROR(mLua.runInNewSandbox("invalid.lua"), "[string \"invalid.lua\"]:1:"); diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index afd9fa2d3c..fb8e48e461 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -1,6 +1,7 @@ #include "gmock/gmock.h" #include +#include #include #include "testing_util.hpp" @@ -45,6 +46,7 @@ namespace EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 12); EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get(), 13); EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(5, 12, 13)"); + EXPECT_EQ(LuaUtil::toString(lua.safe_script("return v")), "(5, 12, 13)"); EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length()").get(), 5); EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get(), 25); EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get()); diff --git a/apps/openmw_test_suite/misc/test_stringops.cpp b/apps/openmw_test_suite/misc/test_stringops.cpp index 086908692d..173cfa4447 100644 --- a/apps/openmw_test_suite/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/misc/test_stringops.cpp @@ -1,6 +1,10 @@ #include #include "components/misc/stringops.hpp" +#include +#include +#include + struct PartialBinarySearchTest : public ::testing::Test { protected: @@ -12,10 +16,6 @@ struct PartialBinarySearchTest : public ::testing::Test std::sort(mDataVec.begin(), mDataVec.end(), Misc::StringUtils::ciLess); } - void TearDown() override - { - } - bool matches(const std::string& keyword) { return Misc::StringUtils::partialBinarySearch(mDataVec.begin(), mDataVec.end(), keyword) != mDataVec.end(); @@ -51,3 +51,97 @@ TEST_F (PartialBinarySearchTest, ci_test) std::string unicode1 = "\u04151 \u0418"; // CYRILLIC CAPITAL LETTER IE, CYRILLIC CAPITAL LETTER I EXPECT_TRUE( Misc::StringUtils::lowerCase(unicode1) == unicode1 ); } + +namespace +{ + using ::Misc::StringUtils; + using namespace ::testing; + + template + struct MiscStringUtilsCiEqualEmptyTest : Test {}; + + TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualEmptyTest); + + TYPED_TEST_P(MiscStringUtilsCiEqualEmptyTest, empty_strings_should_be_equal) + { + EXPECT_TRUE(StringUtils::ciEqual(typename TypeParam::first_type {}, typename TypeParam::second_type {})); + } + + REGISTER_TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualEmptyTest, + empty_strings_should_be_equal + ); + + using EmptyStringTypePairsTypes = Types< + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair + >; + + INSTANTIATE_TYPED_TEST_SUITE_P(EmptyStringTypePairs, MiscStringUtilsCiEqualEmptyTest, EmptyStringTypePairsTypes); + + template + struct MiscStringUtilsCiEqualNotEmptyTest : Test {}; + + TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualNotEmptyTest); + + using RawValue = const char[4]; + + constexpr RawValue foo = "foo"; + constexpr RawValue fooUpper = "FOO"; + constexpr RawValue bar = "bar"; + + template + using Value = std::conditional_t, RawValue&, T>; + + TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, same_strings_should_be_equal) + { + const Value a {foo}; + const Value b {foo}; + EXPECT_TRUE(StringUtils::ciEqual(a, b)) << a << "\n" << b; + } + + TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, same_strings_with_different_case_sensetivity_should_be_equal) + { + const Value a {foo}; + const Value b {fooUpper}; + EXPECT_TRUE(StringUtils::ciEqual(a, b)) << a << "\n" << b; + } + + TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, different_strings_content_should_not_be_equal) + { + const Value a {foo}; + const Value b {bar}; + EXPECT_FALSE(StringUtils::ciEqual(a, b)) << a << "\n" << b; + } + + REGISTER_TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualNotEmptyTest, + same_strings_should_be_equal, + same_strings_with_different_case_sensetivity_should_be_equal, + different_strings_content_should_not_be_equal + ); + + using NotEmptyStringTypePairsTypes = Types< + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair + >; + + INSTANTIATE_TYPED_TEST_SUITE_P(NotEmptyStringTypePairs, MiscStringUtilsCiEqualNotEmptyTest, NotEmptyStringTypePairsTypes); + + TEST(MiscStringUtilsCiEqualTest, string_with_different_length_should_not_be_equal) + { + EXPECT_FALSE(StringUtils::ciEqual(std::string("a"), std::string("aa"))); + } +} diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 2558ec81de..03cd63480a 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -1,4 +1,3 @@ - set(WIZARD componentselectionpage.cpp conclusionpage.cpp @@ -34,21 +33,6 @@ set(WIZARD_HEADER utils/componentlistwidget.hpp ) -# Headers that must be pre-processed -set(WIZARD_HEADER_MOC - componentselectionpage.hpp - conclusionpage.hpp - existinginstallationpage.hpp - importpage.hpp - installationtargetpage.hpp - intropage.hpp - languageselectionpage.hpp - mainwizard.hpp - methodselectionpage.hpp - - utils/componentlistwidget.hpp -) - set(WIZARD_UI ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui @@ -63,7 +47,6 @@ set(WIZARD_UI if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) - set (WIZARD_HEADER_MOC ${WIZARD_HEADER_MOC} installationpage.hpp unshield/unshieldworker.hpp) set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) @@ -80,7 +63,6 @@ if(WIN32) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) -QT5_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) QT5_WRAP_UI(UI_HDRS ${WIZARD_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -94,7 +76,6 @@ openmw_add_executable(openmw-wizard ${WIZARD} ${WIZARD_HEADER} ${RCC_SRCS} - ${MOC_SRCS} ${UI_HDRS} ) @@ -125,3 +106,7 @@ endif() if (WIN32) INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION ".") endif(WIN32) + +if(USE_QT) + set_property(TARGET openmw-wizard PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 176a02d507..0adc185b38 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -80,10 +80,6 @@ foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND COMPONENT_QT_FILES "${f}") endforeach (f) -file (GLOB MOC_H "${dir}/${u}.hpp") -foreach (fi ${MOC_H}) -list (APPEND COMPONENT_MOC_FILES "${fi}") -endforeach (fi) endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_qt_dir) @@ -97,44 +93,21 @@ add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") endmacro (add_unit) -macro (add_qt_unit project dir unit) -add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") -add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") -endmacro (add_qt_unit) - macro (add_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") endmacro (add_hdr) -macro (add_qt_hdr project dir unit) -add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") -endmacro (add_qt_hdr) - macro (opencs_units dir) foreach (u ${ARGN}) -add_qt_unit (OPENCS ${dir} ${u}) -endforeach (u) -endmacro (opencs_units) - -macro (opencs_units_noqt dir) -foreach (u ${ARGN}) add_unit (OPENCS ${dir} ${u}) endforeach (u) -endmacro (opencs_units_noqt) +endmacro (opencs_units) macro (opencs_hdrs dir) foreach (u ${ARGN}) -add_qt_hdr (OPENCS ${dir} ${u}) -endforeach (u) -endmacro (opencs_hdrs) - -macro (opencs_hdrs_noqt dir) -foreach (u ${ARGN}) add_hdr (OPENCS ${dir} ${u}) endforeach (u) -endmacro (opencs_hdrs_noqt) +endmacro (opencs_hdrs) include(CMakeParseArguments) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3de864ea52..7ee3d184d8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -197,6 +197,10 @@ add_component_dir(detournavigator navmeshcacheitem ) +add_component_dir(loadinglistener + reporter + ) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) @@ -222,7 +226,6 @@ if (USE_QT) ) QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -276,7 +279,7 @@ if (WIN32) endif() if (USE_QT) - add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) + add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${ESM_UI_HDR}) target_link_libraries(components_qt components Qt5::Widgets Qt5::Core) target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") endif() @@ -285,6 +288,15 @@ if (GIT_CHECKOUT) add_dependencies (components git-version) endif (GIT_CHECKOUT) +if (OSG_STATIC AND CMAKE_SYSTEM_NAME MATCHES "Linux") + find_package(X11 REQUIRED COMPONENTS Xinerama Xrandr) + target_link_libraries(components ${CMAKE_DL_LIBS} X11::X11 X11::Xinerama X11::Xrandr) + find_package(Fontconfig MODULE) + if(Fontconfig_FOUND) + target_link_libraries(components Fontconfig::Fontconfig) + endif() +endif() + if (WIN32) target_link_libraries(components shlwapi) endif() @@ -304,3 +316,35 @@ endif() set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) + +if(OSG_STATIC) + unset(_osg_plugins_static_files) + add_library(components_osg_plugins INTERFACE) + foreach(_plugin ${USED_OSG_PLUGINS}) + string(TOUPPER ${_plugin} _plugin_uc) + if(OPENMW_USE_SYSTEM_OSG) + list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) + else() + list(APPEND _osg_plugins_static_files $) + target_link_libraries(components_osg_plugins INTERFACE $) + add_dependencies(components_osg_plugins ${${_plugin_uc}_LIBRARY}) + endif() + endforeach() + # We use --whole-archive because OSG plugins use registration. + get_whole_archive_options(_opts ${_osg_plugins_static_files}) + target_link_options(components_osg_plugins INTERFACE ${_opts}) + target_link_libraries(components components_osg_plugins) + + if(OPENMW_USE_SYSTEM_OSG) + # OSG plugin pkgconfig files are missing these dependencies. + # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 + find_package(Freetype REQUIRED) + find_package(JPEG REQUIRED) + find_package(PNG REQUIRED) + target_link_libraries(components Freetype::Freetype JPEG::JPEG PNG::PNG) + endif() +endif(OSG_STATIC) + +if(USE_QT) + set_property(TARGET components_qt PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index e7e5886589..19b87aa820 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -27,7 +27,7 @@ namespace DetourNavigator std::optional CachedRecastMeshManager::removeObject(const ObjectId id) { - const auto object = mImpl.removeObject(id); + auto object = mImpl.removeObject(id); if (object) mCached.lock()->reset(); return object; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 38314f08a5..63d7e13f6b 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -248,7 +248,7 @@ namespace DetourNavigator const auto tile = tiles.find(tilePosition); if (tile == tiles.end()) return std::optional(); - const auto tileResult = tile->second->removeObject(id); + auto tileResult = tile->second->removeObject(id); if (tile->second->isEmpty()) { tiles.erase(tile); diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 487b7bd70b..da43cc38ec 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -191,24 +192,10 @@ namespace Gui void FontLoader::loadBitmapFonts(bool exportToFile) { - const std::map& index = mVFS->getIndex(); - - std::string pattern = "Fonts/"; - mVFS->normalizeFilename(pattern); - - std::map::const_iterator found = index.lower_bound(pattern); - while (found != index.end()) + for (const auto& name : mVFS->getRecursiveDirectoryIterator("Fonts/")) { - const std::string& name = found->first; - if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".fnt") == 0) - loadBitmapFont(name, exportToFile); - } - else - break; - ++found; + if (Misc::getFileExtension(name) == "fnt") + loadBitmapFont(name, exportToFile); } } diff --git a/components/loadinglistener/reporter.cpp b/components/loadinglistener/reporter.cpp new file mode 100644 index 0000000000..0ad04fded1 --- /dev/null +++ b/components/loadinglistener/reporter.cpp @@ -0,0 +1,41 @@ +#include "reporter.hpp" +#include "loadinglistener.hpp" + +#include +#include +#include + +namespace Loading +{ + void Reporter::addTotal(std::size_t value) + { + const std::lock_guard lock(mMutex); + mTotal += value; + mUpdated.notify_all(); + } + + void Reporter::addProgress(std::size_t value) + { + const std::lock_guard lock(mMutex); + mProgress += value; + mUpdated.notify_all(); + } + + void Reporter::complete() + { + const std::lock_guard lock(mMutex); + mDone = true; + mUpdated.notify_all(); + } + + void Reporter::wait(Listener& listener) const + { + std::unique_lock lock(mMutex); + while (!mDone) + { + listener.setProgressRange(mTotal); + listener.setProgress(mProgress); + mUpdated.wait(lock); + } + } +} diff --git a/components/loadinglistener/reporter.hpp b/components/loadinglistener/reporter.hpp new file mode 100644 index 0000000000..b59c519082 --- /dev/null +++ b/components/loadinglistener/reporter.hpp @@ -0,0 +1,32 @@ +#ifndef COMPONENTS_LOADINGLISTENER_REPORTER_H +#define COMPONENTS_LOADINGLISTENER_REPORTER_H + +#include +#include +#include + +namespace Loading +{ + class Listener; + + class Reporter + { + public: + void addTotal(std::size_t value); + + void addProgress(std::size_t value); + + void complete(); + + void wait(Listener& listener) const; + + private: + std::size_t mProgress = 0; + std::size_t mTotal = 0; + bool mDone = false; + mutable std::mutex mMutex; + mutable std::condition_variable mUpdated; + }; +} + +#endif diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 1b52db11ad..fd42aa7fb2 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -167,4 +167,14 @@ namespace LuaUtil #endif } + std::string toString(const sol::object& obj) + { + if (obj == sol::nil) + return "nil"; + else if (obj.get_type() == sol::type::string) + return "\"" + obj.as() + "\""; + else + return call(sol::state_view(obj.lua_state())["tostring"], obj); + } + } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 6bac1612e7..acaaadea76 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -103,6 +103,9 @@ namespace LuaUtil return getFieldOrNil(table.as()[first], str...); } + // String representation of a Lua object. Should be used for debugging/logging purposes only. + std::string toString(const sol::object&); + } #endif // COMPONENTS_LUA_LUASTATE_H diff --git a/components/misc/pathhelpers.hpp b/components/misc/pathhelpers.hpp new file mode 100644 index 0000000000..88913a1f7b --- /dev/null +++ b/components/misc/pathhelpers.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_COMPONENTS_MISC_PATHHELPERS_H +#define OPENMW_COMPONENTS_MISC_PATHHELPERS_H + +#include + +namespace Misc +{ + inline std::string_view getFileExtension(std::string_view file) + { + if (auto extPos = file.find_last_of('.'); extPos != std::string::npos) + { + file.remove_prefix(extPos + 1); + return file; + } + return {}; + } +} + +#endif diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 2a865606fd..0863522356 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "utf8stream.hpp" @@ -109,18 +111,34 @@ public: return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); } - static bool ciEqual(const std::string &x, const std::string &y) { - if (x.size() != y.size()) { + template + static bool ciEqual(const X& x, const Y& y) + { + if (std::size(x) != std::size(y)) return false; - } - std::string::const_iterator xit = x.begin(); - std::string::const_iterator yit = y.begin(); - for (; xit != x.end(); ++xit, ++yit) { - if (toLower(*xit) != toLower(*yit)) { - return false; - } - } - return true; + return std::equal(std::begin(x), std::end(x), std::begin(y), + [] (char l, char r) { return toLower(l) == toLower(r); }); + } + + template + static auto ciEqual(const char(& x)[n], const char(& y)[n]) + { + static_assert(n > 0); + return ciEqual(std::string_view(x, n - 1), std::string_view(y, n - 1)); + } + + template + static auto ciEqual(const char(& x)[n], const T& y) + { + static_assert(n > 0); + return ciEqual(std::string_view(x, n - 1), y); + } + + template + static auto ciEqual(const T& x, const char(& y)[n]) + { + static_assert(n > 0); + return ciEqual(x, std::string_view(y, n - 1)); } static int ciCompareLen(const std::string &x, const std::string &y, size_t len) @@ -157,9 +175,9 @@ public: } /// Returns lower case copy of input string - static std::string lowerCase(const std::string &in) + static std::string lowerCase(std::string_view in) { - std::string out = in; + std::string out(in); lowerCaseInPlace(out); return out; } diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index ce896610a9..798a6778e6 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -58,7 +58,7 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s for(int i = 0;i < numShapes;++i) { btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); - btTransform trans = comp->getChildTransform(i); + const btTransform& trans = comp->getChildTransform(i); newShape->addChildShape(trans, child); } diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index b1b0f74176..0d0f81962b 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -121,8 +122,7 @@ BulletShapeManager::~BulletShapeManager() osg::ref_ptr BulletShapeManager::getShape(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); @@ -130,12 +130,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & shape = osg::ref_ptr(static_cast(obj.get())); else { - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); - - if (ext == "nif") + if (Misc::getFileExtension(normalized) == "nif") { NifBullet::BulletNifLoader loader; shape = loader.load(*mNifFileManager->get(normalized)); @@ -180,8 +175,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr BulletShapeManager::cacheInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr instance = createInstance(normalized); if (instance) @@ -191,8 +185,7 @@ osg::ref_ptr BulletShapeManager::cacheInstance(const std::s osg::ref_ptr BulletShapeManager::getInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 37e76359ff..a544b7b621 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "objectcache.hpp" @@ -83,8 +84,7 @@ namespace Resource osg::ref_ptr ImageManager::getImage(const std::string &filename) { - std::string normalized = filename; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(filename); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) @@ -103,10 +103,7 @@ namespace Resource return mWarningImage; } - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); + const std::string ext(Misc::getFileExtension(normalized)); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); if (!reader) { diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 77c31d9ad7..01a8639aa2 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "animation.hpp" @@ -30,7 +31,7 @@ namespace Resource std::vector emulatedAnimations; - for (auto animation : mAnimationManager->getAnimationList()) + for (const auto& animation : mAnimationManager->getAnimationList()) { if (animation) { @@ -133,8 +134,7 @@ namespace Resource osg::ref_ptr KeyframeManager::get(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) @@ -142,8 +142,7 @@ namespace Resource else { osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); - std::string ext = Resource::getFileExtension(normalized); - if (ext == "kf") + if (Misc::getFileExtension(normalized) == "kf") { NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); } diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index ccb065e3bf..b2b71f4635 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -59,6 +59,7 @@ namespace Resource /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay (double expiryDelay) override { mExpiryDelay = expiryDelay; } + float getExpiryDelay() const { return mExpiryDelay; } const VFS::Manager* getVFS() const { return mVFS; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 33a9ab99a1..76e73da384 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -358,10 +359,7 @@ namespace Resource bool SceneManager::checkLoaded(const std::string &name, double timeStamp) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); - - return mCache->checkInObjectCache(normalized, timeStamp); + return mCache->checkInObjectCache(mVFS->normalizeFilename(name), timeStamp); } /// @brief Callback to read image files from the VFS. @@ -391,12 +389,12 @@ namespace Resource osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { - std::string ext = Resource::getFileExtension(normalizedFilename); + auto ext = Misc::getFileExtension(normalizedFilename); if (ext == "nif") return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else { - osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { std::stringstream errormsg; @@ -533,8 +531,7 @@ namespace Resource osg::ref_ptr SceneManager::getTemplate(const std::string &name, bool compile) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) @@ -603,8 +600,7 @@ namespace Resource osg::ref_ptr SceneManager::cacheInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr node = createInstance(normalized); @@ -642,8 +638,7 @@ namespace Resource osg::ref_ptr SceneManager::getInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) @@ -822,12 +817,4 @@ namespace Resource shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } - - std::string getFileExtension(const std::string& file) - { - size_t extPos = file.find_last_of('.'); - if (extPos != std::string::npos && extPos+1 < file.size()) - return file.substr(extPos+1); - return std::string(); - } } diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 597c7adf48..6690148c74 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -15,6 +15,7 @@ #include #include "visitor.hpp" +#include "clone.hpp" namespace SceneUtil { @@ -49,10 +50,10 @@ namespace SceneUtil return; osg::Node* node = &drawable; - while (node->getNumParents()) + for (auto it = getNodePath().rbegin()+1; it != getNodePath().rend(); ++it) { - osg::Group* parent = node->getParent(0); - if (!parent || !filterMatches(parent->getName())) + osg::Node* parent = *it; + if (!filterMatches(parent->getName())) break; node = parent; } @@ -63,12 +64,7 @@ namespace SceneUtil { for (const osg::ref_ptr& node : mToCopy) { - if (node->getNumParents() > 1) - Log(Debug::Error) << "Error CopyRigVisitor: node has " << node->getNumParents() << " parents"; - while (node->getNumParents()) - node->getParent(0)->removeChild(node); - - mParent->addChild(node); + mParent->addChild(static_cast(node->clone(SceneUtil::CopyOp()))); } mToCopy.clear(); } @@ -90,25 +86,28 @@ namespace SceneUtil std::string mFilter2; }; - void mergeUserData(osg::UserDataContainer* source, osg::Object* target) + void mergeUserData(const osg::UserDataContainer* source, osg::Object* target) { + if (!source) + return; + if (!target->getUserDataContainer()) - target->setUserDataContainer(source); + target->setUserDataContainer(osg::clone(source, osg::CopyOp::SHALLOW_COPY)); else { for (unsigned int i=0; igetNumUserObjects(); ++i) - target->getUserDataContainer()->addUserObject(source->getUserObject(i)); + target->getUserDataContainer()->addUserObject(osg::clone(source->getUserObject(i), osg::CopyOp::SHALLOW_COPY)); } } - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode) + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode) { - if (dynamic_cast(toAttach.get())) + if (dynamic_cast(toAttach.get())) { osg::ref_ptr handle = new osg::Group; CopyRigVisitor copyVisitor(handle, filter); - toAttach->accept(copyVisitor); + const_cast(toAttach.get())->accept(copyVisitor); copyVisitor.doCopy(); if (handle->getNumChildren() == 1) @@ -122,14 +121,16 @@ namespace SceneUtil else { master->asGroup()->addChild(handle); - handle->setUserDataContainer(toAttach->getUserDataContainer()); + mergeUserData(toAttach->getUserDataContainer(), handle); return handle; } } else { + osg::ref_ptr clonedToAttach = static_cast(toAttach->clone(SceneUtil::CopyOp())); + FindByNameVisitor findBoneOffset("BoneOffset"); - toAttach->accept(findBoneOffset); + clonedToAttach->accept(findBoneOffset); osg::ref_ptr trans; @@ -172,13 +173,13 @@ namespace SceneUtil if (trans) { attachNode->addChild(trans); - trans->addChild(toAttach); + trans->addChild(clonedToAttach); return trans; } else { - attachNode->addChild(toAttach); - return toAttach; + attachNode->addChild(clonedToAttach); + return clonedToAttach; } } } diff --git a/components/sceneutil/attach.hpp b/components/sceneutil/attach.hpp index a8a2239a84..806fc53488 100644 --- a/components/sceneutil/attach.hpp +++ b/components/sceneutil/attach.hpp @@ -14,12 +14,12 @@ namespace osg namespace SceneUtil { - /// Attach parts of the \a toAttach scenegraph to the \a master scenegraph, using the specified filter and attachment node. + /// Clone and attach parts of the \a toAttach scenegraph to the \a master scenegraph, using the specified filter and attachment node. /// If the \a toAttach scene graph contains skinned objects, we will attach only those (filtered by the \a filter). /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. /// @note The master scene graph is expected to include a skeleton. /// @return A newly created node that is directly attached to the master scene graph - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode); + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode); } diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 07a6e691ad..eaf5668c4a 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -2,8 +2,6 @@ #include -#include -#include #include #include @@ -35,11 +33,6 @@ namespace SceneUtil mUpdaterToOldPs[cloned] = updater->getParticleSystem(0); return cloned; } - - if (dynamic_cast(node) || dynamic_cast(node)) - { - return osg::clone(node, *this); - } return osg::CopyOp::operator()(node); } diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 14dbc14178..af62b581c9 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -54,6 +54,7 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); + mShaderAlphaTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); for (size_t i = 0; i < sCastingPrograms.size(); ++i) diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index f1f15f786f..fde87c66ba 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -9,6 +9,9 @@ #include +#include +#include + namespace SceneUtil { @@ -24,7 +27,7 @@ namespace SceneUtil void FindByClassVisitor::apply(osg::Node &node) { - if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) + if (Misc::StringUtils::ciEqual(std::string_view(node.className()), mNameToFind)) mFoundNodes.push_back(&node); traverse(node); @@ -32,13 +35,13 @@ namespace SceneUtil void FindByNameVisitor::apply(osg::Group &group) { - if (!checkGroup(group)) + if (!mFoundNode && !checkGroup(group)) traverse(group); } void FindByNameVisitor::apply(osg::MatrixTransform &node) { - if (!checkGroup(node)) + if (!mFoundNode && !checkGroup(node)) traverse(node); } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 9b057cdfc6..33f79415f1 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -371,11 +371,10 @@ namespace Shader void ShaderManager::setGlobalDefines(DefineMap & globalDefines) { mGlobalDefines = globalDefines; - for (auto shaderMapElement: mShaders) + for (const auto& [key, shader]: mShaders) { - std::string templateId = shaderMapElement.first.first; - ShaderManager::DefineMap defines = shaderMapElement.first.second; - osg::ref_ptr shader = shaderMapElement.second; + std::string templateId = key.first; + ShaderManager::DefineMap defines = key.second; if (shader == nullptr) // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. continue; @@ -391,13 +390,13 @@ namespace Shader void ShaderManager::releaseGLObjects(osg::State *state) { std::lock_guard lock(mMutex); - for (auto shader : mShaders) + for (const auto& [_, shader] : mShaders) { - if (shader.second != nullptr) - shader.second->releaseGLObjects(state); + if (shader != nullptr) + shader->releaseGLObjects(state); } - for (auto program : mPrograms) - program.second->releaseGLObjects(state); + for (const auto& [_, program] : mPrograms) + program->releaseGLObjects(state); } } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 26511d4654..cbd2863e46 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -340,15 +340,6 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - - if (diffuseMap) - { - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - // We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out. - // Also it should probably belong to the shader manager or be applied by the shadows bin - writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); - } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); @@ -467,6 +458,9 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } + if (defineMap["diffuseMap"] == "0") + writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index f9eb7ae635..399df16d34 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -245,13 +245,13 @@ namespace Terrain { { std::lock_guard lock(mIndexBufferMutex); - for (auto indexbuffer : mIndexBufferMap) - indexbuffer.second->releaseGLObjects(state); + for (const auto& [_, indexbuffer] : mIndexBufferMap) + indexbuffer->releaseGLObjects(state); } { std::lock_guard lock(mUvBufferMutex); - for (auto uvbuffer : mUvBufferMap) - uvbuffer.second->releaseGLObjects(state); + for (const auto& [_, uvbuffer] : mUvBufferMap) + uvbuffer->releaseGLObjects(state); } } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index a744471de5..8809a75bb9 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -45,7 +45,7 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& cen ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index e26fc1b617..6a228a75af 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -258,17 +259,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManagers.push_back(mChunkManager.get()); } -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, unsigned int nodeMask, float lodFactor, float chunkSize) - : TerrainGrid(parent, storage, nodeMask) - , mViewDataMap(new ViewDataMap) - , mQuadTreeBuilt(false) - , mLodFactor(lodFactor) - , mVertexLodMod(0) - , mViewDistance(std::numeric_limits::max()) - , mMinSize(chunkSize) -{ -} - QuadTreeWorld::~QuadTreeWorld() { } @@ -324,7 +314,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const return lodFlags; } -void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers, bool compile) +void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers, bool compile, float reuseDistance) { if (!vd->hasChanged() && entry.mRenderingNode) return; @@ -352,6 +342,8 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f for (QuadTreeWorld::ChunkManager* m : chunkManagers) { + if (m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance*10) + continue; osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); } @@ -446,7 +438,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false); + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false, mViewDataMap->getReuseDistance()); entry.mRenderingNode->accept(nv); } @@ -496,7 +488,7 @@ View* QuadTreeWorld::createView() return mViewDataMap->createIndependentView(); } -void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, std::atomic &progress, int& progressTotal) +void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, Loading::Reporter& reporter) { ensureQuadTreeBuilt(); @@ -506,16 +498,25 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); - if (!progressTotal) - for (unsigned int i=0; igetNumEntries(); ++i) - progressTotal += vd->getEntry(i).mNode->getSize(); + std::size_t progressTotal = 0; + for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) + progressTotal += vd->getEntry(i).mNode->getSize(); + + reporter.addTotal(progressTotal); const float cellWorldSize = mStorage->getCellWorldSize(); for (unsigned int i=0; igetNumEntries() && !abort; ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true); - progress += entry.mNode->getSize(); + + + + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true, mViewDataMap->getReuseDistance()); + reporter.addProgress(entry.mNode->getSize()); + + + + } vd->markUnchanged(); } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 2ddea42049..3dd96a0b84 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,8 +22,6 @@ namespace Terrain public: QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); - QuadTreeWorld(osg::Group *parent, Storage *storage, unsigned int nodeMask, float lodFactor, float chunkSize); - ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); @@ -39,7 +37,7 @@ namespace Terrain void unloadCell(int x, int y) override; View* createView() override; - void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange) override; + void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) override; bool storeView(const View* view, double referenceTime) override; void rebuildViews() override; @@ -51,6 +49,11 @@ namespace Terrain virtual ~ChunkManager(){} virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) = 0; virtual unsigned int getNodeMask() { return 0; } + + void setViewDistance(float viewDistance) { mViewDistance = viewDistance; } + float getViewDistance() const { return mViewDistance; } + private: + float mViewDistance = 0.f; }; void addChunkManager(ChunkManager*); diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index 996bf909c8..e4d043ffc4 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -141,9 +141,9 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo vd = found->second; needsUpdate = false; - if (!vd->suitableToUse(activeGrid) || (vd->getViewPoint()-viewPoint).length2() >= mReuseDistance*mReuseDistance || vd->getWorldUpdateRevision() < mWorldUpdateRevision) + if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - float shortestDist = viewer ? mReuseDistance*mReuseDistance : std::numeric_limits::max(); + float shortestDist = std::numeric_limits::max(); const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { @@ -162,11 +162,6 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo vd->copyFrom(*mostSuitableView); return vd; } - else if (!mostSuitableView) - { - vd->setViewPoint(viewPoint); - needsUpdate = true; - } } if (!vd->suitableToUse(activeGrid)) { diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 0289352585..378d07663c 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -95,6 +95,8 @@ namespace Terrain void rebuildViews(); bool storeView(const ViewData* view, double referenceTime); + float getReuseDistance() const { return mReuseDistance; } + private: std::list mViewVector; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 5797d894ef..b62a1cb568 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -32,6 +32,11 @@ namespace SceneUtil class WorkQueue; } +namespace Loading +{ + class Reporter; +} + namespace Terrain { class Storage; @@ -148,7 +153,7 @@ namespace Terrain /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. - virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange) {} + virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) {} /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// @note Not thread safe. diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 045fe3cf5c..1cb8745497 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -86,14 +86,11 @@ namespace VFS return mIndex.find(normalized) != mIndex.end(); } - const std::map& Manager::getIndex() const + std::string Manager::normalizeFilename(const std::string& name) const { - return mIndex; - } - - void Manager::normalizeFilename(std::string &name) const - { - normalize_path(name, mStrict); + std::string result = name; + normalize_path(result, mStrict); + return result; } std::string Manager::getArchive(const std::string& name) const @@ -107,4 +104,9 @@ namespace VFS } return {}; } + + RecursiveDirectoryIterator Manager::getRecursiveDirectoryIterator(const std::string& path) const + { + return RecursiveDirectoryIterator(mIndex, normalizeFilename(path)); + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 5a09a995eb..b98d8021d4 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -12,6 +12,51 @@ namespace VFS class Archive; class File; + class RecursiveDirectoryIterator; + RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + + class RecursiveDirectoryIterator + { + public: + RecursiveDirectoryIterator(const std::map& index, const std::string& path) + : mPath(path) + , mIndex(&index) + , mIt(index.lower_bound(path)) + {} + + RecursiveDirectoryIterator(const RecursiveDirectoryIterator&) = default; + + const std::string& operator*() const { return mIt->first; } + const std::string* operator->() const { return &mIt->first; } + + bool operator!=(const RecursiveDirectoryIterator& other) { return mPath != other.mPath || mIt != other.mIt; } + + RecursiveDirectoryIterator& operator++() + { + if (++mIt == mIndex->end() || !starts_with(mIt->first, mPath)) + *this = end(*this); + return *this; + } + + friend RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + + private: + static bool starts_with(const std::string& text, const std::string& start) { return text.rfind(start, 0) == 0; } + + std::string mPath; + const std::map* mIndex; + std::map::const_iterator mIt; + }; + + inline RecursiveDirectoryIterator begin(RecursiveDirectoryIterator iter) { return iter; } + + inline RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter) + { + RecursiveDirectoryIterator result(iter); + result.mIt = result.mIndex->end(); + return result; + } + /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is @@ -40,13 +85,9 @@ namespace VFS /// @note May be called from any thread once the index has been built. bool exists(const std::string& name) const; - /// Get a complete list of files from all archives - /// @note May be called from any thread once the index has been built. - const std::map& getIndex() const; - /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. /// @note May be called from any thread once the index has been built. - void normalizeFilename(std::string& name) const; + [[nodiscard]] std::string normalizeFilename(const std::string& name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. @@ -59,6 +100,13 @@ namespace VFS Files::IStreamPtr getNormalized(const std::string& normalizedName) const; std::string getArchive(const std::string& name) const; + + /// Recursivly iterate over the elements of the given path + /// In practice it return all files of the VFS starting with the given path + /// @note the path is normalized + /// @note May be called from any thread once the index has been built. + RecursiveDirectoryIterator getRecursiveDirectoryIterator(const std::string& path) const; + private: bool mStrict; diff --git a/docs/source/manuals/installation/install-openmw.rst b/docs/source/manuals/installation/install-openmw.rst index fab00ab9b7..362637746d 100644 --- a/docs/source/manuals/installation/install-openmw.rst +++ b/docs/source/manuals/installation/install-openmw.rst @@ -18,7 +18,7 @@ and run the install package once downloaded. It's now installed! The (bleeding edge) Source Way ============================== -Visit the `Development Environment Setup `_ +Visit the `Development Environment Setup `_ section of the Wiki for detailed instructions on how to build the engine. The Ubuntu Way diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index 97cfe37a5c..bc955b703d 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -29,7 +29,7 @@ Savegames +--------------+-----------------------------------------------------------------------------------------------------+ | OS | Location | +==============+=====================================================================================================+ -| Linux | ``$HOME/.config/openmw/saves`` | +| Linux | ``$HOME/.local/share/openmw/saves`` | +--------------+-----------------------------------------------------------------------------------------------------+ | Mac | ``$HOME/Library/Application\ Support/openmw/saves`` | +--------------+---------------+-------------------------------------------------------------------------------------+ diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index 7c5a965e01..3e943e4284 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -40,20 +40,6 @@ May affect performance a lot. This setting can only be configured by editing the settings configuration file. -min chunk size --------------- - -:Type: floating point -:Range: 0.125, 0.25, 0.5, 1.0 -:Default: 0.5 - -Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value -chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage -(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient. -Smaller values do an opposite. - -This setting can only be configured by editing the settings configuration file. - stomp mode ---------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4737fcf5ee..7f57ba529d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1087,9 +1087,6 @@ density = 1.0 # A maximum distance in game units on which groundcover is rendered. rendering distance = 6144.0 -# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) -min chunk size = 0.5 - # Whether grass should respond to the player treading on it. # 0 - Grass cannot be trampled. # 1 - The player's XY position is taken into account. diff --git a/scripts/find_missing_merge_requests.py b/scripts/find_missing_merge_requests.py new file mode 100755 index 0000000000..09d3e9a581 --- /dev/null +++ b/scripts/find_missing_merge_requests.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +import click +import multiprocessing +import pathlib +import requests +import urllib.parse + + +@click.command() +@click.option('--token_path', type=str, default=pathlib.Path.home() / '.gitlab_token', + help='Path to text file with Gitlab token.') +@click.option('--project_id', type=int, default=7107382, + help='Gitlab project id.') +@click.option('--host', type=str, default='gitlab.com', + help='Gitlab host.') +@click.option('--workers', type=int, default=10, + help='Number of parallel workers.') +@click.option('--target_branch', type=str, default='master', + help='Merge request target branch.') +@click.option('--begin_page', type=int, default=1, + help='Begin with given /merge_requests page.') +@click.option('--end_page', type=int, default=4, + help='End before given /merge_requests page.') +@click.option('--per_page', type=int, default=100, + help='Number of merge requests per page.') +def main(token_path, project_id, host, workers, target_branch, begin_page, end_page, per_page): + token = read_token(token_path) + base_url = f'https://{host}/api/v4/projects/{project_id}/' + checked = 0 + filtered = 0 + missing = 0 + for page in range(begin_page, end_page): + merge_requests = requests.get( + url=urllib.parse.urljoin(base_url, 'merge_requests'), + headers={'PRIVATE-TOKEN': token}, + params=dict(state='merged', per_page=per_page, page=page), + ).json() + if not merge_requests: + break + checked += len(merge_requests) + merge_requests = [v for v in merge_requests if v['target_branch'] == target_branch] + if not merge_requests: + continue + filtered += len(merge_requests) + with multiprocessing.Pool(workers) as pool: + missing_merge_requests = pool.map(FilterMissingMergeRequest(token, base_url), merge_requests) + for mr in missing_merge_requests: + if mr is not None: + missing += 1 + print(f"MR {mr['reference']} ({mr['id']}) is missing from branch {mr['target_branch']}," + f" previously was merged as {mr['merge_commit_sha']}") + print(f'Checked {checked} MRs ({filtered} with {target_branch} target branch), {missing} are missing') + + +class FilterMissingMergeRequest: + def __init__(self, token, base_url): + self.token = token + self.base_url = base_url + + def __call__(self, merge_request): + commit_refs = requests.get( + url=urllib.parse.urljoin(self.base_url, f"repository/commits/{merge_request['merge_commit_sha']}/refs"), + headers={'PRIVATE-TOKEN': self.token}, + ).json() + if 'message' in commit_refs and commit_refs['message'] == '404 Commit Not Found': + return merge_request + if not present_in_branch(commit_refs, branch=merge_request['target_branch']): + return merge_request + + +def present_in_branch(commit_refs, branch): + return bool(next((v for v in commit_refs if v['type'] == 'branch' and v['name'] == branch), None)) + + +def read_token(path): + with open(path) as stream: + return stream.readline().strip() + + +if __name__ == '__main__': + main() diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 140a911fea..f0e6ce4010 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -13,11 +13,14 @@ import statistics import sys import termtables + @click.command() @click.option('--print_keys', is_flag=True, help='Print a list of all present keys in the input file.') @click.option('--timeseries', type=str, multiple=True, help='Show a graph for given metric over time.') +@click.option('--commulative_timeseries', type=str, multiple=True, + help='Show a graph for commulative sum of a given metric over time.') @click.option('--hist', type=str, multiple=True, help='Show a histogram for all values of given metric.') @click.option('--hist_ratio', nargs=2, type=str, multiple=True, @@ -34,33 +37,38 @@ import termtables help='Print table with stats for a given metric containing min, max, mean, median etc.') @click.option('--timeseries_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') +@click.option('--commulative_timeseries_sum', is_flag=True, + help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') @click.option('--stats_sum', is_flag=True, help='Add a row to stats table for a sum per frame of all given stats metrics.') @click.option('--begin_frame', type=int, default=0, help='Start processing from this frame.') @click.option('--end_frame', type=int, default=sys.maxsize, help='End processing at this frame.') -@click.argument('path', default='', type=click.Path()) +@click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, - timeseries_sum, stats_sum, begin_frame, end_frame, path): - data = list(read_data(path)) - keys = collect_unique_keys(data) - frames = collect_per_frame(data=data, keys=keys, begin_frame=begin_frame, end_frame=end_frame) + timeseries_sum, stats_sum, begin_frame, end_frame, path, + commulative_timeseries, commulative_timeseries_sum): + sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} + keys = collect_unique_keys(sources) + frames = collect_per_frame(sources=sources, keys=keys, begin_frame=begin_frame, end_frame=end_frame) if print_keys: for v in keys: print(v) if timeseries: - draw_timeseries(frames=frames, keys=timeseries, timeseries_sum=timeseries_sum) + draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum) + if commulative_timeseries: + draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum) if hist: - draw_hists(frames=frames, keys=hist) + draw_hists(sources=frames, keys=hist) if hist_ratio: - draw_hist_ratio(frames=frames, pairs=hist_ratio) + draw_hist_ratio(sources=frames, pairs=hist_ratio) if stdev_hist: - draw_stdev_hists(frames=frames, stdev_hists=stdev_hist) + draw_stdev_hists(sources=frames, stdev_hists=stdev_hist) if plot: - draw_plots(frames=frames, plots=plot) + draw_plots(sources=frames, plots=plot) if stats: - print_stats(frames=frames, keys=stats, stats_sum=stats_sum) + print_stats(sources=frames, keys=stats, stats_sum=stats_sum) matplotlib.pyplot.show() @@ -84,114 +92,140 @@ def read_data(path): frame[key] = to_number(value) -def collect_per_frame(data, keys, begin_frame, end_frame): - result = collections.defaultdict(list) - for frame in data: - for key in keys: - if key in frame: - result[key].append(frame[key]) - else: - result[key].append(None) - for key, values in result.items(): - result[key] = numpy.array(values[begin_frame:end_frame]) +def collect_per_frame(sources, keys, begin_frame, end_frame): + result = collections.defaultdict(lambda: collections.defaultdict(list)) + for name, frames in sources.items(): + for frame in frames: + for key in keys: + if key in frame: + result[name][key].append(frame[key]) + else: + result[name][key].append(None) + for name, sources in result.items(): + for key, values in sources.items(): + result[name][key] = numpy.array(values[begin_frame:end_frame]) return result -def collect_unique_keys(frames): +def collect_unique_keys(sources): result = set() - for frame in frames: - for key in frame.keys(): - result.add(key) + for frames in sources.values(): + for frame in frames: + for key in frame.keys(): + result.add(key) return sorted(result) -def draw_timeseries(frames, keys, timeseries_sum): +def draw_timeseries(sources, keys, add_sum): fig, ax = matplotlib.pyplot.subplots() - x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) - for key in keys: - ax.plot(x, frames[key], label=key) - if timeseries_sum: - ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label='sum') + for name, frames in sources.items(): + x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) + for key in keys: + print(key, name) + ax.plot(x, frames[key], label=f'{key}:{name}') + if add_sum: + ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}') ax.grid(True) ax.legend() fig.canvas.set_window_title('timeseries') -def draw_hists(frames, keys): +def draw_commulative_timeseries(sources, keys, add_sum): + fig, ax = matplotlib.pyplot.subplots() + for name, frames in sources.items(): + x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) + for key in keys: + ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}') + if add_sum: + ax.plot(x, numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}') + ax.grid(True) + ax.legend() + fig.canvas.set_window_title('commulative_timeseries') + + +def draw_hists(sources, keys): fig, ax = matplotlib.pyplot.subplots() bins = numpy.linspace( - start=min(min(v) for k, v in frames.items() if k in keys), - stop=max(max(v) for k, v in frames.items() if k in keys), + start=min(min(min(v) for k, v in f.items() if k in keys) for f in sources.values()), + stop=max(max(max(v) for k, v in f.items() if k in keys) for f in sources.values()), num=20, ) - for key in keys: - ax.hist(frames[key], bins=bins, label=key, alpha=1 / len(keys)) + for name, frames in sources.items(): + for key in keys: + ax.hist(frames[key], bins=bins, label=f'{key}:{name}', alpha=1 / (len(keys) * len(sources))) ax.set_xticks(bins) ax.grid(True) ax.legend() fig.canvas.set_window_title('hists') -def draw_hist_ratio(frames, pairs): +def draw_hist_ratio(sources, pairs): fig, ax = matplotlib.pyplot.subplots() bins = numpy.linspace( - start=min(min(a / b for a, b in zip(frames[a], frames[b])) for a, b in pairs), - stop=max(max(a / b for a, b in zip(frames[a], frames[b])) for a, b in pairs), + start=min(min(min(a / b for a, b in zip(f[a], f[b])) for a, b in pairs) for f in sources.values()), + stop=max(max(max(a / b for a, b in zip(f[a], f[b])) for a, b in pairs) for f in sources.values()), num=20, ) - for a, b in pairs: - ax.hist(frames[a] / frames[b], bins=bins, label=f'{a} / {b}', alpha=1 / len(pairs)) + for name, frames in sources.items(): + for a, b in pairs: + ax.hist(frames[a] / frames[b], bins=bins, label=f'{a} / {b}:{name}', alpha=1 / (len(pairs) * len(sources))) ax.set_xticks(bins) ax.grid(True) ax.legend() - fig.canvas.set_window_title('hists') + fig.canvas.set_window_title('hists_ratio') -def draw_stdev_hists(frames, stdev_hists): +def draw_stdev_hists(sources, stdev_hists): for key, scale in stdev_hists: scale = float(scale) fig, ax = matplotlib.pyplot.subplots() - median = statistics.median(frames[key]) - stdev = statistics.stdev(frames[key]) + first_frames = next(v for v in sources.values()) + median = statistics.median(first_frames[key]) + stdev = statistics.stdev(first_frames[key]) start = median - stdev / 2 * scale stop = median + stdev / 2 * scale bins = numpy.linspace(start=start, stop=stop, num=9) - values = [v for v in frames[key] if start <= v <= stop] - ax.hist(values, bins=bins, label=key, alpha=1 / len(stdev_hists)) + for name, frames in sources.items(): + values = [v for v in frames[key] if start <= v <= stop] + ax.hist(values, bins=bins, label=f'{key}:{name}', alpha=1 / (len(stdev_hists) * len(sources))) ax.set_xticks(bins) ax.grid(True) ax.legend() fig.canvas.set_window_title('stdev_hists') -def draw_plots(frames, plots): +def draw_plots(sources, plots): fig, ax = matplotlib.pyplot.subplots() - for x_key, y_key, agg in plots: - if agg is None: - ax.plot(frames[x_key], frames[y_key], label=f'x={x_key}, y={y_key}') - elif agg: - agg_f = dict( - mean=statistics.mean, - median=statistics.median, - )[agg] - grouped = collections.defaultdict(list) - for x, y in zip(frames[x_key], frames[y_key]): - grouped[x].append(y) - aggregated = sorted((k, agg_f(v)) for k, v in grouped.items()) - ax.plot( - numpy.array([v[0] for v in aggregated]), - numpy.array([v[1] for v in aggregated]), - label=f'x={x_key}, y={y_key}, agg={agg}', - ) + for name, frames in sources.items(): + for x_key, y_key, agg in plots: + if agg is None: + ax.plot(frames[x_key], frames[y_key], label=f'x={x_key}, y={y_key}:{name}') + elif agg: + agg_f = dict( + mean=statistics.mean, + median=statistics.median, + )[agg] + grouped = collections.defaultdict(list) + for x, y in zip(frames[x_key], frames[y_key]): + grouped[x].append(y) + aggregated = sorted((k, agg_f(v)) for k, v in grouped.items()) + ax.plot( + numpy.array([v[0] for v in aggregated]), + numpy.array([v[1] for v in aggregated]), + label=f'x={x_key}, y={y_key}, agg={agg}:{name}', + ) ax.grid(True) ax.legend() fig.canvas.set_window_title('plots') -def print_stats(frames, keys, stats_sum): - stats = [make_stats(key=key, values=filter_not_none(frames[key])) for key in keys] - if stats_sum: - stats.append(make_stats(key='sum', values=sum_multiple(frames, keys))) +def print_stats(sources, keys, stats_sum): + stats = list() + for name, frames in sources.items(): + for key in keys: + stats.append(make_stats(source=name, key=key, values=filter_not_none(frames[key]))) + if stats_sum: + stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys))) metrics = list(stats[0].keys()) max_key_size = max(len(tuple(v.values())[0]) for v in stats) termtables.print( @@ -215,8 +249,9 @@ def sum_multiple(frames, keys): return numpy.array([result[k] for k in sorted(result.keys())]) -def make_stats(key, values): +def make_stats(source, key, values): return collections.OrderedDict( + source=source, key=key, number=len(values), min=min(values),