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
@@ -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;
};
}