Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
ly_get_list_relative_pal_filename(pal_source_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME})
ly_add_target(
NAME SaveData.Static STATIC
NAMESPACE Gem
PLATFORM_INCLUDE_FILES
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
FILES_CMAKE
savedata_files.cmake
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
${pal_source_dir}
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::AzFramework
)
ly_add_target(
NAME SaveData ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
OUTPUT_NAME Gem.SaveData.d96ab03f53d14c9e83f9b4528c8576d7.v0.1.0
FILES_CMAKE
savedata_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Gem::SaveData.Static
)
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME SaveData.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
savedata_tests_files.cmake
${pal_source_dir}/platform_test_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
Gem::SaveData.Static
)
ly_add_googletest(
NAME Gem::SaveData.Tests
)
endif()
@@ -0,0 +1,116 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Input/User/LocalUserId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to listen for notifications related to the saving of persistent user data.
class SaveDataNotifications : public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: save data notifications are addressed to a single address
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: save data notifications can be handled by multiple listeners
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
////////////////////////////////////////////////////////////////////////////////////////////
//! DataBuffer is an alias for the shared_ptr to void loaded using a LoadDataBuffer request.
//! Unlike SaveDataRequests::DataBuffer (a unique_ptr), SaveDataNotifications::DataBuffer is
//! a shared_ptr so that listeners can decide whether they want/need to hold onto the memory.
using DataBuffer = AZStd::shared_ptr<void>;
////////////////////////////////////////////////////////////////////////////////////////////
//! Enum representing the result of a save or load data buffer request.
enum class Result
{
Success, //!< The save/load data buffer request was successful.
ErrorCanceled, //!< The save/load data buffer request failed: user cancelled.
ErrorCorrupt, //!< The save/load data buffer request failed: buffer corrupt.
ErrorInvalid, //!< The save/load data buffer request failed: invalid params.
ErrorNotFound, //!< The save/load data buffer request failed: file not found.
ErrorIOFailure, //!< The save/load data buffer request failed: file IO failure.
ErrorInProgress, //!< The save/load data buffer request failed: already in progress.
ErrorOutOfMemory, //!< The save/load data buffer request failed: insufficient memory.
ErrorSyncFailure, //!< The save/load data buffer request failed: synchronization issue.
ErrorUnknownUser, //!< The save/load data buffer request failed: local user id unknown.
ErrorUnspecified //!< The save/load data buffer request failed: reason is unspecified.
};
////////////////////////////////////////////////////////////////////////////////////////////
//! The parameters sent with a data buffer saved notification.
struct DataBufferSavedParams
{
////////////////////////////////////////////////////////////////////////////////////////
//! The name of the data buffer that was saved. Used as a filename on most platforms, or
//! in another way to uniquely identify this save data buffer for the associated user id.
AZStd::string dataBufferName;
////////////////////////////////////////////////////////////////////////////////////////
//! The local user id the data buffer that was saved is associated with.
AzFramework::LocalUserId localUserId = AzFramework::LocalUserIdNone;
////////////////////////////////////////////////////////////////////////////////////////
//! The result of the save data buffer request.
Result result = Result::Success;
};
////////////////////////////////////////////////////////////////////////////////////////////
//! The parameters sent with a data buffer loaded notification.
struct DataBufferLoadedParams
{
////////////////////////////////////////////////////////////////////////////////////////
//! The data buffer that was loaded.
DataBuffer dataBuffer = nullptr;
////////////////////////////////////////////////////////////////////////////////////////
//! The size of the data buffer that was loaded.
AZ::u64 dataBufferSize = 0;
////////////////////////////////////////////////////////////////////////////////////////
//! The name of the data buffer that was loaded. Used as a filename on most platforms or
//! in another way to uniquely identify this save data buffer for the associated user id.
AZStd::string dataBufferName;
////////////////////////////////////////////////////////////////////////////////////////
//! The local user id the data buffer that was loaded is associated with.
AzFramework::LocalUserId localUserId = AzFramework::LocalUserIdNone;
////////////////////////////////////////////////////////////////////////////////////////
//! The result of the load data buffer request.
Result result = Result::Success;
};
////////////////////////////////////////////////////////////////////////////////////////////
//! Override to be notified when a data buffer save has completed, successfully or otherwise.
//! Will always be broadcast from the main thread.
//! \param[in] dataBufferSavedParams The data buffer saved notification parameters.
virtual void OnDataBufferSaved(const DataBufferSavedParams& dataBufferSavedParams) = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Override to be notified when a data buffer load has completed, successfully or otherwise.
//! Will always be broadcast from the main thread.
//! \param[in] dataBufferLoadedParams The data buffer loaded notification parameters.
virtual void OnDataBufferLoaded(const DataBufferLoadedParams& dataBufferLoadedParams) = 0;
};
using SaveDataNotificationBus = AZ::EBus<SaveDataNotifications>;
} // namespace SaveData
@@ -0,0 +1,270 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SaveData/SaveDataNotificationBus.h>
#include <AzFramework/Input/User/LocalUserId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to make queries/requests related to saving/loading persistent user data.
class SaveDataRequests : public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: requests can only be sent to and addressed by a single instance (singleton)
///@{
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Alias for verbose callback function type
///@{
using OnDataBufferSaved = AZStd::function<void(const SaveDataNotifications::DataBufferSavedParams&)>;
using OnDataBufferLoaded = AZStd::function<void(const SaveDataNotifications::DataBufferLoadedParams&)>;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! DataBuffer is an alias for the unique_ptr to void saved using a SaveDataBuffer requst.
//! DataBuffers being saved must have a custom deleter that conforms to DataBufferDeleter.
//!
//! DataBufferDeleterAzFree means the buffer will be de-allocated using azfree once it goes
//! out of scope, meaning it MUST have been allocated in the first place using azmalloc.
//!
//! DataBufferDeleterNone means the calling code must delete the data buffer, in which case
//! it is also responsibile for ensuring it remains valid until the save or load completes.
//!
//! If you need to allocate the buffer through some other mechanism but still want it to be
//! deleted after saved, you can provide a custom deleter conforming to DataBufferDeleter.
///@{
using DataBufferDeleter = void(*)(void*);
using DataBuffer = AZStd::unique_ptr<void, DataBufferDeleter>;
static void DataBufferDeleterNone(void*) {}
static void DataBufferDeleterAzFree(void* ptr)
{
azfree(ptr);
}
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! The parameters used to send a save or load serializable object request.
//! \tparam SerializableType The type of serializable object to save or load.
template<typename SerializableType>
struct SaveOrLoadObjectParams
{
////////////////////////////////////////////////////////////////////////////////////////
//! Alias for verbose callback function type
using OnObjectSavedOrLoaded = AZStd::function<void(const SaveOrLoadObjectParams&,
SaveDataNotifications::Result)>;
////////////////////////////////////////////////////////////////////////////////////////
//! A shared ptr to the serializable object to save or load.
AZStd::shared_ptr<SerializableType> serializableObject;
////////////////////////////////////////////////////////////////////////////////////////
//! The serialize context to use when serializing the object, use nullptr for global one.
AZ::SerializeContext* serializeContext = nullptr;
////////////////////////////////////////////////////////////////////////////////////////
//! The name of the data buffer to be saved or loaded. Is a filename on most platforms,
//! but will always uniquely identify the data buffer for the associated local user.
AZStd::string dataBufferName;
////////////////////////////////////////////////////////////////////////////////////////
//! The local user id the data buffer to be saved or loaded is associated with.
AzFramework::LocalUserId localUserId = AzFramework::LocalUserIdNone;
////////////////////////////////////////////////////////////////////////////////////////
//! Callback function to invoke on the main thread once the object has saved or loaded.
OnObjectSavedOrLoaded callback = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////
//! Save a serializable object to persistent storage.
//! \tparam SerializableType The type of serializable object to save.
//! \param[in] saveObjectParams The save object request parameters.
template<typename SerializableType>
static void SaveObject(const SaveOrLoadObjectParams<SerializableType>& saveObjectParams);
////////////////////////////////////////////////////////////////////////////////////////////
//! Load a serializable object from persistent storage.
//! \tparam SerializableType The type of serializable object to load.
//! \param[in] loadObjectParams The load object request parameters.
template<typename SerializableType>
static void LoadObject(const SaveOrLoadObjectParams<SerializableType>& loadObjectParams);
////////////////////////////////////////////////////////////////////////////////////////////
//! The parameters used to send a save data buffer request.
struct SaveDataBufferParams
{
////////////////////////////////////////////////////////////////////////////////////////
//! The data buffer to be saved. Please also see DataBufferDeleter. It is mutable so the
//! SaveDataBufferParams struct can be passed around by const ref to achieve 'conceptual
//! constness', but also move-captured by the lambda function that will perform the save.
mutable DataBuffer dataBuffer = DataBuffer(nullptr, &DataBufferDeleterNone);
////////////////////////////////////////////////////////////////////////////////////////
//! The size of the data buffer to be saved.
AZ::u64 dataBufferSize = 0;
////////////////////////////////////////////////////////////////////////////////////////
//! The name of the data buffer to be saved. Used as a filename on most platforms, or in
//! another way to uniquely identify this save data buffer for the associated local user.
AZStd::string dataBufferName;
////////////////////////////////////////////////////////////////////////////////////////
//! The local user id the data buffer to be saved is associated with.
AzFramework::LocalUserId localUserId = AzFramework::LocalUserIdNone;
////////////////////////////////////////////////////////////////////////////////////////
//! Callback function to invoke on the main thread once the data buffer has been saved.
OnDataBufferSaved callback = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////
//! The parameters used to send a load data buffer request.
struct LoadDataBufferParams
{
////////////////////////////////////////////////////////////////////////////////////////
//! The name of the data buffer to be loaded. Used as a filename on most platforms or in
//! another way to uniquely identify this save data buffer for the associated local user.
AZStd::string dataBufferName;
////////////////////////////////////////////////////////////////////////////////////////
//! The local user id the data buffer to be loaded is associated with.
AzFramework::LocalUserId localUserId = AzFramework::LocalUserIdNone;
////////////////////////////////////////////////////////////////////////////////////////
//! Callback function to invoke on the main thread once the data buffer has been loaded.
OnDataBufferLoaded callback = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////
//! Save a data buffer to persistent storage.
//! \param[in] saveDataBufferRequestParams The save data buffer request parameters.
virtual void SaveDataBuffer(const SaveDataBufferParams& saveDataBufferParams) = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Load a data buffer from persistent storage.
//! \param[in] loadDataBufferParams The load data buffer request parameters.
virtual void LoadDataBuffer(const LoadDataBufferParams& loadDataBufferParams) = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Set the path to the application's save data dircetory. If the supplied path is absolute,
//! it will be used directy, otherwise if it's relative it will be appended to the location
//! deemed most appropriate by the host OS for storing application specific user save data.
//!
//! If this is never called, save data will be saved in and loaded from a directory with the
//! same name as the executable, relative to the default location for storing user save data.
//!
//! One some systems (ie. consoles), the location of save data is fixed and/or inaccessible
//! using the standard file-system, in which case calling this function will have no effect.
//!
//! But on systems where we are able to override the default save data directory path, care
//! should be taken that it is only done once at startup before any attempt to load or save.
//!
//! \param[in] saveDataDirectoryPath The new path to the application's save data dircetory.
virtual void SetSaveDataDirectoryPath(const char* saveDataDirectoryPath) = 0;
};
using SaveDataRequestBus = AZ::EBus<SaveDataRequests>;
////////////////////////////////////////////////////////////////////////////////////////////////
template<class SerializableType>
inline void SaveDataRequests::SaveObject(const SaveOrLoadObjectParams<SerializableType>& saveObjectParams)
{
// Save the serializable object to a data buffer.
AZStd::vector<AZ::u8> dataBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8>> dataStream(&dataBuffer);
const bool saved = AZ::Utils::SaveObjectToStream(dataStream,
AZ::ObjectStream::ST_BINARY,
saveObjectParams.serializableObject.get(),
saveObjectParams.serializeContext);
if (!saved)
{
AZ_Error("SaveDataRequests::SaveObject", false,
"Failed to save serializable object to data stream.");
if (saveObjectParams.callback)
{
saveObjectParams.callback(saveObjectParams, SaveDataNotifications::Result::ErrorCorrupt);
}
return;
}
// Save the data buffer to persistent storage.
const AZ::u64 dataBufferSize = dataBuffer.size();
SaveDataBufferParams saveDataBufferParams;
if (dataBufferSize)
{
saveDataBufferParams.dataBuffer = DataBuffer(azmalloc(dataBufferSize), DataBufferDeleterAzFree);
memcpy(saveDataBufferParams.dataBuffer.get(), dataBuffer.data(), dataBufferSize);
}
saveDataBufferParams.dataBufferSize = dataBufferSize;
saveDataBufferParams.dataBufferName = saveObjectParams.dataBufferName;
saveDataBufferParams.localUserId = saveObjectParams.localUserId;
saveDataBufferParams.callback = [saveObjectParams](const SaveDataNotifications::DataBufferSavedParams& dataBufferSavedParams)
{
if (saveObjectParams.callback)
{
saveObjectParams.callback(saveObjectParams, dataBufferSavedParams.result);
}
};
SaveDataRequestBus::Broadcast(&SaveDataRequests::SaveDataBuffer, saveDataBufferParams);
}
////////////////////////////////////////////////////////////////////////////////////////////////
template<class SerializableType>
inline void SaveDataRequests::LoadObject(const SaveOrLoadObjectParams<SerializableType>& loadObjectParams)
{
// Load the data buffer from persistent storage.
LoadDataBufferParams loadDataBufferParams;
loadDataBufferParams.dataBufferName = loadObjectParams.dataBufferName;
loadDataBufferParams.localUserId = loadObjectParams.localUserId;
loadDataBufferParams.callback = [loadObjectParams](const SaveDataNotifications::DataBufferLoadedParams& dataBufferLoadedParams)
{
SaveDataNotifications::Result result = dataBufferLoadedParams.result;
if (result == SaveDataNotifications::Result::Success)
{
// Load the serializable object from the data buffer.
const bool loaded = AZ::Utils::LoadObjectFromBufferInPlace(dataBufferLoadedParams.dataBuffer.get(),
dataBufferLoadedParams.dataBufferSize,
*(loadObjectParams.serializableObject),
loadObjectParams.serializeContext);
if (!loaded)
{
AZ_Error("SaveDataRequests::LoadObject", loaded,
"Failed to load serializable object from data stream.");
result = SaveDataNotifications::Result::ErrorCorrupt;
}
}
if (loadObjectParams.callback)
{
loadObjectParams.callback(loadObjectParams, result);
}
};
SaveDataRequestBus::Broadcast(&SaveDataRequests::LoadDataBuffer, loadDataBufferParams);
}
} // namespace SaveData
@@ -0,0 +1,150 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SaveDataSystemComponent.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/conversions.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for the save data system component on Android
class SaveDataSystemComponentAndroid : public SaveDataSystemComponent::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
static constexpr const char* DefaultSaveDataDirectoryName = "SaveData";
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(SaveDataSystemComponentAndroid, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] saveDataSystemComponent Reference to the parent being implemented
SaveDataSystemComponentAndroid(SaveDataSystemComponent& saveDataSystemComponent);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~SaveDataSystemComponentAndroid() override;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::SaveDataBuffer
void SaveDataBuffer(const SaveDataRequests::SaveDataBufferParams& saveDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::LoadDataBuffer
void LoadDataBuffer(const SaveDataRequests::LoadDataBufferParams& loadDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::SetSaveDataDirectoryPath
void SetSaveDataDirectoryPath(const char* saveDataDirectoryPath) override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to construct the full save data file path.
//! \param[in] dataBufferName The name of the save data buffer.
//! \param[in] localUserId The local user id the save data buffer is associated with.
AZStd::string GetSaveDataFilePath(const AZStd::string& dataBufferName,
AzFramework::LocalUserId localUserId);
////////////////////////////////////////////////////////////////////////////////////////////
//! The absolute path to the application's save data dircetory.
AZStd::string m_saveDataDircetoryPathAbsolute = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string GetDefaultAndroidUserSaveDataPath()
{
return AZStd::string::format("%s/", AZ::Android::Utils::GetAppPublicStoragePath());
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool IsAbsolutePath(const char* path)
{
return path && path[0] == '/';
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponent::Implementation* SaveDataSystemComponent::Implementation::Create(SaveDataSystemComponent& saveDataSystemComponent)
{
return aznew SaveDataSystemComponentAndroid(saveDataSystemComponent);
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponentAndroid::SaveDataSystemComponentAndroid(SaveDataSystemComponent& saveDataSystemComponent)
: SaveDataSystemComponent::Implementation(saveDataSystemComponent)
, m_saveDataDircetoryPathAbsolute(GetDefaultAndroidUserSaveDataPath() +
DefaultSaveDataDirectoryName + "/")
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponentAndroid::~SaveDataSystemComponentAndroid()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentAndroid::SaveDataBuffer(const SaveDataRequests::SaveDataBufferParams& saveDataBufferParams)
{
const AZStd::string& absoluteFilePath = GetSaveDataFilePath(saveDataBufferParams.dataBufferName,
saveDataBufferParams.localUserId);
SaveDataBufferToFileSystem(saveDataBufferParams, absoluteFilePath);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentAndroid::LoadDataBuffer(const SaveDataRequests::LoadDataBufferParams& loadDataBufferParams)
{
const AZStd::string& absoluteFilePath = GetSaveDataFilePath(loadDataBufferParams.dataBufferName,
loadDataBufferParams.localUserId);
LoadDataBufferFromFileSystem(loadDataBufferParams, absoluteFilePath);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentAndroid::SetSaveDataDirectoryPath(const char* saveDataDirectoryPath)
{
if (IsAbsolutePath(saveDataDirectoryPath))
{
m_saveDataDircetoryPathAbsolute = saveDataDirectoryPath;
}
else
{
m_saveDataDircetoryPathAbsolute = GetDefaultAndroidUserSaveDataPath() + saveDataDirectoryPath;
}
AZ_Assert(!m_saveDataDircetoryPathAbsolute.empty(), "Cannot set an empty save data directory path.");
// Append the trailing path separator if needed
if (m_saveDataDircetoryPathAbsolute.back() != '/' ||
m_saveDataDircetoryPathAbsolute.back() != '\\')
{
m_saveDataDircetoryPathAbsolute += '/';
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string SaveDataSystemComponentAndroid::GetSaveDataFilePath(const AZStd::string& dataBufferName,
AzFramework::LocalUserId localUserId)
{
AZStd::string saveDataFilePath = m_saveDataDircetoryPathAbsolute;
if (localUserId != AzFramework::LocalUserIdNone)
{
saveDataFilePath += AZStd::string::format("User_%u/", localUserId);
}
saveDataFilePath += dataBufferName;
return saveDataFilePath;
}
} // namespace SaveData
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_SAVEDATA_TEST_USER_ID 9
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SaveData_Traits_Android.h>
@@ -0,0 +1,10 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,16 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
SaveData_SystemComponent_Android.cpp
SaveData_Traits_Platform.h
SaveData_Traits_Android.h
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Unimplemented/SaveDataTest_Unimplemented.cpp
)
@@ -0,0 +1,10 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,173 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SaveDataSystemComponent.h>
#include <AzCore/IO/SystemFile.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSPathUtilities.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for the save data system component on iOS and macOS
class SaveDataSystemComponentApple : public SaveDataSystemComponent::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
static constexpr const char* DefaultSaveDataDirectoryName = "SaveData";
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(SaveDataSystemComponentApple, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] saveDataSystemComponent Reference to the parent being implemented
SaveDataSystemComponentApple(SaveDataSystemComponent& saveDataSystemComponent);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~SaveDataSystemComponentApple() override;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::SaveDataBuffer
void SaveDataBuffer(const SaveDataRequests::SaveDataBufferParams& saveDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::LoadDataBuffer
void LoadDataBuffer(const SaveDataRequests::LoadDataBufferParams& loadDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::SetSaveDataDirectoryPath
void SetSaveDataDirectoryPath(const char* saveDataDirectoryPath) override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to construct the full save data file path.
//! \param[in] dataBufferName The name of the save data buffer.
//! \param[in] localUserId The local user id the save data buffer is associated with.
AZStd::string GetSaveDataFilePath(const AZStd::string& dataBufferName,
AzFramework::LocalUserId localUserId);
////////////////////////////////////////////////////////////////////////////////////////////
//! The absolute path to the application's save data dircetory.
AZStd::string m_saveDataDircetoryPathAbsolute = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string GetDefaultAppleUserSaveDataPath()
{
AZStd::string returnValue;
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,
NSUserDomainMask,
YES);
if ([paths count] != 0)
{
returnValue = [[paths objectAtIndex:0] UTF8String];
}
returnValue += '/';
return returnValue;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string GetExecutableName()
{
const AZStd::string bundlePathString = [[[NSBundle mainBundle] bundlePath] UTF8String];
const size_t executableNameStart = bundlePathString.find_last_of('/') + 1;
const size_t executableNameEnd = bundlePathString.find_last_of('.');
const size_t executableNameLength = executableNameEnd - executableNameStart;
AZ_Assert(executableNameLength > 0, "Could not extract executable name from: %s", bundlePathString.c_str());
return bundlePathString.substr(executableNameStart, executableNameLength);
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool IsAbsolutePath(const char* path)
{
return path && path[0] == '/';
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponent::Implementation* SaveDataSystemComponent::Implementation::Create(SaveDataSystemComponent& saveDataSystemComponent)
{
return aznew SaveDataSystemComponentApple(saveDataSystemComponent);
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponentApple::SaveDataSystemComponentApple(SaveDataSystemComponent& saveDataSystemComponent)
: SaveDataSystemComponent::Implementation(saveDataSystemComponent)
, m_saveDataDircetoryPathAbsolute(GetDefaultAppleUserSaveDataPath() +
GetExecutableName() + "/" +
DefaultSaveDataDirectoryName + "/")
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponentApple::~SaveDataSystemComponentApple()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentApple::SaveDataBuffer(const SaveDataRequests::SaveDataBufferParams& saveDataBufferParams)
{
const AZStd::string& absoluteFilePath = GetSaveDataFilePath(saveDataBufferParams.dataBufferName,
saveDataBufferParams.localUserId);
SaveDataBufferToFileSystem(saveDataBufferParams, absoluteFilePath);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentApple::LoadDataBuffer(const SaveDataRequests::LoadDataBufferParams& loadDataBufferParams)
{
const AZStd::string& absoluteFilePath = GetSaveDataFilePath(loadDataBufferParams.dataBufferName,
loadDataBufferParams.localUserId);
LoadDataBufferFromFileSystem(loadDataBufferParams, absoluteFilePath);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentApple::SetSaveDataDirectoryPath(const char* saveDataDirectoryPath)
{
if (IsAbsolutePath(saveDataDirectoryPath))
{
m_saveDataDircetoryPathAbsolute = saveDataDirectoryPath;
}
else
{
m_saveDataDircetoryPathAbsolute = GetDefaultAppleUserSaveDataPath() + saveDataDirectoryPath;
}
AZ_Assert(!m_saveDataDircetoryPathAbsolute.empty(), "Cannot set an empty save data directory path.");
// Append the trailing path separator if needed
if (m_saveDataDircetoryPathAbsolute.back() != '/' ||
m_saveDataDircetoryPathAbsolute.back() != '\\')
{
m_saveDataDircetoryPathAbsolute += '/';
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string SaveDataSystemComponentApple::GetSaveDataFilePath(const AZStd::string& dataBufferName,
AzFramework::LocalUserId localUserId)
{
AZStd::string saveDataFilePath = m_saveDataDircetoryPathAbsolute;
if (localUserId != AzFramework::LocalUserIdNone)
{
saveDataFilePath += AZStd::string::format("User_%u\\", localUserId);
}
saveDataFilePath += dataBufferName;
return saveDataFilePath;
}
} // namespace SaveData
@@ -0,0 +1,33 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include <AzFramework/Input/User/LocalUserId.h>
#include "SaveData_Traits_Platform.h"
#include "SaveDataTest.h"
void SaveDataTest::SetupInternal()
{
}
void SaveDataTest::TearDownInternal()
{
}
AzFramework::LocalUserId SaveDataTest::GetDefaultTestUserId()
{
return AZ_TRAIT_SAVEDATA_TEST_USER_ID;
}
@@ -0,0 +1,23 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SaveDataSystemComponent.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponent::Implementation* SaveDataSystemComponent::Implementation::Create(SaveDataSystemComponent&)
{
return nullptr;
}
} // namespace SaveData
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_SAVEDATA_TEST_USER_ID 9
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SaveData_Traits_Linux.h>
@@ -0,0 +1,10 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,16 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Unimplemented/SaveData_SystemComponent_Unimplemented.cpp
SaveData_Traits_Platform.h
SaveData_Traits_Linux.h
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Unimplemented/SaveDataTest_Unimplemented.cpp
)
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_SAVEDATA_TEST_USER_ID 9
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SaveData_Traits_Mac.h>
@@ -0,0 +1,10 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,16 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Apple/SaveData_SystemComponent_Apple.mm
SaveData_Traits_Platform.h
SaveData_Traits_Mac.h
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Unimplemented/SaveDataTest_Unimplemented.cpp
)
@@ -0,0 +1,12 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(PAL_TRAIT_ENABLE_SAVEDATA_UNIT_TEST TRUE)
@@ -0,0 +1,195 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SaveDataSystemComponent.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/conversions.h>
#include <windows.h>
#include <shlobj.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Platform specific implementation for the save data system component on Windows
class SaveDataSystemComponentWindows : public SaveDataSystemComponent::Implementation
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
static constexpr const char* DefaultSaveDataDirectoryName = "SaveData";
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(SaveDataSystemComponentWindows, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] saveDataSystemComponent Reference to the parent being implemented
SaveDataSystemComponentWindows(SaveDataSystemComponent& saveDataSystemComponent);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~SaveDataSystemComponentWindows() override;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::SaveDataBuffer
void SaveDataBuffer(const SaveDataRequests::SaveDataBufferParams& saveDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::LoadDataBuffer
void LoadDataBuffer(const SaveDataRequests::LoadDataBufferParams& loadDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataSystemComponent::Implementation::SetSaveDataDirectoryPath
void SetSaveDataDirectoryPath(const char* saveDataDirectoryPath) override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to construct the full save data file path.
//! \param[in] dataBufferName The name of the save data buffer.
//! \param[in] localUserId The local user id the save data buffer is associated with.
AZStd::string GetSaveDataFilePath(const AZStd::string& dataBufferName,
AzFramework::LocalUserId localUserId);
////////////////////////////////////////////////////////////////////////////////////////////
//! The absolute path to the application's save data dircetory.
AZStd::string m_saveDataDircetoryPathAbsolute = nullptr;
};
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string GetDefaultWindowsUserSaveDataPath()
{
// Unfortunately, there is no universally accepted default "Save Data" directory on Windows,
// so we are forced to choose between the following commonly used user save data locations:
//
// C:\Users\{username}\AppData\Local (FOLDERID_LocalAppData)
// C:\Users\{username}\AppData\Roaming (FOLDERID_RoamingAppData)
// C:\Users\{username}\Documents (FOLDERID_Documents)
// C:\Users\{username}\Saved Games (FOLDERID_SavedGames)
//
// which are all best retrieved using the Windows SHGetKnownFolderPath function:
// https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// Get the 'known folder path'
wchar_t* knownFolderPathUTF16 = nullptr;
long result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &knownFolderPathUTF16);
AZ_Assert(SUCCEEDED(result), "SHGetKnownFolderPath could not retrieve LocalAppData folder");
// Convert it from UTF-16 to UTF-8
AZStd::string defaultWindowsUserSaveDataPathUTF8;
AZStd::to_string(defaultWindowsUserSaveDataPathUTF8, AZStd::wstring(knownFolderPathUTF16));
// Free the memory allocated by SHGetKnownFolderPath
CoTaskMemFree(knownFolderPathUTF16);
// Append the trailing path separator and return
defaultWindowsUserSaveDataPathUTF8 += '\\';
return defaultWindowsUserSaveDataPathUTF8;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string GetExecutableName()
{
char moduleFileName[AZ_MAX_PATH_LEN];
DWORD pathLen = GetModuleFileNameA(nullptr, moduleFileName, AZ_MAX_PATH_LEN);
const AZStd::string moduleFileNameString(moduleFileName);
const size_t executableNameStart = moduleFileNameString.find_last_of('\\') + 1;
const size_t executableNameEnd = moduleFileNameString.find_last_of('.');
const size_t executableNameLength = executableNameEnd - executableNameStart;
AZ_Assert(executableNameLength > 0, "Could not extract executable name from: %s", moduleFileName);
return moduleFileNameString.substr(executableNameStart, executableNameLength);
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool IsAbsolutePath(const char* path)
{
char drive[16];
_splitpath_s(path, drive, 16, nullptr, 0, nullptr, 0, nullptr, 0);
return strlen(drive) > 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponent::Implementation* SaveDataSystemComponent::Implementation::Create(SaveDataSystemComponent& saveDataSystemComponent)
{
return aznew SaveDataSystemComponentWindows(saveDataSystemComponent);
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponentWindows::SaveDataSystemComponentWindows(SaveDataSystemComponent& saveDataSystemComponent)
: SaveDataSystemComponent::Implementation(saveDataSystemComponent)
, m_saveDataDircetoryPathAbsolute(GetDefaultWindowsUserSaveDataPath() +
GetExecutableName() + "\\" +
DefaultSaveDataDirectoryName + "\\")
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponentWindows::~SaveDataSystemComponentWindows()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentWindows::SaveDataBuffer(const SaveDataRequests::SaveDataBufferParams& saveDataBufferParams)
{
const AZStd::string& absoluteFilePath = GetSaveDataFilePath(saveDataBufferParams.dataBufferName,
saveDataBufferParams.localUserId);
SaveDataBufferToFileSystem(saveDataBufferParams, absoluteFilePath);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentWindows::LoadDataBuffer(const SaveDataRequests::LoadDataBufferParams& loadDataBufferParams)
{
const AZStd::string& absoluteFilePath = GetSaveDataFilePath(loadDataBufferParams.dataBufferName,
loadDataBufferParams.localUserId);
LoadDataBufferFromFileSystem(loadDataBufferParams, absoluteFilePath);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponentWindows::SetSaveDataDirectoryPath(const char* saveDataDirectoryPath)
{
if (IsAbsolutePath(saveDataDirectoryPath))
{
m_saveDataDircetoryPathAbsolute = saveDataDirectoryPath;
}
else
{
m_saveDataDircetoryPathAbsolute = GetDefaultWindowsUserSaveDataPath() + saveDataDirectoryPath;
}
AZ_Assert(!m_saveDataDircetoryPathAbsolute.empty(), "Cannot set an empty save data directory path.");
// Append the trailing path separator if needed
if (m_saveDataDircetoryPathAbsolute.back() != '/' ||
m_saveDataDircetoryPathAbsolute.back() != '\\')
{
m_saveDataDircetoryPathAbsolute += '\\';
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string SaveDataSystemComponentWindows::GetSaveDataFilePath(const AZStd::string& dataBufferName,
AzFramework::LocalUserId localUserId)
{
AZStd::string saveDataFilePath = m_saveDataDircetoryPathAbsolute;
if (localUserId != AzFramework::LocalUserIdNone)
{
saveDataFilePath += AZStd::string::format("User_%u\\", localUserId);
}
saveDataFilePath += dataBufferName;
return saveDataFilePath;
}
} // namespace SaveData
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SaveData_Traits_Windows.h>
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_SAVEDATA_TEST_USER_ID 9
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Unimplemented/SaveDataTest_Unimplemented.cpp
)
@@ -0,0 +1,10 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,16 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
SaveData_SystemComponent_Windows.cpp
SaveData_Traits_Platform.h
SaveData_Traits_Windows.h
)
@@ -0,0 +1,12 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(PAL_TRAIT_ENABLE_SAVEDATA_UNIT_TEST TRUE)
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SaveData_Traits_iOS.h>
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_SAVEDATA_TEST_USER_ID 9
@@ -0,0 +1,11 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
@@ -0,0 +1,16 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Apple/SaveData_SystemComponent_Apple.mm
SaveData_Traits_Platform.h
SaveData_Traits_iOS.h
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../Common/Unimplemented/SaveDataTest_Unimplemented.cpp
)
@@ -0,0 +1,51 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <SaveDataSystemComponent.h>
namespace SaveData
{
class SaveDataModule
: public AZ::Module
{
public:
AZ_RTTI(SaveDataModule, "{4FD9776B-0C36-476F-A7C4-161404BCCCF3}", AZ::Module);
AZ_CLASS_ALLOCATOR(SaveDataModule, AZ::SystemAllocator, 0);
SaveDataModule()
: AZ::Module()
{
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
SaveDataSystemComponent::CreateDescriptor(),
});
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<SaveDataSystemComponent>(),
};
}
};
}
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_SaveData, SaveData::SaveDataModule)
@@ -0,0 +1,418 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SaveDataSystemComponent.h>
#include <SaveData/SaveDataNotificationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/smart_ptr/make_shared.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
const char* SaveDataFileExtension = ".savedata";
const char* TempSaveDataFileExtension = ".tmpsavedata";
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<SaveDataSystemComponent, AZ::Component>()
->Version(0);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<SaveDataSystemComponent>("SaveData", "Provides functionality for saving and loading persistent user data.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("SaveDataService"));
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("SaveDataService"));
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Activate()
{
m_pimpl.reset(Implementation::Create(*this));
SaveDataRequestBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Deactivate()
{
SaveDataRequestBus::Handler::BusDisconnect();
m_pimpl.reset();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::SaveDataBuffer(const SaveDataBufferParams& saveDataBufferParams)
{
if (m_pimpl)
{
m_pimpl->SaveDataBuffer(saveDataBufferParams);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::LoadDataBuffer(const LoadDataBufferParams& loadDataBufferParams)
{
if (m_pimpl)
{
m_pimpl->LoadDataBuffer(loadDataBufferParams);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::SetSaveDataDirectoryPath(const char* saveDataDirectoryPath)
{
if (m_pimpl)
{
m_pimpl->SetSaveDataDirectoryPath(saveDataDirectoryPath);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponent::Implementation::Implementation(SaveDataSystemComponent& saveDataSystemComponent)
: m_saveDataSystemComponent(saveDataSystemComponent)
{
AZ::TickBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
SaveDataSystemComponent::Implementation::~Implementation()
{
AZ::TickBus::Handler::BusDisconnect();
// Make sure we join all active threads, regardless of their completion state.
JoinAllActiveThreads();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Implementation::OnSaveDataBufferComplete(const AZStd::string& dataBufferName,
const AzFramework::LocalUserId localUserId,
const SaveDataRequests::OnDataBufferSaved& callback,
const SaveDataNotifications::Result& result)
{
// Always queue the OnDataBufferSaved notification back on the main thread.
// Even if this is being called from the main thread already, this ensures
// the callback / notifications are aways sent at the same time each frame.
AZ::TickBus::QueueFunction([dataBufferName, localUserId, callback, result]()
{
SaveDataNotifications::DataBufferSavedParams dataBufferSavedParams;
dataBufferSavedParams.dataBufferName = dataBufferName;
dataBufferSavedParams.localUserId = localUserId;
dataBufferSavedParams.result = result;
if (callback)
{
callback(dataBufferSavedParams);
}
SaveDataNotificationBus::Broadcast(&SaveDataNotifications::OnDataBufferSaved,
dataBufferSavedParams);
});
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Implementation::SaveDataBufferToFileSystem(const SaveDataBufferParams& saveDataBufferParams,
const AZStd::string& absoluteFilePath,
bool waitForCompletion,
bool useTemporaryFile)
{
// Perform parameter error checking but handle gracefully
AZ_Assert(saveDataBufferParams.dataBuffer, "Invalid param: dataBuffer");
AZ_Assert(saveDataBufferParams.dataBufferSize, "Invalid param: dataBufferSize");
AZ_Assert(!saveDataBufferParams.dataBufferName.empty(), "Invalid param: dataBufferName");
if (!saveDataBufferParams.dataBuffer ||
!saveDataBufferParams.dataBufferSize ||
saveDataBufferParams.dataBufferName.empty())
{
OnSaveDataBufferComplete(saveDataBufferParams.dataBufferName,
saveDataBufferParams.localUserId,
saveDataBufferParams.callback,
SaveDataNotifications::Result::ErrorInvalid);
return;
}
// Start a new thread to perform the save, capturing the necessary parameters by value,
// except for the data buffer itself which must be moved (because it is a unique_ptr).
AZStd::thread_desc saveThreadDesc;
saveThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
saveThreadDesc.m_name = "SaveDataBufferToFileSystem";
ThreadCompletionPair* threadCompletionPair = nullptr;
{
AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
m_activeThreads.emplace_back();
threadCompletionPair = &m_activeThreads.back();
}
// This is safe access outside the lock guard because we only remove elements from the list
// after the thread completion flag has been set to true (see also JoinAllCompletedThreads).
threadCompletionPair->m_thread = AZStd::make_unique<AZStd::thread>([&threadCompleteFlag = threadCompletionPair->m_threadComplete,
dataBuffer = AZStd::move(saveDataBufferParams.dataBuffer),
dataBufferSize = saveDataBufferParams.dataBufferSize,
dataBufferName = saveDataBufferParams.dataBufferName,
onSavedCallback = saveDataBufferParams.callback,
localUserId = saveDataBufferParams.localUserId,
absoluteFilePath,
useTemporaryFile]()
{
SaveDataNotifications::Result result = SaveDataNotifications::Result::ErrorUnspecified;
// If useTemporaryFile == true we save first to a '.tmp' file so we
// do not overwrite existing save data until we are sure of success.
const AZStd::string tempSaveDataFilePath = absoluteFilePath + TempSaveDataFileExtension;
const AZStd::string finalSaveDataFilePath = absoluteFilePath + SaveDataFileExtension;
// Open the temp save data file for writing, creating it (and
// any intermediate directories) if it doesn't already exist.
AZ::IO::SystemFile systemFile;
const bool openFileResult = systemFile.Open(useTemporaryFile ? tempSaveDataFilePath.c_str() : finalSaveDataFilePath.c_str(),
AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY |
AZ::IO::SystemFile::SF_OPEN_CREATE |
AZ::IO::SystemFile::SF_OPEN_CREATE_PATH);
if (!openFileResult)
{
result = SaveDataNotifications::Result::ErrorIOFailure;
}
else
{
// Write the data buffer to the temp file and then close it.
const AZ::IO::SystemFile::SizeType bytesWritten = systemFile.Write(dataBuffer.get(),
dataBufferSize);
systemFile.Close();
// Verify that we wrote the correct number of bytes.
if (bytesWritten != dataBufferSize)
{
result = SaveDataNotifications::Result::ErrorIOFailure;
}
else if (useTemporaryFile)
{
// Rename the temp save data file we successfully wrote to.
const bool renameFileResult = AZ::IO::SystemFile::Rename(tempSaveDataFilePath.c_str(),
finalSaveDataFilePath.c_str(),
true);
result = renameFileResult ? SaveDataNotifications::Result::Success :
SaveDataNotifications::Result::ErrorIOFailure;
// Delete the temp save data file.
AZ::IO::SystemFile::Delete(tempSaveDataFilePath.c_str());
}
else
{
result = SaveDataNotifications::Result::Success;
}
}
// Invoke the callback and broadcast the OnDataBufferSaved notification from the main thread.
OnSaveDataBufferComplete(dataBufferName, localUserId, onSavedCallback, result);
// Set the thread completion flag so it will be joined in JoinAllCompletedThreads.
threadCompleteFlag = true;
}, &saveThreadDesc);
if (waitForCompletion)
{
// The thread completion flag will be set in join, and the thread completion
// pair removed from m_activeThreads when JoinAllCompletedThreads is called.
threadCompletionPair->m_thread->join();
threadCompletionPair->m_thread.reset();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Implementation::OnLoadDataBufferComplete(SaveDataNotifications::DataBuffer dataBuffer,
AZ::u64 dataBufferSize,
const AZStd::string& dataBufferName,
const AzFramework::LocalUserId localUserId,
const SaveDataRequests::OnDataBufferLoaded& callback,
const SaveDataNotifications::Result& result)
{
AZ::TickBus::QueueFunction([dataBuffer, dataBufferSize, dataBufferName, localUserId, callback, result]()
{
SaveDataNotifications::DataBufferLoadedParams dataBufferLoadedParams;
dataBufferLoadedParams.dataBuffer = dataBuffer;
dataBufferLoadedParams.dataBufferSize = dataBufferSize;
dataBufferLoadedParams.dataBufferName = dataBufferName;
dataBufferLoadedParams.localUserId = localUserId;
dataBufferLoadedParams.result = result;
if (callback)
{
callback(dataBufferLoadedParams);
}
SaveDataNotificationBus::Broadcast(&SaveDataNotifications::OnDataBufferLoaded,
dataBufferLoadedParams);
});
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Implementation::LoadDataBufferFromFileSystem(const LoadDataBufferParams& loadDataBufferParams,
const AZStd::string& absoluteFilePath,
bool waitForCompletion)
{
// Perform parameter error checking but handle gracefully
AZ_Assert(!loadDataBufferParams.dataBufferName.empty(), "Invalid param: dataBufferName");
if (loadDataBufferParams.dataBufferName.empty())
{
OnLoadDataBufferComplete(nullptr,
0,
loadDataBufferParams.dataBufferName,
loadDataBufferParams.localUserId,
loadDataBufferParams.callback,
SaveDataNotifications::Result::ErrorInvalid);
return;
}
// Start a new thread to perform the load.
AZStd::thread_desc loadThreadDesc;
loadThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
loadThreadDesc.m_name = "LoadDataBufferFromFileSystem";
ThreadCompletionPair* threadCompletionPair = nullptr;
{
AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
m_activeThreads.emplace_back();
threadCompletionPair = &m_activeThreads.back();
}
// This is safe access outside the lock guard because we only remove elements from the list
// after the thread completion flag has been set to true (see also JoinAllCompletedThreads).
threadCompletionPair->m_thread = AZStd::make_unique<AZStd::thread>([&threadCompleteFlag = threadCompletionPair->m_threadComplete,
loadDataBufferParams,
absoluteFilePath]()
{
SaveDataNotifications::DataBuffer dataBuffer = nullptr;
AZ::u64 dataBufferSize = 0;
SaveDataNotifications::Result result = SaveDataNotifications::Result::ErrorUnspecified;
// Open the save data file for reading.
AZ::IO::SystemFile systemFile;
const AZStd::string finalSaveDataFilePath = absoluteFilePath + SaveDataFileExtension;
const bool openFileResult = systemFile.Open(finalSaveDataFilePath.c_str(),
AZ::IO::SystemFile::SF_OPEN_READ_ONLY);
if (!openFileResult)
{
result = SaveDataNotifications::Result::ErrorNotFound;
}
else
{
// Allocate the memory we'll read the data buffer into.
// Please note that we use a custom deleter to free it.
const AZ::IO::SystemFile::SizeType fileLength = systemFile.Length();
dataBuffer = SaveDataNotifications::DataBuffer(azmalloc(fileLength),
[](void* p) { azfree(p); });
if (!dataBuffer)
{
AZ_Error("LoadDataBufferFromFileSystem", false, "Failed to allocate %llu bytes", fileLength);
result = SaveDataNotifications::Result::ErrorOutOfMemory;
}
else
{
// Read the contents of the file into a data buffer and then close it.
dataBufferSize = systemFile.Read(fileLength, dataBuffer.get());
systemFile.Close();
// Verify that we read the correct number of bytes.
result = (dataBufferSize == fileLength) ? SaveDataNotifications::Result::Success :
SaveDataNotifications::Result::ErrorIOFailure;
}
}
// Invoke the callback and broadcast the OnDataBufferLoaded notification from the main thread.
OnLoadDataBufferComplete(dataBuffer,
dataBufferSize,
loadDataBufferParams.dataBufferName,
loadDataBufferParams.localUserId,
loadDataBufferParams.callback,
result);
// Set the thread completion flag so it will be joined in JoinAllCompletedThreads.
threadCompleteFlag = true;
}, &loadThreadDesc);
if (waitForCompletion)
{
// The thread completion flag will be set in join, and the thread completion
// pair removed from m_activeThreads when JoinAllCompletedThreads is called.
threadCompletionPair->m_thread->join();
threadCompletionPair->m_thread.reset();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Implementation::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint scriptTimePoint)
{
// We could potentially only do this every n milliseconds, or perhaps try and signal when a
// thread completes and only check it then, but in almost all cases there will only ever be
// one save or load thread running at any time (if there are any at all), so iterating over
// the list each frame to check each atomic bool should not have any impact on performance.
JoinAllCompletedThreads();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Implementation::JoinAllActiveThreads()
{
AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
for (auto& threadCompletionPair : m_activeThreads)
{
if (threadCompletionPair.m_thread && threadCompletionPair.m_thread->joinable())
{
threadCompletionPair.m_thread->join();
threadCompletionPair.m_thread.reset();
}
}
// It's important not to call clear (or otherwise modify m_activeThreads) here, but rather
// only in JoinAllCompletedThreads where we explicitly check for the m_threadComplete flag.
}
////////////////////////////////////////////////////////////////////////////////////////////////
void SaveDataSystemComponent::Implementation::JoinAllCompletedThreads()
{
AZStd::lock_guard<AZStd::mutex> lock(m_activeThreadsMutex);
auto it = m_activeThreads.begin();
while (it != m_activeThreads.end())
{
if (it->m_threadComplete)
{
if (it->m_thread && it->m_thread->joinable())
{
it->m_thread->join();
}
it = m_activeThreads.erase(it);
}
else
{
++it;
}
}
}
}
@@ -0,0 +1,202 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SaveData/SaveDataRequestBus.h>
#include <SaveData_Traits_Platform.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/containers/list.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace SaveData
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! A system component providing functionality related to saving / loading persistent user data.
class SaveDataSystemComponent : public AZ::Component
, public SaveDataRequestBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// AZ::Component Setup
AZ_COMPONENT(SaveDataSystemComponent, "{35790061-347E-47F1-B803-9523752ECD39}");
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::ComponentDescriptor::GetIncompatibleServices
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
SaveDataSystemComponent() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~SaveDataSystemComponent() override = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Activate
void Activate() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Deactivate
void Deactivate() override;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataRequests::SaveDataBuffer
void SaveDataBuffer(const SaveDataBufferParams& saveDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataRequests::LoadDataBuffer
void LoadDataBuffer(const LoadDataBufferParams& loadDataBufferParams) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref SaveData::SaveDataRequests::SetSaveDataDirectoryPath
void SetSaveDataDirectoryPath(const char* saveDataDirectoryPath) override;
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Base class for platform specific implementations of the save data system component
class Implementation : public AZ::TickBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(Implementation, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
//! Default factory create function
//! \param[in] saveDataSystemComponent Reference to the parent being implemented
static Implementation* Create(SaveDataSystemComponent& saveDataSystemComponent);
////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] saveDataSystemComponent Reference to the parent being implemented
Implementation(SaveDataSystemComponent& saveDataSystemComponent);
////////////////////////////////////////////////////////////////////////////////////////
// Disable copying
AZ_DISABLE_COPY_MOVE(Implementation);
////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
virtual ~Implementation();
////////////////////////////////////////////////////////////////////////////////////////
//! Save a data buffer.
//! \param[in] saveDataBufferRequestParams The save data buffer request parameters.
virtual void SaveDataBuffer(const SaveDataBufferParams& saveDataBufferParams) = 0;
////////////////////////////////////////////////////////////////////////////////////////
//! Load a data buffer.
//! \param[in] loadDataBufferParams The load data buffer request parameters.
virtual void LoadDataBuffer(const LoadDataBufferParams& loadDataBufferParams) = 0;
////////////////////////////////////////////////////////////////////////////////////////
//! Set the path to the application's save data dircetory. Does nothing on some systems.
//! \param[in] saveDataDirectoryPath The path to the application's save data dircetory.
virtual void SetSaveDataDirectoryPath(const char* saveDataDirectoryPath) = 0;
protected:
////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to broadcast SaveDataNotifications::OnDataBufferSaved events in
//! addition to any callback specified when SaveDataRequests::SaveDataBuffer was called.
//! \param[in] dataBufferName The name of the data buffer that was saved.
//! \param[in] localUserId The local user id the data that was saved is associated with.
//! \param[in] result The result of the save data buffer request.
//! \param[in] callback The data buffer saved callback to invoke.
static void OnSaveDataBufferComplete(const AZStd::string& dataBufferName,
const AzFramework::LocalUserId localUserId,
const SaveDataRequests::OnDataBufferSaved& callback,
const SaveDataNotifications::Result& result);
////////////////////////////////////////////////////////////////////////////////////////
//! Save a data buffer to the file system.
//! \param[in] saveDataBufferRequestParams The save data buffer request parameters.
//! \param[in] absoluteFilePath The absolute file path where to save the data buffer.
//! \param[in] waitForCompletion Should we wait until the save data thread completes?
//! \param[in] useTemporaryFile Should we write to a temporary file that gets renamed?
void SaveDataBufferToFileSystem(const SaveDataBufferParams& saveDataBufferParams,
const AZStd::string& absoluteFilePath,
bool waitForCompletion = false,
bool useTemporaryFile = true);
////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to broadcast SaveDataNotifications::OnDataBufferLoaded events in
//! addition to any callback specified when SaveDataRequests::LoadDataBuffer was called.
//! \param[in] dataBuffer The data buffer that was loaded.
//! \param[in] dataBufferSize The size of the data buffer that was loaded.
//! \param[in] dataBufferName The name of the data buffer that was loaded.
//! \param[in] localUserId The local user id the data that was loaded is associated with.
//! \param[in] result The result of the load data buffer request.
//! \param[in] callback The data buffer loaded callback to invoke.
static void OnLoadDataBufferComplete(SaveDataNotifications::DataBuffer dataBuffer,
AZ::u64 dataBufferSize,
const AZStd::string& dataBufferName,
const AzFramework::LocalUserId localUserId,
const SaveDataRequests::OnDataBufferLoaded& callback,
const SaveDataNotifications::Result& result);
////////////////////////////////////////////////////////////////////////////////////////
//! Load a data buffer from the file system.
//! \param[in] loadDataBufferParams The load data buffer request parameters.
//! \param[in] absoluteFilePath The absolute file path from where to load the data buffer.
//! \param[in] waitForCompletion Should we wait until the load data thread completes?
void LoadDataBufferFromFileSystem(const LoadDataBufferParams& loadDataBufferParams,
const AZStd::string& absoluteFilePath,
bool waitForCompletion = false);
////////////////////////////////////////////////////////////////////////////////////////
//! Pairing of a save/load thread with an atomic bool indicating whether it is complete
struct ThreadCompletionPair
{
AZStd::unique_ptr<AZStd::thread> m_thread;
AZStd::atomic_bool m_threadComplete{ false };
};
////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::TickEvents::OnTick
void OnTick(float deltaTime, AZ::ScriptTimePoint scriptTimePoint) override;
////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to join all threads that are active
void JoinAllActiveThreads();
////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to join all threads that have been marked as completed
void JoinAllCompletedThreads();
////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZStd::mutex m_activeThreadsMutex; //! Mutex to restrict access to the active threads
AZStd::list<ThreadCompletionPair> m_activeThreads; //!< A container of active threads
SaveDataSystemComponent& m_saveDataSystemComponent; //!< Reference to the parent
};
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! Private pointer to the platform specific implementation
AZStd::unique_ptr<Implementation> m_pimpl;
};
}
+593
View File
@@ -0,0 +1,593 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SaveDataSystemComponent.h>
#include <AzTest/AzTest.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/UnitTest/TestTypes.h>
#include "SaveDataTest.h"
#if !AZ_TRAIT_DISABLE_ALL_SAVE_DATA_TESTS
void SaveDataTest::SetUp()
{
AllocatorsTestFixture::SetUp();
m_saveDataSystemComponent = AZStd::make_unique<SaveData::SaveDataSystemComponent>();
m_saveDataSystemComponent->Activate();
SetupInternal();
}
void SaveDataTest::TearDown()
{
TearDownInternal();
m_saveDataSystemComponent->Deactivate();
m_saveDataSystemComponent.reset();
AllocatorsTestFixture::TearDown();
}
class OnSavedHandler : public SaveData::SaveDataNotificationBus::Handler
{
public:
OnSavedHandler()
{
BusConnect();
}
void OnDataBufferSaved(const DataBufferSavedParams& dataBufferSavedParams) override
{
lastSavedParams = dataBufferSavedParams;
notificationReceived = true;
}
void OnDataBufferLoaded([[maybe_unused]] const DataBufferLoadedParams& dataBufferLoadedParams) override {}
DataBufferSavedParams lastSavedParams;
bool notificationReceived = false;
};
class OnLoadedHandler : public SaveData::SaveDataNotificationBus::Handler
{
public:
OnLoadedHandler()
{
BusConnect();
}
void OnDataBufferSaved([[maybe_unused]] const DataBufferSavedParams& dataBufferSavedParams) override {}
void OnDataBufferLoaded(const DataBufferLoadedParams& dataBufferLoadedParams) override
{
lastLoadedParams = dataBufferLoadedParams;
notificationReceived = true;
}
DataBufferLoadedParams lastLoadedParams;
bool notificationReceived = false;
};
const AZ::u64 testSaveDataSize = 9;
const char* testSaveDataName = "TestSaveData";
char testSaveData[testSaveDataSize] = {'a', 'b', 'c', '1', '2', '3', 'x', 'y', 'z'};
AZStd::string GetTestSaveDataCustomDirectoryNameRelative()
{
return "Amazon/Lumberyard/SaveDataTest";
}
#if defined(AZ_PLATFORM_WINDOWS)
# include <windows.h>
AZStd::string GetTestSaveDataCustomDirectoryNameAbsolute()
{
const DWORD bufferSize = 256;
char buffer[bufferSize] = {0};
GetTempPathA(bufferSize, buffer);
return buffer;
}
#else
AZStd::string GetTestSaveDataCustomDirectoryNameAbsolute()
{
return "/tmp";
}
#endif // defined(AZ_PLATFORM_WINDOWS)
void SaveTestDataBuffer(const AzFramework::LocalUserId& localUserId = AzFramework::LocalUserIdNone,
bool useDataBufferDeleterAzFree = false)
{
// Setup the save data params
SaveData::SaveDataRequests::SaveDataBufferParams params;
if (useDataBufferDeleterAzFree)
{
// The default deleter is DataBufferDeleterNone, which cannot be changed when calling unique_ptr::reset,
// but we are able to reset the pointer and assign a new deleter at the same time using move assignment.
void* testSaveDataAllocated = azmalloc(testSaveDataSize);
memcpy(testSaveDataAllocated, testSaveData, testSaveDataSize);
params.dataBuffer = SaveData::SaveDataRequests::DataBuffer(testSaveDataAllocated,
&SaveData::SaveDataRequests::DataBufferDeleterAzFree);
}
else
{
// The default deleter is DataBufferDeleterNone, so if we don't need to change it we can just call reset
params.dataBuffer.reset(testSaveData);
}
params.dataBufferSize = testSaveDataSize;
params.dataBufferName = testSaveDataName;
params.localUserId = localUserId;
params.callback = [localUserId](const SaveData::SaveDataNotifications::DataBufferSavedParams& onSavedParams)
{
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(onSavedParams.result == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(onSavedParams.dataBufferName == testSaveDataName);
EXPECT_TRUE(onSavedParams.localUserId == localUserId);
EXPECT_TRUE(onSavedParams.result == SaveData::SaveDataNotifications::Result::Success);
};
// Create the notification handler and send the save data request
OnSavedHandler onSavedHandler;
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SaveDataBuffer, params);
// Execute queued tick bus events until we receive the notification
while (!onSavedHandler.notificationReceived)
{
AZ::TickBus::ExecuteQueuedEvents();
}
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(onSavedHandler.lastSavedParams.result == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(onSavedHandler.lastSavedParams.dataBufferName == testSaveDataName);
EXPECT_TRUE(onSavedHandler.lastSavedParams.localUserId == localUserId);
EXPECT_TRUE(onSavedHandler.lastSavedParams.result == SaveData::SaveDataNotifications::Result::Success);
}
void LoadTestDataBuffer(const AzFramework::LocalUserId& localUserId = AzFramework::LocalUserIdNone)
{
// Setup the load data params
SaveData::SaveDataRequests::LoadDataBufferParams params;
params.dataBufferName = testSaveDataName;
params.localUserId = localUserId;
params.callback = [localUserId](const SaveData::SaveDataNotifications::DataBufferLoadedParams& onLoadedParams)
{
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(onLoadedParams.result == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(onLoadedParams.dataBuffer != nullptr);
EXPECT_TRUE(onLoadedParams.dataBufferName == testSaveDataName);
EXPECT_TRUE(onLoadedParams.dataBufferSize == testSaveDataSize);
EXPECT_TRUE(onLoadedParams.localUserId == localUserId);
EXPECT_TRUE(onLoadedParams.result == SaveData::SaveDataNotifications::Result::Success);
if (onLoadedParams.result == SaveData::SaveDataNotifications::Result::Success)
{
EXPECT_TRUE(memcmp(testSaveData, onLoadedParams.dataBuffer.get(), testSaveDataSize) == 0);
}
};
// Create the notification handler and send the load data request
OnLoadedHandler onLoadedHandler;
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::LoadDataBuffer, params);
// Execute queued tick bus events until we receive the notification
while (!onLoadedHandler.notificationReceived)
{
AZ::TickBus::ExecuteQueuedEvents();
}
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.result == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.dataBuffer != nullptr);
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.dataBufferName == testSaveDataName);
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.dataBufferSize == testSaveDataSize);
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.localUserId == localUserId);
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.result == SaveData::SaveDataNotifications::Result::Success);
if (onLoadedHandler.lastLoadedParams.result == SaveData::SaveDataNotifications::Result::Success)
{
EXPECT_TRUE(memcmp(testSaveData, onLoadedHandler.lastLoadedParams.dataBuffer.get(), testSaveDataSize) == 0);
}
}
TEST_F(SaveDataTest, SaveDataBuffer)
{
SaveTestDataBuffer();
}
TEST_F(SaveDataTest, LoadDataBuffer)
{
LoadTestDataBuffer();
}
TEST_F(SaveDataTest, SaveDataBufferForUser)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveTestDataBuffer(userId);
}
TEST_F(SaveDataTest, LoadDataBufferForUser)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
LoadTestDataBuffer(userId);
}
TEST_F(SaveDataTest, SaveDataBufferToCustomDirectoryRelative)
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
SaveTestDataBuffer();
}
TEST_F(SaveDataTest, LoadDataBufferFromCustomDirectoryRelative)
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
LoadTestDataBuffer();
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_SaveDataBufferToCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, SaveDataBufferToCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
SaveTestDataBuffer();
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_LoadDataBufferFromCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, LoadDataBufferFromCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
LoadTestDataBuffer();
}
TEST_F(SaveDataTest, SaveDataBufferForUserToCustomDirectoryRelative)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
SaveTestDataBuffer(userId);
}
TEST_F(SaveDataTest, LoadDataBufferForUserFromCustomDirectoryRelative)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
LoadTestDataBuffer(userId);
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_SaveDataBufferForUserToCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, SaveDataBufferForUserToCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
SaveTestDataBuffer(userId);
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_LoadDataBufferForUserFromCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, LoadDataBufferForUserFromCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
LoadTestDataBuffer(userId);
}
TEST_F(SaveDataTest, SaveDataBufferUsingDataBufferDeleterAzFree)
{
SaveTestDataBuffer(AzFramework::LocalUserIdNone, true);
}
class TestObject
{
public:
virtual ~TestObject() = default;
static constexpr const char* DataBufferName = "TestSaveObject";
AZ_TYPE_INFO(TestObject, "{9CE29971-8FE2-41FF-AD5B-CB15F1B92834}");
static void Reflect(AZ::SerializeContext& sc)
{
sc.Class<TestObject>()
->Version(1)
->Field("testString", &TestObject::testString)
->Field("testFloat", &TestObject::testFloat)
->Field("testInt", &TestObject::testInt)
->Field("testBool", &TestObject::testBool)
;
}
bool operator==(const TestObject& other) const
{
return (testString == other.testString) &&
(testFloat == other.testFloat) &&
(testInt == other.testInt) &&
(testBool == other.testBool);
}
void SetNonDefaultValues()
{
testString = "NonDefaultString";
testFloat = 9.9f;
testInt = 1234567890;
testBool = true;
}
AZStd::string testString;
float testFloat = 0.0f;
int testInt = 0;
bool testBool = false;
};
void SaveTestObject(const AzFramework::LocalUserId& localUserId = AzFramework::LocalUserIdNone)
{
// Reflect the test object.
AZ::SerializeContext serializeContext;
TestObject::Reflect(serializeContext);
// Create a test object and change the default values.
TestObject defaultTestObject;
AZStd::shared_ptr<TestObject> testObject = AZStd::make_shared<TestObject>();
EXPECT_TRUE(*testObject == defaultTestObject);
testObject->SetNonDefaultValues();
EXPECT_FALSE(*testObject == defaultTestObject);
// Setup the save data params
SaveData::SaveDataRequests::SaveOrLoadObjectParams<TestObject> params;
params.serializableObject = testObject;
params.serializeContext = &serializeContext;
params.dataBufferName = TestObject::DataBufferName;
params.localUserId = localUserId;
params.callback = [params](const SaveData::SaveDataRequests::SaveOrLoadObjectParams<TestObject>& callbackParams,
SaveData::SaveDataNotifications::Result callbackResult)
{
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (params.localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(callbackResult == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(callbackResult == SaveData::SaveDataNotifications::Result::Success);
EXPECT_TRUE(*(callbackParams.serializableObject) == *(params.serializableObject));
EXPECT_TRUE(callbackParams.serializableObject == params.serializableObject);
EXPECT_TRUE(callbackParams.serializeContext == params.serializeContext);
EXPECT_TRUE(callbackParams.dataBufferName == params.dataBufferName);
EXPECT_TRUE(callbackParams.localUserId == params.localUserId);
};
// Create the notification handler and send the save data request
OnSavedHandler onSavedHandler;
SaveData::SaveDataRequests::SaveObject(params);
// Execute queued tick bus events until we receive the notification
while (!onSavedHandler.notificationReceived)
{
AZ::TickBus::ExecuteQueuedEvents();
}
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (params.localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(onSavedHandler.lastSavedParams.result == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(onSavedHandler.lastSavedParams.dataBufferName == TestObject::DataBufferName);
EXPECT_TRUE(onSavedHandler.lastSavedParams.localUserId == localUserId);
EXPECT_TRUE(onSavedHandler.lastSavedParams.result == SaveData::SaveDataNotifications::Result::Success);
}
void LoadTestObject(const AzFramework::LocalUserId& localUserId = AzFramework::LocalUserIdNone)
{
// Reflect the test object.
AZ::SerializeContext serializeContext;
TestObject::Reflect(serializeContext);
// Create a test object to load.
TestObject defaultTestObject;
TestObject nonDefaultTestObject;
nonDefaultTestObject.SetNonDefaultValues();
AZStd::shared_ptr<TestObject> testObject = AZStd::make_shared<TestObject>();
EXPECT_TRUE(*testObject == defaultTestObject);
EXPECT_FALSE(*testObject == nonDefaultTestObject);
// Setup the load data params
SaveData::SaveDataRequests::SaveOrLoadObjectParams<TestObject> params;
params.serializableObject = testObject;
params.serializeContext = &serializeContext;
params.dataBufferName = TestObject::DataBufferName;
params.localUserId = localUserId;
params.callback = [params, defaultTestObject, nonDefaultTestObject]
(const SaveData::SaveDataRequests::SaveOrLoadObjectParams<TestObject>& callbackParams,
SaveData::SaveDataNotifications::Result callbackResult)
{
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (params.localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(callbackResult == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(callbackResult == SaveData::SaveDataNotifications::Result::Success);
EXPECT_TRUE(*(callbackParams.serializableObject) == *(params.serializableObject));
EXPECT_FALSE(*(callbackParams.serializableObject) == defaultTestObject);
EXPECT_TRUE(*(callbackParams.serializableObject) == nonDefaultTestObject);
EXPECT_TRUE(callbackParams.serializableObject == params.serializableObject);
EXPECT_TRUE(callbackParams.serializeContext == params.serializeContext);
EXPECT_TRUE(callbackParams.dataBufferName == params.dataBufferName);
EXPECT_TRUE(callbackParams.localUserId == params.localUserId);
};
// Create the notification handler and send the load data request
OnLoadedHandler onLoadedHandler;
SaveData::SaveDataRequests::LoadObject(params);
// Execute queued tick bus events until we receive the notification
while (!onLoadedHandler.notificationReceived)
{
AZ::TickBus::ExecuteQueuedEvents();
}
#if AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
if (params.localUserId == AzFramework::LocalUserIdNone)
{
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.result == SaveData::SaveDataNotifications::Result::ErrorUnknownUser);
return;
}
#endif // AZ_TRAIT_SAVEDATA_TEST_REQUIRES_SPECIFIC_USER_ID
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.dataBuffer != nullptr);
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.dataBufferName == TestObject::DataBufferName);
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.localUserId == localUserId);
EXPECT_TRUE(onLoadedHandler.lastLoadedParams.result == SaveData::SaveDataNotifications::Result::Success);
}
TEST_F(SaveDataTest, SaveObject)
{
SaveTestObject();
}
TEST_F(SaveDataTest, LoadObject)
{
LoadTestObject();
}
TEST_F(SaveDataTest, SaveObjectForUser)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveTestObject(userId);
}
TEST_F(SaveDataTest, LoadObjectForUser)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
LoadTestObject(userId);
}
TEST_F(SaveDataTest, SaveObjectToCustomDirectoryRelative)
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
SaveTestObject();
}
TEST_F(SaveDataTest, LoadObjectFromCustomDirectoryRelative)
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
LoadTestObject();
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_SaveObjectToCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, SaveObjectToCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
SaveTestObject();
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_LoadObjectFromCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, LoadObjectFromCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
LoadTestObject();
}
TEST_F(SaveDataTest, SaveObjectForUserToCustomDirectoryRelative)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
SaveTestObject(userId);
}
TEST_F(SaveDataTest, LoadObjectForUserFromCustomDirectoryRelative)
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameRelative().c_str());
LoadTestObject(userId);
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_SaveObjectForUserToCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, SaveObjectForUserToCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
SaveTestObject(userId);
}
#if AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
TEST_F(SaveDataTest, DISABLED_LoadObjectForUserFromCustomDirectoryAbsolute)
#else
TEST_F(SaveDataTest, LoadObjectForUserFromCustomDirectoryAbsolute)
#endif // AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS
{
AzFramework::LocalUserId userId = SaveDataTest::GetDefaultTestUserId();
SaveData::SaveDataRequestBus::Broadcast(&SaveData::SaveDataRequests::SetSaveDataDirectoryPath,
GetTestSaveDataCustomDirectoryNameAbsolute().c_str());
LoadTestObject(userId);
}
#endif // !AZ_TRAIT_DISABLE_ALL_SAVE_DATA_TESTS
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
+36
View File
@@ -0,0 +1,36 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Input/User/LocalUserId.h>
#include <SaveDataSystemComponent.h>
class SaveDataTest
: public UnitTest::AllocatorsTestFixture
{
public:
static AzFramework::LocalUserId GetDefaultTestUserId();
protected:
void SetUp() override;
void TearDown() override;
void SetupInternal();
void TearDownInternal();
private:
AZStd::unique_ptr<SaveData::SaveDataSystemComponent> m_saveDataSystemComponent;
};
+17
View File
@@ -0,0 +1,17 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Include/SaveData/SaveDataNotificationBus.h
Include/SaveData/SaveDataRequestBus.h
Source/SaveDataSystemComponent.cpp
Source/SaveDataSystemComponent.h
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/SaveDataModule.cpp
)
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Tests/SaveDataTest.h
Tests/SaveDataTest.cpp
)