You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/SaveData/Code/Include/SaveData/SaveDataRequestBus.h

266 lines
15 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#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