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/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceTransaction.h

313 lines
17 KiB
C++

/*
* 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 <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.h>
namespace AZ
{
class Entity;
class SerializeContext;
}
namespace AzToolsFramework
{
namespace SliceUtilities
{
/**
* Utility class for performing transactional operations on slices, such as creating or pushing changes to slices.
* Use SliceTransaction::BeginNewSlice or SliceTransaction::BeginSlicePush to create a new transaction.
* See the above methods' API documentation for more information about populating and committing transactions.
*
* A single transaction should not be interacted with simultaneously from different threads.
* However, it is safe to pass a transaction pointer across threads, to jobs, etc.
*/
class SliceTransaction
{
public:
AZ_CLASS_ALLOCATOR(SliceTransaction, AZ::SystemAllocator, 0);
typedef AZ::Outcome<void, AZStd::string> Result;
typedef AZStd::intrusive_ptr<SliceTransaction> TransactionPtr;
typedef AZ::Data::Asset<AZ::SliceAsset> SliceAssetPtr;
typedef AZStd::function<Result(TransactionPtr, const char*, SliceAssetPtr&)> PreSaveCallback;
typedef AZStd::function<void(TransactionPtr, const char*, const SliceAssetPtr&)> PostSaveCallback;
friend struct AZStd::IntrusivePtrCountPolicy<SliceTransaction>;
/**
* Flags passed to BeginNewSlice().
*/
enum SliceCreationFlags
{
CreateAsDynamic = (1<<0),
};
/**
* Flags passed to BeginSlicePush().
*/
enum SlicePushFlags
{
};
/**
* Flags passed to AddEntity().
*/
enum SliceAddEntityFlags
{
DiscardSliceAncestry = (1<<0), ///< Adds the entity as a loose entity, detaching it from any existing slice hierarchy, meaning it will no longer inherit changes to any slice instances it was part of
};
/**
* Flags passed to Commit().
*/
enum SliceCommitFlags
{
DisableUndoCapture = (1<<0), ///< Disables undo batches from being created within the transaction
};
~SliceTransaction();
/**
* Begin a transaction for creating a new slice.
* Entities and nested slice instances can be added via AddEntity() and AddSliceInstance().
* Use AddEntity() to add new entities to the slice.
* Use AddSliceInstance() to add new nested slice instances.
* \param name - Optional internal naming for slice. Will use "Slice" if none is provided.
* \param serializeContext - Optional serialize context instance. Global serialize context will be used if none is provided.
* \param sliceCreationFlags - See \ref SliceCreationFlags
* \return Always returns a valid pointer to the new transaction.
*/
static TransactionPtr BeginNewSlice(const char* name, AZ::SerializeContext* serializeContext = nullptr, AZ::u32 sliceCreationFlags = 0);
/**
* Begin a transaction for overwriting a slice with another slice component
* \param asset - Pointer to Slice asset being overwritten
* \param overwriteComponent - Slice component containing overwrite data
* \param serializeContext - Optional serialize context instance. Global serialize context will be used if none is provided.
* \return pointer to the new transaction, is null if the specified asset is invalid.
*/
static TransactionPtr BeginSliceOverwrite(const SliceAssetPtr& asset, const AZ::SliceComponent& overwriteComponent, AZ::SerializeContext* serializeContext = nullptr);
/**
* Begin a transaction for pushing changes to an existing slice asset.
* Use AddEntity() to add new entities to the slice.
* Use AddSliceInstance() to add new nested slice instances.
* Use UpdateEntity() to update whole existing entities.
* Use UpdateEntityField() to update a single field on an existing entity.
* \param asset - Pointer to slice asset to which changes are being pushed.
* \param serializeContext - Optional serialize context instance. Global serialize context will be used if none is provided.
* \param slicePushFlags - See \ref SlicePushFlags
* \return pointer to the new transaction, is null if the specified asset is invalid.
*/
static TransactionPtr BeginSlicePush(const SliceAssetPtr& asset, AZ::SerializeContext* serializeContext = nullptr, AZ::u32 slicePushFlags = 0);
/**
* For push transactions only: Adds a live entity to the transaction. Entity's data will be pushed to its ancestor in the slice.
* \param entity - live entity to update.
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result UpdateEntity(AZ::Entity* entity);
/**
* For push transactions only: Adds a live entity to the transaction. Entity's data will be pushed to its ancestor in the slice.
* \param entity - live entity to update.
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result UpdateEntity(const AZ::EntityId& entityId);
/**
* For push transactions only: Adds a live entity to the transaction, but with a specific field address. Field data will be pushed to its ancestor in the slice.
* \param entity - live entity to update.
* \param fieldNodeAddress - InstanceDataHierarchy address of the field to push to the entity in the slice.
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result UpdateEntityField(AZ::Entity* entity, const InstanceDataNode::Address& fieldNodeAddress);
/**
* For push transactions only: Adds a live entity to the transaction, but with a specific field address. Field data will be pushed to its ancestor in the slice.
* \param entity - live entity to update.
* \param fieldNodeAddress - InstanceDataHierarchy address of the field to push to the entity in the slice.
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result UpdateEntityField(const AZ::EntityId& entityId, const InstanceDataNode::Address& fieldNodeAddress);
/**
* For new slice or push transactions. Adds a new entity to the target slice, keeping slice ancestry by default if it is part
* of a slice. Use SliceAddEntityFlags::DiscardSliceAncestry to add as a loose entity.
* \param entity - live entity to add.
* \param addEntityFlags - \ref SliceAddEntityFlags
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result AddEntity(const AZ::Entity* entity, AZ::u32 addEntityFlags = 0);
/**
* For new slice or push transactions. Adds a new entity to the target slice, keeping slice ancestry by default if it is part
* of a slice. Use SliceAddEntityFlags::DiscardSliceAncestry to add as a loose entity.
* \param entity - live entity to add.
* \param addEntityFlags - \ref SliceAddEntityFlags
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result AddEntity(AZ::EntityId entityId, AZ::u32 addEntityFlags = 0);
/**
* For push transactions only: Removes an existing entity from the slice.
* \param entity - live entity whose ancestor should be removed from the slice, or entity directly in target slice to remove.
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result RemoveEntity(AZ::Entity* entity);
/**
* For push transactions only: Removes an existing entity from the slice.
* \param entity - live entity whose ancestor should be removed from the slice, or entity directly in target slice to remove.
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result RemoveEntity(AZ::EntityId entityId);
/**
* For new slice or push transactions. Adds a live slice instance to be nested in the target slice.
* \param sliceAddress - pointer to live slice instance.
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result AddSliceInstance(const AZ::SliceComponent::SliceInstanceAddress& sliceAddress);
/**
* Completes and commits the transaction to disk at the specified location.
* \param fullPath - full path to which the asset will be written.
* \param preSaveCallback - user providable callback to pre-process the asset just prior to writing to disk.
* \param postSaveCallback - user providable callback invoked just after the asset is written to disk.
* \param sliceCommitFlags - \ref SliceCommitFlags
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result Commit(const char* fullPath, PreSaveCallback preSaveCallback, PostSaveCallback postSaveCallback, AZ::u32 sliceCommitFlags = 0);
/**
* Completes and commits the transaction to disk at the specified location.
* \param targetAssetId - Asset Id to be committed.
* \param preSaveCallback - user providable callback to pre-process the asset just prior to writing to disk.
* \param postSaveCallback - user providable callback invoked just after the asset is written to disk.
* \param sliceCommitFlags - \ref SliceCommitFlags
* \return Result, AZ::Success if successful, otherwise AZ::Failure. Use GetError() to retrieve string describing error.
*/
Result Commit(const AZ::Data::AssetId& targetAssetId, PreSaveCallback preSaveCallback, PostSaveCallback postSaveCallback, AZ::u32 sliceCommitFlags = SliceCommitFlags::DisableUndoCapture);
/**
* Retrieves an EntityId->EntityId map from the live entities that were added to the slice individually or as instances,
* to the Ids of the corresponding ancestor within the target slice.
* This can be useful if after creating a slice, you'd like a full mapping from the live entities used to create the slice
* to their respective entities in the asset.
*/
const AZ::SliceComponent::EntityIdToEntityIdMap& GetLiveToAssetEntityIdMap() const;
bool AddLiveToAssetEntityIdMapping(const AZStd::pair<AZ::EntityId, AZ::EntityId>& mapping);
const AZ::SliceComponent::EntityIdToEntityIdMap& GetAddedEntityIdRemaps() const;
/**
* Retrieves a pointer to the target asset. This is a modified clone of the original target asset that shares the correct asset id.
*/
const SliceAssetPtr& GetTargetAsset() const { return m_targetAsset; }
/**
* Retrieves a pointer to the original target asset.
* If this is a new slice operation it will be an empty asset as there was no original target.
* If this is to update an existing slice it will represent the slice asset being updated.
*/
const SliceAssetPtr& GetOriginalTargetAsset() const { return m_originalTargetAsset; }
private:
SliceTransaction(AZ::SerializeContext* serializeContext = nullptr);
/// Clone asset in preparation for final write. PreSave operations will be applied to the clone.
SliceAssetPtr CloneAssetForSave();
/// Applies enabled pre-save behavior, and invokes user pre-save callback.
Result PreSave(const char* fullPath, SliceAssetPtr& asset, PreSaveCallback preSaveCallback, AZ::u32 sliceCommitFlags);
/// Locate an entity's corresponding ancestor in the transaction's target slice.
/// If the ancestor is found, the corresponding Id entry is added to the provided idMap.
AZ::EntityId FindTargetAncestorAndUpdateInstanceIdMap(AZ::EntityId id, AZ::SliceComponent::EntityIdToEntityIdMap& idMap, const AZ::SliceComponent::SliceInstanceAddress* ignoreSliceInstance = nullptr) const;
/// Resets the transaction.
void Reset();
/// intrusive_ptr
void add_ref();
void release();
private:
SliceTransaction(const SliceTransaction&) = delete;
SliceTransaction& operator=(const SliceTransaction&) = delete;
enum class TransactionType
{
None,
NewSlice,
UpdateSlice,
OverwriteSlice,
};
struct EntityToPush
{
EntityToPush(AZ::EntityId targetEntityId, AZ::EntityId sourceEntityId, const InstanceDataNode::Address& nodeAddress = InstanceDataNode::Address())
: m_targetEntityId(targetEntityId)
, m_sourceEntityId(sourceEntityId)
, m_fieldNodeAddress(nodeAddress) {}
AZ::EntityId m_targetEntityId;
AZ::EntityId m_sourceEntityId;
InstanceDataNode::Address m_fieldNodeAddress;
};
struct SliceInstanceToPush
{
SliceInstanceToPush()
: m_includeEntireInstance(false)
, m_instanceAddress(nullptr, nullptr)
{}
bool m_includeEntireInstance; ///< Whether to include all entities of the instance
AZStd::unordered_set<AZ::EntityId> m_entitiesToInclude; ///< If m_includeEntireInstance == false, the entities we want to include
AZ::SliceComponent::SliceInstanceAddress m_instanceAddress; ///< Source slice instance address
};
typedef AZStd::unordered_map<AZ::SliceComponent::SliceInstanceAddress, SliceInstanceToPush> SliceInstancesToPushMap;
TransactionType m_transactionType;
AZ::SerializeContext* m_serializeContext;
SliceAssetPtr m_originalTargetAsset; ///< For Slice Pushes, the original in-memory asset passed to BeginSlicePush
SliceAssetPtr m_targetAsset; ///< The asset in-memory that the transaction is making changes to (for creation, new one, for pushes, clone of assetToReplace)
SliceInstancesToPushMap m_addedSliceInstances;
AZStd::vector<EntityToPush> m_entitiesToPush;
AZStd::vector<AZ::EntityId> m_entitiesToRemove;
AZ::SliceComponent::EntityIdToEntityIdMap m_liveToAssetIdMap;
bool m_hasEntityAdds = false;///< Whether entities have been added as part of this transaction
AZStd::unordered_map<AZ::EntityId, AZ::EntityId> m_addedEntityIdRemaps;
AZStd::atomic_int m_refCount; // intrusive_ptr
};
} // namespace Slice
} // namespace AzToolsFramework