SaveDataSystemComponent.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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. #include <SaveDataSystemComponent.h>
  9. #include <SaveData/SaveDataNotificationBus.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/Serialization/EditContext.h>
  12. #include <AzCore/Serialization/EditContextConstants.inl>
  13. #include <AzCore/IO/SystemFile.h>
  14. #include <AzCore/std/parallel/lock.h>
  15. #include <AzCore/std/parallel/thread.h>
  16. #include <AzCore/std/smart_ptr/make_shared.h>
  17. ////////////////////////////////////////////////////////////////////////////////////////////////////
  18. namespace SaveData
  19. {
  20. ////////////////////////////////////////////////////////////////////////////////////////////////
  21. const char* SaveDataFileExtension = ".savedata";
  22. const char* TempSaveDataFileExtension = ".tmpsavedata";
  23. ////////////////////////////////////////////////////////////////////////////////////////////////
  24. void SaveDataSystemComponent::Reflect(AZ::ReflectContext* context)
  25. {
  26. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  27. {
  28. serialize->Class<SaveDataSystemComponent, AZ::Component>()
  29. ->Version(0);
  30. if (AZ::EditContext* ec = serialize->GetEditContext())
  31. {
  32. ec->Class<SaveDataSystemComponent>("SaveData", "Provides functionality for saving and loading persistent user data.")
  33. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  34. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  35. ;
  36. }
  37. }
  38. }
  39. ////////////////////////////////////////////////////////////////////////////////////////////////
  40. void SaveDataSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  41. {
  42. provided.push_back(AZ_CRC_CE("SaveDataService"));
  43. }
  44. ////////////////////////////////////////////////////////////////////////////////////////////////
  45. void SaveDataSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  46. {
  47. incompatible.push_back(AZ_CRC_CE("SaveDataService"));
  48. }
  49. ////////////////////////////////////////////////////////////////////////////////////////////////
  50. void SaveDataSystemComponent::Activate()
  51. {
  52. m_pimpl.reset(Implementation::Create(*this));
  53. SaveDataRequestBus::Handler::BusConnect();
  54. }
  55. ////////////////////////////////////////////////////////////////////////////////////////////////
  56. void SaveDataSystemComponent::Deactivate()
  57. {
  58. SaveDataRequestBus::Handler::BusDisconnect();
  59. m_pimpl.reset();
  60. }
  61. ////////////////////////////////////////////////////////////////////////////////////////////////
  62. void SaveDataSystemComponent::SaveDataBuffer(const SaveDataBufferParams& saveDataBufferParams)
  63. {
  64. if (m_pimpl)
  65. {
  66. m_pimpl->SaveDataBuffer(saveDataBufferParams);
  67. }
  68. }
  69. ////////////////////////////////////////////////////////////////////////////////////////////////
  70. void SaveDataSystemComponent::LoadDataBuffer(const LoadDataBufferParams& loadDataBufferParams)
  71. {
  72. if (m_pimpl)
  73. {
  74. m_pimpl->LoadDataBuffer(loadDataBufferParams);
  75. }
  76. }
  77. ////////////////////////////////////////////////////////////////////////////////////////////////
  78. void SaveDataSystemComponent::SetSaveDataDirectoryPath(const char* saveDataDirectoryPath)
  79. {
  80. if (m_pimpl)
  81. {
  82. m_pimpl->SetSaveDataDirectoryPath(saveDataDirectoryPath);
  83. }
  84. }
  85. ////////////////////////////////////////////////////////////////////////////////////////////////
  86. SaveDataSystemComponent::Implementation::Implementation(SaveDataSystemComponent& saveDataSystemComponent)
  87. : m_saveDataSystemComponent(saveDataSystemComponent)
  88. {
  89. AZ::TickBus::Handler::BusConnect();
  90. }
  91. ////////////////////////////////////////////////////////////////////////////////////////////////
  92. SaveDataSystemComponent::Implementation::~Implementation()
  93. {
  94. AZ::TickBus::Handler::BusDisconnect();
  95. // Make sure we join all active threads, regardless of their completion state.
  96. JoinAllActiveThreads();
  97. }
  98. ////////////////////////////////////////////////////////////////////////////////////////////////
  99. void SaveDataSystemComponent::Implementation::OnSaveDataBufferComplete(const AZStd::string& dataBufferName,
  100. const AzFramework::LocalUserId localUserId,
  101. const SaveDataRequests::OnDataBufferSaved& callback,
  102. const SaveDataNotifications::Result& result)
  103. {
  104. // Always queue the OnDataBufferSaved notification back on the main thread.
  105. // Even if this is being called from the main thread already, this ensures
  106. // the callback / notifications are aways sent at the same time each frame.
  107. AZ::TickBus::QueueFunction([dataBufferName, localUserId, callback, result]()
  108. {
  109. SaveDataNotifications::DataBufferSavedParams dataBufferSavedParams;
  110. dataBufferSavedParams.dataBufferName = dataBufferName;
  111. dataBufferSavedParams.localUserId = localUserId;
  112. dataBufferSavedParams.result = result;
  113. if (callback)
  114. {
  115. callback(dataBufferSavedParams);
  116. }
  117. SaveDataNotificationBus::Broadcast(&SaveDataNotifications::OnDataBufferSaved,
  118. dataBufferSavedParams);
  119. });
  120. }
  121. ////////////////////////////////////////////////////////////////////////////////////////////////
  122. void SaveDataSystemComponent::Implementation::SaveDataBufferToFileSystem(const SaveDataBufferParams& saveDataBufferParams,
  123. const AZStd::string& absoluteFilePath,
  124. bool waitForCompletion,
  125. bool useTemporaryFile)
  126. {
  127. // Perform parameter error checking but handle gracefully
  128. AZ_Assert(saveDataBufferParams.dataBuffer, "Invalid param: dataBuffer");
  129. AZ_Assert(saveDataBufferParams.dataBufferSize, "Invalid param: dataBufferSize");
  130. AZ_Assert(!saveDataBufferParams.dataBufferName.empty(), "Invalid param: dataBufferName");
  131. if (!saveDataBufferParams.dataBuffer ||
  132. !saveDataBufferParams.dataBufferSize ||
  133. saveDataBufferParams.dataBufferName.empty())
  134. {
  135. OnSaveDataBufferComplete(saveDataBufferParams.dataBufferName,
  136. saveDataBufferParams.localUserId,
  137. saveDataBufferParams.callback,
  138. SaveDataNotifications::Result::ErrorInvalid);
  139. return;
  140. }
  141. // Start a new thread to perform the save, capturing the necessary parameters by value,
  142. // except for the data buffer itself which must be moved (because it is a unique_ptr).
  143. AZStd::thread_desc saveThreadDesc;
  144. saveThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
  145. saveThreadDesc.m_name = "SaveDataBufferToFileSystem";
  146. ThreadCompletionPair* threadCompletionPair = nullptr;
  147. {
  148. AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
  149. m_activeThreads.emplace_back();
  150. threadCompletionPair = &m_activeThreads.back();
  151. }
  152. // This is safe access outside the lock guard because we only remove elements from the list
  153. // after the thread completion flag has been set to true (see also JoinAllCompletedThreads).
  154. threadCompletionPair->m_thread = AZStd::make_unique<AZStd::thread>(saveThreadDesc,
  155. [&threadCompleteFlag = threadCompletionPair->m_threadComplete,
  156. dataBuffer = AZStd::move(saveDataBufferParams.dataBuffer),
  157. dataBufferSize = saveDataBufferParams.dataBufferSize,
  158. dataBufferName = saveDataBufferParams.dataBufferName,
  159. onSavedCallback = saveDataBufferParams.callback,
  160. localUserId = saveDataBufferParams.localUserId,
  161. absoluteFilePath,
  162. useTemporaryFile]()
  163. {
  164. SaveDataNotifications::Result result = SaveDataNotifications::Result::ErrorUnspecified;
  165. // If useTemporaryFile == true we save first to a '.tmp' file so we
  166. // do not overwrite existing save data until we are sure of success.
  167. const AZStd::string tempSaveDataFilePath = absoluteFilePath + TempSaveDataFileExtension;
  168. const AZStd::string finalSaveDataFilePath = absoluteFilePath + SaveDataFileExtension;
  169. // Open the temp save data file for writing, creating it (and
  170. // any intermediate directories) if it doesn't already exist.
  171. AZ::IO::SystemFile systemFile;
  172. const bool openFileResult = systemFile.Open(useTemporaryFile ? tempSaveDataFilePath.c_str() : finalSaveDataFilePath.c_str(),
  173. AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY |
  174. AZ::IO::SystemFile::SF_OPEN_CREATE |
  175. AZ::IO::SystemFile::SF_OPEN_CREATE_PATH);
  176. if (!openFileResult)
  177. {
  178. result = SaveDataNotifications::Result::ErrorIOFailure;
  179. }
  180. else
  181. {
  182. // Write the data buffer to the temp file and then close it.
  183. const AZ::IO::SystemFile::SizeType bytesWritten = systemFile.Write(dataBuffer.get(),
  184. dataBufferSize);
  185. systemFile.Close();
  186. // Verify that we wrote the correct number of bytes.
  187. if (bytesWritten != dataBufferSize)
  188. {
  189. result = SaveDataNotifications::Result::ErrorIOFailure;
  190. }
  191. else if (useTemporaryFile)
  192. {
  193. // Rename the temp save data file we successfully wrote to.
  194. const bool renameFileResult = AZ::IO::SystemFile::Rename(tempSaveDataFilePath.c_str(),
  195. finalSaveDataFilePath.c_str(),
  196. true);
  197. result = renameFileResult ? SaveDataNotifications::Result::Success :
  198. SaveDataNotifications::Result::ErrorIOFailure;
  199. // Delete the temp save data file.
  200. AZ::IO::SystemFile::Delete(tempSaveDataFilePath.c_str());
  201. }
  202. else
  203. {
  204. result = SaveDataNotifications::Result::Success;
  205. }
  206. }
  207. // Invoke the callback and broadcast the OnDataBufferSaved notification from the main thread.
  208. OnSaveDataBufferComplete(dataBufferName, localUserId, onSavedCallback, result);
  209. // Set the thread completion flag so it will be joined in JoinAllCompletedThreads.
  210. threadCompleteFlag = true;
  211. });
  212. if (waitForCompletion)
  213. {
  214. // The thread completion flag will be set in join, and the thread completion
  215. // pair removed from m_activeThreads when JoinAllCompletedThreads is called.
  216. threadCompletionPair->m_thread->join();
  217. threadCompletionPair->m_thread.reset();
  218. }
  219. }
  220. ////////////////////////////////////////////////////////////////////////////////////////////////
  221. void SaveDataSystemComponent::Implementation::OnLoadDataBufferComplete(SaveDataNotifications::DataBuffer dataBuffer,
  222. AZ::u64 dataBufferSize,
  223. const AZStd::string& dataBufferName,
  224. const AzFramework::LocalUserId localUserId,
  225. const SaveDataRequests::OnDataBufferLoaded& callback,
  226. const SaveDataNotifications::Result& result)
  227. {
  228. AZ::TickBus::QueueFunction([dataBuffer, dataBufferSize, dataBufferName, localUserId, callback, result]()
  229. {
  230. SaveDataNotifications::DataBufferLoadedParams dataBufferLoadedParams;
  231. dataBufferLoadedParams.dataBuffer = dataBuffer;
  232. dataBufferLoadedParams.dataBufferSize = dataBufferSize;
  233. dataBufferLoadedParams.dataBufferName = dataBufferName;
  234. dataBufferLoadedParams.localUserId = localUserId;
  235. dataBufferLoadedParams.result = result;
  236. if (callback)
  237. {
  238. callback(dataBufferLoadedParams);
  239. }
  240. SaveDataNotificationBus::Broadcast(&SaveDataNotifications::OnDataBufferLoaded,
  241. dataBufferLoadedParams);
  242. });
  243. }
  244. ////////////////////////////////////////////////////////////////////////////////////////////////
  245. void SaveDataSystemComponent::Implementation::LoadDataBufferFromFileSystem(const LoadDataBufferParams& loadDataBufferParams,
  246. const AZStd::string& absoluteFilePath,
  247. bool waitForCompletion)
  248. {
  249. // Perform parameter error checking but handle gracefully
  250. AZ_Assert(!loadDataBufferParams.dataBufferName.empty(), "Invalid param: dataBufferName");
  251. if (loadDataBufferParams.dataBufferName.empty())
  252. {
  253. OnLoadDataBufferComplete(nullptr,
  254. 0,
  255. loadDataBufferParams.dataBufferName,
  256. loadDataBufferParams.localUserId,
  257. loadDataBufferParams.callback,
  258. SaveDataNotifications::Result::ErrorInvalid);
  259. return;
  260. }
  261. // Start a new thread to perform the load.
  262. AZStd::thread_desc loadThreadDesc;
  263. loadThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
  264. loadThreadDesc.m_name = "LoadDataBufferFromFileSystem";
  265. ThreadCompletionPair* threadCompletionPair = nullptr;
  266. {
  267. AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
  268. m_activeThreads.emplace_back();
  269. threadCompletionPair = &m_activeThreads.back();
  270. }
  271. // This is safe access outside the lock guard because we only remove elements from the list
  272. // after the thread completion flag has been set to true (see also JoinAllCompletedThreads).
  273. threadCompletionPair->m_thread = AZStd::make_unique<AZStd::thread>(loadThreadDesc,
  274. [&threadCompleteFlag = threadCompletionPair->m_threadComplete,
  275. loadDataBufferParams,
  276. absoluteFilePath]()
  277. {
  278. SaveDataNotifications::DataBuffer dataBuffer = nullptr;
  279. AZ::u64 dataBufferSize = 0;
  280. SaveDataNotifications::Result result = SaveDataNotifications::Result::ErrorUnspecified;
  281. // Open the save data file for reading.
  282. AZ::IO::SystemFile systemFile;
  283. const AZStd::string finalSaveDataFilePath = absoluteFilePath + SaveDataFileExtension;
  284. const bool openFileResult = systemFile.Open(finalSaveDataFilePath.c_str(),
  285. AZ::IO::SystemFile::SF_OPEN_READ_ONLY);
  286. if (!openFileResult)
  287. {
  288. result = SaveDataNotifications::Result::ErrorNotFound;
  289. }
  290. else
  291. {
  292. // Allocate the memory we'll read the data buffer into.
  293. // Please note that we use a custom deleter to free it.
  294. const AZ::IO::SystemFile::SizeType fileLength = systemFile.Length();
  295. dataBuffer = SaveDataNotifications::DataBuffer(azmalloc(fileLength),
  296. [](void* p) { azfree(p); });
  297. if (!dataBuffer)
  298. {
  299. AZ_Error("LoadDataBufferFromFileSystem", false, "Failed to allocate %llu bytes", fileLength);
  300. result = SaveDataNotifications::Result::ErrorOutOfMemory;
  301. }
  302. else
  303. {
  304. // Read the contents of the file into a data buffer and then close it.
  305. dataBufferSize = systemFile.Read(fileLength, dataBuffer.get());
  306. systemFile.Close();
  307. // Verify that we read the correct number of bytes.
  308. result = (dataBufferSize == fileLength) ? SaveDataNotifications::Result::Success :
  309. SaveDataNotifications::Result::ErrorIOFailure;
  310. }
  311. }
  312. // Invoke the callback and broadcast the OnDataBufferLoaded notification from the main thread.
  313. OnLoadDataBufferComplete(dataBuffer,
  314. dataBufferSize,
  315. loadDataBufferParams.dataBufferName,
  316. loadDataBufferParams.localUserId,
  317. loadDataBufferParams.callback,
  318. result);
  319. // Set the thread completion flag so it will be joined in JoinAllCompletedThreads.
  320. threadCompleteFlag = true;
  321. });
  322. if (waitForCompletion)
  323. {
  324. // The thread completion flag will be set in join, and the thread completion
  325. // pair removed from m_activeThreads when JoinAllCompletedThreads is called.
  326. threadCompletionPair->m_thread->join();
  327. threadCompletionPair->m_thread.reset();
  328. }
  329. }
  330. ////////////////////////////////////////////////////////////////////////////////////////////////
  331. void SaveDataSystemComponent::Implementation::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint scriptTimePoint)
  332. {
  333. // We could potentially only do this every n milliseconds, or perhaps try and signal when a
  334. // thread completes and only check it then, but in almost all cases there will only ever be
  335. // one save or load thread running at any time (if there are any at all), so iterating over
  336. // the list each frame to check each atomic bool should not have any impact on performance.
  337. JoinAllCompletedThreads();
  338. }
  339. ////////////////////////////////////////////////////////////////////////////////////////////////
  340. void SaveDataSystemComponent::Implementation::JoinAllActiveThreads()
  341. {
  342. AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
  343. for (auto& threadCompletionPair : m_activeThreads)
  344. {
  345. if (threadCompletionPair.m_thread && threadCompletionPair.m_thread->joinable())
  346. {
  347. threadCompletionPair.m_thread->join();
  348. threadCompletionPair.m_thread.reset();
  349. }
  350. }
  351. // It's important not to call clear (or otherwise modify m_activeThreads) here, but rather
  352. // only in JoinAllCompletedThreads where we explicitly check for the m_threadComplete flag.
  353. }
  354. ////////////////////////////////////////////////////////////////////////////////////////////////
  355. void SaveDataSystemComponent::Implementation::JoinAllCompletedThreads()
  356. {
  357. AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
  358. auto it = m_activeThreads.begin();
  359. while (it != m_activeThreads.end())
  360. {
  361. if (it->m_threadComplete)
  362. {
  363. if (it->m_thread && it->m_thread->joinable())
  364. {
  365. it->m_thread->join();
  366. }
  367. it = m_activeThreads.erase(it);
  368. }
  369. else
  370. {
  371. ++it;
  372. }
  373. }
  374. }
  375. }