FileStateCache.h 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #pragma once
  9. #include <AzCore/EBus/EBus.h>
  10. #include <native/AssetManager/assetScanFolderInfo.h>
  11. #include <QString>
  12. #include <QSet>
  13. #include <QFileInfo>
  14. #include <AzCore/Interface/Interface.h>
  15. #include <AzCore/EBus/Event.h>
  16. #include <AzCore/IO/FileIO.h>
  17. namespace AssetProcessor
  18. {
  19. struct AssetFileInfo;
  20. struct FileStateInfo
  21. {
  22. FileStateInfo() = default;
  23. FileStateInfo(QString filePath, QDateTime modTime, AZ::u64 fileSize, bool isDirectory)
  24. : m_absolutePath(filePath), m_modTime(modTime), m_fileSize(fileSize), m_isDirectory(isDirectory) {}
  25. explicit FileStateInfo(const AssetFileInfo& assetFileInfo)
  26. : m_absolutePath(assetFileInfo.m_filePath), m_fileSize(assetFileInfo.m_fileSize), m_isDirectory(assetFileInfo.m_isDirectory), m_modTime(assetFileInfo.m_modTime)
  27. {
  28. }
  29. bool operator==(const FileStateInfo& rhs) const;
  30. QString m_absolutePath{};
  31. QDateTime m_modTime{};
  32. AZ::u64 m_fileSize{};
  33. bool m_isDirectory{};
  34. };
  35. //! IFileStateRequests is the pure interface for all File State Requests, which can optionally
  36. //! use a transparent cache. You can call this from anywhere using
  37. //! AZ::Interface<IFileStateRequests>::Get()-> function calls
  38. //! Note that in order to satisfy the API here,
  39. //! Exists, GetFileInfo, GetHash, and other file related functions are expected to function as if
  40. //! case insensitive - that is, on case-sensitive file systems, the implementation should
  41. //! work even if the input file name is not the actual file name on the system and the GetFileInfo
  42. //! function should for example return the actual file name and case of the file on the system.
  43. struct IFileStateRequests
  44. {
  45. AZ_RTTI(IFileStateRequests, "{2D883B3A-DCA3-4CE0-976C-4511C3277371}");
  46. IFileStateRequests() = default;
  47. virtual ~IFileStateRequests() = default;
  48. using FileHash = AZ::u64;
  49. static constexpr FileHash InvalidFileHash = 0;
  50. /// Fetches info on the file/directory if it exists. Returns true if it exists, false otherwise
  51. virtual bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const = 0;
  52. /// Convenience function to check if a file or directory exists.
  53. virtual bool Exists(const QString& absolutePath) const = 0;
  54. virtual bool GetHash(const QString& absolutePath, FileHash* foundHash) = 0;
  55. //! Called when the caller knows a hash and file info already.
  56. //! This can for example warm up the cache so that it can return hashes without actually hashing.
  57. //! (optional for implementations)
  58. virtual void WarmUpCache(const AssetFileInfo& existingInfo, const FileHash hash = InvalidFileHash) = 0;
  59. virtual void RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler) = 0;
  60. AZ_DISABLE_COPY_MOVE(IFileStateRequests);
  61. };
  62. class FileStateBase
  63. : public IFileStateRequests
  64. {
  65. public:
  66. FileStateBase()
  67. {
  68. AZ::Interface<IFileStateRequests>::Register(this);
  69. }
  70. virtual ~FileStateBase()
  71. {
  72. AZ::Interface<IFileStateRequests>::Unregister(this);
  73. }
  74. /// Bulk adds file state to the cache
  75. virtual void AddInfoSet(QSet<AssetFileInfo> /*infoSet*/) {}
  76. /// Adds a single file to the cache. This will query the OS for the current state
  77. virtual void AddFile(const QString& /*absolutePath*/) {}
  78. /// Updates a single file in the cache. This will query the OS for the current state
  79. virtual void UpdateFile(const QString& /*absolutePath*/) {}
  80. /// Removes a file from the cache
  81. virtual void RemoveFile(const QString& /*absolutePath*/) {}
  82. virtual void WarmUpCache(const AssetFileInfo& /*existingInfo*/, const FileHash /*hash*/) {}
  83. };
  84. //! Caches file state information retrieved by the file scanner and file watcher.
  85. //! Profiling has shown it is faster (at least on windows) compared to asking the OS for file information every time.
  86. //! Note that this cache absolutely depends on the file watcher and file scanner to keep it up to date.
  87. //! It also means it will cause errors to use this cache on anything outside a watched/scanned folder, so sources
  88. //! and intermediates only. (Checking this on every operation would be prohibitively expensive).
  89. class FileStateCache final :
  90. public FileStateBase
  91. {
  92. public:
  93. // FileStateRequestBus implementation
  94. bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const override;
  95. bool Exists(const QString& absolutePath) const override;
  96. bool GetHash(const QString& absolutePath, FileHash* foundHash) override;
  97. void RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler) override;
  98. void AddInfoSet(QSet<AssetFileInfo> infoSet) override;
  99. void AddFile(const QString& absolutePath) override;
  100. void UpdateFile(const QString& absolutePath) override;
  101. void RemoveFile(const QString& absolutePath) override;
  102. void WarmUpCache(const AssetFileInfo& existingInfo, const FileHash hash = IFileStateRequests::InvalidFileHash) override;
  103. private:
  104. /// Invalidates the hash for a file so it will be re-computed next time it's requested
  105. void InvalidateHash(const QString& absolutePath);
  106. /// Handles converting a file path into a uniform format for use as a map key
  107. QString PathToKey(const QString& absolutePath) const;
  108. /// Add/Update a single file
  109. void AddOrUpdateFileInternal(QFileInfo fileInfo);
  110. /// Recursively collects all the files contained in the directory specified by absolutePath
  111. void ScanFolder(const QString& absolutePath);
  112. mutable AZStd::recursive_mutex m_mapMutex;
  113. QHash<QString, FileStateInfo> m_fileInfoMap;
  114. QHash<QString, FileHash> m_fileHashMap;
  115. AZ::Event<FileStateInfo> m_deleteEvent;
  116. /// Cache of input path values to their final, normalized map key format.
  117. /// Profiling has shown path normalization to be a hotspot.
  118. mutable QHash<QString, QString> m_keyCache;
  119. using LockGuardType = AZStd::lock_guard<decltype(m_mapMutex)>;
  120. };
  121. //! Pass through version of the FileStateCache which does not cache anything. Every request is redirected to the OS.
  122. //! Note that in order to satisfy the API here, it must function as if case insensitive, so it can't just directly
  123. //! call through to the OS and must use case-correcting functions on case-sensitive file systems.
  124. class FileStatePassthrough final :
  125. public FileStateBase
  126. {
  127. public:
  128. // FileStateRequestBus implementation
  129. bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const override;
  130. bool Exists(const QString& absolutePath) const override;
  131. bool GetHash(const QString& absolutePath, FileHash* foundHash) override;
  132. void RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler) override;
  133. void SignalDeleteEvent(const QString& absolutePath) const;
  134. protected:
  135. AZ::Event<FileStateInfo> m_deleteEvent;
  136. };
  137. } // namespace AssetProcessor