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,192 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_APPLICATIONAPI_H
#define AZFRAMEWORK_APPLICATIONAPI_H
#pragma once
#include <AzCore/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/Component.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/CommandLine/CommandLine.h>
namespace AZ
{
class Entity;
class ComponentApplication;
namespace Data
{
class AssetDatabase;
}
}
namespace AzFramework
{
class ApplicationRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - Application is a singleton
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//////////////////////////////////////////////////////////////////////////
ApplicationRequests() = default;
~ApplicationRequests() = default;
using Bus = AZ::EBus<ApplicationRequests>;
typedef AZStd::recursive_mutex MutexType;
/// Fixup slashes and lowercase path.
virtual void NormalizePath(AZStd::string& /*path*/) = 0;
/// Fixup slashes.
virtual void NormalizePathKeepCase(AZStd::string& /*path*/) = 0;
/// Make path relative, based on the application root.
virtual void MakePathRootRelative(AZStd::string& /*fullPath*/) {}
/// Make path relative, based on the asset root.
virtual void MakePathAssetRootRelative(AZStd::string& /*fullPath*/) {}
/// Make path relative to the provided root.
virtual void MakePathRelative(AZStd::string& /*fullPath*/, const char* /*rootPath*/) {}
/// Retrieves the asset root path for the application.
virtual const char* GetAssetRoot() const { return nullptr; }
/// Gets the engine root path where the modules for the current engine are located.
virtual const char* GetEngineRoot() const { return nullptr; }
/// Retrieves the app root path for the application.
virtual const char* GetAppRoot() const { return nullptr; }
/// Sets the asset root path for the application.
virtual void SetAssetRoot(const char* /*assetRoot*/) {}
#pragma push_macro("GetCommandLine")
#undef GetCommandLine
/// Get the Command Line arguments passed in.
virtual const CommandLine* GetCommandLine() { return nullptr; }
#pragma pop_macro("GetCommandLine")
/// Get the Command Line arguments passed in. (Avoids collisions with platform specific macros.)
virtual const CommandLine* GetApplicationCommandLine() { return nullptr; }
/// Pump the system event loop once, regardless of whether there are any events to process.
virtual void PumpSystemEventLoopOnce() {}
/// Pump the system event loop until there are no events left to process.
virtual void PumpSystemEventLoopUntilEmpty() {}
/// Execute a function in a new thread and pump the system event loop at the specified frequency until the thread returns.
virtual void PumpSystemEventLoopWhileDoingWorkInNewThread(const AZStd::chrono::milliseconds& /*eventPumpFrequency*/,
const AZStd::function<void()>& /*workForNewThread*/,
const char* /*newThreadName*/) {}
/// Run the main loop until ExitMainLoop is called.
virtual void RunMainLoop() {}
/// Request to exit the main loop.
virtual void ExitMainLoop() {}
/// Returns true is ExitMainLoop has been called, false otherwise.
virtual bool WasExitMainLoopRequested() { return false; }
/// Terminate the application due to an error
virtual void TerminateOnError(int errorCode) { exit(errorCode); }
/// Resolve a path thats relative to the engine folder to an absolute path
virtual void ResolveEnginePath(AZStd::string& /*engineRelativePath*/) const {}
/// Calculate the branch token from the current application's asset root
virtual void CalculateBranchTokenForAppRoot(AZStd::string& token) const = 0;
/*!
* Returns a Type Uuid of the component for the given componentId and entityId.
* if no component matches the entity and component Id pair, a Null Uuid is returned
* \param entityId - the Id of the entity containing the component
* \param componentId - the Id of the component whose TypeId you wish to get
*/
virtual AZ::Uuid GetComponentTypeId(const AZ::EntityId& entityId, const AZ::ComponentId& componentId) { (void)entityId; (void)componentId; return AZ::Uuid::CreateNull(); };
template<typename ComponentFactoryType>
void RegisterComponentType()
{
RegisterComponent(new ComponentFactoryType());
}
};
class ApplicationLifecycleEvents
: public AZ::EBusTraits
{
public:
enum class Event
{
None = 0,
Unconstrain,
Constrain,
Suspend,
Resume
};
// Bus Configuration
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~ApplicationLifecycleEvents() {}
using Bus = AZ::EBus<ApplicationLifecycleEvents>;
// AzFramework::Application listens for all the system specific
// events, and translates them into the system independent ones
// defined here. Applications are free to listen to one or both
// sets of events but responding just to the system independent
// ones should be sufficient for most applications.
virtual void OnApplicationConstrained(Event /*lastEvent*/) {}
virtual void OnApplicationUnconstrained(Event /*lastEvent*/) {}
virtual void OnApplicationSuspended(Event /*lastEvent*/) {}
virtual void OnApplicationResumed(Event /*lastEvent*/) {}
virtual void OnMobileApplicationWillTerminate() {}
virtual void OnMobileApplicationLowMemoryWarning() {}
// Events triggered when the application window has been
// created/destoryed. This is currently only supported
// on Android so the renderer can correctly manage the
// rendering context.
virtual void OnApplicationWindowCreated() {}
virtual void OnApplicationWindowDestroy() {}
// Event triggered when an orientation change occurs.
// This is currently only supported on Android so the
// renderer can handle orientation changes.
virtual void OnApplicationWindowRedrawNeeded() {}
// Event triggered when the application is about to stop.
// This is useful to unload certain resources before any entities are destroyed.
virtual void OnApplicationAboutToStop() {}
};
} // namespace AzFramework
#endif // AZFRAMEWORK_APPLICATIONAPI_H
@@ -0,0 +1,26 @@
/*
* 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/RTTI/RTTI.h>
namespace AzFramework
{
class AtomActiveInterface
{
public:
AZ_RTTI(AtomActiveInterface, "{4BB59C86-0848-485D-AB28-700540470B2B}");
AtomActiveInterface() = default;
virtual ~AtomActiveInterface() = default;
};
} // namespace AzFramework
@@ -0,0 +1,799 @@
/*
* 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/PlatformIncl.h> // This should be the first include to make sure Windows.h is defined with NOMINMAX
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Slice/SliceSystemComponent.h>
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/Serialization/DataPatch.h>
#include <AzCore/Debug/FrameProfilerComponent.h>
#include <AzCore/NativeUI/NativeUISystemComponent.h>
#include <AzCore/Module/ModuleManagerBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <AzFramework/Asset/AssetBundleManifest.h>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/Asset/CustomAssetTypeComponent.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/Asset/AssetRegistry.h>
#include <AzFramework/Components/ConsoleBus.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Entity/BehaviorEntity.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzFramework/FileTag/FileTagComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/IO/RemoteStorageDrive.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Physics/Utils.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzFramework/Archive/Archive.h>
#include <AzFramework/Archive/ArchiveFileIO.h>
#include <AzFramework/Script/ScriptRemoteDebugging.h>
#include <AzFramework/Script/ScriptComponent.h>
#include <AzFramework/StreamingInstall/StreamingInstall.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Driller/RemoteDrillerInterface.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzCore/Console/Console.h>
#include <AzFramework/Viewport/ViewportBus.h>
#include <GridMate/Memory.h>
#include "Application.h"
#include <AzFramework/AzFrameworkModule.h>
#include <cctype>
#include <stdio.h>
static const char* s_azFrameworkWarningWindow = "AzFramework";
static const char* s_engineConfigFileName = "engine.json";
static const char* s_engineConfigEngineVersionKey = "LumberyardVersion";
namespace AzFramework
{
namespace ApplicationInternal
{
// A Helper function that can load an app descriptor from file.
AZ::Outcome<AZStd::unique_ptr<AZ::ComponentApplication::Descriptor>, AZStd::string> LoadDescriptorFromFilePath(const char* appDescriptorFilePath, AZ::SerializeContext& serializeContext)
{
AZStd::unique_ptr<AZ::ComponentApplication::Descriptor> loadedDescriptor;
AZ::IO::SystemFile appDescriptorFile;
if (!appDescriptorFile.Open(appDescriptorFilePath, AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
{
return AZ::Failure(AZStd::string::format("Failed to open file: %s", appDescriptorFilePath));
}
AZ::IO::SystemFileStream appDescriptorFileStream(&appDescriptorFile, true);
if (!appDescriptorFileStream.IsOpen())
{
return AZ::Failure(AZStd::string::format("Failed to stream file: %s", appDescriptorFilePath));
}
// Callback function for allocating the root elements in the file.
AZ::ObjectStream::InplaceLoadRootInfoCB inplaceLoadCb =
[](void** rootAddress, const AZ::SerializeContext::ClassData**, const AZ::Uuid& classId, AZ::SerializeContext*)
{
if (rootAddress && classId == azrtti_typeid<AZ::ComponentApplication::Descriptor>())
{
// ComponentApplication::Descriptor is normally a singleton.
// Force a unique instance to be created.
*rootAddress = aznew AZ::ComponentApplication::Descriptor();
}
};
// Callback function for saving the root elements in the file.
AZ::ObjectStream::ClassReadyCB classReadyCb =
[&loadedDescriptor](void* classPtr, const AZ::Uuid& classId, AZ::SerializeContext* context)
{
// Save descriptor, delete anything else loaded from file.
if (classId == azrtti_typeid<AZ::ComponentApplication::Descriptor>())
{
loadedDescriptor.reset(static_cast<AZ::ComponentApplication::Descriptor*>(classPtr));
}
else if (const AZ::SerializeContext::ClassData* classData = context->FindClassData(classId))
{
classData->m_factory->Destroy(classPtr);
}
else
{
AZ_Error("Application", false, "Unexpected type %s found in application descriptor file. This memory will leak.",
classId.ToString<AZStd::string>().c_str());
}
};
// There's other stuff in the file we may not recognize (system components), but we're not interested in that stuff.
AZ::ObjectStream::FilterDescriptor loadFilter(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_IGNORE_UNKNOWN_CLASSES);
if (!AZ::ObjectStream::LoadBlocking(&appDescriptorFileStream, serializeContext, classReadyCb, loadFilter, inplaceLoadCb))
{
return AZ::Failure(AZStd::string::format("Failed to load objects from file: %s", appDescriptorFilePath));
}
if (!loadedDescriptor)
{
return AZ::Failure(AZStd::string::format("Failed to find descriptor object in file: %s", appDescriptorFilePath));
}
return AZ::Success(AZStd::move(loadedDescriptor));
}
}
Application::Application()
: Application(nullptr, nullptr)
{
}
Application::Application(int* argc, char*** argv)
: ComponentApplication(
argc ? *argc : 0,
argv ? *argv : nullptr
)
{
// Startup default local FileIO (hits OSAllocator) if not already setup.
if (AZ::IO::FileIOBase::GetDirectInstance() == nullptr)
{
m_directFileIO = AZStd::make_unique<AZ::IO::LocalFileIO>();
AZ::IO::FileIOBase::SetDirectInstance(m_directFileIO.get());
}
// Initializes the IArchive for reading archive(.pak) files
if (auto archive = AZ::Interface<AZ::IO::IArchive>::Get(); !archive)
{
m_archive = AZStd::make_unique<AZ::IO::Archive>();
AZ::Interface<AZ::IO::IArchive>::Register(m_archive.get());
}
// Set the ArchiveFileIO as the default FileIOBase instance
if (AZ::IO::FileIOBase::GetInstance() == nullptr)
{
m_archiveFileIO = AZStd::make_unique<AZ::IO::ArchiveFileIO>(m_archive.get());
AZ::IO::FileIOBase::SetInstance(m_archiveFileIO.get());
SetFileIOAliases();
}
ApplicationRequests::Bus::Handler::BusConnect();
AZ::UserSettingsFileLocatorBus::Handler::BusConnect();
NetSystemRequestBus::Handler::BusConnect();
}
Application::~Application()
{
if (m_isStarted)
{
Stop();
}
NetSystemRequestBus::Handler::BusDisconnect();
AZ::UserSettingsFileLocatorBus::Handler::BusDisconnect();
ApplicationRequests::Bus::Handler::BusDisconnect();
// Unset the Archive file IO if it is set as the direct instance
if (AZ::IO::FileIOBase::GetInstance() == m_archiveFileIO.get())
{
AZ::IO::FileIOBase::SetInstance(nullptr);
}
m_archiveFileIO.reset();
// Destroy the IArchive instance
if (AZ::Interface<AZ::IO::IArchive>::Get() == m_archive.get())
{
AZ::Interface<AZ::IO::IArchive>::Unregister(m_archive.get());
}
m_archive.reset();
// Unset the Local file IO if it is set as the direct instance
if (AZ::IO::FileIOBase::GetDirectInstance() == m_directFileIO.get())
{
AZ::IO::FileIOBase::SetDirectInstance(nullptr);
}
// Destroy the Direct instance after the IArchive has been destroyed
// Archive classes relies on the FileIOBase DirectInstance to close
// files properly
m_directFileIO.reset();
// The AZ::Console skips destruction and always leaks to allow it to be used in static memory
}
void Application::Start(const Descriptor& descriptor, const StartupParameters& startupParameters)
{
AZ::Entity* systemEntity = Create(descriptor, startupParameters);
// Attempt to use the "CacheGameFolder" key in the settings registry to set the asset root
// If that fails, fallback to using the App root as the asset root
if (!m_settingsRegistry->Get(m_assetRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheGameFolder))
{
m_assetRoot = GetAppRoot();
}
// Sets FileIOAliases again in case the App root was overridden by the
// startupParamets in ComponentApplication::Create
SetFileIOAliases();
if (systemEntity)
{
StartCommon(systemEntity);
}
}
void Application::StartCommon(AZ::Entity* systemEntity)
{
m_pimpl.reset(Implementation::Create());
systemEntity->Init();
systemEntity->Activate();
AZ_Assert(systemEntity->GetState() == AZ::Entity::State::Active, "System Entity failed to activate.");
m_isStarted = true;
}
void Application::PreModuleLoad()
{
// Calculate the engine root by reading the engine.json file
AZStd::string engineJsonPath = AZStd::string_view{ m_appRoot };
engineJsonPath += s_engineConfigFileName;
AzFramework::StringFunc::Path::Normalize(engineJsonPath);
AZ::IO::LocalFileIO localFileIO;
auto readJsonResult = AzFramework::FileFunc::ReadJsonFile(engineJsonPath, &localFileIO);
if (readJsonResult.IsSuccess())
{
SetRootPath(RootPathType::EngineRoot, m_appRoot.c_str());
AZ_TracePrintf(s_azFrameworkWarningWindow, "Engine Path: %s\n", m_engineRoot.c_str());
}
else
{
// If there is any problem reading the engine.json file, then default to engine root to the app root
AZ_Warning(s_azFrameworkWarningWindow, false, "Unable to read engine.json file '%s' (%s). Defaulting the engine root to '%s'", engineJsonPath.c_str(), readJsonResult.GetError().c_str(), m_appRoot.c_str());
SetRootPath(RootPathType::EngineRoot, m_appRoot.c_str());
}
}
void Application::Stop()
{
if (m_isStarted)
{
ApplicationLifecycleEvents::Bus::Broadcast(&ApplicationLifecycleEvents::OnApplicationAboutToStop);
m_pimpl.reset();
/* The following line of code is a temporary fix.
* GridMate's ReplicaChunkDescriptor is stored in a global environment variable 'm_globalDescriptorTable'
* which does not get cleared when Application shuts down. We need to un-reflect here to clear ReplicaChunkDescriptor
* so that ReplicaChunkDescriptor::m_vdt doesn't get flooded when we repeatedly instantiate Application in unit tests.
*/
AZ::ReflectionEnvironment::GetReflectionManager()->RemoveReflectContext<NetworkContext>();
// Free any memory owned by the command line container.
m_commandLine = CommandLine();
Destroy();
m_isStarted = false;
}
}
void Application::RegisterCoreComponents()
{
AZ::ComponentApplication::RegisterCoreComponents();
// This is internal Amazon code, so register it's components for metrics tracking, otherwise the name of the component won't get sent back.
AZStd::vector<AZ::Uuid> componentUuidsForMetricsCollection
{
azrtti_typeid<AZ::MemoryComponent>(),
azrtti_typeid<AZ::StreamerComponent>(),
azrtti_typeid<AZ::JobManagerComponent>(),
azrtti_typeid<AZ::AssetManagerComponent>(),
azrtti_typeid<AZ::UserSettingsComponent>(),
azrtti_typeid<AZ::Debug::FrameProfilerComponent>(),
azrtti_typeid<AZ::NativeUI::NativeUISystemComponent>(),
azrtti_typeid<AZ::SliceComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
azrtti_typeid<AzFramework::FileTag::ExcludeFileComponent>(),
azrtti_typeid<AzFramework::NetBindingComponent>(),
azrtti_typeid<AzFramework::NetBindingSystemComponent>(),
azrtti_typeid<AzFramework::TransformComponent>(),
azrtti_typeid<AzFramework::SceneSystemComponent>(),
azrtti_typeid<AzFramework::AzFrameworkConfigurationSystemComponent>(),
azrtti_typeid<AzFramework::GameEntityContextComponent>(),
#if !defined(_RELEASE)
azrtti_typeid<AzFramework::TargetManagementComponent>(),
#endif
azrtti_typeid<AzFramework::AssetSystem::AssetSystemComponent>(),
azrtti_typeid<AzFramework::InputSystemComponent>(),
azrtti_typeid<AzFramework::DrillerNetworkAgentComponent>(),
#if !defined(AZCORE_EXCLUDE_LUA)
azrtti_typeid<AZ::ScriptSystemComponent>(),
azrtti_typeid<AzFramework::ScriptComponent>(),
#endif // #if !defined(AZCORE_EXCLUDE_LUA)
};
EBUS_EVENT(AzFramework::MetricsPlainTextNameRegistrationBus, RegisterForNameSending, componentUuidsForMetricsCollection);
}
void Application::Reflect(AZ::ReflectContext* context)
{
AZ::ComponentApplication::Reflect(context);
AZ::DataPatch::Reflect(context);
AZ::EntityUtils::Reflect(context);
AZ::NonUniformScaleRequests::Reflect(context);
AzFramework::BehaviorEntity::Reflect(context);
AzFramework::EntityContext::Reflect(context);
AzFramework::SliceEntityOwnershipService::Reflect(context);
AzFramework::SimpleAssetReferenceBase::Reflect(context);
AzFramework::ConsoleRequests::Reflect(context);
AzFramework::ConsoleNotifications::Reflect(context);
AzFramework::ViewportRequests::Reflect(context);
AzFramework::BoundsRequests::Reflect(context);
AzFramework::ScreenGeometryReflect(context);
AzFramework::RemoteStorageDriveConfig::Reflect(context);
Physics::ReflectionUtils::ReflectPhysicsApi(context);
AzFramework::Terrain::TerrainDataRequests::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
AzFramework::AssetRegistry::ReflectSerialize(serializeContext);
CameraState::Reflect(*serializeContext);
AzFramework::AssetBundleManifest::ReflectSerialize(serializeContext);
}
}
AZ::ComponentTypeList Application::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = ComponentApplication::GetRequiredSystemComponents();
components.insert(components.end(), {
azrtti_typeid<AZ::MemoryComponent>(),
azrtti_typeid<AZ::StreamerComponent>(),
azrtti_typeid<AZ::AssetManagerComponent>(),
azrtti_typeid<AZ::UserSettingsComponent>(),
azrtti_typeid<AZ::ScriptSystemComponent>(),
azrtti_typeid<AZ::JobManagerComponent>(),
azrtti_typeid<AZ::NativeUI::NativeUISystemComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
azrtti_typeid<AzFramework::FileTag::ExcludeFileComponent>(),
azrtti_typeid<AzFramework::SceneSystemComponent>(),
azrtti_typeid<AzFramework::AzFrameworkConfigurationSystemComponent>(),
azrtti_typeid<AzFramework::GameEntityContextComponent>(),
azrtti_typeid<AzFramework::RenderGeometry::GameIntersectorComponent>(),
azrtti_typeid<AzFramework::AssetSystem::AssetSystemComponent>(),
azrtti_typeid<AzFramework::InputSystemComponent>(),
azrtti_typeid<AzFramework::DrillerNetworkAgentComponent>(),
azrtti_typeid<AzFramework::StreamingInstall::StreamingInstallSystemComponent>(),
AZ::Uuid("{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}"), // ScriptDebugAgent
});
return components;
}
AZStd::string Application::ResolveFilePath(AZ::u32 providerId)
{
(void)providerId;
AZStd::string result;
AzFramework::StringFunc::Path::Join(GetAppRoot(), "UserSettings.xml", result, /*bCaseInsenitive*/false);
return result;
}
AZ::Component* Application::EnsureComponentAdded(AZ::Entity* systemEntity, const AZ::Uuid& typeId)
{
AZ::Component* component = systemEntity->FindComponent(typeId);
if (!component)
{
if (systemEntity->IsComponentReadyToAdd(typeId))
{
component = systemEntity->CreateComponent(typeId);
}
else
{
AZ_Assert(false, "Failed to add component of type %s because conditions are not met.", typeId.ToString<AZStd::string>().c_str());
}
}
return component;
}
void Application::CreateStaticModules(AZStd::vector<AZ::Module*>& outModules)
{
AZ::ComponentApplication::CreateStaticModules(outModules);
outModules.emplace_back(aznew AzFrameworkModule());
}
const char* Application::GetAssetRoot() const
{
return m_assetRoot.c_str();
}
const char* Application::GetAppRoot() const
{
return m_appRoot.c_str();
}
const char* Application::GetCurrentConfigurationName() const
{
#if defined(_RELEASE)
return "Release";
#elif defined(_DEBUG)
return "Debug";
#else
return "Profile";
#endif
}
void Application::CreateReflectionManager()
{
ComponentApplication::CreateReflectionManager();
// Setup NetworkContext
AZ::ReflectionEnvironment::GetReflectionManager()->AddReflectContext<NetworkContext>();
}
////////////////////////////////////////////////////////////////////////////
AZ::Uuid Application::GetComponentTypeId(const AZ::EntityId& entityId, const AZ::ComponentId& componentId)
{
AZ::Uuid uuid(AZ::Uuid::CreateNull());
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
if (entity)
{
AZ::Component* component = entity->FindComponent(componentId);
if (component)
{
uuid = component->RTTI_GetType();
}
}
return uuid;
}
////////////////////////////////////////////////////////////////////////////
NetworkContext* Application::GetNetworkContext()
{
NetworkContext* result = nullptr;
if (auto reflectionManager = AZ::ReflectionEnvironment::GetReflectionManager())
{
result = reflectionManager->GetReflectContext<NetworkContext>();
}
return result;
}
void Application::ResolveEnginePath(AZStd::string& engineRelativePath) const
{
AZStd::string fullPath = AZStd::string(m_engineRoot) + AZStd::string(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING) + engineRelativePath;
engineRelativePath = fullPath;
}
void Application::CalculateBranchTokenForAppRoot(AZStd::string& token) const
{
AzFramework::StringFunc::AssetPath::CalculateBranchToken(AZStd::string(m_appRoot), token);
}
////////////////////////////////////////////////////////////////////////////
void Application::SetAssetRoot(const char* assetRoot)
{
SetRootPath(RootPathType::AssetRoot, assetRoot);
}
////////////////////////////////////////////////////////////////////////////
void Application::MakePathRootRelative(AZStd::string& fullPath)
{
MakePathRelative(fullPath, m_appRoot.c_str());
}
////////////////////////////////////////////////////////////////////////////
void Application::MakePathAssetRootRelative(AZStd::string& fullPath)
{
// relative file paths wrt AssetRoot are always lowercase
AZStd::to_lower(fullPath.begin(), fullPath.end());
MakePathRelative(fullPath, m_assetRoot.c_str());
}
////////////////////////////////////////////////////////////////////////////
void Application::MakePathRelative(AZStd::string& fullPath, const char* rootPath)
{
AZ_Assert(rootPath, "Provided root path is null.");
NormalizePathKeepCase(fullPath);
AZStd::string root(rootPath);
NormalizePathKeepCase(root);
if (!azstrnicmp(fullPath.c_str(), root.c_str(), root.length()))
{
fullPath = fullPath.substr(root.length());
}
while (!fullPath.empty() && fullPath[0] == AZ_CORRECT_DATABASE_SEPARATOR)
{
fullPath.erase(fullPath.begin());
}
}
////////////////////////////////////////////////////////////////////////////
void Application::NormalizePath(AZStd::string& path)
{
ComponentApplication::NormalizePath(path.begin(), path.end(), true);
}
////////////////////////////////////////////////////////////////////////////
void Application::NormalizePathKeepCase(AZStd::string& path)
{
ComponentApplication::NormalizePath(path.begin(), path.end(), false);
}
////////////////////////////////////////////////////////////////////////////
void Application::PumpSystemEventLoopOnce()
{
if (m_pimpl)
{
m_pimpl->PumpSystemEventLoopOnce();
}
}
////////////////////////////////////////////////////////////////////////////
void Application::PumpSystemEventLoopUntilEmpty()
{
if (m_pimpl)
{
m_pimpl->PumpSystemEventLoopUntilEmpty();
}
}
////////////////////////////////////////////////////////////////////////////
void Application::PumpSystemEventLoopWhileDoingWorkInNewThread(const AZStd::chrono::milliseconds& eventPumpFrequency,
const AZStd::function<void()>& workForNewThread,
const char* newThreadName)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
AZStd::thread_desc newThreadDesc;
newThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
newThreadDesc.m_name = newThreadName;
AZStd::binary_semaphore binarySemaphore;
AZStd::thread newThread([&workForNewThread, &binarySemaphore, &newThreadName]
{
AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::AzFramework,
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:ThreadWorker %s", newThreadName);
workForNewThread();
binarySemaphore.release();
}, &newThreadDesc);
while (!binarySemaphore.try_acquire_for(eventPumpFrequency))
{
PumpSystemEventLoopUntilEmpty();
}
{
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzFramework,
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:WaitOnThread %s", newThreadName);
newThread.join();
}
// Pump once at the end so we're back at 0 instead of potentially eventPumpFrequency - 1 ms since the last event pump
PumpSystemEventLoopUntilEmpty();
}
////////////////////////////////////////////////////////////////////////////
void Application::RunMainLoop()
{
while (!m_exitMainLoopRequested)
{
PumpSystemEventLoopUntilEmpty();
Tick();
}
}
////////////////////////////////////////////////////////////////////////////
AZ_CVAR(float, t_frameTimeOverride, 0.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "If > 0, overrides the application delta frame-time with the provided value");
void Application::Tick(float deltaOverride /*= -1.f*/)
{
ComponentApplication::Tick((t_frameTimeOverride > 0.0f) ? t_frameTimeOverride : deltaOverride);
}
////////////////////////////////////////////////////////////////////////////
void Application::TerminateOnError(int errorCode)
{
if (m_pimpl)
{
m_pimpl->TerminateOnError(errorCode);
}
else
{
exit(errorCode);
}
}
void Application::SetRootPath(RootPathType type, const char* source)
{
size_t sourceLen = strlen(source);
constexpr AZStd::string_view pathSeparators{ AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR };
// Determine if we need to append a trailing path separator
bool appendTrailingPathSep = sourceLen > 0 && pathSeparators.find_first_of(source[sourceLen - 1]) == AZStd::string_view::npos;
// Copy the source path to the intended root path and correct the path separators as well
switch (type)
{
case RootPathType::AppRoot:
{
AZ_Assert(sourceLen < m_appRoot.max_size(), "String overflow for App Root: %s", source);
m_appRoot = source;
AZStd::replace(std::begin(m_appRoot), std::end(m_appRoot), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (appendTrailingPathSep)
{
m_appRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
break;
case RootPathType::AssetRoot:
{
AZ_Assert(sourceLen < m_assetRoot.max_size(), "String overflow for Asset Root: %s", source);
m_assetRoot = source;
AZStd::replace(std::begin(m_assetRoot), std::end(m_assetRoot), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (appendTrailingPathSep)
{
m_assetRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
break;
case RootPathType::EngineRoot:
{
AZ_Assert(sourceLen < m_engineRoot.max_size(), "String overflow for Engine Root: %s", source);
m_engineRoot = source;
AZStd::replace(std::begin(m_engineRoot), std::end(m_engineRoot), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (appendTrailingPathSep)
{
m_engineRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
break;
default:
AZ_Assert(false, "Invalid RootPathType (%d)", static_cast<int>(type));
}
}
void Application::SetFileIOAliases()
{
if (AZ::IO::FileIOBase::GetInstance() == m_archiveFileIO.get())
{
auto fileIoBase = m_archiveFileIO.get();
// Set up the default file aliases based on the settings registry
fileIoBase->SetAlias("@root@", GetAppRoot());
fileIoBase->SetAlias("@assets@", GetAssetRoot());
fileIoBase->SetAlias("@engroot@", GetAppRoot());
fileIoBase->SetAlias("@devroot@", GetAppRoot());
fileIoBase->SetAlias("@devassets@", GetAppRoot());
fileIoBase->SetAlias("@exefolder@", GetExecutableFolder());
{
AZ::SettingsRegistryInterface::FixedValueString pathAliases;
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
{
fileIoBase->SetAlias("@root@", pathAliases.c_str());
}
pathAliases.clear();
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheGameFolder))
{
fileIoBase->SetAlias("@assets@", pathAliases.c_str());
}
pathAliases.clear();
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
fileIoBase->SetAlias("@engroot@", pathAliases.c_str());
fileIoBase->SetAlias("@devroot@", pathAliases.c_str());
}
pathAliases.clear();
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder))
{
fileIoBase->SetAlias("@devassets@", pathAliases.c_str());
}
}
{
auto userPath = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "user", fileIoBase->GetAlias("@root@"));
fileIoBase->SetAlias("@user@", userPath.c_str());
}
{
auto logPath = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "log", fileIoBase->GetAlias("@user@"));
fileIoBase->SetAlias("@log@", logPath.c_str());
// Create the Log folder if it doesn't exist
fileIoBase->CreatePath("@log@");
}
auto cachePathOriginal = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "cache", fileIoBase->GetAlias("@user@"));
// The number of max attempts ultimately dictates the number of Lumberyard instances that can run
// simultaneously. This should be a reasonably high number so that it doesn't artificially limit
// the number of instances (ex: parallel level exports via multiple Editor runs). It also shouldn't
// be set *infinitely* high - each cache folder is GBs in size, and finding a free directory is a
// linear search, so the more instances we allow, the longer the search will take.
// 128 seems like a reasonable compromise.
constexpr int maxAttempts = 128;
AZ::StringFunc::Path::FixedString cachePath(cachePathOriginal);
#if AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
int attemptNumber;
for (attemptNumber = 0; attemptNumber < maxAttempts; ++attemptNumber)
{
if (attemptNumber != 0)
{
cachePath = AZ::StringFunc::Path::FixedString::format("%s%i", cachePathOriginal.c_str(), attemptNumber);
}
fileIoBase->CreatePath(cachePath.c_str());
// if the directory already exists, check for locked file
auto cacheLockFilePath = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "lockfile.txt", cachePath.c_str());
AZ::IO::HandleType lockFileHandle;
if (fileIoBase->Open(cacheLockFilePath.c_str(), AZ::IO::OpenMode::ModeWrite, lockFileHandle))
{
fileIoBase->Close(lockFileHandle);
break;
}
}
if (attemptNumber >= maxAttempts)
{
cachePath = cachePathOriginal;
AZ_TracePrintf("Application", "Couldn't find a valid asset cache folder after %i attempts."
" Setting cache folder to cachePath %s\n", maxAttempts, cachePath.c_str());
}
#endif
fileIoBase->SetAlias("@cache@", cachePath.c_str());
}
}
} // namespace AzFramework
@@ -0,0 +1,203 @@
/*
* 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/base.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/UserSettings/UserSettings.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzFramework/Network/NetSystemBus.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <AzFramework/API/ApplicationAPI.h>
namespace AZ
{
class Component;
namespace Internal
{
class ComponentFactoryInterface;
}
namespace IO
{
class Archive;
class FileIOBase;
class LocalFileIO;
}
}
namespace AzFramework
{
class Application
: public AZ::ComponentApplication
, public AZ::UserSettingsFileLocatorBus::Handler
, public ApplicationRequests::Bus::Handler
, public NetSystemRequestBus::Handler
{
public:
// Base class for platform specific implementations of the application.
class Implementation
{
public:
static Implementation* Create();
virtual ~Implementation() = default;
virtual void PumpSystemEventLoopOnce() = 0;
virtual void PumpSystemEventLoopUntilEmpty() = 0;
virtual void TerminateOnError(int errorCode) { exit(errorCode); }
};
AZ_RTTI(Application, "{0BD2388B-F435-461C-9C84-D0A96CAF32E4}", AZ::ComponentApplication);
AZ_CLASS_ALLOCATOR(Application, AZ::SystemAllocator, 0);
// Publicized types & methods from base ComponentApplication.
using AZ::ComponentApplication::Descriptor;
using AZ::ComponentApplication::StartupParameters;
using AZ::ComponentApplication::GetSerializeContext;
using AZ::ComponentApplication::RegisterComponentDescriptor;
/**
* You can pass your command line parameters from main here
* so that they are thus available in GetCommandLine later, and can be retrieved.
* see notes in GetArgC() and GetArgV() for details about the arguments.
*/
Application(int* argc, char*** argv); ///< recommended: supply &argc and &argv from void main(...) here.
Application(); ///< for backward compatibility. If you call this, GetArgC and GetArgV will return nullptr.
~Application();
/**
* Executes the AZ:ComponentApplication::Create method and initializes Application constructs.
* Uses a variant to maintain backwards compatibility with both the Start(const Descriptor& descriptor, const StartupParamters&)
* and the Start(const char* descriptorFile, const StaartupParameters&) overloads
*/
virtual void Start(const Descriptor& descriptor, const StartupParameters& startupParameters = StartupParameters());
/**
* Executes AZ::ComponentApplication::Destroy, and shuts down Application specific constructs.
*/
virtual void Stop();
void Tick(float deltaOverride = -1.f) override;
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
void CreateStaticModules(AZStd::vector<AZ::Module*>& outModules) override;
//////////////////////////////////////////////////////////////////////////
//! ApplicationRequests::Bus::Handler
const char* GetAssetRoot() const override;
const char* GetEngineRoot() const override { return m_engineRoot.c_str(); }
const char* GetAppRoot() const override;
void ResolveEnginePath(AZStd::string& engineRelativePath) const override;
void CalculateBranchTokenForAppRoot(AZStd::string& token) const override;
#pragma push_macro("GetCommandLine")
#undef GetCommandLine
const CommandLine* GetCommandLine() override { return &m_commandLine; }
#pragma pop_macro("GetCommandLine")
const CommandLine* GetApplicationCommandLine() override { return &m_commandLine; }
void SetAssetRoot(const char* assetRoot) override;
void MakePathRootRelative(AZStd::string& fullPath) override;
void MakePathAssetRootRelative(AZStd::string& fullPath) override;
void MakePathRelative(AZStd::string& fullPath, const char* rootPath) override;
void NormalizePath(AZStd::string& path) override;
void NormalizePathKeepCase(AZStd::string& path) override;
void PumpSystemEventLoopOnce() override;
void PumpSystemEventLoopUntilEmpty() override;
void PumpSystemEventLoopWhileDoingWorkInNewThread(const AZStd::chrono::milliseconds& eventPumpFrequency,
const AZStd::function<void()>& workForNewThread,
const char* newThreadName) override;
void RunMainLoop() override;
void ExitMainLoop() override { m_exitMainLoopRequested = true; }
bool WasExitMainLoopRequested() override { return m_exitMainLoopRequested; }
void TerminateOnError(int errorCode) override;
AZ::Uuid GetComponentTypeId(const AZ::EntityId& entityId, const AZ::ComponentId& componentId) override;
//////////////////////////////////////////////////////////////////////////
// Convenience function that should be called instead of the standard exit() function to ensure platform requirements are met.
static void Exit(int errorCode) { ApplicationRequests::Bus::Broadcast(&ApplicationRequests::TerminateOnError, errorCode); }
//////////////////////////////////////////////////////////////////////////
//! NetSystemEventBus::Handler
//////////////////////////////////////////////////////////////////////////
NetworkContext* GetNetworkContext() override;
protected:
/**
* Called by Start method. Override to add custom startup logic.
*/
virtual void StartCommon(AZ::Entity* systemEntity);
/**
* set the LocalFileIO and ArchiveFileIO instances file aliases if the
* FileIOBase environment variable is pointing to the instances owned by
* the application
*/
void SetFileIOAliases();
void PreModuleLoad() override;
//////////////////////////////////////////////////////////////////////////
//! AZ::ComponentApplication
void RegisterCoreComponents() override;
void Reflect(AZ::ReflectContext* context) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//! UserSettingsFileLocatorBus
AZStd::string ResolveFilePath(AZ::u32 providerId) override;
//////////////////////////////////////////////////////////////////////////
AZ::Component* EnsureComponentAdded(AZ::Entity* systemEntity, const AZ::Uuid& typeId);
template <typename ComponentType>
AZ::Component* EnsureComponentAdded(AZ::Entity* systemEntity)
{
return EnsureComponentAdded(systemEntity, ComponentType::RTTI_Type());
}
virtual const char* GetCurrentConfigurationName() const;
void CreateReflectionManager() override;
AZ::StringFunc::Path::FixedString m_configFilePath;
AZ::StringFunc::Path::FixedString m_assetRoot;
AZ::StringFunc::Path::FixedString m_engineRoot; ///> Location of the engine root folder that this application is based on
AZStd::unique_ptr<AZ::IO::LocalFileIO> m_directFileIO; ///> The Direct file IO instance is a LocalFileIO.
AZStd::unique_ptr<AZ::IO::FileIOBase> m_archiveFileIO; ///> The Default file IO instance is a ArchiveFileIO.
AZStd::unique_ptr<AZ::IO::Archive> m_archive; ///> The AZ::IO::Instance
AZStd::unique_ptr<Implementation> m_pimpl;
bool m_ownsConsole = false;
bool m_exitMainLoopRequested = false;
enum class RootPathType
{
AppRoot,
AssetRoot,
EngineRoot
};
void SetRootPath(RootPathType type, const char* source);
};
} // namespace AzFramework
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,393 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Got rid of unzip usage, now using ZipDir for much more effective
// memory usage (~3-6 times less memory, and no allocator overhead)
// to keep the directory of the zip file; better overall effectiveness and
// more readable and manageable code, made the connection to Streaming Engine
#pragma once
#include <AzCore/IO/CompressionBus.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/osstring.h>
#include <AzFramework/Archive/IArchive.h>
#include <AzFramework/Archive/ZipDirCache.h>
namespace AzFramework
{
class AssetBundleManifest;
class AssetRegistry;
}
namespace AZ::IO
{
class Archive;
// this is the header in the cache of the file data
struct CCachedFileData
: public AZStd::intrusive_base
{
AZ_CLASS_ALLOCATOR(CCachedFileData, AZ::SystemAllocator, 0);
CCachedFileData(ZipDir::CachePtr pZip, uint32_t nArchiveFlags, ZipDir::FileEntry* pFileEntry, AZStd::string_view szFilename);
~CCachedFileData();
CCachedFileData(const CCachedFileData&) = delete;
CCachedFileData& operator=(const CCachedFileData&) = delete;
// return the data in the file, or nullptr if error
// by default, if bRefreshCache is true, and the data isn't in the cache already,
// the cache is refreshed. Otherwise, it returns whatever cache is (nullptr if the data isn't cached yet)
// decompress can be harmlessly set to true if you want the data back decompressed.
// set them to false only if you want to operate on the raw data while its still compressed.
void* GetData(bool bRefreshCache = true, bool decompress = true);
// Uncompress file data directly to provided memory.
bool GetDataTo(void* pFileData, int nDataSize, bool bDecompress = true);
// Return number of copied bytes, or -1 if did not read anything
int64_t ReadData(void* pBuffer, int64_t nFileOffset, int64_t nReadSize);
ZipDir::Cache* GetZip() { return m_pZip.get(); }
ZipDir::FileEntry* GetFileEntry() { return m_pFileEntry; }
uint32_t GetFileDataOffset();
void* m_pFileData;
// the zip file in which this file is opened
ZipDir::CachePtr m_pZip;
uint32_t m_nArchiveFlags;
// the file entry : if this is nullptr, the entry is free and all the other fields are meaningless
ZipDir::FileEntry* m_pFileEntry;
};
using CCachedFileDataPtr = AZStd::intrusive_ptr<CCachedFileData>;
namespace ArchiveInternal
{
struct CCachedFileRawData;
struct CZipPseudoFile;
};
//////////////////////////////////////////////////////////////////////
class Archive
: public IArchive
, public AZ::IO::CompressionBus::Handler
{
public:
AZ_RTTI(Archive, "{764A2260-FF8A-4C86-B958-EBB0B69D9DFA}", IArchive);
AZ_CLASS_ALLOCATOR(Archive, AZ::OSAllocator, 0);
private:
friend struct CCachedFileData;
friend class FindData;
friend class NestedArchive;
friend struct SAutoCollectFileAccessTime;
// the array of pseudo-files : emulated files in the virtual zip file system
// the handle to the file is its index inside this array.
// some of the entries can be free. The entries need to be destructed manually
using ZipPseudoFileArray = AZStd::vector<AZStd::unique_ptr<ArchiveInternal::CZipPseudoFile>, AZ::OSStdAllocator>;
// This is a cached data for the FGetCachedFileData call.
struct CachedRawDataEntry;
using CachedFileRawDataSet = AZStd::unordered_map<AZ::IO::HandleType, CachedRawDataEntry, AZStd::hash<AZ::IO::HandleType>, AZStd::equal_to<>, AZ::OSStdAllocator>;
// open zip cache objects that can be reused. They're self-[un]registered
// they're sorted by the path to archive file
using ArchiveArray = AZStd::vector<INestedArchive*, AZ::OSStdAllocator>;
// the array of opened caches - they get destructed by themselves (these are intrusive, see the ZipDir::Cache documentation)
struct PackDesc
{
AZ::IO::Path m_pathBindRoot; // the zip binding root
AZStd::string strFileName; // the zip file name (with path) - very useful for debugging so please don't remove
bool m_containsLevelPak = false; // indicates whether this archive has level.pak inside it or not
const char* GetFullPath() const { return pZip->GetFilePath(); }
AZStd::intrusive_ptr<INestedArchive> pArchive;
ZipDir::CachePtr pZip;
};
using ZipArray = AZStd::vector<PackDesc, AZ::OSStdAllocator>;
// ArchiveFindDataSet entire purpose is to keep a reference to the intrusive_ptr of ArchiveFindData
// so that it doesn't go out of scope
using ArchiveFindDataSet = AZStd::set<AZStd::intrusive_ptr<AZ::IO::FindData>, AZ::OSStdAllocator>;
// given the source relative path, constructs the full path to the file according to the flags
const char* AdjustFileName(AZStd::string_view src, char* dst, size_t dstSize, uint32_t nFlags, bool skipMods = false) override;
/**
* Currently access to PseudoFile operations are not thread safe, as we touch variable like m_nCurSeek
* without any synchronization. There is also the assumption that only one thread at a time will open/read/close
* a single file in a PAK, multiple threads can open different files in a PAK. If requirements change (or turns out this is a bug in Archive)
* we can add readwrite lock inside the CZipPseudoFile and we can pass a second argument lockForOperation where we can lock
* the file specific lock while getting a handle. This way you can safely execute the operation. This is no done by default
* because the API implies that this is not the intended use case. e.g. FSeek and FRead are separate operations, if you have multiple threads
* reading data, they can both execute FSeek/FRead and unless we lock the operation set, this will not work.
*/
ArchiveInternal::CZipPseudoFile* GetPseudoFile(AZ::IO::HandleType fileHandle) const;
public:
Archive();
~Archive();
//! CompressionBus Handler implementation.
void FindCompressionInfo(bool& found, AZ::IO::CompressionInfo& info, const AZStd::string_view filename) override;
//! Processes an alias command line containing multiple aliases.
void ParseAliases(AZStd::string_view szCommandLine) override;
//! adds or removes an alias from the list - if bAdd set to false will remove it
void SetAlias(AZStd::string_view szName, AZStd::string_view szAlias, bool bAdd) override;
//! gets an alias from the list, if any exist.
//! if bReturnSame==true, it will return the input name if an alias doesn't exist. Otherwise returns nullptr
const char* GetAlias(AZStd::string_view szName, bool bReturnSame = true) override;
// Set the localization folder
void SetLocalizationFolder(AZStd::string_view sLocalizationFolder) override;
const char* GetLocalizationFolder() const override { return m_sLocalizationFolder.c_str(); }
const char* GetLocalizationRoot() const override { return m_sLocalizationRoot.c_str(); }
// lock all the operations
void Lock() override;
void Unlock() override;
// open the physical archive file - creates if it doesn't exist
// returns nullptr if it's invalid or can't open the file
AZStd::intrusive_ptr<INestedArchive> OpenArchive(AZStd::string_view szPath, AZStd::string_view bindRoot = {}, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr) override;
// returns the path to the archive in which the file was opened
const char* GetFileArchivePath(AZ::IO::HandleType fileHandle) override;
//////////////////////////////////////////////////////////////////////////
//! Return pointer to pool if available
void* PoolMalloc(size_t size) override;
//! Free pool
void PoolFree(void* p) override;
AZStd::intrusive_ptr<AZ::IO::MemoryBlock> PoolAllocMemoryBlock(size_t nSize, const char* sUsage, size_t nAlign) override;
// interface IArchive ---------------------------------------------------------------------------
void RegisterFileAccessSink(IArchiveFileAccessSink* pSink) override;
void UnregisterFileAccessSink(IArchiveFileAccessSink* pSink) override;
bool Init(AZStd::string_view szBasePath) override;
void Release() override;
bool IsInstalledToHDD(AZStd::string_view acFilePath = 0) const override;
bool OpenPack(AZStd::string_view pName, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
bool OpenPack(AZStd::string_view szBindRoot, AZStd::string_view pName, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
// after this call, the file will be unlocked and closed, and its contents won't be used to search for files
bool ClosePack(AZStd::string_view pName, uint32_t nFlags = 0) override;
bool OpenPacks(AZStd::string_view pWildcard, uint32_t nFlags = 0, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) override;
bool OpenPacks(AZStd::string_view szBindRoot, AZStd::string_view pWildcard, uint32_t nFlags = 0, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) override;
// closes pack files by the path and wildcard
bool ClosePacks(AZStd::string_view pWildcard, uint32_t nFlags = 0) override;
//returns if a archive exists matching the wildcard
bool FindPacks(AZStd::string_view pWildcardIn) override;
// prevent access to specific archive files
bool SetPacksAccessible(bool bAccessible, AZStd::string_view pWildcard, uint32_t nFlags = 0) override;
bool SetPackAccessible(bool bAccessible, AZStd::string_view pName, uint32_t nFlags = 0) override;
// returns the file modification time
uint64_t GetModificationTime(AZ::IO::HandleType fileHandle) override;
bool LoadPakToMemory(AZStd::string_view pName, EInMemoryArchiveLocation nLoadArchiveToMemory, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pMemoryBlock = nullptr) override;
void LoadPaksToMemory(int nMaxArchiveSize, bool bLoadToMemory) override;
AZ::IO::HandleType FOpen(AZStd::string_view pName, const char* mode, uint32_t nPathFlags = 0) override;
size_t FReadRaw(void* data, size_t length, size_t elems, AZ::IO::HandleType handle) override;
size_t FReadRawAll(void* data, size_t nFileSize, AZ::IO::HandleType handle) override;
void* FGetCachedFileData(AZ::IO::HandleType handle, size_t& nFileSize) override;
size_t FWrite(const void* data, size_t length, size_t elems, AZ::IO::HandleType handle) override;
size_t FSeek(AZ::IO::HandleType handle, uint64_t seek, int mode) override;
uint64_t FTell(AZ::IO::HandleType handle) override;
int FFlush(AZ::IO::HandleType handle) override;
int FClose(AZ::IO::HandleType handle) override;
AZ::IO::ArchiveFileIterator FindFirst(AZStd::string_view pDir, uint32_t nPathFlags = 0, bool bAllOwUseFileSystem = false) override;
AZ::IO::ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator fileIterator) override;
bool FindClose(AZ::IO::ArchiveFileIterator fileIterator) override;
int FEof(AZ::IO::HandleType handle) override;
char* FGets(char*, int, AZ::IO::HandleType) override;
int Getc(AZ::IO::HandleType) override;
int FPrintf(AZ::IO::HandleType handle, const char* format, ...) override;
size_t FGetSize(AZ::IO::HandleType fileHandle) override;
size_t FGetSize(AZStd::string_view sFilename, bool bAllowUseFileSystem = false) override;
bool IsInPak(AZ::IO::HandleType handle) override;
bool RemoveFile(AZStd::string_view pName) override; // remove file from FS (if supported)
bool RemoveDir(AZStd::string_view pName) override; // remove directory from FS (if supported)
bool IsAbsPath(AZStd::string_view pPath) override;
bool IsFileExist(AZStd::string_view sFilename, EFileSearchLocation fileLocation = eFileLocation_Any) override;
bool IsFolder(AZStd::string_view sPath) override;
IArchive::SignedFileSize GetFileSizeOnDisk(AZStd::string_view filename) override;
// creates a directory
bool MakeDir(AZStd::string_view szPath, bool bGamePathMapping = false) override;
// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
// returns one of the Z_* errors (Z_OK upon success)
// MT-safe
int RawCompress(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, int nLevel = -1) override;
// Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file
// returns one of the Z_* errors (Z_OK upon success)
// This function just mimics the standard uncompress (with modification taken from unzReadCurrentFile)
// with 2 differences: there are no 16-bit checks, and
// it initializes the inflation to start without waiting for compression method byte, as this is the
// way it's stored into zip file
int RawUncompress(void* pUncompressed, size_t* pDestSize, const void* pCompressed, size_t nSrcSize) override;
//////////////////////////////////////////////////////////////////////////
// Files opening recorder.
//////////////////////////////////////////////////////////////////////////
void RecordFileOpen(ERecordFileOpenList eMode) override;
ERecordFileOpenList GetRecordFileOpenList() override;
void RecordFile(AZ::IO::HandleType in, AZStd::string_view szFilename) override;
IResourceList* GetResourceList(ERecordFileOpenList eList) override;
void SetResourceList(ERecordFileOpenList eList, IResourceList* pResourceList) override;
uint32_t ComputeCRC(AZStd::string_view szPath, uint32_t nFileOpenFlags = 0) override;
bool ComputeMD5(AZStd::string_view szPath, uint8_t* md5, uint32_t nFileOpenFlags = 0, bool useDirectFileAccess = false) override;
void DisableRuntimeFileAccess(bool status) override
{
m_disableRuntimeFileAccess[0] = status;
m_disableRuntimeFileAccess[1] = status;
}
bool DisableRuntimeFileAccess(bool status, AZStd::thread_id threadId) override;
bool CheckFileAccessDisabled(AZStd::string_view name, const char* mode) override;
void SetRenderThreadId(AZStd::thread_id renderThreadId) override
{
m_renderThreadId = renderThreadId;
}
// gets the current archive priority
ArchiveLocationPriority GetPakPriority() const override;
uint64_t GetFileOffsetOnMedia(AZStd::string_view szName) const override;
EStreamSourceMediaType GetFileMediaType(AZStd::string_view szName) const override;
auto GetLevelPackOpenEvent()->LevelPackOpenEvent* override;
auto GetLevelPackCloseEvent()->LevelPackCloseEvent* override;
// Return cached file data for entries inside archive file.
CCachedFileDataPtr GetOpenedFileDataInZip(AZ::IO::HandleType file);
ZipDir::FileEntry* FindPakFileEntry(AZStd::string_view szPath, uint32_t& nArchiveFlags,
ZipDir::CachePtr* pZip = {}, bool bSkipInMemoryArchives = {}) const;
private:
bool OpenPackCommon(AZStd::string_view szBindRoot, AZStd::string_view pName, uint32_t nArchiveFlags, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, bool addLevels = true);
bool OpenPacksCommon(AZStd::string_view szDir, AZStd::string_view pWildcardIn, uint32_t nArchiveFlags, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr, bool addLevels = true);
ZipDir::FileEntry* FindPakFileEntry(AZStd::string_view szPath) const;
void CheckFileAccess(AZStd::string_view szFilename);
// this function gets the file data for the given file, if found.
// The file data object may be created in this function,
// and it's important that the intrusive is returned: another thread may release the existing
// cached data before the function returns
// the path must be absolute normalized lower-case with forward-slashes
CCachedFileDataPtr GetFileData(AZStd::string_view szName, uint32_t& nArchiveFlags, ZipDir::CachePtr* pZip = nullptr);
// Get the data for a file by name within an archive if it exists
CCachedFileDataPtr GetFileData(ZipDir::CachePtr pZip, AZStd::string_view szName);
void LogFileAccessCallStack(AZStd::string_view name, AZStd::string_view nameFull, const char* mode);
// Registers a non-owning pointer of the NestedArchive with the Archive instance
void Register(INestedArchive* pArchive);
void Unregister(INestedArchive* pArchive);
INestedArchive* FindArchive(AZStd::string_view szFullPath) const;
//! Return the Manifest from a bundle, if it exists
AZStd::shared_ptr<AzFramework::AssetBundleManifest> GetBundleManifest(ZipDir::CachePtr pZip);
AZStd::shared_ptr<AzFramework::AssetRegistry> GetBundleCatalog(ZipDir::CachePtr pZip, const AZStd::string& catalogName);
AZStd::vector<AZStd::string> ScanForLevels(ZipDir::CachePtr pZip);
mutable AZStd::shared_mutex m_csOpenFiles;
ZipPseudoFileArray m_arrOpenFiles;
CachedFileRawDataSet m_cachedFileRawDataSet;
AZStd::mutex m_cachedFileRawDataMutex;
// For m_pCachedFileRawDataSet
using RawDataCacheLockGuard = AZStd::scoped_lock<decltype(m_cachedFileRawDataMutex)>;
// The F* emulation functions critical section: protects all F* functions
// that don't have a chance to be called recursively (to avoid deadlocks)
AZStd::mutex m_csMain;
mutable AZStd::shared_mutex m_archiveMutex;
ArchiveArray m_arrArchives;
mutable AZStd::shared_mutex m_csZips;
ZipArray m_arrZips;
//////////////////////////////////////////////////////////////////////////
// Opened files collector.
//////////////////////////////////////////////////////////////////////////
IArchive::ERecordFileOpenList m_eRecordFileOpenList = RFOM_Disabled;
using RecordedFilesSet = AZStd::set<AZ::OSString, AZ::IO::AZStdStringLessCaseInsensitive, AZ::OSStdAllocator>;
RecordedFilesSet m_recordedFilesSet;
AZStd::intrusive_ptr<IResourceList> m_pEngineStartupResourceList;
AZStd::intrusive_ptr<IResourceList> m_pLevelResourceList;
AZStd::intrusive_ptr<IResourceList> m_pNextLevelResourceList;
float m_fFileAccessTime{}; // Time used to perform file operations
AZStd::vector<IArchiveFileAccessSink*, AZ::OSStdAllocator> m_FileAccessSinks; // useful for gathering file access statistics
bool m_disableRuntimeFileAccess[2]{};
//threads which we don't want to access files from during the game
AZStd::thread_id m_mainThreadId{};
AZStd::thread_id m_renderThreadId{};
AZStd::fixed_string<128> m_sLocalizationFolder;
AZStd::fixed_string<128> m_sLocalizationRoot;
AZStd::set<uint32_t, AZStd::less<>, AZ::OSStdAllocator> m_filesCachedOnHDD;
LevelPackOpenEvent m_levelOpenEvent;
LevelPackCloseEvent m_levelCloseEvent;
};
}
namespace AZ::IO::ArchiveInternal
{
// Utility function to de-alias archive file opening and file-within-archive opening
// if the file specified was an absolute path but it points at one of the aliases, de-alias it and replace it with that alias.
// this works around problems where the level editor is in control but still mounts asset packs (ie, level.pak mounted as @assets@)
AZStd::optional<AZ::IO::FixedMaxPath> ConvertAbsolutePathToAliasedPath(AZStd::string_view sourcePath,
AZStd::string_view aliasToLookFor = "@devassets@", AZStd::string_view aliasToReplaceWith = "@assets@");
}
@@ -0,0 +1,40 @@
/*
* 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/EBus/EBus.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Asset/AssetBundleManifest.h>
#include <AzFramework/Asset/AssetRegistry.h>
namespace AZ::IO
{
/*!
* Events from Archive
*/
class ArchiveNotifications
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual void BundleOpened([[maybe_unused]] const char* bundleName, AZStd::shared_ptr<AzFramework::AssetBundleManifest> bundleManifest, [[maybe_unused]] const char* nextBundle, AZStd::shared_ptr<AzFramework::AssetRegistry> bundleCatalog) {}
virtual void BundleClosed([[maybe_unused]] const char* bundleName) {}
// Sent when a file is accessed through Archive
virtual void FileAccess([[maybe_unused]] const char* filePath) {}
};
using ArchiveNotificationBus = AZ::EBus<ArchiveNotifications>;
}
@@ -0,0 +1,607 @@
/*
* 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/IO/SystemFile.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/functional.h> // for function<> in the find files callback.
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Archive/ArchiveFileIO.h>
#include <AzFramework/Archive/IArchive.h>
#include <cinttypes>
namespace AZ::IO
{
constexpr size_t ArchiveFileiOMaxBuffersize = 16 * 1024;
ArchiveFileIO::ArchiveFileIO(IArchive* archive)
: m_archive(archive)
{
}
ArchiveFileIO::~ArchiveFileIO()
{
// close all files. This makes it mimic the behavior of base FileIO even though it sits on archive.
decltype(m_trackedFiles) trackedFiles;
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_operationGuard);
AZStd::swap(trackedFiles, m_trackedFiles);
}
for (const auto&[trackedFileHandle, trackedFile] : trackedFiles)
{
AZ_Warning("File IO", false, "File handle still open while ArchiveFileIO being closed: %s", trackedFile.c_str());
Close(trackedFileHandle);
}
}
void ArchiveFileIO::SetArchive(IArchive* archive)
{
m_archive = archive;
}
IArchive* ArchiveFileIO::GetArchive() const
{
return m_archive;
}
IO::Result ArchiveFileIO::Open(const char* filePath, IO::OpenMode openMode, IO::HandleType& fileHandle)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Open(filePath, openMode, fileHandle);
}
return IO::ResultCode::Error;
}
fileHandle = m_archive->FOpen(filePath, IO::GetStringModeFromOpenMode(openMode));
if (fileHandle == IO::InvalidHandle)
{
return IO::ResultCode::Error;
}
//track the open file handles
char resolvedPath[AZ::IO::MaxPathLength];
bool pathResolved = ResolvePath(filePath, resolvedPath, AZStd::size(resolvedPath));
AZStd::string_view trackedPath{ pathResolved ? resolvedPath : AZStd::string_view(filePath) };
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_operationGuard);
m_trackedFiles.emplace(fileHandle, trackedPath);
return IO::ResultCode::Success;
}
IO::Result ArchiveFileIO::Close(IO::HandleType fileHandle)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Close(fileHandle);
}
return IO::ResultCode::Error; // we are likely shutting down and archive has already dropped all its handles already.
}
if (m_archive->FClose(fileHandle) == 0)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_operationGuard);
auto remoteFileIter = m_trackedFiles.find(fileHandle);
if (remoteFileIter != m_trackedFiles.end())
{
m_trackedFiles.erase(remoteFileIter);
}
return IO::ResultCode::Success;
}
return IO::ResultCode::Error;
}
IO::Result ArchiveFileIO::Tell(IO::HandleType fileHandle, AZ::u64& offset)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Tell(fileHandle, offset);
}
return IO::ResultCode::Error;
}
offset = m_archive->FTell(fileHandle);
return IO::ResultCode::Success;
}
IO::Result ArchiveFileIO::Seek(IO::HandleType fileHandle, AZ::s64 offset, IO::SeekType type)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Seek(fileHandle, offset, type);
}
return IO::ResultCode::Error;
}
size_t seekResult = m_archive->FSeek(fileHandle, static_cast<uint64_t>(offset), GetFSeekModeFromSeekType(type));
return seekResult == 0 ? IO::ResultCode::Success : IO::ResultCode::Error;
}
IO::Result ArchiveFileIO::Size(IO::HandleType fileHandle, AZ::u64& size)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Size(fileHandle, size);
}
return IO::ResultCode::Error;
}
size = m_archive->FGetSize(fileHandle);
if (size || m_archive->IsInPak(fileHandle))
{
return IO::ResultCode::Success;
}
if (GetDirectInstance())
{
return GetDirectInstance()->Size(fileHandle, size);
}
return IO::ResultCode::Error;
}
IO::Result ArchiveFileIO::Size(const char* filePath, AZ::u64& size)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Size(filePath, size);
}
return IO::ResultCode::Error;
}
size = m_archive->FGetSize(filePath, true);
if (!size)
{
return m_archive->IsFileExist(filePath, IArchive::eFileLocation_Any) ? IO::ResultCode::Success : IO::ResultCode::Error;
}
return IO::ResultCode::Success;
}
IO::Result ArchiveFileIO::Read(IO::HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Read(fileHandle, buffer, size, failOnFewerThanSizeBytesRead, bytesRead);
}
return IO::ResultCode::Error;
}
size_t result = m_archive->FReadRaw(buffer, 1, size, fileHandle);
if (bytesRead)
{
*bytesRead = static_cast<AZ::u64>(result);
}
if (failOnFewerThanSizeBytesRead)
{
return result != size ? IO::ResultCode::Error : IO::ResultCode::Success;
}
return result == 0 ? IO::ResultCode::Error : IO::ResultCode::Success;
}
IO::Result ArchiveFileIO::Write(IO::HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Write(fileHandle, buffer, size, bytesWritten);
}
return IO::ResultCode::Error;
}
size_t result = m_archive->FWrite(buffer, 1, size, fileHandle);
if (bytesWritten)
{
*bytesWritten = static_cast<AZ::u64>(result);
}
return result != size ? IO::ResultCode::Error : IO::ResultCode::Success;
}
IO::Result ArchiveFileIO::Flush(IO::HandleType fileHandle)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Flush(fileHandle);
}
return IO::ResultCode::Error;
}
return m_archive->FFlush(fileHandle) == 0 ? IO::ResultCode::Success : IO::ResultCode::Error;
}
bool ArchiveFileIO::Eof(IO::HandleType fileHandle)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Eof(fileHandle);
}
return false;
}
return (m_archive->FEof(fileHandle) != 0);
}
bool ArchiveFileIO::Exists(const char* filePath)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Exists(filePath);
}
return false;
}
return m_archive->IsFileExist(filePath);
}
AZ::u64 ArchiveFileIO::ModificationTime(const char* filePath)
{
IO::HandleType openFile = IO::InvalidHandle;
if (!Open(filePath, IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary, openFile))
{
return 0;
}
AZ::u64 result = ModificationTime(openFile);
Close(openFile);
return result;
}
AZ::u64 ArchiveFileIO::ModificationTime(IO::HandleType fileHandle)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->ModificationTime(fileHandle);
}
return 0;
}
return m_archive->GetModificationTime(fileHandle);
}
bool ArchiveFileIO::IsDirectory(const char* filePath)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->IsDirectory(filePath);
}
return false;
}
return m_archive->IsFolder(filePath);
}
IO::Result ArchiveFileIO::CreatePath(const char* filePath)
{
// since you can't create a path inside a pak file
// we will pass this to the underlying real fileio!
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return IO::ResultCode::Error;
}
return realUnderlyingFileIO->CreatePath(filePath);
}
IO::Result ArchiveFileIO::DestroyPath(const char* filePath)
{
// since you can't destroy a path inside a pak file
// we will pass this to the underlying real fileio
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return IO::ResultCode::Error;
}
return realUnderlyingFileIO->DestroyPath(filePath);
}
IO::Result ArchiveFileIO::Remove(const char* filePath)
{
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->Remove(filePath);
}
return IO::ResultCode::Error;
}
return (m_archive->RemoveFile(filePath) ? IO::ResultCode::Success : IO::ResultCode::Error);
}
IO::Result ArchiveFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
// you can actually copy a file from inside a pak to a destination path if you want to...
IO::HandleType sourceFile = IO::InvalidHandle;
IO::HandleType destinationFile = IO::InvalidHandle;
if (!Open(sourceFilePath, IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary, sourceFile))
{
return IO::ResultCode::Error;
}
// avoid using AZStd::string if possible - use OSString instead of StringFunc
AZ::OSString destPath(destinationFilePath);
AZ::OSString::size_type pos = destPath.find_last_of(AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
if (pos != AZ::OSString::npos)
{
destPath.resize(pos);
}
CreatePath(destPath.c_str());
if (!Open(destinationFilePath, IO::OpenMode::ModeWrite | IO::OpenMode::ModeBinary, destinationFile))
{
Close(sourceFile);
return IO::ResultCode::Error;
}
// standard buffered copy.
bool failureEncountered = false;
AZ::u64 bytesRemaining = 0;
if (!Size(sourceFilePath, bytesRemaining))
{
Close(destinationFile);
Close(sourceFile);
return IO::ResultCode::Error;
}
while (bytesRemaining > 0)
{
size_t bytesThisTime = AZStd::GetMin<size_t>(bytesRemaining, ArchiveFileIoMaxBuffersize);
if (!Read(sourceFile, m_copyBuffer.data(), bytesThisTime, true))
{
failureEncountered = true;
break;
}
AZ::u64 actualBytesWritten = 0;
if ((!Write(destinationFile, m_copyBuffer.data(), bytesThisTime, &actualBytesWritten)) || (actualBytesWritten == 0))
{
failureEncountered = true;
break;
}
bytesRemaining -= actualBytesWritten;
}
Close(sourceFile);
Close(destinationFile);
return (failureEncountered || (bytesRemaining > 0)) ? IO::ResultCode::Error : IO::ResultCode::Success;
}
bool ArchiveFileIO::IsReadOnly(const char* filePath)
{
// a tricky one! files inside a pack are technically readonly...
IO::HandleType openedHandle = IO::InvalidHandle;
if (!Open(filePath, IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary, openedHandle))
{
return false; // this will also return false if there is no archive, so no need to check it again
}
bool inPak = m_archive->IsInPak(openedHandle);
Close(openedHandle);
if (inPak)
{
return true; // things inside packfiles are read only by default since you cannot modify them while pak is mounted.
}
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return false;
}
return realUnderlyingFileIO->IsReadOnly(filePath);
}
IO::Result ArchiveFileIO::Rename(const char* sourceFilePath, const char* destinationFilePath)
{
// since you cannot perform this opearation inside a pak file, we do it on the real file
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return IO::ResultCode::Error;
}
return realUnderlyingFileIO->Rename(sourceFilePath, destinationFilePath);
}
IO::Result ArchiveFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback)
{
// note that the underlying findFiles takes both path and filter.
if (!filePath)
{
return IO::ResultCode::Error;
}
if (!m_archive)
{
if (GetDirectInstance())
{
return GetDirectInstance()->FindFiles(filePath, filter, callback);
}
return IO::ResultCode::Error;
}
AZStd::fixed_string<AZ_MAX_PATH_LEN> total = filePath;
if (total.empty())
{
return IO::ResultCode::Error;
}
if (!total.ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR) && !total.ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR))
{
total.append(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
}
total.append(filter);
AZ::IO::ArchiveFileIterator fileIterator = m_archive->FindFirst(total.c_str());
if (!fileIterator)
{
return IO::ResultCode::Success; // its not an actual fatal error to not find anything.
}
for (;fileIterator; fileIterator = m_archive->FindNext(fileIterator))
{
total = AZStd::fixed_string<AZ_MAX_PATH_LEN>::format("%s/%.*s", filePath, aznumeric_cast<int>(fileIterator.m_filename.size()), fileIterator.m_filename.data());
AZStd::optional resolvedAliasLength = ConvertToAlias(total.data(), total.capacity());
if (resolvedAliasLength)
{
total.resize_no_construct(*resolvedAliasLength);
if (!callback(total.c_str()))
{
break;
}
}
}
m_archive->FindClose(fileIterator);
return IO::ResultCode::Success;
}
bool ArchiveFileIO::GetFilename(IO::HandleType fileHandle, char* filename, AZ::u64 filenameSize) const
{
// because we sit on archive we need to keep track of archive files too.
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_operationGuard);
const auto fileIt = m_trackedFiles.find(fileHandle);
if (fileIt != m_trackedFiles.end())
{
AZ_Assert(filenameSize >= fileIt->second.length(), "Filename size %" PRIu64 " is larger than the size of the tracked file %s:%zu", fileIt->second.c_str(), fileIt->second.size());
azstrncpy(filename, filenameSize, fileIt->second.c_str(), fileIt->second.length());
return true;
}
if (GetDirectInstance())
{
return GetDirectInstance()->GetFilename(fileHandle, filename, filenameSize);
}
return false;
}
// the rest of these functions just pipe through to the underlying real file IO, since
// archive doesn't know about any of this.
void ArchiveFileIO::SetAlias(const char* alias, const char* path)
{
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return;
}
realUnderlyingFileIO->SetAlias(alias, path);
}
const char* ArchiveFileIO::GetAlias(const char* alias) const
{
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return nullptr;
}
return realUnderlyingFileIO->GetAlias(alias);
}
void ArchiveFileIO::ClearAlias(const char* alias)
{
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return;
}
realUnderlyingFileIO->GetAlias(alias);
}
AZStd::optional<AZ::u64> ArchiveFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const
{
if ((!inOutBuffer) || (bufferLength == 0))
{
return 0;
}
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
inOutBuffer[0] = 0;
return AZStd::nullopt;
}
return realUnderlyingFileIO->ConvertToAlias(inOutBuffer, bufferLength);
}
bool ArchiveFileIO::ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& path) const
{
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return false;
}
return realUnderlyingFileIO->ConvertToAlias(convertedPath, path);
}
bool ArchiveFileIO::ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const
{
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return false;
}
return realUnderlyingFileIO->ResolvePath(path, resolvedPath, resolvedPathSize);
}
bool ArchiveFileIO::ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const
{
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return false;
}
return realUnderlyingFileIO->ResolvePath(resolvedPath, path);
}
}//namespace AZ:IO
@@ -0,0 +1,88 @@
/*
* 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/IO/FileIO.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/fixed_vector.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/string/osstring.h>
namespace AZ::IO
{
inline constexpr size_t ArchiveFileIoMaxBuffersize = 16 * 1024;
struct IArchive;
//! ArchiveFileIO
//! An implementation of the FileIOBase which pipes all operations via Archive,
//! which itself pipes all operations via Local or RemoteFileIO.
//! this allows us to talk to files inside packfiles, without having to change the interface.
class ArchiveFileIO
: public AZ::IO::FileIOBase
{
public:
AZ_RTTI(ArchiveFileIO, "{679F8DB8-CC61-4BC8-ADDB-170E3D428B5D}", FileIOBase);
AZ_CLASS_ALLOCATOR(ArchiveFileIO, OSAllocator, 0);
ArchiveFileIO(IArchive* archive);
~ArchiveFileIO();
void SetArchive(IArchive* archive);
IArchive* GetArchive() const;
////////////////////////////////////////////////////////////////////////////////////////
//implementation of FileIOBase
IO::Result Open(const char* filePath, IO::OpenMode mode, IO::HandleType& fileHandle) override;
IO::Result Close(IO::HandleType fileHandle) override;
IO::Result Tell(IO::HandleType fileHandle, AZ::u64& offset) override;
IO::Result Seek(IO::HandleType fileHandle, AZ::s64 offset, IO::SeekType type) override;
IO::Result Size(IO::HandleType fileHandle, AZ::u64& size) override;
IO::Result Read(IO::HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead = false, AZ::u64* bytesRead = nullptr) override;
IO::Result Write(IO::HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten = nullptr) override;
IO::Result Flush(IO::HandleType fileHandle) override;
bool Eof(IO::HandleType fileHandle) override;
AZ::u64 ModificationTime(IO::HandleType fileHandle) override;
bool Exists(const char* filePath) override;
IO::Result Size(const char* filePath, AZ::u64& size) override;
AZ::u64 ModificationTime(const char* filePath) override;
bool IsDirectory(const char* filePath) override;
bool IsReadOnly(const char* filePath) override;
IO::Result CreatePath(const char* filePath) override;
IO::Result DestroyPath(const char* filePath) override;
IO::Result Remove(const char* filePath) override;
IO::Result Copy(const char* sourceFilePath, const char* destinationFilePath) override;
IO::Result Rename(const char* sourceFilePath, const char* destinationFilePath) override;
IO::Result FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback) override;
void SetAlias(const char* alias, const char* path) override;
void ClearAlias(const char* alias) override;
AZStd::optional<AZ::u64> ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const override;
bool ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& path) const;
using FileIOBase::ConvertToAlias;
const char* GetAlias(const char* alias) const override;
bool ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const override;
bool ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const override;
using FileIOBase::ResolvePath;
bool GetFilename(IO::HandleType fileHandle, char* filename, AZ::u64 filenameSize) const override;
////////////////////////////////////////////////////////////////////////////////////////////
protected:
// we keep a list of file names ever opened so that we can easily return it.
mutable AZStd::recursive_mutex m_operationGuard;
AZStd::unordered_map<IO::HandleType, AZ::OSString, AZStd::hash<IO::HandleType>, AZStd::equal_to<IO::HandleType>, AZ::OSStdAllocator> m_trackedFiles;
AZStd::fixed_vector<char, ArchiveFileIoMaxBuffersize> m_copyBuffer;
IArchive* m_archive;
};
} // namespace AZ::IO
@@ -0,0 +1,272 @@
/*
* 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/IO/SystemFile.h>
#include <AzCore/std/string/wildcard.h>
#include <AzFramework/Archive/Archive.h>
#include <AzFramework/Archive/ArchiveFindData.h>
#include <AzFramework/Archive/ArchiveVars.h>
#include <AzFramework/Archive/ZipDirFind.h>
namespace AZ::IO
{
bool AZStdStringLessCaseInsensitive::operator()(AZStd::string_view left, AZStd::string_view right) const
{
size_t compareLength = (AZStd::min)(left.size(), right.size());
if (compareLength == 0)
{
return left.size() < right.size();
}
int compareResult = azstrnicmp(left.data(), right.data(), compareLength);
return compareResult < 0;
}
FileDesc::FileDesc(Attribute fileAttribute, uint64_t fileSize, time_t accessTime, time_t creationTime, time_t writeTime)
: nAttrib{ fileAttribute }
, nSize{ fileSize }
, tAccess{ accessTime }
, tCreate{ creationTime }
, tWrite{ writeTime }
{
}
ArchiveFileIterator::ArchiveFileIterator(FindData* findData, AZStd::string_view filename, const FileDesc& fileDesc)
: m_findData{ findData }
, m_filename{ filename }
, m_fileDesc{ fileDesc }
{
}
ArchiveFileIterator ArchiveFileIterator::operator++()
{
ArchiveFileIterator resultIter;
if (m_findData)
{
resultIter = m_findData->Fetch();
}
return resultIter;
}
ArchiveFileIterator ArchiveFileIterator::operator++(int)
{
return operator++();
}
ArchiveFileIterator::operator bool() const
{
return m_findData && m_lastFetchValid;
}
void FindData::Scan(IArchive* archive, AZStd::string_view szDir, bool bAllowUseFS)
{
// get the priority into local variable to avoid it changing in the course of
// this function execution
ArchiveLocationPriority nVarPakPriority = archive->GetPakPriority();
if (nVarPakPriority == ArchiveLocationPriority::ePakPriorityFileFirst)
{
// first, find the file system files
ScanFS(archive, szDir);
ScanZips(archive, szDir);
}
else
{
// first, find the zip files
ScanZips(archive, szDir);
if (bAllowUseFS || nVarPakPriority != ArchiveLocationPriority::ePakPriorityPakOnly)
{
ScanFS(archive, szDir);
}
}
}
void FindData::ScanFS([[maybe_unused]] IArchive* archive, AZStd::string_view szDirIn)
{
AZStd::string searchDirectory;
AZStd::string pattern;
{
AZ::IO::PathString directory{ szDirIn };
AZ::StringFunc::Path::GetFullPath(directory.c_str(), searchDirectory);
AZ::StringFunc::Path::GetFullFileName(directory.c_str(), pattern);
}
AZ::IO::FileIOBase::GetDirectInstance()->FindFiles(searchDirectory.c_str(), pattern.c_str(), [&](const char* filePath) -> bool
{
AZ::IO::FileDesc fileDesc;
AZStd::string fullFilePath;
AZ::StringFunc::Path::GetFullFileName(filePath, fullFilePath);
if (AZ::IO::FileIOBase::GetDirectInstance()->IsDirectory(filePath))
{
fileDesc.nAttrib = fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::Subdirectory;
}
else
{
if (AZ::IO::FileIOBase::GetDirectInstance()->IsReadOnly(filePath))
{
fileDesc.nAttrib = fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::ReadOnly;
}
AZ::u64 fileSize = 0;
AZ::IO::FileIOBase::GetDirectInstance()->Size(filePath, fileSize);
fileDesc.nSize = fileSize;
fileDesc.tWrite = AZ::IO::FileIOBase::GetDirectInstance()->ModificationTime(filePath);
// These times are not supported by our file interface
fileDesc.tAccess = fileDesc.tWrite;
fileDesc.tCreate = fileDesc.tWrite;
}
m_mapFiles.emplace(AZStd::move(fullFilePath), fileDesc);
return true;
});
}
//////////////////////////////////////////////////////////////////////////
void FindData::ScanZips(IArchive* archive, AZStd::string_view szDir)
{
AZ::IO::FixedMaxPath sourcePath;
if (!AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(sourcePath, szDir))
{
AZ_Assert(false, "Unable to resolve Path for file path %.*s", aznumeric_cast<int>(szDir.size()), szDir.data());
return;
}
auto ScanInZip = [this](ZipDir::Cache* zipCache, AZStd::string_view relativePath)
{
ZipDir::FindFile findFileEntry(zipCache);
for (findFileEntry.FindFirst(relativePath); findFileEntry.GetFileEntry(); findFileEntry.FindNext())
{
ZipDir::FileEntry* fileEntry = findFileEntry.GetFileEntry();
AZStd::string_view fname = findFileEntry.GetFileName();
if (fname.empty())
{
AZ_Fatal("Archive", "Empty filename within zip file: '%s'", zipCache->GetFilePath());
}
AZ::IO::FileDesc fileDesc;
fileDesc.nAttrib = AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive;
fileDesc.nSize = fileEntry->desc.lSizeUncompressed;
fileDesc.tWrite = fileEntry->GetModificationTime();
m_mapFiles.emplace(fname, fileDesc);
}
ZipDir::FindDir findDirectoryEntry(zipCache);
for (findDirectoryEntry.FindFirst(relativePath); findDirectoryEntry.GetDirEntry(); findDirectoryEntry.FindNext())
{
AZStd::string_view fname = findDirectoryEntry.GetDirName();
if (fname.empty())
{
AZ_Fatal("Archive", "Empty directory name within zip file: '%s'", zipCache->GetFilePath());
}
AZ::IO::FileDesc fileDesc;
fileDesc.nAttrib = AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive | AZ::IO::FileDesc::Attribute::Subdirectory;
m_mapFiles.emplace(fname, fileDesc);
}
};
auto archiveInst = static_cast<Archive*>(archive);
AZStd::shared_lock lock(archiveInst->m_csZips);
for (auto it = archiveInst->m_arrZips.begin(); it != archiveInst->m_arrZips.end(); ++it)
{
// filter out the stuff which does not match.
// the problem here is that szDir might be something like "@assets@/levels/*"
// but our archive might be mounted at the root, or at some other folder at like "@assets@" or "@assets@/levels/mylevel"
// so there's really no way to filter out opening the pack and looking at the files inside.
// however, the bind root is not part of the inner zip entry name either
// and the ZipDir::FindFile actually expects just the chopped off piece.
// we have to find whats in common between them and check that:
auto resolvedBindRoot = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(it->m_pathBindRoot);
if (!resolvedBindRoot)
{
AZ_Assert(false, "Unable to resolve Path for archive %s bind root %s", it->GetFullPath(), it->m_pathBindRoot.c_str());
return;
}
AZ::IO::FixedMaxPath bindRoot{ *resolvedBindRoot };
auto [bindRootIter, sourcePathIter] = AZStd::mismatch(AZStd::begin(bindRoot), AZStd::end(bindRoot),
AZStd::begin(sourcePath), AZStd::end(sourcePath));
if (sourcePathIter == AZStd::begin(sourcePath))
{
// The path has no characters in common , early out the search as filepath is not part of the iterated zip
continue;
}
AZ::IO::FixedMaxPath sourcePathRemainder;
for (; sourcePathIter != AZStd::end(sourcePath); ++sourcePathIter)
{
sourcePathRemainder /= *sourcePathIter;
}
// Example:
// "@assets@\\levels\\*" <--- szDir
// "@assets@\\" <--- mount point
// ~~~~~~~~~~~ Common part
// "levels\\*" <---- remainder that is not in common
// "" <--- mount point remainder. In this case, we should scan the contents of the pak for the remainder
// Example:
// "@assets@\\levels\\*" <--- szDir
// "@assets@\\levels\\mylevel\\" <--- mount point (its level.pak)
// ~~~~~~~~~~~~~~~~~~ common part
// "*" <---- remainder that is not in common
// "mylevel\\" <--- mount point remainder.
// example:
// "@assets@\\levels\\otherlevel\\*" <--- szDir
// "@assets@\\levels\\mylevel\\" <--- mount point (its level.pak)
// "otherlevel\\*" <---- remainder
// "mylevel\\" <--- mount point remainder.
// the general strategy here is that IF there is a mount point remainder
// then it means that the pack's mount point itself might be a return value, not the files inside the pack
// in that case, we compare the mount point remainder itself with the search filter
if (bindRootIter != bindRoot.end())
{
// Retrieve next path component of the mount point remainder
if (!bindRootIter->empty() && AZStd::wildcard_match(sourcePathRemainder.Native(), bindRootIter->Native()))
{
AZ::IO::FileDesc fileDesc{ AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive | AZ::IO::FileDesc::Attribute::Subdirectory };
m_mapFiles.emplace(bindRootIter->Native(), fileDesc);
}
}
else
{
// if we get here, it means that the search pattern's root and the mount point for this pack are identical
// which means we may search inside the pack.
ScanInZip(it->pZip.get(), sourcePathRemainder.Native());
}
}
}
AZ::IO::ArchiveFileIterator FindData::Fetch()
{
AZ::IO::ArchiveFileIterator fileIterator;
fileIterator.m_findData = this;
if (m_mapFiles.empty())
{
return fileIterator;
}
auto pakFileIter = m_mapFiles.begin();
fileIterator.m_filename = pakFileIter->first;
fileIterator.m_fileDesc = pakFileIter->second;
fileIterator.m_lastFetchValid = true;
// Remove Fetched item from the FindData map so that the iteration continues
m_mapFiles.erase(pakFileIter);
return fileIterator;
}
}
@@ -0,0 +1,86 @@
/*
* 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_base.h>
#include <AzCore/std/string/fixed_string.h>
namespace AZ::IO
{
struct IArchive;
struct FileDesc
{
enum class Attribute : uint32_t
{
ReadOnly = 0x1,
Subdirectory = 0x10,
Archive = 0x80000000
};
Attribute nAttrib{};
uint64_t nSize{};
time_t tAccess{ -1 };
time_t tCreate{ -1 };
time_t tWrite{ -1 };
FileDesc() = default;
explicit FileDesc(Attribute fileAttribute, uint64_t fileSize = 0, time_t accessTime = -1, time_t creationTime = -1, time_t writeTime = -1);
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::IO::FileDesc::Attribute);
class FindData;
struct ArchiveFileIterator
{
ArchiveFileIterator() = default;
ArchiveFileIterator(FindData* findData, AZStd::string_view filename, const FileDesc& fileDesc);
ArchiveFileIterator operator++();
ArchiveFileIterator operator++(int);
explicit operator bool() const;
inline static constexpr size_t FilenameMaxLength = 256;
AZStd::fixed_string<FilenameMaxLength> m_filename;
FileDesc m_fileDesc;
AZStd::intrusive_ptr<FindData> m_findData{};
private:
friend class FindData;
bool m_lastFetchValid{};
};
struct AZStdStringLessCaseInsensitive
{
bool operator()(AZStd::string_view left, AZStd::string_view right) const;
using is_transparent = void;
};
class FindData
: public AZStd::intrusive_base
{
public:
AZ_CLASS_ALLOCATOR(FindData, AZ::SystemAllocator, 0);
FindData() = default;
AZ::IO::ArchiveFileIterator Fetch();
void Scan(IArchive* archive, AZStd::string_view path, bool bAllowUseFS = false);
protected:
void ScanFS(IArchive* archive, AZStd::string_view path);
void ScanZips(IArchive* archive, AZStd::string_view path);
using FileMap = AZStd::map<AZStd::string, AZ::IO::FileDesc, AZStdStringLessCaseInsensitive>;
FileMap m_mapFiles;
};
}
@@ -0,0 +1,61 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <AzFramework/Archive/ArchiveVars_Platform.h>
namespace AZ::IO
{
enum class ArchiveLocationPriority
{
ePakPriorityFileFirst = 0,
ePakPriorityPakFirst = 1,
ePakPriorityPakOnly = 2
};
// variables that control behavior of Archive/StreamEngine subsystems
struct ArchiveVars
{
#if defined(_RELEASE)
inline static constexpr bool IsReleaseConfig{ true };
#else
inline static constexpr bool IsReleaseConfig{};
#endif
public:
int nReadSlice{};
int nSaveTotalResourceList{};
int nSaveFastloadResourceList{};
int nSaveMenuCommonResourceList{};
int nSaveLevelResourceList{};
int nValidateFileHashes{ IsReleaseConfig ? 0 : 1 };
int nUncachedStreamReads{ 1 };
int nInMemoryPerPakSizeLimit{ 6 }; // Limits in MB
int nTotalInMemoryPakSizeLimit{ 30 };
int nLoadCache{};
int nLoadModePaks{};
int nStreamCache{ STREAM_CACHE_DEFAULT };
ArchiveLocationPriority nPriority{ IsReleaseConfig
? ArchiveLocationPriority::ePakPriorityPakOnly
: ArchiveLocationPriority::ePakPriorityFileFirst }; // Which file location to favor (loose vs. pak files)
int nMessageInvalidFileAccess{};
int nLogInvalidFileAccess{ IsReleaseConfig ? 0 : 1 };
int nLoadFrontendShaderCache{ FRONTEND_SHADER_CACHE_DEFAULT };
int nDisableNonLevelRelatedPaks{ 1 };
int nWarnOnPakAccessFails{ 1 }; // Whether to treat failed pak access as a warning or log message
int nSetLogLevel{ 3 };
int nLogAllFileAccess{};
};
}
@@ -0,0 +1,73 @@
/*
* 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/base.h>
namespace CompressionCodec
{
enum class Codec : uint8_t
{
INVALID = static_cast<uint8_t>(-1),
ZLIB = 0,
ZSTD,
LZ4,
NUM_CODECS
};
inline constexpr Codec s_AllCodecs[] = { Codec::ZLIB, Codec::ZSTD, Codec::LZ4 };
inline bool CheckMagic(const void* pCompressedData, const uint32_t magicNumber, const uint32_t magicSkippable)
{
uint32_t compressedMagic = 0;
//If the address is 4bytes aligned, then it is safe to dereference as a 4byte integral.
if ((reinterpret_cast<uintptr_t>(pCompressedData) & 0x3) == 0)
{
compressedMagic = *reinterpret_cast<const uint32_t*>(pCompressedData);
}
else
{
//We should read one byte at a time to avoid alignment issues.
const uint8_t* pCompressedBytes = static_cast<const uint8_t*>(pCompressedData);
compressedMagic = static_cast<uint32_t>(pCompressedBytes[0]) | (static_cast<uint32_t>(pCompressedBytes[1]) << 8) |
(static_cast<uint32_t>(pCompressedBytes[2]) << 16) | (static_cast<uint32_t>(pCompressedBytes[3]) << 24);
}
if (compressedMagic == magicNumber)
{
return true;
}
if ((compressedMagic & 0xFFFFFFF0) == magicSkippable)
{
return true;
}
return false;
}
inline bool TestForLZ4Magic(const void* pCompressedData)
{
constexpr uint32_t lz4MagicNumber = 0x184D2204;
constexpr uint32_t lz4MagicSkippable = 0x184D2A50;
return CheckMagic(pCompressedData, lz4MagicNumber, lz4MagicSkippable);
}
inline bool TestForZSTDMagic(const void* pCompressedData)
{
constexpr uint32_t zstdMagicNumber = 0xFD2FB528;
constexpr uint32_t zstdMagicSkippable = 0x184D2A50; //Same as LZ4
return CheckMagic(pCompressedData, zstdMagicNumber, zstdMagicSkippable);
}
};
@@ -0,0 +1,517 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <AzCore/EBus/Event.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Archive/ArchiveFindData.h>
enum EStreamSourceMediaType : int32_t;
namespace AZ::IO
{
enum class ArchiveLocationPriority;
struct IResourceList;
struct INestedArchive;
struct IArchive;
using PathString = AZStd::fixed_string<AZ::IO::MaxPathLength>;
using StackString = AZStd::fixed_string<512>;
struct MemoryBlock;
struct MemoryBlockDeleter
{
void operator()(const AZStd::intrusive_refcount<AZStd::atomic_uint, MemoryBlockDeleter>* ptr) const;
AZ::IAllocatorAllocate* m_allocator{};
};
struct MemoryBlock
: AZStd::intrusive_refcount<AZStd::atomic_uint, MemoryBlockDeleter>
{
MemoryBlock() = default;
MemoryBlock(MemoryBlockDeleter deleter)
: AZStd::intrusive_refcount<AZStd::atomic_uint, MemoryBlockDeleter>{ deleter }
{}
struct AddressDeleter
{
using DeleteFunc = void(*)(uint8_t*);
AddressDeleter()
: m_deleteFunc{ nullptr }
{}
AddressDeleter(DeleteFunc deleteFunc)
: m_deleteFunc{ deleteFunc }
{}
void operator()(uint8_t* ptr) const
{
if (m_deleteFunc)
{
m_deleteFunc(ptr);
}
else
{
delete[] ptr;
}
}
DeleteFunc m_deleteFunc;
};
using AddressPtr = AZStd::unique_ptr<uint8_t[], AddressDeleter>;
AddressPtr m_address;
size_t m_size{};
};
inline void MemoryBlockDeleter::operator()(const AZStd::intrusive_refcount<AZStd::atomic_uint, MemoryBlockDeleter>* ptr) const
{
auto address = const_cast<MemoryBlock*>(static_cast<const MemoryBlock*>(ptr));
if (m_allocator)
{
address ->~MemoryBlock();
m_allocator->DeAllocate(address);
}
else
{
delete address;
}
}
struct IArchiveFileAccessSink
{
virtual ~IArchiveFileAccessSink() {}
// Arguments:
// in - 0 if asynchronous read
// szFullPath - must not be 0
virtual void ReportFileOpen(AZ::IO::HandleType inFileHandle, AZStd::string_view szFullPath) = 0;
};
// Summary
// Interface to the Archive file system
// See Also
// Archive
struct IArchive
{
AZ_RTTI(IArchive, "{764A2260-FF8A-4C86-B958-EBB0B69D9DFA}");
using FileTime = uint64_t;
// Flags used in file path resolution rules
enum EPathResolutionRules
{
// If used, the source path will be treated as the destination path
// and no transformations will be done. Pass this flag when the path is to be the actual
// path on the disk/in the packs and doesn't need adjustment (or after it has come through adjustments already)
// if this is set, AdjustFileName will not map the input path into the master folder (Ex: Shaders will not be converted to Game\Shaders)
FLAGS_PATH_REAL = 1 << 16,
// AdjustFileName will always copy the file path to the destination path:
// regardless of the returned value, szDestpath can be used
FLAGS_COPY_DEST_ALWAYS = 1 << 17,
// Adds trailing slash to the path
FLAGS_ADD_TRAILING_SLASH = 1L << 18,
// if this is set, AdjustFileName will not make relative paths into full paths
FLAGS_NO_FULL_PATH = 1 << 21,
// if this is set, AdjustFileName will redirect path to disc
FLAGS_REDIRECT_TO_DISC = 1 << 22,
// if this is set, AdjustFileName will not adjust path for writing files
FLAGS_FOR_WRITING = 1 << 23,
// if this is set, the archive would be stored in memory (gpu)
FLAGS_PAK_IN_MEMORY = 1 << 25,
// Store all file names as crc32 in a flat directory structure.
FLAGS_FILENAMES_AS_CRC32 = 1 << 26,
// if this is set, AdjustFileName will try to find the file under any mod paths we know about
FLAGS_CHECK_MOD_PATHS = 1 << 27,
// if this is set, AdjustFileName will always check the filesystem/disk and not check inside open archives
FLAGS_NEVER_IN_PAK = 1 << 28,
// returns existing file name from the local data or existing cache file name
// used by the resource compiler to pass the real file name
FLAGS_RESOLVE_TO_CACHE = 1 << 29,
// if this is set, the archive would be stored in memory (cpu)
FLAGS_PAK_IN_MEMORY_CPU = 1 << 30,
// if this is set, the level pak is inside another archive
FLAGS_LEVEL_PAK_INSIDE_PAK = 1 << 31,
};
// Used for widening FOpen functionality. They're ignored for the regular File System files.
enum EFOpenFlags
{
// If possible, will prevent the file from being read from memory.
FOPEN_HINT_DIRECT_OPERATION = 1,
// Will prevent a "missing file" warnings to be created.
FOPEN_HINT_QUIET = 1 << 1,
// File should be on disk
FOPEN_ONDISK = 1 << 2,
// Open is done by the streaming thread.
FOPEN_FORSTREAMING = 1 << 3,
};
//
enum ERecordFileOpenList
{
RFOM_Disabled, // file open are not recorded (fast, no extra memory)
RFOM_EngineStartup, // before a level is loaded
RFOM_Level, // during level loading till export2game -> resourcelist.txt, used to generate the list for level2level loading
RFOM_NextLevel // used for level2level loading
};
// the size of the buffer that receives the full path to the file
inline static constexpr size_t MaxPath = 1024;
//file location enum used in isFileExist to control where the archive system looks for the file.
enum EFileSearchLocation
{
eFileLocation_Any = 0,
eFileLocation_OnDisk,
eFileLocation_InPak,
};
enum EInMemoryArchiveLocation
{
eInMemoryPakLocale_Unload = 0,
eInMemoryPakLocale_CPU,
eInMemoryPakLocale_GPU,
eInMemoryPakLocale_PAK,
};
using SignedFileSize = int64_t;
virtual ~IArchive() = default;
/**
* Deprecated: Use the AZ::IO::FileIOBase::ResolvePath function below that doesn't accept the nFlags or skipMods parameters
* given the source relative path, constructs the full path to the file according to the flags
* returns the pointer to the constructed path (can be either szSourcePath, or szDestPath, or NULL in case of error
*/
//
virtual const char* AdjustFileName(AZStd::string_view src, char* dst, size_t dstSize, uint32_t nFlags, bool skipMods = false) = 0;
virtual bool Init(AZStd::string_view szBasePath) = 0;
virtual void Release() = 0;
// Summary:
// Returns true if given archivepath is installed to HDD
// If no file path is given it will return true if whole application is installed to HDD
virtual bool IsInstalledToHDD(AZStd::string_view acFilePath = 0) const = 0;
// after this call, the archive file will be searched for files when they aren't on the OS file system
// Arguments:
// pName - must not be 0
virtual bool OpenPack(AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = {},
AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) = 0;
// after this call, the archive file will be searched for files when they aren't on the OS file system
virtual bool OpenPack(AZStd::string_view pBindingRoot, AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL,
AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = {}, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) = 0;
// after this call, the file will be unlocked and closed, and its contents won't be used to search for files
virtual bool ClosePack(AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
// opens pack files by the path and wildcard
virtual bool OpenPacks(AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) = 0;
// opens pack files by the path and wildcard
virtual bool OpenPacks(AZStd::string_view pBindingRoot, AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL,
AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) = 0;
// closes pack files by the path and wildcard
virtual bool ClosePacks(AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
//returns if a archive exists matching the wildcard
virtual bool FindPacks(AZStd::string_view pWildcardIn) = 0;
// Set access status of a archive files with a wildcard
virtual bool SetPacksAccessible(bool bAccessible, AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
// Set access status of a pack file
virtual bool SetPackAccessible(bool bAccessible, AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
// Load or unload archive file completely to memory.
virtual bool LoadPakToMemory(AZStd::string_view pName, EInMemoryArchiveLocation eLoadToMemory, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pMemoryBlock = nullptr) = 0;
virtual void LoadPaksToMemory(int nMaxArchiveSize, bool bLoadToMemory) = 0;
// Processes an alias command line containing multiple aliases.
virtual void ParseAliases(AZStd::string_view szCommandLine) = 0;
// adds or removes an alias from the list
virtual void SetAlias(AZStd::string_view szName, AZStd::string_view szAlias, bool bAdd) = 0;
// gets an alias from the list, if any exist.
// if bReturnSame==true, it will return the input name if an alias doesn't exist. Otherwise returns NULL
virtual const char* GetAlias(AZStd::string_view szName, bool bReturnSame = true) = 0;
// lock all the operations
virtual void Lock() = 0;
virtual void Unlock() = 0;
// Set and Get the localization folder name (Languages, Localization, ...)
virtual void SetLocalizationFolder(AZStd::string_view sLocalizationFolder) = 0;
virtual const char* GetLocalizationFolder() const = 0;
virtual const char* GetLocalizationRoot() const = 0;
// Open file handle, file can be on disk or in Archive file.
// Possible mode is r,b,x
// ex: AZ::IO::HandleType fileHandle = FOpen( "test.txt","rbx" );
// mode x is a direct access mode, when used file reads will go directly into the low level file system without any internal data caching.
// Text mode is not supported for files in Archives.
// for nFlags @see IArchive::EFOpenFlags
virtual AZ::IO::HandleType FOpen(AZStd::string_view pName, const char* mode, uint32_t nFlags = 0) = 0;
// Read raw data from file, no endian conversion.
virtual size_t FReadRaw(void* data, size_t length, size_t elems, AZ::IO::HandleType fileHandle) = 0;
// Read all file contents into the provided memory, nSizeOfFile must be the same as returned by GetFileSize(handle)
// Current seek pointer is ignored and reseted to 0.
// no endian conversion.
virtual size_t FReadRawAll(void* data, size_t nFileSize, AZ::IO::HandleType fileHandle) = 0;
// Get pointer to the internally cached, loaded data of the file.
// WARNING! The returned pointer is only valid while the fileHandle has not been closed.
virtual void* FGetCachedFileData(AZ::IO::HandleType fileHandle, size_t& nFileSize) = 0;
// Write file data, cannot be used for writing into the Archive.
// Use INestedArchive interface for writing into the archivefiles.
virtual size_t FWrite(const void* data, size_t length, size_t elems, AZ::IO::HandleType fileHandle) = 0;
virtual int FPrintf(AZ::IO::HandleType fileHandle, const char* format, ...) = 0;
virtual char* FGets(char*, int, AZ::IO::HandleType) = 0;
virtual int Getc(AZ::IO::HandleType) = 0;
virtual size_t FGetSize(AZ::IO::HandleType fileHandle) = 0;
virtual size_t FGetSize(AZStd::string_view pName, bool bAllowUseFileSystem = false) = 0;
virtual bool IsInPak(AZ::IO::HandleType fileHandle) = 0;
virtual bool RemoveFile(AZStd::string_view pName) = 0; // remove file from FS (if supported)
virtual bool RemoveDir(AZStd::string_view pName) = 0; // remove directory from FS (if supported)
virtual bool IsAbsPath(AZStd::string_view pPath) = 0; // determines if pPath is an absolute or relative path
virtual size_t FSeek(AZ::IO::HandleType fileHandle, uint64_t seek, int mode) = 0;
virtual uint64_t FTell(AZ::IO::HandleType fileHandle) = 0;
virtual int FClose(AZ::IO::HandleType fileHandle) = 0;
virtual int FEof(AZ::IO::HandleType fileHandle) = 0;
virtual int FFlush(AZ::IO::HandleType fileHandle) = 0;
//! Return pointer to pool if available
virtual void* PoolMalloc(size_t size) = 0;
//! Free pool
virtual void PoolFree(void* p) = 0;
// Return an interface to the Memory Block allocated on the File Pool memory.
// sUsage indicates for what usage this memory was requested.
virtual AZStd::intrusive_ptr<AZ::IO::MemoryBlock> PoolAllocMemoryBlock(size_t nSize, const char* sUsage, size_t nAlign = 1) = 0;
// Arguments:
// nFlags is a combination of EPathResolutionRules flags.
virtual ArchiveFileIterator FindFirst(AZStd::string_view pDir, uint32_t nFlags = 0, bool bAllowUseFileSystem = false) = 0;
virtual ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator handle) = 0;
virtual bool FindClose(AZ::IO::ArchiveFileIterator handle) = 0;
// virtual bool IsOutOfDate(const char * szCompiledName, const char * szMasterFile)=0;
//returns file modification time
virtual IArchive::FileTime GetModificationTime(AZ::IO::HandleType fileHandle) = 0;
// Description:
// Checks if specified file exist in filesystem.
virtual bool IsFileExist(AZStd::string_view sFilename, EFileSearchLocation = eFileLocation_Any) = 0;
// Checks if path is a folder
virtual bool IsFolder(AZStd::string_view sPath) = 0;
virtual IArchive::SignedFileSize GetFileSizeOnDisk(AZStd::string_view filename) = 0;
// creates a directory
virtual bool MakeDir(AZStd::string_view szPath, bool bGamePathMapping = false) = 0;
// open the physical archive file - creates if it doesn't exist
// returns NULL if it's invalid or can't open the file
// nFlags is a combination of flags from EArchiveFlags enum.
virtual AZStd::intrusive_ptr<INestedArchive> OpenArchive(AZStd::string_view szPath, AZStd::string_view = {}, uint32_t nFlags = 0,
AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr) = 0;
// returns the path to the archive in which the file was opened
// returns NULL if the file is a physical file, and "" if the path to archive is unknown (shouldn't ever happen)
virtual const char* GetFileArchivePath(AZ::IO::HandleType fileHandle) = 0;
// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
// returns one of the Z_* errors (Z_OK upon success)
// MT-safe
virtual int RawCompress(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, int nLevel = -1) = 0;
// Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file
// returns one of the Z_* errors (Z_OK upon success)
// This function just mimics the standard uncompress (with modification taken from unzReadCurrentFile)
// with 2 differences: there are no 16-bit checks, and
// it initializes the inflation to start without waiting for compression method byte, as this is the
// way it's stored into zip file
virtual int RawUncompress(void* pUncompressed, size_t* pDestSize, const void* pCompressed, size_t nSrcSize) = 0;
//////////////////////////////////////////////////////////////////////////
// Files collector.
//////////////////////////////////////////////////////////////////////////
// Turn on/off recording of filenames of opened files.
virtual void RecordFileOpen(ERecordFileOpenList eList) = 0;
// Record this file if recording is enabled.
// Arguments:
// in - 0 if asynchronous read
virtual void RecordFile(AZ::IO::HandleType infileHandle, AZStd::string_view szFilename) = 0;
// Summary:
// Get resource list of all recorded files, the next level, ...
virtual IResourceList* GetResourceList(ERecordFileOpenList eList) = 0;
virtual void SetResourceList(ERecordFileOpenList eList, IResourceList* pResourceList) = 0;
// get the current mode, can be set by RecordFileOpen()
virtual IArchive::ERecordFileOpenList GetRecordFileOpenList() = 0;
// computes CRC (zip compatible) for a file
// useful if a huge uncompressed file is generation in non continuous way
// good for big files - low memory overhead (1MB)
// Arguments:
// szPath - must not be 0
// Returns:
// error code
virtual uint32_t ComputeCRC(AZStd::string_view szPath, uint32_t nFileOpenFlags = 0) = 0;
// computes MD5 checksum for a file
// good for big files - low memory overhead (1MB)
// Arguments:
// szPath - must not be 0
// md5 - destination array of uint8_t [16]
// Returns:
// true if success, false on failure
virtual bool ComputeMD5(AZStd::string_view szPath, uint8_t* md5, uint32_t nFileOpenFlags = 0, bool useDirectFileAccess = false) = 0;
// useful for gathering file access statistics, assert if it was inserted already but then it does not become insersted
// Arguments:
// pSink - must not be 0
virtual void RegisterFileAccessSink(IArchiveFileAccessSink* pSink) = 0;
// assert if it was not registered already
// Arguments:
// pSink - must not be 0
virtual void UnregisterFileAccessSink(IArchiveFileAccessSink* pSink) = 0;
// When enabled, files accessed at runtime will be tracked
virtual void DisableRuntimeFileAccess(bool status) = 0;
virtual bool DisableRuntimeFileAccess(bool status, AZStd::thread_id threadId) = 0;
virtual bool CheckFileAccessDisabled(AZStd::string_view name, const char* mode) = 0;
virtual void SetRenderThreadId(AZStd::thread_id renderThreadId) = 0;
// gets the current pak priority
virtual ArchiveLocationPriority GetPakPriority() const = 0;
// Summary:
// Return offset in archive file (ideally has to return offset on DVD) for streaming requests sorting
virtual uint64_t GetFileOffsetOnMedia(AZStd::string_view szName) const = 0;
// Summary:
// Return media type for the file
virtual EStreamSourceMediaType GetFileMediaType(AZStd::string_view szName) const = 0;
// Event sent when a archive file is opened that contains a level.pak
// @param const AZStd::vector<AZStd::string>& - Array of directories containing level.pak files
using LevelPackOpenEvent = AZ::Event<const AZStd::vector<AZStd::string>&>;
virtual auto GetLevelPackOpenEvent()->LevelPackOpenEvent* = 0;
// Event sent when a archive contains a level.pak is closed
// @param const AZStd::string_view - Name of the pak file that was closed
using LevelPackCloseEvent = AZ::Event<AZStd::string_view>;
virtual auto GetLevelPackCloseEvent()->LevelPackCloseEvent* = 0;
// Type-safe endian conversion read.
template<class T>
size_t FRead(T* data, size_t elems, AZ::IO::HandleType fileHandle, bool bSwapEndian = false)
{
size_t count = FReadRaw(data, sizeof(T), elems, fileHandle);
SwapEndian(data, count, bSwapEndian);
return count;
}
// Type-independent Write.
template<class T>
void FWrite(T* data, size_t elems, AZ::IO::HandleType fileHandle)
{
FWrite((void*)data, sizeof(T), elems, fileHandle);
}
inline static constexpr IArchive::SignedFileSize FILE_NOT_PRESENT = -1;
};
class ScopedFileHandle
{
public:
ScopedFileHandle(IArchive& archiveInstance, AZStd::string_view fileName, const char* mode)
: m_archive(archiveInstance)
{
m_fileHandle = m_archive.FOpen(fileName, mode);
}
~ScopedFileHandle()
{
if (m_fileHandle != AZ::IO::InvalidHandle)
{
m_archive.FClose(m_fileHandle);
}
}
operator AZ::IO::HandleType()
{
return m_fileHandle;
}
bool IsValid() const
{
return m_fileHandle != AZ::IO::InvalidHandle;
}
private:
AZ::IO::HandleType m_fileHandle;
IArchive& m_archive;
};
// The IResourceList provides an access to the collection of the resource`s file names.
// Client can add a new file names to the resource list and check if resource already in the list.
struct IResourceList
: public AZStd::intrusive_base
{
// <interfuscator:shuffle>
// Description:
// Adds a new resource to the list.
virtual void Add(AZStd::string_view sResourceFile) = 0;
// Description:
// Clears resource list.
virtual void Clear() = 0;
// Description:
// Checks if specified resource exist in the list.
virtual bool IsExist(AZStd::string_view sResourceFile) = 0;
// Description:
// Loads a resource list from the resource list file.
virtual bool Load(AZStd::string_view sResourceListFilename) = 0;
//////////////////////////////////////////////////////////////////////////
// Enumeration.
//////////////////////////////////////////////////////////////////////////
// Description:
// Returns the file name of the first resource or NULL if resource list is empty.
virtual const char* GetFirst() = 0;
// Description:
// Returns the file name of the next resource or NULL if reached the end.
// Client must call GetFirst before calling GetNext.
virtual const char* GetNext() = 0;
};
}
@@ -0,0 +1,208 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <AzCore/Math/Crc.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/string/string_view.h>
#include <AzFramework/Archive/Codec.h>
namespace AZ::IO
{
// This represents one particular archive.
struct INestedArchive
: public AZStd::intrusive_base
{
// Compression methods
enum ECompressionMethods
{
METHOD_STORE = 0,
METHOD_COMPRESS = 8,
METHOD_DEFLATE = 8,
METHOD_COMPRESS_AND_ENCRYPT = 11
};
// Compression levels
enum ECompressionLevels
{
LEVEL_FASTEST = 0,
LEVEL_FASTER = 2,
LEVEL_NORMAL = 8,
LEVEL_BETTER = 8,
LEVEL_BEST = 9,
LEVEL_DEFAULT = -1
};
enum EPakFlags
{
// support for absolute and other complex path specifications -
// all paths will be treated relatively to the current directory (normally MasterCD)
FLAGS_ABSOLUTE_PATHS = 1,
// if this is set, the object will only understand relative to the zip file paths,
// but this can give an opportunity to optimize for frequent quick accesses
// FLAGS_SIMPLE_RELATIVE_PATHS and FLAGS_ABSOLUTE_PATHS are mutually exclusive
FLAGS_RELATIVE_PATHS_ONLY = 1 << 1,
// if this flag is set, the archive update/remove operations will not work
// this is useful when you open a read-only or already opened for reading files.
// If FLAGS_OPEN_READ_ONLY | FLAGS_SIMPLE_RELATIVE_PATHS are set, IArchive
// will try to return an object optimized for memory, with long life cycle
FLAGS_READ_ONLY = 1 << 2,
// if this flag is set, FLAGS_OPEN_READ_ONLY
// flags are also implied. The returned object will be optimized for quick access and
// memory footprint
FLAGS_OPTIMIZED_READ_ONLY = (1 << 3),
// if this is set, the existing file (if any) will be overwritten
FLAGS_CREATE_NEW = 1 << 4,
// if this flag is set, and the file is opened for writing, and some files were updated
// so that the archive is no more continuous, the archive will nevertheless NOT be compacted
// upon closing the archive file. This can be faster if you open/close the archive for writing
// multiple times
FLAGS_DONT_COMPACT = 1 << 5,
// flag is set when complete pak has been loaded into memory
FLAGS_IN_MEMORY = 1 << 6,
FLAGS_IN_MEMORY_CPU = 1 << 7,
FLAGS_IN_MEMORY_MASK = FLAGS_IN_MEMORY | FLAGS_IN_MEMORY_CPU,
// Store all file names as crc32 in a flat directory structure.
FLAGS_FILENAMES_AS_CRC32 = 1 << 8,
// flag is set when pak is stored on HDD
FLAGS_ON_HDD = 1 << 9,
//Override pak - paks opened with this flag go at the end of the list and contents will be found before other paks
//Used for patching
FLAGS_OVERRIDE_PAK = 1 << 10,
// Disable a pak file without unloading it, this flag is used in combination with patches and multiplayer
// to ensure that specific paks stay in the position(to keep the same priority) but beeing disabled
// when running multiplayer
FLAGS_DISABLE_PAK = 1 << 11,
// flag is set when pak is inside another pak
FLAGS_INSIDE_PAK = 1 << 12,
};
using Handle = void*;
virtual ~INestedArchive() = default;
// Get archive's root folder
virtual Handle GetRootFolderHandle() = 0;
// Summary:
// Adds a new file to the zip or update an existing one.
// Description:
// Adds a new file to the zip or update an existing one
// adds a directory (creates several nested directories if needed)
// compression methods supported are METHOD_STORE == 0 (store) and
// METHOD_DEFLATE == METHOD_COMPRESS == 8 (deflate) , compression
// level is LEVEL_FASTEST == 0 till LEVEL_BEST == 9 or LEVEL_DEFAULT == -1
// for default (like in zlib)
virtual int UpdateFile(AZStd::string_view szRelativePath, const void* pUncompressed, uint64_t nSize, uint32_t nCompressionMethod = 0,
int nCompressionLevel = -1, CompressionCodec::Codec codec = CompressionCodec::Codec::ZLIB) = 0;
// Summary:
// Adds a new file to the zip or update an existing one if it is not compressed - just stored - start a big file
// ( name might be misleading as if nOverwriteSeekPos is used the update is not continuous )
// Description:
// First step for the UpdateFileConinouseSegment
virtual int StartContinuousFileUpdate(AZStd::string_view szRelativePath, uint64_t nSize) = 0;
// Summary:
// Adds a new file to the zip or update an existing's segment if it is not compressed - just stored
// adds a directory (creates several nested directories if needed)
// ( name might be misleading as if nOverwriteSeekPos is used the update is not continuous )
// Arguments:
// nOverwriteSeekPos - std::numeric_limits<uint64_t>::max(i.e uint64_t(-1)) means the seek pos should not be overwritten (then it needs UpdateFileCRC() to update CRC)
virtual int UpdateFileContinuousSegment(AZStd::string_view szRelativePath, uint64_t nSize, const void* pUncompressed, uint64_t nSegmentSize,
uint64_t nOverwriteSeekPos = (std::numeric_limits<uint64_t>::max)()) = 0;
// Summary:
// needed to update CRC if UpdateFileContinuousSegment() was used with nOverwriteSeekPos
virtual int UpdateFileCRC(AZStd::string_view szRelativePath, AZ::Crc32 dwCRC) = 0;
// Summary:
// Deletes the file from the archive.
virtual int RemoveFile(AZStd::string_view szRelativePath) = 0;
// Summary:
// Deletes the directory, with all its descendants (files and subdirs).
virtual int RemoveDir(AZStd::string_view szRelativePath) = 0;
// Summary:
// Deletes all files and directories in the archive.
virtual int RemoveAll() = 0;
// Summary:
// Finds the file; you don't have to close the returned handle.
// Returns:
// NULL if the file doesn't exist
virtual Handle FindFile(AZStd::string_view szPath) = 0;
// Summary:
// Get the file size (uncompressed).
// Returns:
// The size of the file (unpacked) by the handle
virtual uint64_t GetFileSize(Handle) = 0;
// Summary:
// Reads the file into the preallocated buffer
// Note:
// Must be at least the size returned by GetFileSize.
virtual int ReadFile(Handle, void* pBuffer) = 0;
// Summary:
// Get the full path to the archive file.
virtual const char* GetFullPath() const = 0;
// Summary:
// Get the flags of this object.
// Description:
// The possibles flags are defined in EPakFlags.
// See Also:
// SetFlags, ResetFlags
virtual uint32_t GetFlags() const = 0;
// Summary:
// Sets the flags of this object.
// Description:
// The possibles flags are defined in EPakFlags.
// See Also:
// GetFlags, ResetFlags
virtual bool SetFlags(uint32_t nFlagsToSet) = 0;
// Summary:
// Resets the flags of this object.
// See Also:
// SetFlags, GetFlags
virtual bool ResetFlags(uint32_t nFlagsToSet) = 0;
// Summary:
// Control if files in this pack can be accessed
// Returns:
// true if archive state was changed
virtual bool SetPackAccessible(bool bAccessible) = 0;
// Summary:
// Determines if the archive is read only.
// Returns:
// true if this archive is read-only
inline bool IsReadOnly() const { return (GetFlags() & FLAGS_READ_ONLY) != 0; }
};
}
@@ -0,0 +1,147 @@
/*
* 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/Asset/AssetManagerBus.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/string/regex.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzFramework/FileTag/FileTag.h>
#include <AzFramework/FileTag/FileTagBus.h>
#include <AzFramework/Archive/MissingFileReport.h>
#include <AzFramework/Logging/MissingAssetNotificationBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AZ::IO::Internal
{
// Whether we should ignore this missing file
static bool IsIgnored(const char* szPath);
// Do not report missing LOD files if no CGF files depend on them
// Do not report missing .cgfm files since they're not actually created and used in Lumberyard
// This checking prevents our missing dependency scanner from having a lot of false positives on these files
static bool IgnoreCGFDependencies(const char* szPath);
void ReportFileMissingFromArchive(const char* szPath)
{
int reportLevel{};
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console)
{
console->GetCvarValue("sys_report_files_not_found_in_paks", reportLevel);
}
if (!reportLevel)
{
return;
}
if (IsIgnored(szPath))
{
return;
}
AzFramework::MissingAssetNotificationBus::Broadcast(&AzFramework::MissingAssetNotificationBus::Events::FileMissing, szPath);
const int LogMissingFileAccess = 1;
const int WarnMissingFileAccess = 2;
AZStd::string missingMessage(AZStd::string::format("Missing from bundle: %s", szPath));
switch (reportLevel)
{
case LogMissingFileAccess:
AZ_TracePrintf("Archive", missingMessage.c_str());
break;
case WarnMissingFileAccess:
AZ_Warning("Archive", false, missingMessage.c_str());
break;
default:
AZ_Error("Archive", false, missingMessage.c_str());
break;
}
}
bool IsIgnored(const char* szPath)
{
if (IgnoreCGFDependencies(szPath))
{
return true;
}
using namespace AzFramework::FileTag;
AZStd::vector<AZStd::string> tags{
FileTags[static_cast<unsigned int>(FileTagsIndex::Ignore)],
FileTags[static_cast<unsigned int>(FileTagsIndex::ProductDependency)] };
bool shouldIgnore = false;
QueryFileTagsEventBus::EventResult(shouldIgnore, FileTagType::Exclude, &QueryFileTagsEventBus::Events::Match, szPath, tags);
return shouldIgnore;
}
// This is a quick and simple solution to handle .cgfm and LOD files
// We only report them as missing files if they are created by
// Resource Compiler during the process for source CGF assets
// A better solution could be to load only valid files that are actually needed
bool IgnoreCGFDependencies(const char* szPath)
{
if (AZ::StringFunc::Path::IsExtension(szPath, "cgfm"))
{
// Ignore all the .cgfm files
return true;
}
AZStd::smatch matches;
const AZStd::regex lodRegex("@assets@\\\\(.*)_lod[0-9]+(\\.cgfm?)");
if (!AZStd::regex_match(szPath, matches, lodRegex) || matches.size() != 3)
{
// The current file is not a valid LOD file
return false;
}
AZStd::string nonLodFileName = matches.str(1) + matches.str(2);
AZ::Data::AssetId nonLodFileId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
nonLodFileId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
nonLodFileName.c_str(), AZ::Data::s_invalidAssetType, false);
if (!nonLodFileId.IsValid())
{
// The current LOD file is not generated from a valid CGF file
return false;
}
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> result = AZ::Failure<AZStd::string>("No response");
AZ::Data::AssetCatalogRequestBus::BroadcastResult(result, &AZ::Data::AssetCatalogRequestBus::Events::GetDirectProductDependencies, nonLodFileId);
if (!result.IsSuccess())
{
return false;
}
AZStd::vector<AZ::Data::ProductDependency> dependencies = result.TakeValue();
for (const AZ::Data::ProductDependency& dependency : dependencies)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, dependency.m_assetId);
if (assetInfo.m_assetType == AZ::Data::s_invalidAssetType)
{
// This dependency has unresolved path. Cannot compare it with the current LOD file.
continue;
}
AZStd::string dependencyRelativePath = assetInfo.m_relativePath;
AZStd::replace(dependencyRelativePath.begin(), dependencyRelativePath.end(), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (strstr(szPath, dependencyRelativePath.c_str()))
{
// The current LOD file is a product dependency of the source CGF file
return false;
}
}
return true;
}
}
@@ -0,0 +1,19 @@
/*
* 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
namespace AZ::IO::Internal
{
// Report missing files in the archive when they are loaded
void ReportFileMissingFromArchive(const char *szPath);
}
@@ -0,0 +1,277 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <AzFramework/Archive/NestedArchive.h>
#include <AzFramework/Archive/ZipDirStructures.h>
#include <AzFramework/Archive/ZipDirTree.h>
#include <AzFramework/Archive/Archive.h>
namespace AZ::IO
{
NestedArchive::NestedArchive(IArchive* pArchive, AZStd::string_view strBindRoot, ZipDir::CachePtr pCache, uint32_t nFlags)
: m_archive{ pArchive }
, m_pCache{ pCache }
, m_strBindRoot{ strBindRoot }
, m_nFlags{ nFlags }
{
static_cast<Archive*>(m_archive)->Register(this);
}
NestedArchive::~NestedArchive()
{
static_cast<Archive*>(m_archive)->Unregister(this);
}
auto NestedArchive::GetRootFolderHandle() -> Handle
{
return m_pCache->GetRoot();
}
int NestedArchive::UpdateFileCRC(AZStd::string_view szRelativePath, AZ::Crc32 dwCRC)
{
if (m_nFlags & FLAGS_READ_ONLY)
{
return ZipDir::ZD_ERROR_INVALID_CALL;
}
AZStd::fixed_string<AZ::IO::MaxPathLength> fullPath = AdjustPath(szRelativePath);
if (fullPath.empty())
{
return ZipDir::ZD_ERROR_INVALID_PATH;
}
m_pCache->UpdateFileCRC(fullPath, dwCRC);
return ZipDir::ZD_ERROR_SUCCESS;
}
//////////////////////////////////////////////////////////////////////////
// deletes the file from the archive
int NestedArchive::RemoveFile(AZStd::string_view szRelativePath)
{
if (m_nFlags & FLAGS_READ_ONLY)
{
return ZipDir::ZD_ERROR_INVALID_CALL;
}
AZStd::fixed_string<AZ::IO::MaxPathLength> fullPath = AdjustPath(szRelativePath);
if (fullPath.empty())
{
return ZipDir::ZD_ERROR_INVALID_PATH;
}
return m_pCache->RemoveFile(fullPath);
}
//////////////////////////////////////////////////////////////////////////
// deletes the directory, with all its descendants (files and subdirs)
int NestedArchive::RemoveDir(AZStd::string_view szRelativePath)
{
if (m_nFlags & FLAGS_READ_ONLY)
{
return ZipDir::ZD_ERROR_INVALID_CALL;
}
AZStd::fixed_string<AZ::IO::MaxPathLength> fullPath = AdjustPath(szRelativePath);
if (fullPath.empty())
{
return ZipDir::ZD_ERROR_INVALID_PATH;
}
return m_pCache->RemoveDir(fullPath);
}
int NestedArchive::RemoveAll()
{
return m_pCache->RemoveAll();
}
//////////////////////////////////////////////////////////////////////////
// Adds a new file to the zip or update an existing one
// adds a directory (creates several nested directories if needed)
// compression methods supported are 0 (store) and 8 (deflate) , compression level is 0..9 or -1 for default (like in zlib)
int NestedArchive::UpdateFile(AZStd::string_view szRelativePath, const void* pUncompressed, uint64_t nSize, uint32_t nCompressionMethod, int nCompressionLevel, CompressionCodec::Codec codec)
{
if (m_nFlags & FLAGS_READ_ONLY)
{
return ZipDir::ZD_ERROR_INVALID_CALL;
}
AZStd::fixed_string<AZ::IO::MaxPathLength> fullPath = AdjustPath(szRelativePath);
if (fullPath.empty())
{
return ZipDir::ZD_ERROR_INVALID_PATH;
}
return m_pCache->UpdateFile(fullPath, pUncompressed, nSize, nCompressionMethod, nCompressionLevel, codec);
}
//////////////////////////////////////////////////////////////////////////
// Adds a new file to the zip or update an existing one if it is not compressed - just stored - start a big file
int NestedArchive::StartContinuousFileUpdate(AZStd::string_view szRelativePath, uint64_t nSize)
{
if (m_nFlags & FLAGS_READ_ONLY)
{
return ZipDir::ZD_ERROR_INVALID_CALL;
}
AZStd::fixed_string<AZ::IO::MaxPathLength> fullPath = AdjustPath(szRelativePath);
if (fullPath.empty())
{
return ZipDir::ZD_ERROR_INVALID_PATH;
}
return m_pCache->StartContinuousFileUpdate(fullPath, nSize);
}
//////////////////////////////////////////////////////////////////////////
// Adds a new file to the zip or update an existing segment if it is not compressed - just stored
// adds a directory (creates several nested directories if needed)
int NestedArchive::UpdateFileContinuousSegment(AZStd::string_view szRelativePath, uint64_t nSize, const void* pUncompressed, uint64_t nSegmentSize, uint64_t nOverwriteSeekPos)
{
if (m_nFlags & FLAGS_READ_ONLY)
{
return ZipDir::ZD_ERROR_INVALID_CALL;
}
AZStd::fixed_string<AZ::IO::MaxPathLength> fullPath = AdjustPath(szRelativePath);
if (fullPath.empty())
{
return ZipDir::ZD_ERROR_INVALID_PATH;
}
return m_pCache->UpdateFileContinuousSegment(fullPath, nSize, pUncompressed, nSegmentSize, nOverwriteSeekPos);
}
// finds the file; you don't have to close the returned handle
auto NestedArchive::FindFile(AZStd::string_view szRelativePath) -> Handle
{
AZStd::fixed_string<AZ::IO::MaxPathLength> fullPath = AdjustPath(szRelativePath);
if (fullPath.empty())
{
return nullptr;
}
return m_pCache->FindFile(fullPath);
}
// returns the size of the file (unpacked) by the handle
uint64_t NestedArchive::GetFileSize(Handle fileHandle)
{
AZ_Assert(m_pCache->IsOwnerOf(reinterpret_cast<ZipDir::FileEntry*>(fileHandle)), "File handle is not owned by archive");
return reinterpret_cast<ZipDir::FileEntry*>(fileHandle)->desc.lSizeUncompressed;
}
// reads the file into the preallocated buffer (must be at least the size of GetFileSize())
int NestedArchive::ReadFile(Handle fileHandle, void* pBuffer)
{
AZ_Assert(m_pCache->IsOwnerOf(reinterpret_cast<ZipDir::FileEntry*>(fileHandle)), "File Handle is not owned by archive");
return m_pCache->ReadFile(reinterpret_cast<ZipDir::FileEntry*>(fileHandle), nullptr, pBuffer);
}
const char* NestedArchive::GetFullPath() const
{
return m_pCache->GetFilePath();
}
ZipDir::Cache* NestedArchive::GetCache()
{
return m_pCache.get();
}
uint32_t NestedArchive::GetFlags() const
{
return m_nFlags;
}
bool NestedArchive::SetFlags(uint32_t nFlagsToSet)
{
if (nFlagsToSet & FLAGS_RELATIVE_PATHS_ONLY)
{
m_nFlags |= FLAGS_RELATIVE_PATHS_ONLY;
}
if (nFlagsToSet & FLAGS_ON_HDD)
{
m_nFlags |= FLAGS_ON_HDD;
}
if (nFlagsToSet & FLAGS_RELATIVE_PATHS_ONLY ||
nFlagsToSet & FLAGS_ON_HDD)
{
// we don't support changing of any other flags
return true;
}
return false;
}
bool NestedArchive::ResetFlags(uint32_t nFlagsToReset)
{
if (nFlagsToReset & FLAGS_RELATIVE_PATHS_ONLY)
{
m_nFlags &= ~FLAGS_RELATIVE_PATHS_ONLY;
}
if (nFlagsToReset & ~(FLAGS_RELATIVE_PATHS_ONLY))
{
// we don't support changing of any other flags
return false;
}
return true;
}
bool NestedArchive::SetPackAccessible(bool bAccessible)
{
if (bAccessible)
{
bool bResult = (m_nFlags & INestedArchive::FLAGS_DISABLE_PAK) != 0;
m_nFlags &= ~INestedArchive::FLAGS_DISABLE_PAK;
return bResult;
}
else
{
bool bResult = (m_nFlags & INestedArchive::FLAGS_DISABLE_PAK) == 0;
m_nFlags |= INestedArchive::FLAGS_DISABLE_PAK;
return bResult;
}
}
AZ::IO::FixedMaxPathString NestedArchive::AdjustPath(AZStd::string_view szRelativePath)
{
if (szRelativePath.empty())
{
return {};
}
if (m_nFlags & FLAGS_RELATIVE_PATHS_ONLY)
{
return AZStd::fixed_string<AZ::IO::MaxPathLength>{ szRelativePath };
}
if ((szRelativePath.size() > 1 && szRelativePath[1] == ':') || (m_nFlags & FLAGS_ABSOLUTE_PATHS))
{
// make the normalized full path and try to match it against the binding root of this object
auto resolvedPath = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(szRelativePath);
// Make sure the resolve path is longer than the bind root and that it starts with the bind root
if (!resolvedPath || resolvedPath->Native().size() <= m_strBindRoot.size() || azstrnicmp(resolvedPath->c_str(), m_strBindRoot.c_str(), m_strBindRoot.size()) != 0)
{
return {};
}
// Remove the bind root prefix from the resolved path
resolvedPath->Native().erase(0, m_strBindRoot.size() + 1);
return resolvedPath->Native();
}
return AZ::IO::FixedMaxPathString{ szRelativePath };
}
}
@@ -0,0 +1,107 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <AzFramework/Archive/INestedArchive.h>
#include <AzFramework/Archive/IArchive.h>
#include <AzFramework/Archive/ZipDirCache.h>
namespace AZ::IO
{
struct NestedArchiveSortByName
{
bool operator()(const INestedArchive* left, const INestedArchive* right) const
{
return azstricmp(left->GetFullPath(), right->GetFullPath()) < 0;
}
bool operator()(AZStd::string_view left, const INestedArchive* right) const
{
return azstrnicmp(left.data(), right->GetFullPath(), left.size()) < 0;
}
bool operator()(const INestedArchive* left, AZStd::string_view right) const
{
return azstrnicmp(left->GetFullPath(), right.data(), right.size()) < 0;
}
};
class NestedArchive
: public INestedArchive
{
public:
AZ_CLASS_ALLOCATOR(NestedArchive, AZ::SystemAllocator, 0);
NestedArchive(IArchive* pArchive, AZStd::string_view strBindRoot, ZipDir::CachePtr pCache, uint32_t nFlags = 0);
~NestedArchive() override;
auto GetRootFolderHandle() -> Handle;
// Adds a new file to the zip or update an existing one
// adds a directory (creates several nested directories if needed)
// compression methods supported are 0 (store) and 8 (deflate) , compression level is 0..9 or -1 for default (like in zlib)
int UpdateFile(AZStd::string_view szRelativePath, const void* pUncompressed, uint64_t nSize, uint32_t nCompressionMethod = ZipFile::METHOD_STORE,
int nCompressionLevel = -1, CompressionCodec::Codec codec = CompressionCodec::Codec::ZLIB) override;
// Adds a new file to the zip or update an existing one if it is not compressed - just stored - start a big file
int StartContinuousFileUpdate(AZStd::string_view szRelativePath, uint64_t nSize) override;
// Adds a new file to the zip or update an existing segment if it is not compressed - just stored
// adds a directory (creates several nested directories if needed)
// Arguments:
// nOverwriteSeekPos - std::numeric_limits<uint64_t>::max(i.e uint64_t(-1)) means the seek pos should not be overwritten
int UpdateFileContinuousSegment(AZStd::string_view szRelativePath, uint64_t nSize, const void* pUncompressed, uint64_t nSegmentSize, uint64_t nOverwriteSeekPos) override;
int UpdateFileCRC(AZStd::string_view szRelativePath, AZ::Crc32 dwCRC) override;
// deletes the file from the archive
int RemoveFile(AZStd::string_view szRelativePath) override;
// deletes the directory, with all its descendants (files and subdirectories)
int RemoveDir(AZStd::string_view szRelativePath) override;
// deletes all files from the archive
int RemoveAll();
// finds the file; you don't have to close the returned handle
Handle FindFile(AZStd::string_view szRelativePath);
// returns the size of the file (unpacked) by the handle
uint64_t GetFileSize(Handle fileHandle);
// reads the file into the preallocated buffer (must be at least the size of GetFileSize())
int ReadFile(Handle fileHandle, void* pBuffer);
// returns the full path to the archive file
const char* GetFullPath() const;
ZipDir::Cache* GetCache();
uint32_t GetFlags() const;
bool SetFlags(uint32_t nFlagsToSet);
bool ResetFlags(uint32_t nFlagsToReset);
bool SetPackAccessible(bool bAccessible);
protected:
// returns the pointer to the relative file path to be passed
// to the underlying Cache pointer. Uses the given buffer to construct the path.
// returns nullptr if the file path is invalid
AZ::IO::FixedMaxPathString AdjustPath(AZStd::string_view szRelativePath);
ZipDir::CachePtr m_pCache;
// the binding root may be empty string - in this case, the absolute path binding won't work
AZStd::string m_strBindRoot;
IArchive* m_archive{};
uint32_t m_nFlags{};
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,179 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Declaration of the class that will keep the ZipDir Cache object
// and will provide all its services to access Zip file, plus it will
// provide services to write to the zip file efficiently
// Time to time, the contained Cache object will be recreated during
// an archive add operation
//
#pragma once
#include <AzCore/IO/FileIO.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzFramework/Archive/Codec.h>
#include <AzFramework/Archive/ZipDirStructures.h>
#include <AzFramework/Archive/ZipDirTree.h>
namespace AZ::IO::ZipDir
{
struct FileDataRecord;
class Cache
: public AZStd::intrusive_base
{
public:
AZ_CLASS_ALLOCATOR(Cache, AZ::SystemAllocator, 0);
// the size of the buffer that's using during re-linking the zip file
inline static constexpr size_t g_nSizeRelinkBuffer = 1024 * 1024;
inline static constexpr size_t g_nMaxItemsRelinkBuffer = 128; // max number of files to read before (without) writing
inline static constexpr int compressedBlockHeaderSizeInBytes = 4; //number of bytes we need in front of the compressed block to indicate which compressor was used
Cache();
explicit Cache(AZ::IAllocatorAllocate* allocator);
~Cache()
{
Close();
}
bool IsValid() const
{
return m_fileHandle != AZ::IO::InvalidHandle;
}
// Adds a new file to the zip or update an existing one
// adds a directory (creates several nested directories if needed)
ErrorEnum UpdateFile(AZStd::string_view szRelativePath, const void* pUncompressed, uint64_t nSize, uint32_t nCompressionMethod = ZipFile::METHOD_STORE, int nCompressionLevel = -1, CompressionCodec::Codec codec = CompressionCodec::Codec::ZLIB);
// Adds a new file to the zip or update an existing one if it is not compressed - just stored - start a big file
ErrorEnum StartContinuousFileUpdate(AZStd::string_view szRelativePath, uint64_t nSize);
// Adds a new file to the zip or update an existing segment if it is not compressed - just stored
// adds a directory (creates several nested directories if needed)
// Arguments:
// nOverwriteSeekPos - std::numeric_limits<uint64_t>::max(i.e uint64_t(-1)) means the seek pos should not be overwritten
ErrorEnum UpdateFileContinuousSegment(AZStd::string_view szRelativePath, uint64_t nSize, const void* pUncompressed, uint64_t nSegmentSize, uint64_t nOverwriteSeekPos);
ErrorEnum UpdateFileCRC(AZStd::string_view szRelativePath, AZ::Crc32 dwCRC32);
// deletes the file from the archive
ErrorEnum RemoveFile(AZStd::string_view szRelativePath);
// deletes the directory, with all its descendants (files and subdirs)
ErrorEnum RemoveDir(AZStd::string_view szRelativePath);
// deletes all files and directories in this archive
ErrorEnum RemoveAll();
// closes the current zip file
void Close();
FileEntry* FindFile(AZStd::string_view szPath, bool bFullInfo = false);
ErrorEnum ReadFile(FileEntry* pFileEntry, void* pCompressed, void* pUncompressed);
void Free(void* ptr)
{
m_allocator->DeAllocate(ptr);
}
// refreshes information about the given file entry into this file entry
ErrorEnum Refresh(FileEntryBase* pFileEntry);
// returns the size of memory occupied by the instance of this cache
size_t GetSize() const;
// QUICK check to determine whether the file entry belongs to this object
bool IsOwnerOf(const FileEntry* pFileEntry) const
{
return m_treeDir.IsOwnerOf(pFileEntry);
}
// returns the string - path to the zip file from which this object was constructed.
// this will be "" if the object was constructed with a factory that wasn't created with FLAGS_MEMORIZE_ZIP_PATH
const char* GetFilePath() const
{
return m_strFilePath.c_str();
}
FileEntryTree* GetRoot()
{
return &m_treeDir;
}
// writes the CDR to the disk
bool WriteCDR() { return WriteCDR(m_fileHandle); }
bool WriteCDR(AZ::IO::HandleType fTarget);
bool RelinkZip();
protected:
bool RelinkZip(AZ::IO::HandleType fTmp);
// writes out the file data in the queue into the given file. Empties the queue
bool WriteZipFiles(AZStd::vector<AZStd::intrusive_ptr<FileDataRecord>>& queFiles, AZ::IO::HandleType fTmp);
bool WriteCompressedData(uint8_t* data, size_t size, bool encrypt);
bool WriteNullData(size_t size);
ZipFile::CryCustomEncryptionHeader& GetEncryptionHeader() { return m_headerEncryption; }
ZipFile::CrySignedCDRHeader& GetSignedHeader() { return m_headerSignature; }
ZipFile::CryCustomExtendedHeader& GetExtendedHeader() { return m_headerExtended; }
size_t GetCompressedSizeEstimate(size_t uncompressedSize, CompressionCodec::Codec codec);
protected:
friend class CacheFactory;
friend class FileEntryTransactionAdd;
FileEntryTree m_treeDir;
AZ::IO::HandleType m_fileHandle;
AZ::IAllocatorAllocate* m_allocator;
AZStd::string m_strFilePath;
// String Pool for persistently storing paths as long as they reside in the cache
AZStd::unordered_set<AZStd::string> m_relativePathPool;
// offset to the start of CDR in the file,even if there's no CDR there currently
// when a new file is added, it can start from here, but this value will need to be updated then
uint32_t m_lCDROffset;
enum
{
// if this is set, the file needs to be compacted before it can be used by
// all standard zip tools, because gaps between file datas can be present
FLAGS_UNCOMPACTED = 1 << 0,
// if this is set, the CDR needs to be written to the file
FLAGS_CDR_DIRTY = 1 << 1,
// if this is set, the file is opened in read-only mode. no write operations are to be performed
FLAGS_READ_ONLY = 1 << 2,
// when this is set, compact operation is not performed
FLAGS_DONT_COMPACT = 1 << 3
};
uint32_t m_nFlags;
// CDR buffer.
AZStd::vector<uint8_t> m_CDR_buffer;
ZipFile::EHeaderEncryptionType m_encryptedHeaders;
ZipFile::EHeaderSignatureType m_signedHeaders;
// Zip Headers
ZipFile::CryCustomEncryptionHeader m_headerEncryption;
ZipFile::CrySignedCDRHeader m_headerSignature;
ZipFile::CryCustomExtendedHeader m_headerExtended;
};
using CachePtr = AZStd::intrusive_ptr<Cache>;
}
@@ -0,0 +1,802 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <AzCore/Console/Console.h>
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/sort.h>
#include <AzFramework/Archive/Archive.h>
#include <AzFramework/Archive/ZipDirStructures.h>
#include <AzFramework/Archive/ZipDirTree.h>
#include <AzFramework/Archive/ZipDirCache.h>
#include <AzFramework/Archive/ZipDirCacheFactory.h>
#include <AzFramework/Archive/ZipDirList.h>
#include <AzFramework/Archive/ZipFileFormat.h>
#include <zlib.h>
#include <locale>
#include <cinttypes>
namespace AZ::IO::ZipDir
{
// this sets the window size of the blocks of data read from the end of the file to find the Central Directory Record
// since normally there are no
static constexpr size_t CDRSearchWindowSize = 0x100;
CacheFactory::CacheFactory(InitMethodEnum nInitMethod, uint32_t nFlags)
{
m_nCDREndPos = 0;
m_bBuildFileEntryMap = false; // we only need it for validation/debugging
m_bBuildFileEntryTree = true; // we need it to actually build the optimized structure of directories
m_bBuildOptimizedFileEntry = false;
m_nInitMethod = nInitMethod;
m_nFlags = nFlags;
m_nZipFileSize = 0;
m_encryptedHeaders = ZipFile::HEADERS_NOT_ENCRYPTED;
m_signedHeaders = ZipFile::HEADERS_NOT_SIGNED;
if (m_nFlags & FLAGS_FILENAMES_AS_CRC32)
{
m_bBuildFileEntryMap = false;
m_bBuildFileEntryTree = false;
m_bBuildOptimizedFileEntry = true;
}
if (m_nFlags & FLAGS_READ_INSIDE_PAK)
{
m_fileExt.m_fileIOBase = AZ::IO::FileIOBase::GetInstance();
}
else
{
m_fileExt.m_fileIOBase = AZ::IO::FileIOBase::GetDirectInstance();
}
}
CacheFactory::~CacheFactory()
{
Clear();
}
CachePtr CacheFactory::New(const char* szFileName)
{
m_szFilename = szFileName;
CachePtr pCache{ new Cache{ &AZ::AllocatorInstance<AZ::OSAllocator>::Get()} };
// opens the given zip file and connects to it. Creates a new file if no such file exists
// if successful, returns true.
if (!(m_nFlags & FLAGS_DONT_MEMORIZE_ZIP_PATH))
{
pCache->m_strFilePath = szFileName;
}
if (m_nFlags & FLAGS_DONT_COMPACT)
{
pCache->m_nFlags |= Cache::FLAGS_DONT_COMPACT;
}
// first, try to open the file for reading or reading/writing
if (m_nFlags & FLAGS_READ_ONLY)
{
AZ::IO::FileIOBase::GetDirectInstance()->Open(szFileName, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, m_fileExt.m_fileHandle);
pCache->m_nFlags |= Cache::FLAGS_CDR_DIRTY | Cache::FLAGS_READ_ONLY;
if (m_fileExt.m_fileHandle == AZ::IO::InvalidHandle)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not open file in binary mode for reading");
return {};
}
if (!ReadCache(*pCache))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not read the CDR of the pack file.");
return {};
}
}
else
{
m_fileExt.m_fileHandle = AZ::IO::InvalidHandle;
if (!(m_nFlags & FLAGS_CREATE_NEW))
{
AZ::IO::FileIOBase::GetDirectInstance()->Open(szFileName, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeUpdate | AZ::IO::OpenMode::ModeBinary, m_fileExt.m_fileHandle);
}
bool bOpenForWriting = true;
if (m_fileExt.m_fileHandle != AZ::IO::InvalidHandle)
{
Seek(0, SEEK_END);
size_t nFileSize = (size_t)Tell();
Seek(0, SEEK_SET);
AZ_Assert(nFileSize != 0, "File of size 0 will not be open for reading");
if (nFileSize)
{
if (!ReadCache(*pCache))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not open file in binary mode for reading");
return {};
}
bOpenForWriting = false;
}
}
if (bOpenForWriting)
{
if (m_fileExt.m_fileHandle != AZ::IO::InvalidHandle)
{
AZ::IO::FileIOBase::GetDirectInstance()->Close(m_fileExt.m_fileHandle);
m_fileExt.m_fileHandle = AZ::IO::InvalidHandle;
}
if (AZ::IO::FileIOBase::GetDirectInstance()->Open(szFileName, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeUpdate | AZ::IO::OpenMode::ModeBinary, m_fileExt.m_fileHandle))
{
// there's no such file, but we'll create one. We'll need to write out the CDR here
pCache->m_lCDROffset = 0;
pCache->m_nFlags |= Cache::FLAGS_CDR_DIRTY;
}
}
if (m_fileExt.m_fileHandle == AZ::IO::InvalidHandle)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not open file in binary mode for appending (read/write)");
return {};
}
}
// give the cache the file handle:
pCache->m_fileHandle = m_fileExt.m_fileHandle;
// the factory doesn't own it after that
m_fileExt.m_fileHandle = AZ::IO::InvalidHandle;
return pCache;
}
bool CacheFactory::ReadCache(Cache& rwCache)
{
m_bBuildFileEntryTree = true;
if (!Prepare())
{
return false;
}
// since it's open for R/W, we need to know exactly how much space
// we have for each file to use the gaps efficiently
FileEntryList Adjuster(&m_treeFileEntries, m_CDREnd.lCDROffset);
Adjuster.RefreshEOFOffsets();
m_treeFileEntries.Swap(rwCache.m_treeDir);
m_CDR_buffer.swap(rwCache.m_CDR_buffer); // CDR Buffer contain actually the string pool for the tree directory.
// very important: we need this offset to be able to add to the zip file
rwCache.m_lCDROffset = m_CDREnd.lCDROffset;
rwCache.m_encryptedHeaders = m_encryptedHeaders;
rwCache.m_signedHeaders = m_signedHeaders;
rwCache.m_headerSignature = m_headerSignature;
rwCache.m_headerEncryption = m_headerEncryption;
rwCache.m_headerExtended = m_headerExtended;
return true;
}
// reads everything and prepares the maps
bool CacheFactory::Prepare()
{
if (!FindCDREnd())
{
return false;
}
//Earlier pak file encryption techniques stored the encryption type in the disk number of the CDREnd.
//This works, but can't be used by the more recent techniques that require signed paks to be readable by 7-Zip during dev.
ZipFile::EHeaderEncryptionType headerEnc = (ZipFile::EHeaderEncryptionType)((m_CDREnd.nDisk & 0xC000) >> 14);
if (headerEnc == ZipFile::HEADERS_ENCRYPTED_TEA || headerEnc == ZipFile::HEADERS_ENCRYPTED_STREAMCIPHER)
{
m_encryptedHeaders = headerEnc;
}
m_CDREnd.nDisk = m_CDREnd.nDisk & 0x3fff;
//Pak may be encrypted with CryCustom technique and/or signed. Being signed is compatible (in principle) with the earlier encryption methods.
//The information for this exists in some custom headers at the end of the archive (in the comment section)
if (m_CDREnd.nCommentLength >= sizeof(m_headerExtended))
{
Seek(m_CDREnd.lCDROffset + m_CDREnd.lCDRSize + sizeof(ZipFile::CDREnd));
Read(&m_headerExtended, sizeof(m_headerExtended));
if (m_headerExtended.nHeaderSize != sizeof(m_headerExtended))
{
// Extended Header is not valid
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad extended header");
return false;
}
//We have the header, so read the encryption and signing techniques
m_signedHeaders = (ZipFile::EHeaderSignatureType)m_headerExtended.nSigning;
//Prepare for a quick sanity check on the size of the comment field now that we know what it should contain
//Also check that the techniques are supported
uint16_t expectedCommentLength = sizeof(m_headerExtended);
if (m_headerExtended.nEncryption != ZipFile::HEADERS_NOT_ENCRYPTED && m_encryptedHeaders != ZipFile::HEADERS_NOT_ENCRYPTED)
{
//Encryption technique has been specified in both the disk number (old technique) and the custom header (new technique).
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Unexpected encryption technique in header");
return false;
}
else
{
//The encryption technique has been specified only in the custom header
m_encryptedHeaders = (ZipFile::EHeaderEncryptionType)m_headerExtended.nEncryption;
switch (m_encryptedHeaders)
{
case ZipFile::HEADERS_NOT_ENCRYPTED:
break;
case ZipFile::HEADERS_ENCRYPTED_STREAMCIPHER_KEYTABLE:
expectedCommentLength += sizeof(ZipFile::CryCustomEncryptionHeader);
break;
default:
// Unexpected technique
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad encryption technique in header");
return false;
}
}
//Add the signature header to the expected size
switch (m_signedHeaders)
{
case ZipFile::HEADERS_NOT_SIGNED:
break;
case ZipFile::HEADERS_CDR_SIGNED:
expectedCommentLength += sizeof(ZipFile::CrySignedCDRHeader);
break;
default:
// Unexpected technique
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad signing technique in header");
return false;
}
if (m_CDREnd.nCommentLength == expectedCommentLength)
{
if (m_signedHeaders == ZipFile::HEADERS_CDR_SIGNED)
{
Read(&m_headerSignature, sizeof(m_headerSignature));
if (m_headerSignature.nHeaderSize != sizeof(m_headerSignature))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad signature header");
return false;
}
}
}
else
{
// Unexpected technique
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Comment field is the wrong length");
return false;
}
}
// we don't support multivolume archives
if (m_CDREnd.nDisk != 0
|| m_CDREnd.nCDRStartDisk != 0
|| m_CDREnd.numEntriesOnDisk != m_CDREnd.numEntriesTotal)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_UNSUPPORTED, "Multivolume archive detected. Current version of ZipDir does not support multivolume archives");
return false;
}
// if the central directory offset or size are out of range,
// the CDREnd record is probably corrupt
if (m_CDREnd.lCDROffset > m_nCDREndPos
|| m_CDREnd.lCDRSize > m_nCDREndPos
|| m_CDREnd.lCDROffset + m_CDREnd.lCDRSize > m_nCDREndPos)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "The central directory offset or size are out of range, the pak is probably corrupt, try to repare or delete the file");
return false;
}
BuildFileEntryMap();
return true;
}
struct SortFileEntryByNameOffsetPredicate
{
bool operator()(const FileEntry& f1, const FileEntry& f2) const
{
return f1.nNameOffset < f2.nNameOffset;
}
};
void CacheFactory::Clear()
{
m_fileExt.Close();
m_nCDREndPos = 0;
memset(&m_CDREnd, 0, sizeof(m_CDREnd));
m_mapFileEntries.clear();
m_treeFileEntries.Clear();
m_encryptedHeaders = ZipFile::HEADERS_NOT_ENCRYPTED;
}
//////////////////////////////////////////////////////////////////////////
// searches for CDREnd record in the given file
bool CacheFactory::FindCDREnd()
{
// this buffer will be used to find the CDR End record
// the additional bytes are required to store the potential tail of the CDREnd structure
// when moving the window to the next position in the file
//We cannot create it on the stack for RSX memory usage as we are not permitted to access it via SPU
AZStd::vector<char> pReservedBuffer(CDRSearchWindowSize + sizeof(ZipFile::CDREnd) - 1);
Seek(0, SEEK_END);
int64_t nFileSize = Tell();
//There is a 2GB pak file limit
constexpr size_t pakSizeLimit{ 1U << 31 };
if (nFileSize > pakSizeLimit)
{
AZ_Fatal("Archive", "The file is too large. Can't open a pak file that is greater than 2GB in size. Current size is " PRIi64, nFileSize);
}
m_nZipFileSize = aznumeric_cast<size_t>(nFileSize);
if (nFileSize < sizeof(ZipFile::CDREnd))
{
AZ_Warning("Archive", false, "The file is too small(%" PRIi64 "), it needs to contain the CDREnd structure which is %zu bytes. Please check and delete the file. Truncated files are not deleted automatically",
nFileSize, sizeof(ZipFile::CDREnd));
return false;
}
// this will point to the place where the buffer was loaded
auto nOldBufPos = aznumeric_cast<uint32_t>(nFileSize);
// start scanning well before the end of the file to avoid reading beyond the end
uint32_t nScanPos = nOldBufPos - sizeof(ZipFile::CDREnd);
m_CDREnd.lSignature = 0; // invalid signature as the flag of not-found CDR End structure
while (true)
{
uint32_t nNewBufPos; // the new buf pos
char* pWindow = &pReservedBuffer[0]; // the window pointer into which data will be read (takes into account the possible tail-of-CDREnd)
if (nOldBufPos <= CDRSearchWindowSize)
{
// the old buffer position doesn't let us read the full search window size
// therefore the new buffer pos will be 0 (instead of negative beyond the start of the file)
// and the window pointer will be closer tot he end of the buffer because the end of the buffer
// contains the data from the previous iteration (possibly)
nNewBufPos = 0;
pWindow = &pReservedBuffer[CDRSearchWindowSize - (nOldBufPos - nNewBufPos)];
}
else
{
nNewBufPos = nOldBufPos - CDRSearchWindowSize;
AZ_Assert(nNewBufPos > 0, "The new buffer position must be greater than the last search window");
}
// since dealing with 32bit unsigned, check that filesize is bigger than
// CDREnd plus comment before the following check occurs.
if (nFileSize > (sizeof(ZipFile::CDREnd) + 0xFFFF))
{
// if the new buffer pos is beyond 64k limit for the comment size
if (nNewBufPos < aznumeric_cast<uint32_t>(nFileSize - sizeof(ZipFile::CDREnd) - 0xFFFF))
{
nNewBufPos = aznumeric_cast<uint32_t>(nFileSize - sizeof(ZipFile::CDREnd) - 0xFFFF);
}
}
// if there's nothing to search
if (nNewBufPos >= nOldBufPos)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_NO_CDR, "Cannot find Central Directory Record in pak. This is either not a pak file, or a pak file without Central Directory. It does not mean that the data is permanently lost, but it may be severely damaged. Please repair the file with external tools, there may be enough information left to recover the file completely."); // we didn't find anything
return false;
}
// seek to the start of the new window and read it
Seek(nNewBufPos);
Read(pWindow, nOldBufPos - nNewBufPos);
while (nScanPos >= nNewBufPos)
{
ZipFile::CDREnd* pEnd = (ZipFile::CDREnd*)(pWindow + nScanPos - nNewBufPos);
auto formatSignature = pEnd->lSignature;
if (formatSignature == pEnd->SIGNATURE)
{
auto commentFileLength = pEnd->nCommentLength;
if (commentFileLength == nFileSize - nScanPos - sizeof(ZipFile::CDREnd))
{
// the comment length is exactly what we expected
m_CDREnd = *pEnd;
m_nCDREndPos = nScanPos;
break;
}
else
{
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Central Directory Record is followed by a comment of inconsistent length. This might be a minor misconsistency, please try to repair the file. However, it is dangerous to open the file because I will have to guess some structure offsets, which can lead to permanent unrecoverable damage of the archive content");
return false;
}
}
if (nScanPos == 0)
{
break;
}
--nScanPos;
}
if (m_CDREnd.lSignature == m_CDREnd.SIGNATURE)
{
return true; // we've found it
}
nOldBufPos = nNewBufPos;
memmove(&pReservedBuffer[CDRSearchWindowSize], pWindow, sizeof(ZipFile::CDREnd) - 1);
}
THROW_ZIPDIR_ERROR(ZD_ERROR_UNEXPECTED, "The program flow may not have possibly lead here. This error is unexplainable"); // we shouldn't be here
return false;
}
//////////////////////////////////////////////////////////////////////////
// uses the found CDREnd to scan the CDR and probably the Zip file itself
// builds up the m_mapFileEntries
bool CacheFactory::BuildFileEntryMap()
{
Seek(m_CDREnd.lCDROffset);
if (m_CDREnd.lCDRSize == 0)
{
return true;
}
auto& pBuffer = m_CDR_buffer; // Use persistent buffer.
pBuffer.resize(m_CDREnd.lCDRSize + 16); // Allocate some more because we use this memory as a strings pool.
if (pBuffer.empty()) // couldn't allocate enough memory for temporary copy of CDR
{
THROW_ZIPDIR_ERROR(ZD_ERROR_NO_MEMORY, "Not enough memory to cache Central Directory record for fast initialization. This error may not happen on non-console systems");
return false;
}
if (!ReadHeaderData(&pBuffer[0], m_CDREnd.lCDRSize))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CORRUPTED_DATA, "Archive contains corrupted CDR.");
return false;
}
// now we've read the complete CDR - parse it.
ZipFile::CDRFileHeader* pFile = (ZipFile::CDRFileHeader*)(&pBuffer[0]);
const uint8_t* pEndOfData = &pBuffer[0] + m_CDREnd.lCDRSize;
uint8_t* pFileName;
while ((pFileName = (uint8_t*)(pFile + 1)) <= pEndOfData)
{
// Hacky way to use CDR memory block as a string pool.
pFile->lSignature = 0; // Force signature to always be 0 (First byte of signature maybe a zero termination of the previous file filename).
if ((pFile->nVersionNeeded & 0xFF) > 20)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_UNSUPPORTED, "Cannot read the archive file (nVersionNeeded > 20).");
return false;
}
//if (pFile->lSignature != pFile->SIGNATURE) // Timur, Dont compare signatures as signatue in memory can be overwritten by the code below
//break;
// the end of this file record
const uint8_t* pEndOfRecord = (pFileName + pFile->nFileNameLength + pFile->nExtraFieldLength + pFile->nFileCommentLength);
// if the record overlaps with the End Of CDR structure, something is wrong
if (pEndOfRecord > pEndOfData)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CDR_IS_CORRUPT, "Central Directory record is either corrupt, or truncated, or missing. Cannot read the archive directory");
return false;
}
//////////////////////////////////////////////////////////////////////////
// Analyze advanced section.
//////////////////////////////////////////////////////////////////////////
SExtraZipFileData extra;
const uint8_t* pExtraField = (pFileName + pFile->nFileNameLength);
const uint8_t* pExtraEnd = pExtraField + pFile->nExtraFieldLength;
while (pExtraField < pExtraEnd)
{
const uint8_t* pAttrData = pExtraField + sizeof(ZipFile::ExtraFieldHeader);
ZipFile::ExtraFieldHeader& hdr = *(ZipFile::ExtraFieldHeader*)pExtraField;
switch (hdr.headerID)
{
case ZipFile::EXTRA_NTFS:
{
memcpy(&extra.nLastModifyTime, pAttrData + sizeof(ZipFile::ExtraNTFSHeader), sizeof(extra.nLastModifyTime));
}
break;
}
pExtraField += sizeof(ZipFile::ExtraFieldHeader) + hdr.dataSize;
}
bool bDirectory = false;
if (pFile->nFileNameLength > 0 && AZStd::string_view{ AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR }.find_first_of(pFileName[pFile->nFileNameLength - 1]) != AZStd::string_view::npos)
{
bDirectory = true;
}
if (!bDirectory)
{
// Add this file entry.
char* str = reinterpret_cast<char*>(pFileName);
for (int i = 0; i < pFile->nFileNameLength; i++)
{
str[i] = std::tolower(str[i], std::locale());
if (str[i] == AZ_WRONG_FILESYSTEM_SEPARATOR)
{
str[i] = AZ_CORRECT_FILESYSTEM_SEPARATOR;
}
}
str[pFile->nFileNameLength] = 0; // Not standard!, may overwrite signature of the next memory record data in zip.
AddFileEntry(str, pFile, extra);
}
// move to the next file
pFile = (ZipFile::CDRFileHeader*)pEndOfRecord;
}
// finished reading CDR
return true;
}
//////////////////////////////////////////////////////////////////////////
// give the CDR File Header entry, reads the local file header to validate
// and determine where the actual file lies
void CacheFactory::AddFileEntry(char* strFilePath, const ZipFile::CDRFileHeader* pFileHeader, const SExtraZipFileData& extra)
{
if (pFileHeader->lLocalHeaderOffset > m_CDREnd.lCDROffset)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CDR_IS_CORRUPT, "Central Directory contains file descriptors pointing outside the archive file boundaries. The archive file is either truncated or damaged. Please try to repair the file"); // the file offset is beyond the CDR: impossible
return;
}
if ((pFileHeader->nMethod == ZipFile::METHOD_STORE || pFileHeader->nMethod == ZipFile::METHOD_STORE_AND_STREAMCIPHER_KEYTABLE) && pFileHeader->desc.lSizeUncompressed != pFileHeader->desc.lSizeCompressed)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "File with STORE compression method declares its compressed size not matching its uncompressed size. File descriptor is inconsistent, archive content may be damaged, please try to repair the archive");
return;
}
FileEntryBase fileEntry(*pFileHeader, extra);
// when using encrypted headers we should always initialize data offsets from CDR
if ((m_encryptedHeaders != ZipFile::HEADERS_NOT_ENCRYPTED || m_nInitMethod >= ZD_INIT_FULL) && pFileHeader->desc.lSizeCompressed)
{
InitDataOffset(fileEntry, pFileHeader);
}
if (m_bBuildFileEntryMap)
{
m_mapFileEntries.emplace(strFilePath, fileEntry);
}
if (m_bBuildFileEntryTree)
{
m_treeFileEntries.Add(strFilePath, fileEntry);
}
}
//////////////////////////////////////////////////////////////////////////
// initializes the actual data offset in the file in the fileEntry structure
// searches to the local file header, reads it and calculates the actual offset in the file
void CacheFactory::InitDataOffset(FileEntryBase& fileEntry, const ZipFile::CDRFileHeader* pFileHeader)
{
if (m_encryptedHeaders != ZipFile::HEADERS_NOT_ENCRYPTED)
{
// use CDR instead of local header
// The pak encryption tool asserts that there is no extra data at the end of the local file header, so don't add any extra data from the CDR header.
fileEntry.nFileDataOffset = pFileHeader->lLocalHeaderOffset + sizeof(ZipFile::LocalFileHeader) + pFileHeader->nFileNameLength;
}
else
{
Seek(pFileHeader->lLocalHeaderOffset);
// read the local file header and the name (for validation) into the buffer
AZStd::vector<char>pBuffer;
uint32_t nBufferLength = sizeof(ZipFile::LocalFileHeader) + pFileHeader->nFileNameLength;
pBuffer.resize(nBufferLength);
Read(&pBuffer[0], nBufferLength);
// validate the local file header (compare with the CDR file header - they should contain basically the same information)
const auto* pLocalFileHeader = reinterpret_cast<const ZipFile::LocalFileHeader*>(&pBuffer[0]);
if (pFileHeader->desc != pLocalFileHeader->desc
|| pFileHeader->nMethod != pLocalFileHeader->nMethod
|| pFileHeader->nFileNameLength != pLocalFileHeader->nFileNameLength
// for a tough validation, we can compare the timestamps of the local and central directory entries
// but we won't do that for backward compatibility with ZipDir
//|| pFileHeader->nLastModDate != pLocalFileHeader->nLastModDate
//|| pFileHeader->nLastModTime != pLocalFileHeader->nLastModTime
)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "The local file header descriptor doesn't match the basic parameters declared in the global file header in the file. The archive content is misconsistent and may be damaged. Please try to repair the archive");
return;
}
// now compare the local file name with the one recorded in CDR: they must match.
auto CompareNoCase = [](const char lhs, const char rhs) { return std::tolower(lhs, std::locale()) == std::tolower(rhs, std::locale()); };
auto zipFileDataBegin = pBuffer.begin() + sizeof(ZipFile::LocalFileHeader);
auto zipFileDataEnd = zipFileDataBegin + pFileHeader->nFileNameLength;
if (!AZStd::equal(zipFileDataBegin, zipFileDataEnd, reinterpret_cast<const char*>(pFileHeader + 1), CompareNoCase))
{
// either file name, or the extra field do not match
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "The local file header contains file name which does not match the file name of the global file header. The archive content is misconsistent with its directory. Please repair the archive");
return;
}
fileEntry.nFileDataOffset = pFileHeader->lLocalHeaderOffset + sizeof(ZipFile::LocalFileHeader) + pLocalFileHeader->nFileNameLength + pLocalFileHeader->nExtraFieldLength;
}
// make sure it's the same file and the fileEntry structure is properly initialized
AZ_Assert(fileEntry.nFileHeaderOffset == pFileHeader->lLocalHeaderOffset, "The file entry header offset doesn't match the file header local offst");
fileEntry.nEOFOffset = fileEntry.nFileDataOffset + fileEntry.desc.lSizeCompressed;
if (fileEntry.nFileDataOffset >= m_nCDREndPos)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "The global file header declares the file which crosses the boundaries of the archive. The archive is either corrupted or truncated, please try to repair it");
return;
}
if (m_nInitMethod >= ZD_INIT_VALIDATE)
{
Validate(fileEntry);
}
}
//////////////////////////////////////////////////////////////////////////
// reads the file pointed by the given header and entry (they must be coherent)
// and decompresses it; then calculates and validates its CRC32
void CacheFactory::Validate(const FileEntryBase& fileEntry)
{
AZStd::vector<char> pBuffer;
// validate the file contents
// allocate memory for both the compressed data and uncompressed data
pBuffer.resize(fileEntry.desc.lSizeCompressed + fileEntry.desc.lSizeUncompressed);
char* pUncompressed = &pBuffer[fileEntry.desc.lSizeCompressed];
char* pCompressed = &pBuffer[0];
AZ_Assert(fileEntry.nFileDataOffset != FileEntry::INVALID_DATA_OFFSET, "File entry has invalid data offset of %" PRIx32, FileEntry::INVALID_DATA_OFFSET);
Seek(fileEntry.nFileDataOffset);
Read(pCompressed, fileEntry.desc.lSizeCompressed);
size_t nDestSize = fileEntry.desc.lSizeUncompressed;
int nError = Z_OK;
if (fileEntry.nMethod)
{
nError = ZipRawUncompress(pUncompressed, &nDestSize, pCompressed, fileEntry.desc.lSizeCompressed);
}
else
{
AZ_Assert(fileEntry.desc.lSizeCompressed == fileEntry.desc.lSizeUncompressed, "Uncompressed file does not have the same commpressed %u and uncompressed file sizes %u",
fileEntry.desc.lSizeCompressed, fileEntry.desc.lSizeUncompressed);
memcpy(pUncompressed, pCompressed, fileEntry.desc.lSizeUncompressed);
}
switch (nError)
{
case Z_OK:
break;
case Z_MEM_ERROR:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_NO_MEMORY, "ZLib reported out-of-memory error");
return;
case Z_BUF_ERROR:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_CORRUPTED_DATA, "ZLib reported compressed stream buffer error");
return;
case Z_DATA_ERROR:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_CORRUPTED_DATA, "ZLib reported compressed stream data error");
return;
default:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_FAILED, "ZLib reported an unexpected unknown error");
return;
}
if (nDestSize != fileEntry.desc.lSizeUncompressed)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CORRUPTED_DATA, "Uncompressed stream doesn't match the size of uncompressed file stored in the archive file headers");
return;
}
uLong uCRC32 = AZ::Crc32((Bytef*)pUncompressed, nDestSize);
if (uCRC32 != fileEntry.desc.lCRC32)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CRC32_CHECK, "Uncompressed stream CRC32 check failed");
return;
}
}
//////////////////////////////////////////////////////////////////////////
// extracts the file path from the file header with subsequent information
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* CacheFactory::GetFilePath(const char* pFileName, uint16_t nFileNameLength)
{
static char strResult[AZ_MAX_PATH_LEN];
AZ_Assert(nFileNameLength < AZ_MAX_PATH_LEN, "Only filenames shorter than %zu can be copied from filename parameter", AZ_MAX_PATH_LEN);
memcpy(strResult, pFileName, nFileNameLength);
strResult[nFileNameLength] = 0;
for (int i = 0; i < nFileNameLength; i++)
{
strResult[i] = std::tolower(strResult[i], std::locale{});
}
return strResult;
}
// seeks in the file relative to the starting position
void CacheFactory::Seek(uint32_t nPos, int nOrigin) // throw
{
if (FSeek(&m_fileExt, nPos, nOrigin))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Cannot fseek() to the new position in the file. This is unexpected error and should not happen under any circumstances. Perhaps some network or disk failure error has caused this");
return;
}
}
int64_t CacheFactory::Tell() // throw
{
int64_t nPos = FTell(&m_fileExt);
if (nPos == -1)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Cannot ftell() position in the archive. This is unexpected error and should not happen under any circumstances. Perhaps some network or disk failure error has caused this");
return 0;
}
return nPos;
}
bool CacheFactory::Read(void* pDest, uint32_t nSize) // throw
{
if (FRead(&m_fileExt, pDest, nSize, 1) != 1)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Cannot fread() a portion of data from archive");
return false;
}
return true;
}
bool CacheFactory::ReadHeaderData(void* pDest, uint32_t nSize) // throw
{
if (!Read(pDest, nSize))
{
return false;
}
switch (m_encryptedHeaders)
{
case ZipFile::HEADERS_NOT_ENCRYPTED:
break; //Nothing to do here
default:
AZ_Warning("Archive", false, "Attempting to load encrypted pak by unsupported method, or unencrypted pak when support is disabled");
return false;
}
switch (m_signedHeaders)
{
case ZipFile::HEADERS_CDR_SIGNED:
AZ_Warning("Archive", false, "[ZipDir] HEADERS_CDR_SIGNED not yet supported");
break;
case ZipFile::HEADERS_NOT_SIGNED:
//Nothing to do here
break;
default:
AZ_Warning("Archive", false, "Unsupported pak signature, or use of unsigned pak when support is disabled.");
return false;
break;
}
return true;
}
}
@@ -0,0 +1,146 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// This is the class that can read the directory from Zip file,
// and store it into the directory cache
#pragma once
#include <AzFramework/Archive/IArchive.h>
namespace AZ::IO::ZipDir
{
class Cache;
// an instance of this class is temporarily created on stack to initialize the CZipFile instance
class CacheFactory
{
public:
enum
{
// open RW cache in read-only mode
FLAGS_READ_ONLY = 1,
// do not compact RW-cached zip upon destruction
FLAGS_DONT_COMPACT = 1 << 1,
// if this is set, then the zip paths won't be memorized in the cache objects
FLAGS_DONT_MEMORIZE_ZIP_PATH = 1 << 2,
// if this is set, the archive will be created anew (the existing file will be overwritten)
FLAGS_CREATE_NEW = 1 << 3,
// Cache will be loaded completely into the memory.
FLAGS_IN_MEMORY = 1 << 4,
FLAGS_IN_MEMORY_CPU = 1 << 5,
// Store all file names as crc32 in a flat directory structure.
FLAGS_FILENAMES_AS_CRC32 = 1 << 6,
// if this is set, zip path will be searched inside other zips
FLAGS_READ_INSIDE_PAK = 1 << 7,
};
// initializes the internal structures
// nFlags can have FLAGS_READ_ONLY flag, in this case the object will be opened only for reading
CacheFactory (InitMethodEnum nInitMethod, uint32_t nFlags = 0);
~CacheFactory();
// the new function creates a new cache
CachePtr New(const char* szFileName);
protected:
// reads the zip file into the file entry tree.
bool ReadCache(Cache& rwCache);
void Clear();
// reads everything and prepares the maps
bool Prepare();
// searches for CDREnd record in the given file
bool FindCDREnd();// throw(ErrorEnum);
// uses the found CDREnd to scan the CDR and probably the Zip file itself
// builds up the m_mapFileEntries
bool BuildFileEntryMap();// throw (ErrorEnum);
// give the CDR File Header entry, reads the local file header to validate and determine where
// the actual file lies
// This function can actually modify strFilePath variable, make sure you use a copy of the real path.
void AddFileEntry(char* strFilePath, const ZipFile::CDRFileHeader* pFileHeader, const SExtraZipFileData& extra);// throw (ErrorEnum);
// extracts the file path from the file header with subsequent information
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* GetFilePath(const ZipFile::CDRFileHeader* pFileHeader)
{
return GetFilePath((const char*)(pFileHeader + 1), pFileHeader->nFileNameLength);
}
// extracts the file path from the file header with subsequent information
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* GetFilePath(const ZipFile::LocalFileHeader* pFileHeader)
{
return GetFilePath((const char*)(pFileHeader + 1), pFileHeader->nFileNameLength);
}
// extracts the file path from the file header with subsequent information
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* GetFilePath(const char* pFileName, uint16_t nFileNameLength);
// validates (if the init method has the corresponding value) the given file/header
void Validate(const FileEntryBase& fileEntry);
// initializes the actual data offset in the file in the fileEntry structure
// searches to the local file header, reads it and calculates the actual offset in the file
void InitDataOffset(FileEntryBase& fileEntry, const ZipFile::CDRFileHeader* pFileHeader);
// seeks in the file relative to the starting position
void Seek(uint32_t nPos, int nOrigin = 0); // throw
int64_t Tell(); // throw
bool Read(void* pDest, uint32_t nSize); // throw
bool ReadHeaderData(void* pDest, uint32_t nSize); // throw
protected:
AZStd::string m_szFilename;
CZipFile m_fileExt;
InitMethodEnum m_nInitMethod;
uint32_t m_nFlags;
ZipFile::CDREnd m_CDREnd;
size_t m_nZipFileSize;
uint32_t m_nCDREndPos; // position of the CDR End in the file
// Map: Relative file path => file entry info
using FileEntryMap = AZStd::map<AZStd::string, ZipDir::FileEntryBase>;
FileEntryMap m_mapFileEntries;
FileEntryTree m_treeFileEntries;
AZStd::vector<uint8_t> m_CDR_buffer;
bool m_bBuildFileEntryMap;
bool m_bBuildFileEntryTree;
bool m_bBuildOptimizedFileEntry;
ZipFile::EHeaderEncryptionType m_encryptedHeaders;
ZipFile::EHeaderSignatureType m_signedHeaders;
ZipFile::CryCustomEncryptionHeader m_headerEncryption;
ZipFile::CrySignedCDRHeader m_headerSignature;
ZipFile::CryCustomExtendedHeader m_headerExtended;
};
}
@@ -0,0 +1,232 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/wildcard.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Archive/ZipFileFormat.h>
#include <AzFramework/Archive/ZipDirStructures.h>
#include <AzFramework/Archive/ZipDirCache.h>
#include <AzFramework/Archive/ZipDirFind.h>
namespace AZ::IO::ZipDir
{
bool FindFile::FindFirst(AZStd::string_view szWildcard)
{
if (!PreFind(szWildcard))
{
return false;
}
// finally, this is the name of the file
m_itFile = m_pDirHeader->GetFileBegin();
return SkipNonMatchingFiles();
}
bool FindDir::FindFirst(AZStd::string_view szWildcard)
{
if (!PreFind(szWildcard))
{
return false;
}
// finally, this is the name of the file
m_itDir = m_pDirHeader->GetDirBegin();
return SkipNonMatchingDirs();
}
// matches the file wildcard in the m_szWildcard to the given file/dir name
// this takes into account the fact that xxx. is the alias name for xxx
bool FindData::MatchWildcard(AZStd::string_view szName)
{
if (AZStd::wildcard_match(m_szWildcard, szName))
{
return true;
}
// check if the file object name contains extension sign (.)
size_t extensionOffset = szName.find('.');
if (extensionOffset != AZStd::string_view::npos)
{
return false;
}
// no extension sign - add it
AZStd::fixed_string<AZ_MAX_PATH_LEN> szAlias{ szName };
szAlias.push_back('.');
return AZStd::wildcard_match(m_szWildcard, szAlias);
}
FileEntry* FindFile::FindExact(AZStd::string_view szPath)
{
if (!PreFind(szPath))
{
return nullptr;
}
FileEntryTree::FileMap::iterator itFile = m_pDirHeader->FindFile(m_szWildcard.c_str());
if (itFile == m_pDirHeader->GetFileEnd())
{
m_pDirHeader = nullptr; // we didn't find it, fail the search
return nullptr;
}
m_itFile = itFile;
return m_pDirHeader->GetFileEntry(m_itFile);
}
FileEntryTree* FindDir::FindExact(AZStd::string_view szPath)
{
if (!PreFind(szPath))
{
return nullptr;
}
// the wild card will contain the target directory name
return m_pDirHeader->FindDir(m_szWildcard);
}
//////////////////////////////////////////////////////////////////////////
// after this call returns successfully (with true returned), the m_szWildcard
// contains the file name/wildcard and m_pDirHeader contains the directory where
// the file (s) are to be found
bool FindData::PreFind(AZStd::string_view szWildcard)
{
if (!m_pRoot)
{
return false;
}
// start the search from the root
m_pDirHeader = m_pRoot;
m_szWildcard = szWildcard;
// for each path directory, copy it into the wildcard buffer and try to find the subdirectory
for (AZStd::optional<AZStd::string_view> pathEntry = AZ::StringFunc::TokenizeNext(szWildcard, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR); pathEntry;
pathEntry = AZ::StringFunc::TokenizeNext(szWildcard, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR))
{
// Update wildcard to new path entry
m_szWildcard = *pathEntry;
// If the wildcard parameter that has been passed to TokenizeNext is empty
// Then pathEntry is the final portion of the path
if (!szWildcard.empty())
{
FileEntryTree* dirEntry = m_pDirHeader->FindDir(*pathEntry);
if (!dirEntry)
{
m_pDirHeader = nullptr; // an intermediate directory has not been found continue the search
return false;
}
m_pDirHeader = dirEntry->GetDirectory();
}
}
return true;
}
// goes on to the next entry
bool FindFile::FindNext()
{
if (m_pDirHeader && m_itFile != m_pDirHeader->GetFileEnd())
{
++m_itFile;
return SkipNonMatchingFiles();
}
else
{
return false;
}
}
// goes on to the next entry
bool FindDir::FindNext()
{
if (m_pDirHeader && m_itDir != m_pDirHeader->GetDirEnd())
{
++m_itDir;
return SkipNonMatchingDirs();
}
else
{
return false;
}
}
bool FindFile::SkipNonMatchingFiles()
{
AZ_Assert(m_pDirHeader, "Directory header cannot be nullptr when skipping files.");
for (; m_itFile != m_pDirHeader->GetFileEnd(); ++m_itFile)
{
if (MatchWildcard(GetFileName()))
{
return true;
}
}
// we didn't find anything other file else
return false;
}
bool FindDir::SkipNonMatchingDirs()
{
AZ_Assert(m_pDirHeader, "Directory header is nullptr. Directories cannot be skipped");
for (; m_itDir != m_pDirHeader->GetDirEnd(); ++m_itDir)
{
if (MatchWildcard(GetDirName()))
{
return true;
}
}
// we didn't find anything other file else
return false;
}
FileEntry* FindFile::GetFileEntry()
{
return m_pDirHeader && m_itFile != m_pDirHeader->GetFileEnd() ? m_pDirHeader->GetFileEntry(m_itFile) : nullptr;
}
FileEntryTree* FindDir::GetDirEntry()
{
return m_pDirHeader && m_itDir != m_pDirHeader->GetDirEnd() ? m_pDirHeader->GetDirEntry(m_itDir) : nullptr;
}
AZStd::string_view FindFile::GetFileName()
{
if (m_pDirHeader && m_itFile != m_pDirHeader->GetFileEnd())
{
return m_pDirHeader->GetFileName(m_itFile);
}
else
{
return ""; // default name
}
}
AZStd::string_view FindDir::GetDirName()
{
if (m_pDirHeader && m_itDir != m_pDirHeader->GetDirEnd())
{
return m_pDirHeader->GetDirName(m_itDir);
}
else
{
return ""; // default name
}
}
}
@@ -0,0 +1,115 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Declaration of the class that can be used to search for the entries
// in a zip dir cache
#pragma once
#include <AzCore/std/string/fixed_string.h>
#include <AzFramework/Archive/ZipDirTree.h>
namespace AZ::IO::ZipDir
{
class FileEntryTree;
// create this structure and loop:
// FindData fd (pZip);
// for (fd.FindFirst("*.cgf"); fd.GetFileEntry(); fd.FindNext())
// {} // inside the loop, use GetFileEntry() and GetFileName() to get the file entry and name records
class FindData
{
public:
FindData(FileEntryTree* pRoot)
: m_pRoot(pRoot)
{
}
// returns the directory to which the current object belongs
FileEntryTree* GetParentDir() {return m_pDirHeader; }
protected:
// initializes everything until the point where the file must be searched for
// after this call returns successfully (with true returned), the m_szWildcard
// contains the file name/wildcard and m_pDirHeader contains the directory where
// the file (s) are to be found
bool PreFind(AZStd::string_view szWildcard);
// matches the file wildcard in the m_szWildcard to the given file/dir name
// this takes into account the fact that xxx. is the alias name for xxx
bool MatchWildcard(AZStd::string_view szName);
// the directory inside which the current object (file or directory) is being searched
FileEntryTree* m_pDirHeader{};
FileEntryTree* m_pRoot{}; // the root of the zip file in which to search
// the actual wildcard being used in the current scan - the file name wildcard only!
AZStd::fixed_string<AZ_MAX_PATH_LEN> m_szWildcard;
};
class FindFile
: public FindData
{
public:
FindFile(CachePtr pCache)
: FindData(pCache->GetRoot())
{
}
FindFile(FileEntryTree* pRoot)
: FindData(pRoot)
{
}
// if bExactFile is passed, only the file is searched, and besides with the exact name as passed (no wildcards)
bool FindFirst(AZStd::string_view szWildcard);
FileEntry* FindExact(AZStd::string_view szPath);
// goes on to the next file entry
bool FindNext();
FileEntry* GetFileEntry();
AZStd::string_view GetFileName();
protected:
bool SkipNonMatchingFiles();
FileEntryTree::FileMap::iterator m_itFile; // the current file iterator inside the parent directory
};
class FindDir
: public FindData
{
public:
FindDir(CachePtr pCache)
: FindData(pCache->GetRoot())
{
}
FindDir(FileEntryTree* pRoot)
: FindData(pRoot)
{
}
// if bExactFile is passed, only the file is searched, and besides with the exact name as passed (no wildcards)
bool FindFirst(AZStd::string_view szWildcard);
FileEntryTree* FindExact(AZStd::string_view szPath);
// goes on to the next file entry
bool FindNext();
FileEntryTree* GetDirEntry();
AZStd::string_view GetDirName();
protected:
bool SkipNonMatchingDirs();
FileEntryTree::SubdirMap::iterator m_itDir; // the current sub-directory iterator
};
}
@@ -0,0 +1,211 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzCore/std/sort.h>
#include <AzFramework/Archive/ZipFileFormat.h>
#include <AzFramework/Archive/ZipDirStructures.h>
#include <AzFramework/Archive/ZipDirList.h>
#include <AzFramework/Archive/ZipDirTree.h>
namespace AZ::IO::ZipDir
{
void FileDataRecordDeleter::operator()(const AZStd::intrusive_refcount<AZStd::atomic_uint, FileDataRecordDeleter>* ptr) const
{
auto fileDataRecordAddress = const_cast<FileDataRecord*>(static_cast<const FileDataRecord*>(ptr));
if (m_allocator)
{
fileDataRecordAddress->~FileDataRecord();
m_allocator->DeAllocate(fileDataRecordAddress);
}
else
{
delete fileDataRecordAddress;
}
}
FileDataRecord::FileDataRecord()
: AZStd::intrusive_refcount<AZStd::atomic_uint, FileDataRecordDeleter>{
AZ::AllocatorInstance<AZ::OSAllocator>::IsReady() ?
FileDataRecordDeleter{&AZ::AllocatorInstance<AZ::OSAllocator>::Get() }
: FileDataRecordDeleter{} }
{
}
auto FileDataRecord::New(const FileRecord& rThat, AZ::IAllocatorAllocate* allocator) -> AZStd::intrusive_ptr<FileDataRecord>
{
auto fileDataRecordAlloc = reinterpret_cast<FileDataRecord*>(allocator->Allocate(
sizeof(FileDataRecord) + rThat.pFileEntryBase->desc.lSizeCompressed,
alignof(FileDataRecord),
0,
"FileDataRecord::New"));
if (fileDataRecordAlloc)
{
new (fileDataRecordAlloc) FileDataRecord{};
*static_cast<FileRecord*>(fileDataRecordAlloc) = rThat;
}
return fileDataRecordAlloc;
}
FileRecordList::FileRecordList(FileEntryTree* pTree)
{
clear();
reserve(pTree->NumFilesTotal());
AddAllFiles(pTree, {});
}
//recursively adds the files from this directory and subdirectories
// the strRoot contains the trailing slash
void FileRecordList::AddAllFiles(FileEntryTree* pTree, AZStd::string_view strRoot)
{
for (FileEntryTree::SubdirMap::iterator it = pTree->GetDirBegin(); it != pTree->GetDirEnd(); ++it)
{
AddAllFiles(it->second.get(), AZStd::string::format("%.*s%.*s/", aznumeric_cast<int>(strRoot.size()), strRoot.data(), aznumeric_cast<int>(it->first.size()), it->first.data()));
}
for (FileEntryTree::FileMap::iterator it = pTree->GetFileBegin(); it != pTree->GetFileEnd(); ++it)
{
FileRecord rec;
rec.pFileEntryBase = pTree->GetFileEntry(it);
rec.strPath = AZStd::string::format("%.*s%.*s", aznumeric_cast<int>(strRoot.size()), strRoot.data(), aznumeric_cast<int>(it->first.size()), it->first.data());
push_back(rec);
}
}
// sorts the files by the physical offset in the zip file
void FileRecordList::SortByFileOffset()
{
AZStd::sort(begin(), end(), FileRecordFileOffsetOrder());
}
// returns the size of CDR in the zip file
FileRecordList::ZipStats FileRecordList::GetStats() const
{
ZipStats Stats;
Stats.nSizeCDR = sizeof(ZipFile::CDREnd);
Stats.nSizeCompactData = 0;
// for each file, we'll need to store only its CDR header and the name
for (const_iterator it = begin(); it != end(); ++it)
{
Stats.nSizeCDR += sizeof(ZipFile::CDRFileHeader) + it->strPath.length();
Stats.nSizeCompactData += sizeof(ZipFile::LocalFileHeader) + it->strPath.length() + it->pFileEntryBase->desc.lSizeCompressed;
}
return Stats;
}
// puts the CDR into the given block of mem
size_t FileRecordList::MakeZipCDR(uint32_t lCDROffset, void* pBuffer) const
{
char* pCur = (char*)pBuffer;
for (const_iterator it = begin(); it != end(); ++it)
{
ZipFile::CDRFileHeader& h = *(ZipFile::CDRFileHeader*)pCur;
pCur = (char*)(&h + 1);
h.lSignature = h.SIGNATURE;
h.nVersionMadeBy = 20;
h.nVersionNeeded = 20;
h.nFlags = 0;
h.nMethod = it->pFileEntryBase->nMethod;
h.nLastModTime = it->pFileEntryBase->nLastModTime;
h.nLastModDate = it->pFileEntryBase->nLastModDate;
h.desc = it->pFileEntryBase->desc;
h.nFileNameLength = static_cast<uint16_t>(it->strPath.size());
h.nExtraFieldLength = 0;
h.nFileCommentLength = 0;
h.nDiskNumberStart = 0;
h.nAttrInternal = 0;
h.lAttrExternal = 0;
h.lLocalHeaderOffset = it->pFileEntryBase->nFileHeaderOffset;
memcpy(pCur, it->strPath.c_str(), it->strPath.size());
pCur += it->strPath.size();
}
ZipFile::CDREnd& e = *(ZipFile::CDREnd*)pCur;
e.lSignature = e.SIGNATURE;
e.nDisk = 0;
e.nCDRStartDisk = 0;
e.numEntriesOnDisk = static_cast<uint16_t>(this->size());
e.numEntriesTotal = static_cast<uint16_t>(this->size());
e.lCDRSize = static_cast<uint32_t>(pCur - reinterpret_cast<char*>(pBuffer));
e.lCDROffset = lCDROffset;
e.nCommentLength = 0;
pCur = (char*)(&e + 1);
return pCur - (char*)pBuffer;
}
FileEntryList::FileEntryList(FileEntryTree* pTree, uint32_t lCDROffset)
: m_lCDROffset(lCDROffset)
{
Add(pTree);
}
void FileEntryList::Add(FileEntryTree* pTree)
{
for (FileEntryTree::SubdirMap::iterator itDir = pTree->GetDirBegin(); itDir != pTree->GetDirEnd(); ++itDir)
{
Add(pTree->GetDirEntry(itDir));
}
for (FileEntryTree::FileMap::iterator itFile = pTree->GetFileBegin(); itFile != pTree->GetFileEnd(); ++itFile)
{
insert(pTree->GetFileEntry(itFile));
}
}
// updates each file entry's info about the next file entry
void FileEntryList::RefreshEOFOffsets()
{
iterator it, itNext = begin();
if (itNext != end())
{
while ((it = itNext, ++itNext) != end())
{
// start scan
(*it)->nEOFOffset = (*itNext)->nFileHeaderOffset;
}
// it is the last one..
(*it)->nEOFOffset = m_lCDROffset;
}
}
void FileRecordList::Backup(AZStd::vector<FileEntryBase>& arrFiles) const
{
arrFiles.reserve(size());
for (const auto& fileEntryBase : *this)
{
arrFiles.emplace_back(*fileEntryBase.pFileEntryBase);
}
}
void FileRecordList::Restore(const AZStd::vector<FileEntryBase>& arrFiles)
{
if (arrFiles.size() == size())
{
for (size_t fileEntryIndex = 0; fileEntryIndex < arrFiles.size(); ++fileEntryIndex)
{
AZStd::vector<FileRecord>& fileRecords = *this;
*fileRecords[fileEntryIndex].pFileEntryBase = arrFiles[fileEntryIndex];
}
}
}
}
@@ -0,0 +1,113 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <AzCore/std/smart_ptr/intrusive_refcount.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/set.h>
namespace AZ::IO::ZipDir
{
// this is the array of file entries that's convenient to use to construct CDR
struct FileRecord
{
AZStd::string strPath; // relative path to the file inside zip
FileEntryBase* pFileEntryBase{}; // the file entry itself
};
struct FileDataRecord;
struct FileDataRecordDeleter
{
void operator()(const AZStd::intrusive_refcount<AZStd::atomic_uint, FileDataRecordDeleter>* ptr) const;
AZ::IAllocatorAllocate* m_allocator{};
};
struct FileDataRecord
: public FileRecord
, public AZStd::intrusive_refcount<AZStd::atomic_uint, FileDataRecordDeleter>
{
FileDataRecord();
static auto New(const FileRecord& rThat, AZ::IAllocatorAllocate* allocator) ->AZStd::intrusive_ptr<FileDataRecord>;
void* GetData() {return this + 1; }
};
using FileDataRecordPtr = AZStd::intrusive_ptr<FileDataRecord>;
// this is used for construction of CDR
class FileRecordList
: public AZStd::vector<FileRecord>
{
public:
FileRecordList(class FileEntryTree* pTree);
struct ZipStats
{
// the size of the CDR in the file
size_t nSizeCDR;
// the size of the file data part (local file descriptors and file datas)
// if it's compacted
size_t nSizeCompactData;
};
// sorts the files by the physical offset in the zip file
void SortByFileOffset ();
// returns the size of CDR in the zip file
ZipStats GetStats() const;
// puts the CDR into the given block of mem
size_t MakeZipCDR(uint32_t lCDROffset, void* p) const;
void Backup(AZStd::vector<FileEntryBase>& arrFiles) const;
void Restore(const AZStd::vector<FileEntryBase>& arrFiles);
protected:
//recursively adds the files from this directory and subdirectories
// the strRoot contains the trailing slash
void AddAllFiles(FileEntryTree* pTree, AZStd::string_view strRoot);
};
struct FileRecordFileOffsetOrder
{
bool operator()(const FileRecord& left, const FileRecord& right)
{
return left.pFileEntryBase->nFileHeaderOffset < right.pFileEntryBase->nFileHeaderOffset;
}
};
struct FileEntryFileOffsetOrder
{
bool operator()(FileEntry* pLeft, FileEntry* pRight) const
{
return pLeft->nFileHeaderOffset < pRight->nFileHeaderOffset;
}
};
// this is used for refreshing EOFOffsets
class FileEntryList
: public AZStd::set<FileEntry*, FileEntryFileOffsetOrder>
{
public:
FileEntryList(FileEntryTree* pTree, uint32_t lCDROffset);
// updates each file entry's info about the next file entry
void RefreshEOFOffsets();
protected:
void Add(FileEntryTree* pTree);
uint32_t m_lCDROffset;
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,395 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// This file contains only the support definitions for CZipDir class
// implementation. This it to unload the ZipDir.h from secondary stuff.
#pragma once
#include <AzCore/base.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzFramework/Archive/ZipFileFormat.h>
#if AZ_TRAIT_USE_WINDOWS_FILE_API && AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
#define SUPPORT_UNBUFFERED_IO
#endif
struct z_stream_s;
namespace AZ::IO
{
class FileIOBase;
struct MemoryBlock;
}
namespace AZ::IO::ZipDir
{
struct FileEntry;
struct CZipFile
{
AZ::IO::HandleType m_fileHandle;
#ifdef SUPPORT_UNBUFFERED_IO
AZ::IO::SystemFile m_unbufferedFile;
size_t m_nSectorSize;
void* m_pReadTarget;
#endif
int64_t m_nSize;
int64_t m_nCursor;
const char* m_szFilename;
AZStd::intrusive_ptr<AZ::IO::MemoryBlock> m_pInMemoryData;
AZ::IO::FileIOBase* m_fileIOBase = nullptr;
CZipFile();
CZipFile(const CZipFile& file) = delete;
CZipFile & operator=(const CZipFile&) = delete;
void Swap(CZipFile& other);
bool IsInMemory() const { return m_pInMemoryData != nullptr; }
void LoadToMemory(AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = {});
void UnloadFromMemory();
void Close(bool bUnloadFromMem = true);
#ifdef SUPPORT_UNBUFFERED_IO
bool OpenUnbuffered(const char* filename);
bool EvaluateSectorSize(const char* filename);
#endif
};
// possible errors occurring during the method execution
// to avoid clashing with the global Windows defines, we prefix these with ZD_
enum ErrorEnum
{
ZD_ERROR_SUCCESS = 0,
ZD_ERROR_IO_FAILED,
ZD_ERROR_UNEXPECTED,
ZD_ERROR_UNSUPPORTED,
ZD_ERROR_INVALID_SIGNATURE,
ZD_ERROR_ZIP_FILE_IS_CORRUPT,
ZD_ERROR_DATA_IS_CORRUPT,
ZD_ERROR_NO_CDR,
ZD_ERROR_CDR_IS_CORRUPT,
ZD_ERROR_NO_MEMORY,
ZD_ERROR_VALIDATION_FAILED,
ZD_ERROR_CRC32_CHECK,
ZD_ERROR_ZLIB_FAILED,
ZD_ERROR_ZLIB_CORRUPTED_DATA,
ZD_ERROR_ZLIB_NO_MEMORY,
ZD_ERROR_CORRUPTED_DATA,
ZD_ERROR_INVALID_CALL,
ZD_ERROR_NOT_IMPLEMENTED,
ZD_ERROR_FILE_NOT_FOUND,
ZD_ERROR_DIR_NOT_FOUND,
ZD_ERROR_NAME_TOO_LONG,
ZD_ERROR_INVALID_PATH,
ZD_ERROR_FILE_ALREADY_EXISTS,
ZD_ERROR_ARCHIVE_TOO_LARGE,
};
// the error describes the reason of the error, as well as the error code, line of code where it happened etc.
struct Error
{
Error(ErrorEnum _nError, const char* _szDescription, const char* _szFunction, const char* _szFile, unsigned _nLine)
: nError(_nError)
, m_szDescription(_szDescription)
, szFunction(_szFunction)
, szFile(_szFile)
, nLine(_nLine)
{
}
ErrorEnum nError;
const char* getError();
const char* getDescription() {return m_szDescription; }
const char* szFunction, * szFile;
unsigned nLine;
protected:
// the description of the error; if needed, will be made as a dynamic string
const char* m_szDescription;
};
#define THROW_ZIPDIR_ERROR(ZD_ERR, DESC) AZ_Warning("Archive", false, DESC)
// possible initialization methods
enum InitMethodEnum
{
// initialize as fast as possible, with minimal validation
ZD_INIT_FAST,
// after initialization, scan through all file headers, precache the actual file data offset values and validate the headers
ZD_INIT_FULL,
// scan all file headers and try to decompress the data, searching for corrupted files
ZD_INIT_VALIDATE_IN_MEMORY,
// store archive in memory
ZD_INIT_VALIDATE,
// maximum level of validation, checks for integrity of the archive
ZD_INIT_VALIDATE_MAX = ZD_INIT_VALIDATE
};
// Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file
// returns one of the Z_* errors (Z_OK upon success)
int ZipRawUncompress(void* pUncompressed, size_t* pDestSize, const void* pCompressed, size_t nSrcSize);
// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
// returns one of the Z_* errors (Z_OK upon success), and the size in *pDestSize. the pCompressed buffer must be at least nSrcSize*1.001+12 size
int ZipRawCompress(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, int nLevel);
int ZipRawCompressZSTD(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, int nLevel);
int ZipRawCompressLZ4(const void* pUncompressed, size_t* pDestSize, void* pCompressed, size_t nSrcSize, int nLevel);
// fseek wrapper with memory in file support.
int64_t FSeek(CZipFile* zipFile, int64_t origin, int command);
// fread wrapper with file in memory support
int64_t FRead(CZipFile* zipFile, void* data, size_t nElemSize, size_t nCount);
// ftell wrapper with file in memory support
int64_t FTell(CZipFile* zipFile);
int FEof(CZipFile* zipFile);
uint32_t FileNameHash(AZStd::string_view filename);
//////////////////////////////////////////////////////////////////////////
struct SExtraZipFileData
{
uint64_t nLastModifyTime{};
};
struct FileEntryBase
{
FileEntryBase() = default;
FileEntryBase(const ZipFile::CDRFileHeader& header, const SExtraZipFileData& extra);
inline static constexpr uint32_t INVALID_DATA_OFFSET = 0xFFFFFFFF;
ZipFile::DataDescriptor desc{};
uint32_t nFileDataOffset{}; // offset of the packed info inside the file; NOTE: this can be INVALID_DATA_OFFSET, if not calculated yet!
uint32_t nFileHeaderOffset{ INVALID_DATA_OFFSET }; // offset of the local file header
uint32_t nNameOffset{}; // offset of the file name in the name pool for the directory
uint16_t nMethod{}; // the method of compression (0 if no compression/store)
uint16_t nReserved0{}; // Reserved
// the file modification times
uint16_t nLastModTime{};
uint16_t nLastModDate{};
uint64_t nNTFS_LastModifyTime{};
// the offset to the start of the next file's header - this
// can be used to calculate the available space in zip file
uint32_t nEOFOffset{};
};
// this is the record about the file in the Zip file.
struct FileEntry
: FileEntryBase
{
AZ_CLASS_ALLOCATOR(FileEntry, AZ::SystemAllocator, 0);
// mutex that can be used to product reads for the current file entry
AZStd::mutex m_readLock;
using FileEntryBase::FileEntryBase;
FileEntry(const FileEntry&) = delete;
FileEntry& operator=(const FileEntry&) = delete;
bool IsInitialized()
{
// structure marked as non-initialized should have nFileHeaderOffset == INVALID_DATA_OFFSET
return nFileHeaderOffset != INVALID_DATA_OFFSET;
}
// returns the name of this file, given the pointer to the name pool
const char* GetName(const char* pNamePool) const
{
return pNamePool + nNameOffset;
}
// sets the current time to modification time
// calculates CRC32 for the new data
void OnNewFileData(const void* pUncompressed, uint64_t nSize, uint64_t nCompressedSize, uint32_t nCompressionMethod, bool bContinuous);
uint64_t GetModificationTime();
bool IsCompressed() const
{
return (
nMethod != ZipFile::METHOD_STORE_AND_STREAMCIPHER_KEYTABLE &&
nMethod != ZipFile::METHOD_STORE
);
}
};
// tries to refresh the file entry from the given file (reads from there if needed)
// returns the error code if the operation was impossible to complete
ErrorEnum Refresh(CZipFile* f, FileEntryBase* pFileEntry);
// writes into the file local header (NOT including the name, only the header structure)
// the file must be opened both for reading and writing
ErrorEnum UpdateLocalHeader(AZ::IO::HandleType fileHandle, FileEntryBase* pFileEntry);
// writes into the file local header - without Extra data
// puts the new offset to the file data to the file entry
// in case of error can put INVALID_DATA_OFFSET into the data offset field of file entry
ErrorEnum WriteLocalHeader(AZ::IO::HandleType fileHandle, FileEntryBase* pFileEntry, AZStd::string_view szRelativePath);
// conversion routines for the date/time fields used in Zip
uint16_t DOSDate(tm*);
uint16_t DOSTime(tm*);
const char* DOSTimeCStr(uint16_t nTime);
const char* DOSDateCStr(uint16_t nTime);
struct DirHeader;
// this structure represents a subdirectory descriptor in the directory record.
// it points to the actual directory info (list of its subdirs and files), as well
// as on its name
struct DirEntry
{
AZ_CLASS_ALLOCATOR(DirEntry, AZ::SystemAllocator, 0);
uint32_t nDirHeaderOffset{}; // offset, in bytes, relative to this object, of the actual directory record header
uint32_t nNameOffset{}; // offset of the dir name in the name pool of the parent directory
// returns the name of this directory, given the pointer to the name pool of hte parent directory
const char* GetName(const char* pNamePool) const
{
return pNamePool + nNameOffset;
}
// returns the pointer to the actual directory record.
// call this function only for the actual structure instance contained in a directory record and
// followed by the other directory records
const DirHeader* GetDirectory() const
{
return (const DirHeader*)(((const char*)this) + nDirHeaderOffset);
}
DirHeader* GetDirectory()
{
return (DirHeader*)(((char*)this) + nDirHeaderOffset);
}
};
// this is the head of the directory record
// the name pool follows straight the directory and file entries.
struct DirHeader
{
uint16_t numDirs{}; // number of directory entries - DirEntry structures
uint16_t numFiles{}; // number of file entries - FileEntry structures
// returns the pointer to the name pool that follows this object
// you can only call this method for the structure instance actually followed by the dir record
const char* GetNamePool() const
{
return ((char*)(this + 1)) + (size_t)this->numDirs * sizeof(DirEntry) + (size_t)this->numFiles * sizeof(FileEntry);
}
char* GetNamePool()
{
return ((char*)(this + 1)) + (size_t)this->numDirs * sizeof(DirEntry) + (size_t)this->numFiles * sizeof(FileEntry);
}
// returns the pointer to the i-th directory
// call this only for the actual instance of the structure at the head of dir record
const DirEntry* GetSubdirEntry(uint32_t i) const
{
AZ_Assert(i < numDirs, "Index %u is out of bounds to lookup subdirectory entry . Index must be less than %hu", i, numDirs);
return ((const DirEntry*)(this + 1)) + i;
}
DirEntry* GetSubdirEntry(uint32_t i)
{
return const_cast<DirEntry*>(const_cast<const DirHeader*>(this)->GetSubdirEntry(i));
}
// returns the pointer to the i-th file
// call this only for the actual instance of the structure at the head of dir record
const FileEntry* GetFileEntry(uint32_t i) const
{
AZ_Assert(i < numFiles, "Index %u is out of range of number of file entries %hu", i, numFiles);
return (const FileEntry*)(((const DirEntry*)(this + 1)) + numDirs) + i;
}
FileEntry* GetFileEntry (uint32_t i)
{
return const_cast<FileEntry*>(const_cast<const DirHeader*>(this)->GetFileEntry(i));
}
// finds the subdirectory entry by the name, using the names from the name pool
// assumes: all directories are sorted in alphabetical order.
// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
DirEntry* FindSubdirEntry(AZStd::string_view szName);
// finds the file entry by the name, using the names from the name pool
// assumes: all directories are sorted in alphabetical order.
// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
FileEntry* FindFileEntry(AZStd::string_view szName);
};
// this is the sorting predicate for directory entries
struct DirEntrySortPred
{
DirEntrySortPred(const char* pNamePool)
: m_pNamePool{ pNamePool }
{
}
bool operator()(const FileEntry& left, const FileEntry& right) const
{
return strcmp(left.GetName(m_pNamePool), right.GetName(m_pNamePool)) < 0;
}
bool operator()(const FileEntry& left, AZStd::string_view szRight) const
{
return left.GetName(m_pNamePool) < szRight;
}
bool operator()(AZStd::string_view szLeft, const FileEntry& right) const
{
return szLeft < right.GetName(m_pNamePool);
}
bool operator()(const DirEntry& left, const DirEntry& right) const
{
return strcmp(left.GetName(m_pNamePool), right.GetName(m_pNamePool)) < 0;
}
bool operator()(const DirEntry& left, AZStd::string_view szName) const
{
return left.GetName(m_pNamePool) < szName;
}
bool operator()(const char* szLeft, const DirEntry& right) const
{
return szLeft < right.GetName(m_pNamePool);
}
const char* m_pNamePool;
};
struct UncompressLookahead
{
inline static constexpr size_t Capacity = 16384;
UncompressLookahead()
: cachedStartIdx(0)
, cachedEndIdx(0)
{
}
char buffer[Capacity];
uint32_t cachedStartIdx;
uint32_t cachedEndIdx;
};
}
@@ -0,0 +1,182 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <AzCore/Console/Console.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Archive/ZipFileFormat.h>
#include <AzFramework/Archive/ZipDirStructures.h>
#include <AzFramework/Archive/ZipDirTree.h>
namespace AZ::IO::ZipDir
{
// Adds or finds the file. Returns non-initialized structure if it was added,
// or an IsInitialized() structure if it was found
FileEntry* FileEntryTree::Add(AZStd::string_view szPath)
{
AZStd::optional<AZStd::string_view> pathEntry = AZ::StringFunc::TokenizeNext(szPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
if (!pathEntry)
{
AZ_Assert(false, "An empty file path cannot be added to the zip file entry tree");
return nullptr;
}
// If a path separator was found, add a subdirectory
if (!szPath.empty())
{
auto dirEntryIter = m_mapDirs.find(*pathEntry);
// we have a subdirectory here - create the file in it
if (dirEntryIter == m_mapDirs.end())
{
dirEntryIter = m_mapDirs.emplace(*pathEntry, AZStd::make_unique<FileEntryTree>()).first;
}
return dirEntryIter->second->Add(szPath);
}
// Add the filename
auto fileEntryIter = m_mapFiles.find(*pathEntry);
if (fileEntryIter == m_mapFiles.end())
{
fileEntryIter = m_mapFiles.emplace(*pathEntry, AZStd::make_unique<FileEntry>()).first;
}
return fileEntryIter->second.get();
}
// adds a file to this directory
ErrorEnum FileEntryTree::Add(AZStd::string_view szPath, const FileEntryBase& file)
{
FileEntry* pFile = Add(szPath);
if (!pFile)
{
return ZD_ERROR_INVALID_PATH;
}
if (pFile->IsInitialized())
{
return ZD_ERROR_FILE_ALREADY_EXISTS;
}
static_cast<FileEntryBase&>(*pFile) = file;
return ZD_ERROR_SUCCESS;
}
// returns the number of files in this tree, including this and sublevels
uint32_t FileEntryTree::NumFilesTotal() const
{
uint32_t numFiles = aznumeric_cast<uint32_t>(m_mapFiles.size());
for (const auto& [dirname, fileEntryTree] : m_mapDirs)
{
numFiles += fileEntryTree->NumFilesTotal();
}
return numFiles;
}
//////////////////////////////////////////////////////////////////////////
uint32_t FileEntryTree::NumDirsTotal() const
{
uint32_t numDirs = 1;
for (const auto& [dirname, fileEntryTree] : m_mapDirs)
{
numDirs += fileEntryTree->NumDirsTotal();
}
return numDirs;
}
void FileEntryTree::Clear()
{
m_mapDirs.clear();
m_mapFiles.clear();
}
size_t FileEntryTree::GetSize() const
{
size_t nSize = sizeof(*this);
for (const auto& [dirname, dirEntry] : m_mapDirs)
{
nSize += dirname.size() + sizeof(decltype(m_mapDirs)::value_type) + dirEntry->GetSize();
}
for (const auto& [filename, fileEntry] : m_mapFiles)
{
nSize += filename.size() + sizeof(decltype(m_mapFiles)::value_type);
}
return nSize;
}
bool FileEntryTree::IsOwnerOf(const FileEntry* pFileEntry) const
{
for (const auto& [path, fileEntry] : m_mapFiles)
{
if (pFileEntry == fileEntry.get())
{
return true;
}
}
for (const auto& [path, fileEntryTree] : m_mapDirs)
{
if (fileEntryTree->IsOwnerOf(pFileEntry))
{
return true;
}
}
return false;
}
FileEntryTree* FileEntryTree::FindDir(AZStd::string_view szDirName)
{
if (auto it = m_mapDirs.find(szDirName); it != m_mapDirs.end())
{
return it->second.get();
}
return nullptr;
}
FileEntryTree::FileMap::iterator FileEntryTree::FindFile(AZStd::string_view szFileName)
{
return m_mapFiles.find(szFileName);
}
FileEntry* FileEntryTree::GetFileEntry(FileMap::iterator it)
{
return it == GetFileEnd() ? nullptr : it->second.get();
}
FileEntryTree* FileEntryTree::GetDirEntry(SubdirMap::iterator it)
{
return it == GetDirEnd() ? nullptr : it->second.get();
}
ErrorEnum FileEntryTree::RemoveDir(AZStd::string_view szDirName)
{
SubdirMap::iterator itRemove = m_mapDirs.find(szDirName);
if (itRemove == m_mapDirs.end())
{
return ZD_ERROR_FILE_NOT_FOUND;
}
m_mapDirs.erase(itRemove);
return ZD_ERROR_SUCCESS;
}
ErrorEnum FileEntryTree::RemoveFile(AZStd::string_view szFileName)
{
FileMap::iterator itRemove = m_mapFiles.find(szFileName);
if (itRemove == m_mapFiles.end())
{
return ZD_ERROR_FILE_NOT_FOUND;
}
m_mapFiles.erase(itRemove);
return ZD_ERROR_SUCCESS;
}
}
@@ -0,0 +1,92 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::IO::ZipDir
{
class FileEntryTree
{
public:
AZ_CLASS_ALLOCATOR(FileEntryTree, AZ::SystemAllocator, 0);
~FileEntryTree () {Clear(); }
// adds a file to this directory
// Function can modify szPath input
ErrorEnum Add(AZStd::string_view szPath, const FileEntryBase& file);
// Adds or finds the file. Returns non-initialized structure if it was added,
// or an IsInitialized() structure if it was found
// Function can modify szPath input
FileEntry* Add(AZStd::string_view szPath);
// returns the number of files in this tree, including this and sublevels
uint32_t NumFilesTotal() const;
// Total number of directories
uint32_t NumDirsTotal() const;
void Clear();
void Swap(FileEntryTree& rThat)
{
m_mapDirs.swap(rThat.m_mapDirs);
m_mapFiles.swap(rThat.m_mapFiles);
}
size_t GetSize() const;
bool IsOwnerOf(const FileEntry* pFileEntry) const;
// subdirectories
using SubdirMap = AZStd::map<AZStd::string_view, AZStd::unique_ptr<FileEntryTree>>;
// file entries
using FileMap = AZStd::map<AZStd::string_view, AZStd::unique_ptr<FileEntry>>;
FileEntryTree* FindDir(AZStd::string_view szDirName);
ErrorEnum RemoveDir (AZStd::string_view szDirName);
ErrorEnum RemoveAll ()
{
Clear();
return ZD_ERROR_SUCCESS;
}
FileMap::iterator FindFile(AZStd::string_view szFileName);
ErrorEnum RemoveFile(AZStd::string_view szFileName);
// the FileEntryTree is simultaneously an entry in the dir list AND the directory header
FileEntryTree* GetDirectory()
{
return this;
}
FileMap::iterator GetFileBegin() { return m_mapFiles.begin(); }
FileMap::iterator GetFileEnd() { return m_mapFiles.end(); }
uint32_t NumFiles() const { return aznumeric_cast<uint32_t>(m_mapFiles.size()); }
SubdirMap::iterator GetDirBegin() { return m_mapDirs.begin(); }
SubdirMap::iterator GetDirEnd() { return m_mapDirs.end(); }
uint32_t NumDirs() const { return aznumeric_cast<uint32_t>(m_mapDirs.size()); }
AZStd::string_view GetFileName(FileMap::iterator it) { return it->first; }
AZStd::string_view GetDirName(SubdirMap::iterator it) { return it->first; }
FileEntry* GetFileEntry(FileMap::iterator it);
FileEntryTree* GetDirEntry(SubdirMap::iterator it);
protected:
SubdirMap m_mapDirs;
FileMap m_mapFiles;
};
}
@@ -0,0 +1,290 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/algorithm.h>
namespace AZ::IO::ZipFile
{
using HeaderType = uint32_t;
// General-purpose bit field flags
enum
{
GPF_ENCRYPTED = 1, // If set, indicates that the file is encrypted.
GPF_DATA_DESCRIPTOR = 1 << 3, // if set, the CRC32 and sizes aren't set in the file header, but only in the data descriptor following compressed data
GPF_RESERVED_8_ENHANCED_DEFLATING = 1 << 4, // Reserved for use with method 8, for enhanced deflating.
GPF_COMPRESSED_PATCHED = 1 << 5, // the file is compressed patched data
};
enum
{
BLOCK_CIPHER_NUM_KEYS = 16
};
enum
{
BLOCK_CIPHER_KEY_LENGTH = 16
};
enum
{
RSA_KEY_MESSAGE_LENGTH = 128 //The modulus of our private/public key pair for signing, verification, encryption and decryption
};
// compression methods
enum
{
METHOD_STORE = 0, // The file is stored (no compression)
METHOD_SHRINK = 1, // The file is Shrunk
METHOD_REDUCE_1 = 2, // The file is Reduced with compression factor 1
METHOD_REDUCE_2 = 3, // The file is Reduced with compression factor 2
METHOD_REDUCE_3 = 4, // The file is Reduced with compression factor 3
METHOD_REDUCE_4 = 5, // The file is Reduced with compression factor 4
METHOD_IMPLODE = 6, // The file is Imploded
METHOD_TOKENIZE = 7, // Reserved for Tokenizing compression algorithm
METHOD_DEFLATE = 8, // The file is Deflated
METHOD_DEFLATE64 = 9, // Enhanced Deflating using Deflate64(tm)
METHOD_IMPLODE_PKWARE = 10, // PKWARE Date Compression Library Imploding
METHOD_DEFLATE_AND_ENCRYPT = 11, // Deflate + Custom encryption (TEA)
METHOD_DEFLATE_AND_STREAMCIPHER = 12, // Deflate + stream cipher encryption on a per file basis
METHOD_STORE_AND_STREAMCIPHER_KEYTABLE = 13, // Store + Timur's encryption technique on a per file basis
METHOD_DEFLATE_AND_STREAMCIPHER_KEYTABLE = 14, // Deflate + Timur's encryption technique on a per file basis
};
// end of Central Directory Record
// followed by the .zip file comment (variable size, can be empty, obtained from nCommentLength)
#pragma pack(push, 1)
struct CDREnd
{
inline static constexpr uint32_t SIGNATURE = 0x06054b50;
uint32_t lSignature{}; // end of central dir signature 4 bytes (0x06054b50)
uint16_t nDisk{}; // number of this disk 2 bytes
uint16_t nCDRStartDisk{}; // number of the disk with the start of the central directory 2 bytes
uint16_t numEntriesOnDisk{}; // total number of entries in the central directory on this disk 2 bytes
uint16_t numEntriesTotal{}; // total number of entries in the central directory 2 bytes
uint32_t lCDRSize{}; // size of the central directory 4 bytes
uint32_t lCDROffset{}; // offset of start of central directory with respect to the starting disk number 4 bytes
uint16_t nCommentLength{}; // .ZIP file comment length 2 bytes
// .ZIP file comment (variable size, can be empty) follows
};
#pragma pack(pop)
// encryption settings for zip header - stored in m_headerExtended struct
enum EHeaderEncryptionType : int32_t
{
HEADERS_NOT_ENCRYPTED = 0,
HEADERS_ENCRYPTED_STREAMCIPHER = 1,
HEADERS_ENCRYPTED_TEA = 2, //TEA = Tiny Encryption Algorithm
HEADERS_ENCRYPTED_STREAMCIPHER_KEYTABLE = 3, //Timur's technique. Encrypt each file and the CDR with one of 16 stream cipher keys. Encrypt the table of keys with an RSA key.
};
// Signature settings for zip header
enum EHeaderSignatureType : int32_t
{
HEADERS_NOT_SIGNED = 0,
HEADERS_CDR_SIGNED = 1 //Includes an RSA signature based on the hash of the archive's CDR. Verified in a console compatible way.
};
//Header for HEADERS_ENCRYPTED_CRYCUSTOM technique. Paired with a CrySignedCDRHeader to allow for signing as well as encryption.
//i.e. the comment section for a file that uses this technique needs the following in order:
//CryCustomExtendedHeader, CrySignedCDRHeader, CryCustomEncryptionHeader
#pragma pack(push, 1)
struct CryCustomEncryptionHeader
{
uint32_t nHeaderSize{}; // Size of the extended header.
uint8_t CDR_IV[RSA_KEY_MESSAGE_LENGTH]; //Initial Vector is actually BLOCK_CIPHER_KEY_LENGTH bytes in length, but is encrypted as a RSA_KEY_MESSAGE_LENGTH byte message.
uint8_t keys_table[BLOCK_CIPHER_NUM_KEYS][RSA_KEY_MESSAGE_LENGTH]; //As above, actually BLOCK_CIPHER_KEY_LENGTH but encrypted.
};
#pragma pack(pop)
//Header for HEADERS_SIGNED_CDR technique implemented on consoles. The comment section needs to contain the following in order:
//CryCustomExtendedHeader, CrySignedCDRHeader
#pragma pack(push, 1)
struct CrySignedCDRHeader
{
uint32_t nHeaderSize{}; // Size of the extended header.
uint8_t CDR_signed[RSA_KEY_MESSAGE_LENGTH];
};
#pragma pack(pop)
//Stores type of encryption and signing
#pragma pack(push, 1)
struct CryCustomExtendedHeader
{
uint32_t nHeaderSize{}; // Size of the extended header.
uint16_t nEncryption{}; // Matches one of EHeaderEncryptionType: 0 = No encryption/extension
uint16_t nSigning{}; // Matches one of EHeaderSignatureType: 0 = No signing
};
#pragma pack(pop)
// This descriptor exists only if bit 3 of the general
// purpose bit flag is set (see below). It is byte aligned
// and immediately follows the last byte of compressed data.
// This descriptor is used only when it was not possible to
// seek in the output .ZIP file, e.g., when the output .ZIP file
// was standard output or a non seekable device. For Zip64 format
// archives, the compressed and uncompressed sizes are 8 bytes each.
#pragma pack(push, 1)
struct DataDescriptor
{
uint32_t lCRC32{}; // crc-32 4 bytes
uint32_t lSizeCompressed{}; // compressed size 4 bytes
uint32_t lSizeUncompressed{}; // uncompressed size 4 bytes
bool operator==(const DataDescriptor& d) const
{
return lCRC32 == d.lCRC32 && lSizeCompressed == d.lSizeCompressed && lSizeUncompressed == d.lSizeUncompressed;
}
bool operator!=(const DataDescriptor& d) const
{
return lCRC32 != d.lCRC32 || lSizeCompressed != d.lSizeCompressed || lSizeUncompressed != d.lSizeUncompressed;
}
};
#pragma pack(pop)
// the File Header as it appears in the CDR
// followed by:
// file name (variable size)
// extra field (variable size)
// file comment (variable size)
#pragma pack(push, 1)
struct CDRFileHeader
{
inline static constexpr uint32_t SIGNATURE = 0x02014b50;
uint32_t lSignature{}; // central file header signature 4 bytes (0x02014b50)
uint16_t nVersionMadeBy{}; // version made by 2 bytes
uint16_t nVersionNeeded{}; // version needed to extract 2 bytes
uint16_t nFlags{}; // general purpose bit flag 2 bytes
uint16_t nMethod{}; // compression method 2 bytes
uint16_t nLastModTime{}; // last mod file time 2 bytes
uint16_t nLastModDate{}; // last mod file date 2 bytes
DataDescriptor desc{};
uint16_t nFileNameLength{}; // file name length 2 bytes
uint16_t nExtraFieldLength{}; // extra field length 2 bytes
uint16_t nFileCommentLength{}; // file comment length 2 bytes
uint16_t nDiskNumberStart{}; // disk number start 2 bytes
uint16_t nAttrInternal{}; // internal file attributes 2 bytes
uint32_t lAttrExternal{}; // external file attributes 4 bytes
// This is the offset from the start of the first disk on
// which this file appears, to where the local header should
// be found. If an archive is in zip64 format and the value
// in this field is 0xFFFFFFFF, the size will be in the
// corresponding 8 byte zip64 extended information extra field.
enum
{
ZIP64_LOCAL_HEADER_OFFSET = 0xFFFFFFFF
};
uint32_t lLocalHeaderOffset{}; // relative offset of local header 4 bytes
};
#pragma pack(pop)
// this is the local file header that appears before the compressed data
// followed by:
// file name (variable size)
// extra field (variable size)
#pragma pack(push, 1)
struct LocalFileHeader
{
inline static constexpr uint32_t SIGNATURE = 0x04034b50;
uint32_t lSignature{}; // local file header signature 4 bytes (0x04034b50)
uint16_t nVersionNeeded{}; // version needed to extract 2 bytes
uint16_t nFlags{}; // general purpose bit flag 2 bytes
uint16_t nMethod{}; // compression method 2 bytes
uint16_t nLastModTime{}; // last mod file time 2 bytes
uint16_t nLastModDate{}; // last mod file date 2 bytes
DataDescriptor desc{};
uint16_t nFileNameLength{}; // file name length 2 bytes
uint16_t nExtraFieldLength{}; // extra field length 2 bytes
};
#pragma pack(pop)
// compression methods
enum EExtraHeaderID : uint32_t
{
EXTRA_ZIP64 = 0x0001, // ZIP64 extended information extra field
EXTRA_NTFS = 0x000a, // NTFS
};
//////////////////////////////////////////////////////////////////////////
// header1+data1 + header2+data2 . . .
// Each header should consist of:
// Header ID - 2 bytes
// Data Size - 2 bytes
struct ExtraFieldHeader
{
uint16_t headerID{};
uint16_t dataSize{};
};
struct ExtraNTFSHeader
{
uint32_t reserved{}; // 4 bytes.
uint16_t attrTag{}; // 2 bytes.
uint16_t attrSize{}; // 2 bytes.
};
}
namespace AZStd
{
// Specialize AZStd::endian_swap function for ZipFile structures
inline void endian_swap(AZ::IO::ZipFile::CDREnd& data)
{
AZStd::endian_swap(data.lSignature);
AZStd::endian_swap(data.nDisk);
AZStd::endian_swap(data.nCDRStartDisk);
AZStd::endian_swap(data.numEntriesOnDisk);
AZStd::endian_swap(data.numEntriesTotal);
AZStd::endian_swap(data.lCDRSize);
AZStd::endian_swap(data.lCDROffset);
AZStd::endian_swap(data.nCommentLength);
}
inline void endian_swap(AZ::IO::ZipFile::DataDescriptor& data)
{
AZStd::endian_swap(data.lCRC32);
AZStd::endian_swap(data.lSizeCompressed);
AZStd::endian_swap(data.lSizeUncompressed);
}
inline void endian_swap(AZ::IO::ZipFile::CDRFileHeader& data)
{
AZStd::endian_swap(data.lSignature);
AZStd::endian_swap(data.nVersionMadeBy);
AZStd::endian_swap(data.nVersionNeeded);
AZStd::endian_swap(data.nFlags);
AZStd::endian_swap(data.nMethod);
AZStd::endian_swap(data.nLastModTime);
AZStd::endian_swap(data.nLastModDate);
AZStd::endian_swap(data.desc);
AZStd::endian_swap(data.nFileNameLength);
AZStd::endian_swap(data.nExtraFieldLength);
AZStd::endian_swap(data.nFileCommentLength);
AZStd::endian_swap(data.nDiskNumberStart);
AZStd::endian_swap(data.nAttrInternal);
AZStd::endian_swap(data.lAttrExternal);
}
inline void endian_swap(AZ::IO::ZipFile::LocalFileHeader& data)
{
AZStd::endian_swap(data.lSignature);
AZStd::endian_swap(data.nVersionNeeded);
AZStd::endian_swap(data.nFlags);
AZStd::endian_swap(data.nMethod);
AZStd::endian_swap(data.nLastModTime);
AZStd::endian_swap(data.nLastModDate);
AZStd::endian_swap(data.desc);
AZStd::endian_swap(data.nFileNameLength);
AZStd::endian_swap(data.nExtraFieldLength);
}
}
@@ -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 <AzFramework/Asset/AssetBundleManifest.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzFramework
{
const int AssetBundleManifest::CurrentBundleVersion = 2;
const char AssetBundleManifest::s_manifestFileName[] = "manifest.xml";
void AssetBundleManifest::ReflectSerialize(AZ::SerializeContext* serializeContext)
{
if (serializeContext)
{
serializeContext->Class<AssetBundleManifest>()
->Version(2)
->Field("BundleVersion", &AssetBundleManifest::m_bundleVersion)
->Field("CatalogName", &AssetBundleManifest::m_catalogName)
->Field("DependentBundleNames", &AssetBundleManifest::m_depedendentBundleNames)
->Field("LevelNames", &AssetBundleManifest::m_levelDirs);
}
}
} // namespace AzFramework
@@ -0,0 +1,57 @@
/*
* 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/Serialization/SerializeContext.h>
namespace AZ
{
class SerializeContext;
}
namespace AzFramework
{
// Class to describe metadata about an AssetBundle in Lumberyard
class AssetBundleManifest
{
public:
AZ_TYPE_INFO(AssetBundleManifest, "{8628A669-7B19-4C48-A7CB-F670CC9586FD}");
AZ_CLASS_ALLOCATOR(AssetBundleManifest, AZ::SystemAllocator, 0);
AssetBundleManifest() = default;
static void ReflectSerialize(AZ::SerializeContext* serializeContext);
// Each AssetBundle contains a Catalog file with a unique name used to describe the list
// of files within the AssetBundle in order to update the Asset Registry at runtime when
// loading the bundle
const AZStd::string& GetCatalogName() const { return m_catalogName; }
AZStd::vector<AZStd::string> GetDependentBundleNames() const { return m_depedendentBundleNames; }
AZStd::vector<AZStd::string> GetLevelDirectories() const { return m_levelDirs; }
int GetBundleVersion() const { return m_bundleVersion; }
void SetCatalogName(const AZStd::string& catalogName) { m_catalogName = catalogName; }
void SetBundleVersion(int bundleVersion) { m_bundleVersion = bundleVersion; }
void SetDependentBundleNames(const AZStd::vector<AZStd::string>& dependentBundleNames) { m_depedendentBundleNames = dependentBundleNames; }
void SetLevelsDirectory(const AZStd::vector<AZStd::string>& levelDirs) { m_levelDirs = levelDirs; }
static const char s_manifestFileName[];
static const int CurrentBundleVersion;
private:
AZStd::string m_catalogName;
AZStd::vector<AZStd::string> m_depedendentBundleNames;
AZStd::vector<AZStd::string> m_levelDirs;
int m_bundleVersion = CurrentBundleVersion;
};
} // namespace AzFramework
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,159 @@
/*
* 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/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Asset/NetworkAssetNotification_private.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
namespace AzFramework
{
class AssetRegistry;
class AssetBundleManifest;
/*
* An asset catalog keeps a registry of asset data information (file name, size, type, etc)
*/
class AssetCatalog
: public AZ::Data::AssetCatalog
, public AZ::Data::AssetCatalogRequestBus::Handler
, private AssetSystem::NetworkAssetUpdateInterface
{
public:
AZ_TYPE_INFO(AssetCatalog, "{D80BAFE6-0391-4D40-9C76-1E63D2D7C64F}");
AZ_CLASS_ALLOCATOR(AssetCatalog, AZ::SystemAllocator, 0)
AssetCatalog();
~AssetCatalog() override;
explicit AssetCatalog(bool useDirectConnections);
/// Wipe and reset the catalog.
void Reset();
/// Initialize the catalog for the current asset root.
/// \param catalogRegistryFile - Optionally a previously saved catalog from which to load, rather than scanning.
void InitializeCatalog(const char* catalogRegistryFile = nullptr);
//////////////////////////////////////////////////////////////////////////
// AssetCatalogRequestBus
void EnableCatalogForAsset(const AZ::Data::AssetType& assetType) override;
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override;
AZ::Data::AssetType GetAssetTypeByDisplayName(const AZStd::string_view displayName) override;
void DisableCatalog() override;
void StartMonitoringAssets() override;
void StopMonitoringAssets() override;
bool LoadCatalog(const char* catalogRegistryFile) override;
void ClearCatalog() override;
bool SaveCatalog(const char* catalogRegistryFile) override;
static bool SaveCatalog(const char* catalogRegistryFile, AzFramework::AssetRegistry* catalogRegistry);
bool AddDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog) override;
bool InsertDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog, size_t slotNum) override;
bool InsertDeltaCatalogBefore(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog, AZStd::shared_ptr<AzFramework::AssetRegistry> afterDeltaCatalog) override;
bool RemoveDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog) override;
static bool SaveAssetBundleManifest(const char* assetBundleManifestFile, AzFramework::AssetBundleManifest* bundleManifest);
bool CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector<AZStd::string>& levelDirs) override;
bool CreateDeltaCatalog(const AZStd::vector<AZStd::string>& files, const AZStd::string& filePath) override;
void AddExtension(const char* extension) override;
void RegisterAsset(const AZ::Data::AssetId& id, AZ::Data::AssetInfo& info) override;
void UnregisterAsset(const AZ::Data::AssetId& id) override;
AZStd::string GetAssetPathById(const AZ::Data::AssetId& id) override;
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override;
AZ::Data::AssetId GetAssetIdByPath(const char* path, const AZ::Data::AssetType& typeToRegister, bool autoRegisterIfNotFound) override;
AZStd::vector<AZStd::string> GetRegisteredAssetPaths() override;
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetDirectProductDependencies(const AZ::Data::AssetId& asset) override;
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetAllProductDependencies(const AZ::Data::AssetId& asset) override;
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetAllProductDependenciesFilter(const AZ::Data::AssetId& id, const AZStd::unordered_set<AZ::Data::AssetId>& exclusionList, const AZStd::vector<AZStd::string>& wildcardPatternExclusionList) override;
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetLoadBehaviorProductDependencies(const AZ::Data::AssetId& id, AZStd::unordered_set<AZ::Data::AssetId>& noloadSet,
AZ::Data::PreloadAssetListType& preloadAssetList) override;
bool DoesAssetIdMatchWildcardPattern(const AZ::Data::AssetId& assetId, const AZStd::string& wildcardPattern) override;
AZ::Data::AssetId GenerateAssetIdTEMP(const char* /*path*/) override;
void EnumerateAssets(BeginAssetEnumerationCB beginCB, AssetEnumerationCB enumerateCB, EndAssetEnumerationCB endCB) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::Data::AssetCatalog
AZ::Data::AssetStreamInfo GetStreamInfoForLoad(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType) override;
AZ::Data::AssetStreamInfo GetStreamInfoForSave(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// NetworkAssetUpdateInterface
void AssetChanged(AzFramework::AssetSystem::AssetNotificationMessage message) override;
void AssetRemoved(AzFramework::AssetSystem::AssetNotificationMessage message) override;
//////////////////////////////////////////////////////////////////////////
static AZStd::shared_ptr<AzFramework::AssetRegistry> LoadCatalogFromFile(const char* catalogFile);
protected:
/// \return true if the specified filename's extension matches those handled
/// by the catalog.
bool IsTrackedAssetType(const char* assetFilename) const;
/// Helper function that adds all of searchAssetId's dependencies to the depedencySet/List (leaving out ones that are already in the list)
void AddAssetDependencies(const AZ::Data::AssetId& searchAssetId, AZStd::unordered_set<AZ::Data::AssetId>& assetSet, AZStd::vector<AZ::Data::ProductDependency>& dependencyList, const
AZStd::unordered_set<AZ::Data::AssetId>& exclusionList,
const AZStd::vector<AZStd::string>& wildcardPatternExclusionList,
AZ::Data::PreloadAssetListType& preloadAssetList) const;
// Called by LoadCatalog to load the base
bool LoadBaseCatalogInternal();
// Called by RemoveDeltaCatalog - reassemble our registry from loaded catalog files
bool ReloadCatalogs();
// Add a specific name to the list and check for duplicates
void AddCatalogEntry(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog);
// Apply specific catalog to registry by name
bool ApplyDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog);
// Insert new catalog by position
void InsertCatalogEntry(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog, size_t catalogIndex);
// Clear just the registry
void ResetRegistry();
AZStd::string GetAssetPathByIdInternal(const AZ::Data::AssetId& id) const;
AZ::Data::AssetInfo GetAssetInfoByIdInternal(const AZ::Data::AssetId& id) const;
bool DoesAssetIdMatchWildcardPatternInternal(const AZ::Data::AssetId& assetId, const AZStd::string& wildcardPattern) const;
private:
AZStd::atomic_bool m_shutdownThreadSignal; ///< Signals the monitoring thread to stop.
AZStd::thread m_thread; ///< Monitoring thread
AZStd::string m_assetRoot; ///< Asset root the catalog is bound to.
AZStd::unordered_set<AZStd::string> m_extensions; ///< Valid asset extensions.
mutable AZStd::recursive_mutex m_registryMutex;
AZStd::unique_ptr<AssetRegistry> m_registry;
AZStd::string m_pathBuffer;
mutable AZStd::recursive_mutex m_baseCatalogNameMutex;
AZStd::string m_baseCatalogName;
mutable AZStd::recursive_mutex m_deltaCatalogMutex;
AZStd::vector<AZStd::shared_ptr<AzFramework::AssetRegistry>> m_deltaCatalogList;
//! When managed by a PlatformAddressedAssetCatalogManager let it handle communications
bool m_directConnections{ true };
//! First time initialization when connected to tools will need to allow for updates on top of the catalog to be processed
bool m_initialized{ false };
//! Track when we're currently monitoring assets
bool m_monitoring{ false };
AZStd::mutex m_monitorMutex;
};
} // namespace AzFramework
@@ -0,0 +1,78 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_ASSETCATALOGBUS_H
#define AZFRAMEWORK_ASSETCATALOGBUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Asset/AssetCommon.h>
namespace AZ::Data
{
class AssetInfo;
}
namespace AzFramework
{
/**
* Event bus for asset catalogs.
*/
class AssetCatalogEvents
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
//////////////////////////////////////////////////////////////////////////
/// Game/runtime notification for when catalog is loaded and it's possible to resolve asset Ids.
virtual void OnCatalogLoaded(const char* /*catalogFile*/) {}
/// Notifies listeners that an existing asset has changed on disk (reload has not yet occurred).
virtual void OnCatalogAssetChanged(const AZ::Data::AssetId& /*assetId*/) {}
/// Notifies listeners that a new asset has been discovered.
virtual void OnCatalogAssetAdded(const AZ::Data::AssetId& /*assetId*/) {}
/// Notifies listeners that an asset has been removed. This event occurs after the asset has been removed from the catalog.
/// assetInfo contains the catalog info from before the asset was removed.
virtual void OnCatalogAssetRemoved(const AZ::Data::AssetId& /*assetId*/, const AZ::Data::AssetInfo& /*assetInfo*/) {}
};
using AssetCatalogEventBus = AZ::EBus<AssetCatalogEvents>;
/**
* Event bus for legacy file-based asset changes. New code should use the AssetCatalogEventBus.
*/
class LegacyAssetEvents
: public AZ::EBusTraits
{
public:
///////////////////////////////////////////////////////////////////////
static const bool EnableEventQueue = true; // enabled queued events, asset msgs come from any thread
using EventQueueMutexType = AZStd::mutex;
using BusIdType = AZ::u32; // bus is addressed by CRC of extension
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
///////////////////////////////////////////////////////////////////////
/// Notifies listeners that a file changed
virtual void OnFileChanged(AZStd::string /*assetPath*/) {}
virtual void OnFileRemoved(AZStd::string /*assetPath*/) {}
};
using LegacyAssetEventBus = AZ::EBus<LegacyAssetEvents>;
} // namespace AzFramework
#pragma once
#endif // AZFRAMEWORK_ASSETCATALOGBUS_H
@@ -0,0 +1,104 @@
/*
* 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 "AssetCatalogComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetCatalog.h>
namespace AzFramework
{
//=========================================================================
// DataVersionConverter
//=========================================================================
bool AssetCatalogComponentDataVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
(void)context;
if (classElement.GetVersion() == 1)
{
// Old asset path field is gone.
const int assetRootIndex = classElement.FindElement(AZ_CRC("AssetRoot", 0x3195232d));
if (assetRootIndex >= 0)
{
classElement.RemoveElement(assetRootIndex);
}
}
return true;
}
//=========================================================================
// Reflect
//=========================================================================
void AssetCatalogComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AssetCatalogComponent, AZ::Component>()
->Version(3, &AssetCatalogComponentDataVersionConverter)
->Field("CatalogRegistryFile", &AssetCatalogComponent::m_registryFile)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<AssetCatalogComponent>(
"Asset Catalog", "Maintains a catalog of assets")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Engine")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
}
}
//=========================================================================
// AssetCatalogComponent ctor
//=========================================================================
AssetCatalogComponent::AssetCatalogComponent()
{
}
//=========================================================================
// AssetCatalogComponent dtor
//=========================================================================
AssetCatalogComponent::~AssetCatalogComponent()
{
}
//=========================================================================
// Init
//=========================================================================
void AssetCatalogComponent::Init()
{
}
//=========================================================================
// Activate
//=========================================================================
void AssetCatalogComponent::Activate()
{
m_catalog.reset(aznew AssetCatalog());
}
//=========================================================================
// Deactivate
//=========================================================================
void AssetCatalogComponent::Deactivate()
{
m_catalog.reset();
AzFramework::LegacyAssetEventBus::ClearQueuedEvents();
}
} // namespace AzFramework
@@ -0,0 +1,69 @@
/*
* 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/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
class AssetCatalog;
class AssetCatalogComponent
: public AZ::Component
{
public:
AZ_COMPONENT(AssetCatalogComponent, "{35D9C27B-CD07-4333-89BB-3D077444E10A}");
AssetCatalogComponent();
~AssetCatalogComponent() override;
void Init() override;
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ScannerCatalogService", 0x74e0c82c));
provided.push_back(AZ_CRC("AssetCatalogService", 0xc68ffc57));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ScannerCatalogService", 0x74e0c82c));
incompatible.push_back(AZ_CRC("AssetCatalogService", 0xc68ffc57));
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("AssetDatabaseService", 0x3abf5601));
}
protected:
AssetCatalogComponent(const AssetCatalogComponent&) = delete;
AZStd::unique_ptr<AssetCatalog> m_catalog;
AZStd::string m_registryFile;
};
} // namespace AzFramework
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,249 @@
/*
* 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 <AzFramework/Asset/AssetRegistry.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/IO/SystemFile.h> // for max path
namespace AssetRegistryInternal
{
// to prevent people from shooting themselves in the foot here we are going to normalize the path.
// since sha1 is sensitive to both slash direction and case!
// note that this is not generating asset IDs, its merely creating UUIDs that map from
// asset path -> asset Id, so that storing asset path as strings is not necessary.
AZ::Uuid CreateUUIDForName(const char* name)
{
if (!name)
{
return AZ::Uuid::CreateNull();
}
// avoid allocating memory. Note that the input paths are expected to be relative paths
// from the root and thus should be much shorter than AZ_MAX_PATH_LEN
char tempBuffer[AZ_MAX_PATH_LEN] = { 0 };
tempBuffer[AZ_ARRAY_SIZE(tempBuffer) - 1] = 0;
// here we try to pass over the memory only once.
for (AZStd::size_t pos = 0; pos < AZ_ARRAY_SIZE(tempBuffer) - 1; ++pos)
{
char currentValue = name[pos];
if (!currentValue)
{
tempBuffer[pos] = 0;
break;
}
else if (currentValue == '\\')
{
tempBuffer[pos] = '/';
}
else
{
tempBuffer[pos] = (char)tolower(currentValue);
}
}
return AZ::Uuid::CreateName(tempBuffer);
}
}
namespace AzFramework
{
using namespace AssetRegistryInternal;
//=========================================================================
// AssetRegistry::Clear
//=========================================================================
void AssetRegistry::Clear()
{
m_assetIdToInfo = AssetIdToInfoMap();
m_assetPathToId = AssetPathToIdMap();
}
//=========================================================================
// AssetRegistry::ReflectSerialize
//=========================================================================
void AssetRegistry::ReflectSerialize(AZ::SerializeContext* serializeContext)
{
if (serializeContext)
{
serializeContext->Class<AZ::Data::AssetInfo>()
->Version(2)
->Field("assetId", &AZ::Data::AssetInfo::m_assetId)
->Field("relativePath", &AZ::Data::AssetInfo::m_relativePath)
->Field("sizeBytes", &AZ::Data::AssetInfo::m_sizeBytes)
->Field("assetType", &AZ::Data::AssetInfo::m_assetType);
serializeContext->Class<AZ::Data::ProductDependency>()
->Version(1)
->Field("assetId", &AZ::Data::ProductDependency::m_assetId)
->Field("flags", &AZ::Data::ProductDependency::m_flags);
serializeContext->Class<AssetRegistry>()
->Version(5)
->Field("m_assetIdToInfo", &AssetRegistry::m_assetIdToInfo)
->Field("m_assetPathToIdMap", &AssetRegistry::m_assetPathToId)
->Field("m_legacyAssetIdToRealAssetId", &AssetRegistry::m_legacyAssetIdToRealAssetId)
->Field("m_assetDependencies", &AssetRegistry::m_assetDependencies);
// note that the above m_assetPathToIdMap used to be called m_assetPathToId in prior serialization
// and m_assetPathToIdByUUID prior to that, so do not rename it to those more obvious fields in the future.
}
}
//=========================================================================
// AssetRegistry::RegisterAsset
//=========================================================================
void AssetRegistry::RegisterAsset(AZ::Data::AssetId id, const AZ::Data::AssetInfo& assetInfo)
{
// One day we'd like to remove the reverse lookup of name -> id since nothing should be recording asset names for purposes of logic or lookup.
// But for now, we still support legacy systems which store asset relative pathnames instead of storing asset ID's.
// to achieve this we have two maps
// one map which maps from [asset relative path] -> [Asset ID]
// one map which maps from [Asset ID] -> [AssetInfo struct]
// whats important to note is that the [asset relative path] map, for performance and memory purposes, uses the
// SHA1 hash of the [asset relative path] instead of storing strings.
// this makes it take a fixed amount of space and a fixed amount of time to do a lookup
// whats also important to note is that sometimes, there are aliases for assets due to legacy systems.
// in other words, two different AssetID's can map to the same file info about an asset, because they were known via a different ID-assignment system in the past.
// Its not worth worrying about the case here where two source files (blah.png, blah.tif) output the same asset (blah.dds)
// because either way, there is an asset there, so it has to be added to both maps. there will just be two entries
// in the [Asset ID] -> [AssetInfo struct] that points at the same output file. Notifying the user that this has occurred
// should happen at a much higher level.
SetAssetIdByPath(assetInfo.m_relativePath.c_str(), id);
m_assetIdToInfo.insert_key(id).first->second = assetInfo;
}
//=========================================================================
// AssetRegistry::UnregisterAsset
//=========================================================================
void AssetRegistry::UnregisterAsset(AZ::Data::AssetId id)
{
// just as with RegisterAsset, its not worth worrying about the balance of these two maps.
// When you delete an asset (ie, its actual data is gone) it MUST be removed from the [Id] -> [AssetInfo] map
// and its irrelevant whether it gets removed from the [Path] -> [Id] map because that hash is only used
// to resolve Ids, and if that resolved Id points at a missing asset, the [Path] -> [Id] map will know that.
auto existingAsset = m_assetIdToInfo.find(id);
if (existingAsset != m_assetIdToInfo.end())
{
m_assetPathToId.erase(CreateUUIDForName(existingAsset->second.m_relativePath.c_str()));
}
m_assetIdToInfo.erase(id);
m_assetDependencies.erase(id);
}
void AssetRegistry::RegisterLegacyAssetMapping(const AZ::Data::AssetId& legacyId, const AZ::Data::AssetId& newId)
{
m_legacyAssetIdToRealAssetId[legacyId] = newId;
}
void AssetRegistry::UnregisterLegacyAssetMapping(const AZ::Data::AssetId& legacyId)
{
m_legacyAssetIdToRealAssetId.erase(legacyId);
}
void AssetRegistry::SetAssetDependencies(const AZ::Data::AssetId& id, const AZStd::vector<AZ::Data::ProductDependency>& dependencies)
{
m_assetDependencies[id] = dependencies;
}
void AssetRegistry::RegisterAssetDependency(const AZ::Data::AssetId& id, const AZ::Data::ProductDependency& dependency)
{
m_assetDependencies[id].push_back(dependency);
}
AZStd::vector<AZ::Data::ProductDependency> AssetRegistry::GetAssetDependencies(const AZ::Data::AssetId& id)
{
return m_assetDependencies[id];
}
AZ::Data::AssetId AssetRegistry::GetAssetIdByLegacyAssetId(const AZ::Data::AssetId& legacyAssetId) const
{
auto found = m_legacyAssetIdToRealAssetId.find(legacyAssetId);
if (found != m_legacyAssetIdToRealAssetId.end())
{
return found->second;
}
return AZ::Data::AssetId();
}
AzFramework::AssetRegistry::LegacyAssetIdToRealAssetIdMap AssetRegistry::GetLegacyMappingSubsetFromRealIds(const AZStd::vector<AZ::Data::AssetId>& realIds) const
{
LegacyAssetIdToRealAssetIdMap subset;
auto realIdsBeginItr = realIds.begin();
auto realIdsEndItr = realIds.end();
for (const auto& legacyToRealPair : m_legacyAssetIdToRealAssetId)
{
if (AZStd::find(realIdsBeginItr, realIdsEndItr, legacyToRealPair.second) != realIdsEndItr)
{
subset.insert(legacyToRealPair);
}
}
return subset;
}
AZ::Data::AssetId AssetRegistry::GetAssetIdByPath(const char* assetPath) const
{
if ((!assetPath) || (assetPath[0] == 0))
{
// the empty path has no asset ID.
return AZ::Data::AssetId();
}
auto entry = m_assetPathToId.find(CreateUUIDForName(assetPath));
if (entry != m_assetPathToId.end())
{
return entry->second;
}
return AZ::Data::AssetId();
}
void AssetRegistry::SetAssetIdByPath(const char* assetPath, const AZ::Data::AssetId& id)
{
AZ_Assert(assetPath, "Invalid asset path provided to SetAssetID!\n");
AZ_Assert(id.IsValid(), "Invalid asset id provided to SetAssetID!\n");
if ((!assetPath) || (!id.IsValid()))
{
return;
}
m_assetPathToId.insert_key(CreateUUIDForName(assetPath)).first->second = AZStd::move(id);
}
void AssetRegistry::AddRegistry(AZStd::shared_ptr<AssetRegistry> assetRegistry)
{
for (const auto& element : assetRegistry->m_assetIdToInfo)
{
m_assetIdToInfo[element.first] = element.second;
// remove dependency info that exists for this asset, as the change could have removed any dependenices this asset had.
m_assetDependencies.erase(element.first);
}
for (const auto& element : assetRegistry->m_assetDependencies)
{
m_assetDependencies[element.first] = element.second;
}
for (const auto& element : assetRegistry->m_assetPathToId)
{
m_assetPathToId[element.first] = element.second;
}
for (const auto& element : assetRegistry->m_legacyAssetIdToRealAssetId)
{
m_legacyAssetIdToRealAssetId[element.first] = element.second;
}
}
} // namespace AzFramework
@@ -0,0 +1,83 @@
/*
* 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/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
namespace AzFramework
{
/**
* Data storage for asset registry.
* Maintained separate to facilitate easy serialization to/from disk.
*/
class AssetRegistry
{
friend class AssetCatalog;
public:
AZ_TYPE_INFO(AssetRegistry, "{5DBC20D9-7143-48B3-ADEE-CCBD2FA6D443}");
AZ_CLASS_ALLOCATOR(AssetRegistry, AZ::SystemAllocator, 0);
AssetRegistry() = default;
void RegisterAsset(AZ::Data::AssetId id, const AZ::Data::AssetInfo& assetInfo);
void UnregisterAsset(AZ::Data::AssetId id);
void RegisterLegacyAssetMapping(const AZ::Data::AssetId& legacyId, const AZ::Data::AssetId& newId);
void UnregisterLegacyAssetMapping(const AZ::Data::AssetId& legacyId);
void SetAssetDependencies(const AZ::Data::AssetId& id, const AZStd::vector<AZ::Data::ProductDependency>& dependencies);
void RegisterAssetDependency(const AZ::Data::AssetId& id, const AZ::Data::ProductDependency& dependency);
AZStd::vector<AZ::Data::ProductDependency> GetAssetDependencies(const AZ::Data::AssetId& id);
//! LEGACY - do not use in new code unless interfacing with legacy systems.
//! All new systems should be referring to assets by ID/Type only and should not need to look up by path/
AZ::Data::AssetId GetAssetIdByPath(const char* assetPath) const;
using AssetIdToInfoMap = AZStd::unordered_map < AZ::Data::AssetId, AZ::Data::AssetInfo >;
AssetIdToInfoMap m_assetIdToInfo;
AZStd::unordered_map<AZ::Data::AssetId, AZStd::vector<AZ::Data::ProductDependency>> m_assetDependencies;
void Clear();
// see if the asset ID has been remapped to a new Id:
AZ::Data::AssetId GetAssetIdByLegacyAssetId(const AZ::Data::AssetId& legacyAssetId) const;
using LegacyAssetIdToRealAssetIdMap = AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::AssetId>;
LegacyAssetIdToRealAssetIdMap GetLegacyMappingSubsetFromRealIds(const AZStd::vector<AZ::Data::AssetId>& realIds) const;
static void ReflectSerialize(AZ::SerializeContext* serializeContext);
private:
// Add another registry to our existing registry data. Intended to be called by AssetCatalog::AddDeltaCatalog
void AddRegistry(AZStd::shared_ptr<AssetRegistry> assetRegistry);
// use these only through the legacy getters/setters above.
using AssetPathToIdMap = AZStd::unordered_map < AZ::Uuid, AZ::Data::AssetId >;
AssetPathToIdMap m_assetPathToId; // for legacy lookups only
LegacyAssetIdToRealAssetIdMap m_legacyAssetIdToRealAssetId; // for when we change the UUID-creation scheme
//! LEGACY - do not use in new code unless interfacing with legacy systems.
//! given an assetPath and AssetID, this stores it in the registry to use with the above GetAssetIdByPath function.
//! Called automatically by RegisterAsset.
void SetAssetIdByPath(const char* assetPath, const AZ::Data::AssetId& id);
};
} // namespace AzFramework
@@ -0,0 +1,48 @@
/*
* 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 <AzFramework/Asset/AssetSeedList.h>
namespace AzFramework
{
SeedInfo::SeedInfo(AZ::Data::AssetId assetId, PlatformFlags platformFlags, const AZStd::string& assetRelativePath, const AZStd::string& seedListFilePath)
: m_assetId(assetId)
, m_platformFlags(platformFlags)
, m_assetRelativePath(assetRelativePath)
, m_seedListFilePath(seedListFilePath)
{
}
void SeedInfo::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SeedInfo>()
->Version(2)
->Field("assetId", &SeedInfo::m_assetId)
->Field("platformFlags", &SeedInfo::m_platformFlags)
->Field("pathHint", &SeedInfo::m_assetRelativePath);
}
}
void AssetSeedListReflector::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AssetSeedListReflector>()
->Version(1)
->Field("assetList", &AssetSeedListReflector::m_fileInfoList);
}
}
} // namespace AzFramework
@@ -0,0 +1,48 @@
/*
* 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/Serialization/SerializeContext.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Platform/PlatformDefaults.h>
namespace AzFramework
{
struct SeedInfo
{
AZ_TYPE_INFO(SeedInfo, "{FACC3682-2ACA-4AA4-B85A-07AD276D18A0}");
SeedInfo() = default;
SeedInfo(AZ::Data::AssetId assetId, PlatformFlags platformFlags, const AZStd::string& path, const AZStd::string& seedListFilePath = AZStd::string());
static void Reflect(AZ::ReflectContext* context);
AZ::Data::AssetId m_assetId;
PlatformFlags m_platformFlags;
AZStd::string m_assetRelativePath;
AZStd::string m_seedListFilePath;
};
using AssetSeedList = AZStd::vector<SeedInfo>;
class AssetSeedListReflector
{
public:
AZ_TYPE_INFO(AssetSeedListReflector, "{26E389E4-087B-4C79-883F-7216181189BF}");
AZ_CLASS_ALLOCATOR(AssetSeedListReflector, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
AssetSeedList m_fileInfoList;
};
} // namespace AzFramework
@@ -0,0 +1,344 @@
/*
* 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/EBus/EBus.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/Math/Crc.h> // ensure that AZ_CRC is available to all users of this header
#include <AzFramework/Asset/AssetSystemTypes.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
namespace AzFramework
{
namespace AssetSystem
{
//! AssetSystemInfoBusTraits
//! This bus is for events that occur in the asset system in general, and has no address
class AssetSystemInfoNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // multi listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // single bus
using MutexType = AZStd::recursive_mutex;
virtual ~AssetSystemInfoNotifications() = default;
//! Notifies listeners that the Asset Processor has claimed a file in the cache for updating.
//! The absolute path is provided. This call will be followed by AssetFileReleased.
virtual void AssetFileClaimed(const AZStd::string& /*assetPath*/) {}
//! Notifies listeners that the Asset Processor has released a file in the cache it previously
// exclusively claimed with AssetFileClaim. The absolute path is provided.
virtual void AssetFileReleased(const AZStd::string& /*assetPath*/) {}
//! Notifies listeners the compilation of an asset has started.
virtual void AssetCompilationStarted(const AZStd::string& /*assetPath*/) {}
//! Notifies listeners the compilation of an asset has succeeded.
virtual void AssetCompilationSuccess(const AZStd::string& /*assetPath*/) {}
//! Notifies listeners the compilation of an asset has failed.
virtual void AssetCompilationFailed(const AZStd::string& /*assetPath*/) {}
//! Returns the number of assets in queue for processing.
virtual void CountOfAssetsInQueue(const int& /*count*/) {}
//! Notifies listeners an error has occurred in the asset system
virtual void OnError(AssetSystemErrors /*error*/) {}
};
//! Stores the settings needed to make a connection either to or from an AssetProcessor instance
struct ConnectionSettings
{
enum class ConnectionDirection : AZ::s64
{
ListenForConnectFromAssetProcessor,
ConnectToAssetProcessor
};
//! Name of the game project that is used for negotiating a connection with the AssetProcessor
//! The AssetProcessor needs to be processing Assets for the specified game project
//! (Can be queried from Settings Registry - "sys_game_folder")
AZStd::fixed_string<64> m_projectName;
//! The IP address to use either connect to or from the AssetProcessor
//! (Can be queried from Settings Registry - "remote_ip")
AZStd::fixed_string<32> m_assetProcessorIp{ "127.0.0.1" };
//! The token used to indicate the application is attempting to connect to an AssetProcessor
//! That was built from the same code branch
//! (Can be queried from Settings Registry - "assetProcessor_branch_token")
AZStd::fixed_string<32> m_branchToken;
//! The identifier that will be used for negotiating a connection with the AssetProcessor
AZStd::fixed_string<32> m_connectionIdentifier;
//! The asset platform to use when negotiating a connection with the Asset Processor
//! (Can be queried from Settings Registry - "assets")
AZStd::fixed_string<32> m_assetPlatform{ "pc" };
//! Determines if the connection should either be to the AssetProcessor
//! from this application or if this application should listen for a connection from
//! the AssetProcessor
//! (Can be queried from Settings Registry - "connect_to_remote")
ConnectionDirection m_connectionDirection{ ConnectionDirection::ConnectToAssetProcessor };
//! The port number to use either connect to or from the AssetProcessor
//! (Can be queried from Settings Registry - "remote_port")
AZ::u16 m_assetProcessorPort{ 45643 };
//! Timeout(units: seconds) to use when either connecting to an already launched AssetProcessor
//! or listening for a connection from the AssetProcessor
//! Defaults to 3 seconds
//! (Can be queried from Settings Registry - "connect_ap_timeout")
AZStd::chrono::duration<float> m_connectTimeout{ 3.0f };
//! Timeout(units: seconds) to use when launching a new instance of the AssetProcessor and attempting
//! to connect to that instance
//! Defaults to 15 seconds
//! (Can be queried from Settings Registry - "launch_ap_timeout")
AZStd::chrono::duration<float> m_launchTimeout{ 15.0f };
//! Timeout(units: seconds) that indicates how long to wait for the AssetProcessor to indicate
//! it is ready after successfully connecting
//! The AssetProcessor isn't ready until it processes all critical Assets so this timeout should be
//! adjusted based on myriad of factors
//! i.e How many critical assets a project contains?
//! Are the critical assets being processed using debug AssetBuilders
//! etc...
//! Defaults to 20 minutes
//! (Can be queried from Settings Registry - "wait_ap_ready_timeout")
AZStd::chrono::duration<float> m_waitForReadyTimeout{ 1200.0f };
// Callback which is invoked to output logging information during the connection attempt
using LoggingCallback = AZStd::function<void(AZStd::string_view)>;
LoggingCallback m_loggingCallback;
//! Attempt to Launch the AssetProcessor if connection fails
bool m_launchAssetProcessorOnFailedConnection{ true };
//! Indicates whether to wait until the AssetProcessor sends a response that it is ready
//! if a connection has been established
bool m_waitUntilAssetProcessorIsReady{ true };
//! If set the connection call will attempt to wait indefinitely until the
//! AssetProcessor sends back a failed negotiation message
//! (Can be queried from Settings Registry - "wait_for_connect")
bool m_waitForConnect{};
};
//! Convenience function which can be used to read the AssetProcessor connection settings
//! from the /Amazon/AzCore/Bootstrap section of the SettingsRegistry
bool ReadConnectionSettingsFromSettingsRegistry(ConnectionSettings& outputConnectionSettings);
//! Launch the asset processor
//! \return Whether or not the asset processor launched
bool LaunchAssetProcessor();
//! AssetSystemRequestBusTraits
//! This bus is for making requests to the asset system
class AssetSystemRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // single listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // single bus
using MutexType = AZStd::recursive_mutex;
static const bool LocklessDispatch = true; // no reason to block other threads when a thread is waiting for a response from AP.
virtual ~AssetSystemRequests() = default;
//! Function which can either start a connection to the AssetProcessor or...
//! start a connection thread that will listen for the Asset Processor to connect to the current application
//! If a connection with the AssetProcessor has been established, this method will check whether
//! the ConnectionSettings::m_waitUntilAssetProcessorIsReady is set
//! If it is not set, then it returns true
//! otherwise the SystemEvent queue is pumped while until the AssetProcessor send a ready respond
//! or the wait timeout is reached
//!
//! returns true if successfully connected and connection settings indicate that connection shouldn't wait
//! for the AssetProcessor to be ready.
//! Otherwise returns true if successfully connected and the AssetProcessor is ready
virtual bool EstablishAssetProcessorConnection(const ConnectionSettings& connectionSettings) = 0;
//! Wait until the asset processor is connected or timeout time is reached
//! \return Whether or not we are connected
virtual bool WaitUntilAssetProcessorConnected(AZStd::chrono::duration<float> timeout) = 0;
//! Wait until the asset processor is ready or timeout time is reached
//! \return Whether or not the asset processor is ready
virtual bool WaitUntilAssetProcessorReady(AZStd::chrono::duration<float> timeout) = 0;
//! Is the asset processor status ready
//! \return Whether or not the asset processor is reporting ready status
virtual bool AssetProcessorIsReady() = 0;
//! Are we connected to the asset processor
//! \return Whether or not we are connected
virtual bool ConnectedWithAssetProcessor() = 0;
//! Did the Negotiation with the asset processor fail
//! \return Whether or not negotiation failed with asset processor
virtual bool NegotiationWithAssetProcessorFailed() = 0;
//! Starts the disconnecting thread
virtual void StartDisconnectingAssetProcessor() = 0;
//! Is the asset processor connection in the disconnected state
//! \return true if in disconnected state false otherwise
virtual bool DisconnectedWithAssetProcessor() = 0;
//! Waits at most timeout time for the asset processor connection to go into the disconnected state
//! \return true if in disconnected state false otherwise
virtual bool WaitUntilAssetProcessorDisconnected(AZStd::chrono::duration<float> timeout) = 0;
/** CompileAssetSync
* Compile an asset synchronously. This will only return after compilation, and also escalates it so that it builds immediately.
* Note that the asset path will be heuristically matched like a search term, so things missing an extension or things that
* are just a folder name will cause all assets which match that search term to be escalated and compiled.
* PERFORMANCE WARNING: Only use the FlushIO version if you have just written an asset file you wish to immediately compile,
* potentially before the operating system's disk IO queue is finished writing it.
* It will force a flush of the OS file monitoring queue before considering the request.
**/
virtual AssetStatus CompileAssetSync(const AZStd::string& assetPath) = 0;
virtual AssetStatus CompileAssetSync_FlushIO(const AZStd::string& assetPath) = 0;
virtual AssetStatus CompileAssetSyncById(const AZ::Data::AssetId& assetId) = 0;
virtual AssetStatus CompileAssetSyncById_FlushIO(const AZ::Data::AssetId& assetId) = 0;
/** GetAssetStatusByUuid
* Retrieve the status of an asset synchronously and also escalate it so that it builds sooner than others that are not
* escalated. If possible, prefer this function over the string-based version below.
* PERFORMANCE WARNING: Only use the FlushIO version if you have just written an asset file you wish to immediately compile,
* potentially before the operating system's disk IO queue is finished writing it.
* It will force a flush of the OS file monitoring queue before considering the request.
**/
virtual AssetStatus GetAssetStatusById(const AZ::Data::AssetId& assetId) = 0;
virtual AssetStatus GetAssetStatusById_FlushIO(const AZ::Data::AssetId& assetId) = 0;
/** GetAssetStatus
* Retrieve the status of an asset synchronously and also escalate it so that it builds sooner than others that are not escalated.
* @param assetPath - a relpath to a product in the cache, or a relpath to a source file, or a full path to either
* PERFORMANCE WARNING: Only use the FlushIO version if you have just written an asset file you wish to immediately query the status of,
* potentially before the operating system's disk IO queue is finished writing it.
* It will force a flush of the OS file monitoring queue before considering the request.
**/
virtual AssetStatus GetAssetStatus(const AZStd::string& assetPath) = 0;
virtual AssetStatus GetAssetStatus_FlushIO(const AZStd::string& assetPath) = 0;
/** GetAssetStatusSearchType
* Retrieve the status of an asset synchronously and also escalate it so that it builds sooner than others that are not escalated.
* @param searchTerm - provides a string parameter used for searching. The use of this parameter depends on searchType.
* @param searchType - indicates which type of search to perform, and how to use searchTerm
* RequestAssetStatus::SearchType::Default: Same as GetAssetStatus(). searchTerm is a relpath to a product in the cache,
* or a relpath to a source file, or a full path to either.
* RequestAssetStatus::SearchType::Exact: searchTerm is an exact path to a source data file.
* (see RequestAssetStatus::SearchType for more)
* PERFORMANCE WARNING: Only use the FlushIO version if you have just written an asset file you wish to immediately query the status of,
* potentially before the operating system's disk IO queue is finished writing it.
* It will force a flush of the OS file monitoring queue before considering the request.
**/
virtual AssetStatus GetAssetStatusSearchType(const AZStd::string& searchTerm, int searchType) = 0;
virtual AssetStatus GetAssetStatusSearchType_FlushIO(const AZStd::string& searchTerm, int searchType) = 0;
/** Request that a particular asset be escalated to the top of the build queue, by uuid
* This is an async request - the return value only indicates whether it was sent, not whether it escalated or was found.
* Note that the Uuid of an asset is the Uuid of its source file. If you have an AssetId field, this is the m_uuid part
* inside the AssetId, since that refers to the source file that produced the asset.
* @param assetUuid - the uuid to look up.
* note that this request always flushes IO (on the AssetProcessor side), but you don't pay for it in the caller
* process since it is a fire-and-forget message. This means its the fastest possible way to reliably escalate an asset by UUID
**/
virtual bool EscalateAssetByUuid(const AZ::Uuid& assetUuid) = 0;
/** EscalateAssetBySearchTerm
* Request that a particular asset be escalated to the top of the build queue, by "search term" (ie, source file name)
* This is an async request - the return value only indicates whether it was sent, not whether it escalated or was found.
* Search terms can be:
* Source File Names
* fragments of source file names
* Folder Names or pieces of folder names
* Product file names
* fragments of product file names
* The asset processor will find the closest match and escalate it. So for example if you request escalation on
* "mything.dds" and no such SOURCE FILE exists, it may match mything.fbx heuristically after giving up on the dds.
* If possible, use the above EscalateAsset with the Uuid, which does not require a heuristic match, or use
* the source file name (relative or absolute) as the input, instead of trying to work with product names.
* @param searchTerm - see above
*
* note that this request always flushes IO (on the AssetProcessor side), but you don't pay for it in the caller
* process since it is a fire-and-forget message. This means its the fastest possible way to reliably escalate an asset by name
**/
virtual bool EscalateAssetBySearchTerm(AZStd::string_view searchTerm) = 0;
//! Show the AssetProcessor App
virtual void ShowAssetProcessor() = 0;
//! Show an asset in the AssetProcessor App
virtual void ShowInAssetProcessor(const AZStd::string& assetPath) = 0;
/** Returns the number of unresolved AssetId and path references for the given asset.
* These are product assets that the given asset refers to which are not yet known by the Asset Processor.
* This API can be used to determine if a given asset can safely be loaded and have its asset references resolve successfully.
* @param assetId - Asset to lookup
* @param unresolvedAssetIdReferences - number of AssetId-based references which are unresolved
* @param unresolvedPathReferences - number of path-based references which are unresolved. This count excludes wildcard references which are never resolved
**/
virtual void GetUnresolvedProductReferences(AZ::Data::AssetId assetId, AZ::u32& unresolvedAssetIdReferences, AZ::u32& unresolvedPathReferences) = 0;
//! Compute the ping time between this client and the Asset Processor that's actually handling our requests (proxy relaying is included in the time)
virtual float GetAssetProcessorPingTimeMilliseconds() = 0;
//! Saves the catalog synchronously
virtual bool SaveCatalog() = 0;
};
//! AssetSystemNegotiationBusTraits
//! This bus is for events that occur during negotiation
class AssetSystemConnectionNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // multi listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // single bus
virtual ~AssetSystemConnectionNotifications() = default;
//! Notifies listeners negotiation with Asset Processor failed
virtual void NegotiationFailed() {};
//! Notifies listeners that connection to the Asset Processor failed
virtual void ConnectionFailed() {};
};
namespace ConnectionIdentifiers
{
static const char* Editor = "EDITOR";
static const char* Game = "GAME";
}
//! AssetSystemStatusBusTraits
//! This bus is for AssetSystem status change
class AssetSystemStatus
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; // multi listener
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // single bus
virtual ~AssetSystemStatus() = default;
//! Notifies listeners Asset System turns available
virtual void AssetSystemAvailable() {}
//! Notifies listeners Asset System turns not available
virtual void AssetSystemUnavailable() {}
};
} // namespace AssetSystem
/**
* AssetSystemBus removed - if you have a system which was using it
* use AssetCatalogEventBus for asset updates
*/
using AssetSystemInfoBus = AZ::EBus<AssetSystem::AssetSystemInfoNotifications>;
using AssetSystemRequestBus = AZ::EBus<AssetSystem::AssetSystemRequests>;
using AssetSystemConnectionNotificationsBus = AZ::EBus<AssetSystem::AssetSystemConnectionNotifications>;
using AssetSystemStatusBus = AZ::EBus<AssetSystem::AssetSystemStatus>;
} // namespace AzFramework
@@ -0,0 +1,871 @@
/*
* 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 <AzFramework/Asset/AssetSystemComponent.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/IStreamer.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzFramework/Asset/AssetSeedList.h>
#include <AzFramework/Asset/NetworkAssetNotification_private.h>
#include <AzFramework/Asset/Benchmark/BenchmarkCommands.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
namespace AzFramework
{
namespace AssetBenchmark
{
// These are specifically registered here instead of in BenchmarkCommands.cpp to avoid dead code stripping.
// If BenchmarkCommands.cpp only contained the functions and the calls to AZ_CONSOLEFREEFUNC, there would be
// no external references into that compilation unit, which signals the linker to remove the entire file.
AZ_CONSOLEFREEFUNC(BenchmarkLoadAssetList, AZ::ConsoleFunctorFlags::Null, "Time the loading of all the listed asset names. "
"This will load any assets previously added via 'BenchmarkAddAssetToList' as well as any directly listed with this command.");
AZ_CONSOLEFREEFUNC(BenchmarkClearAssetList, AZ::ConsoleFunctorFlags::Null,
"Clear the list of assets to load with 'BenchmarkLoadAssetList'");
AZ_CONSOLEFREEFUNC(BenchmarkAddAssetsToList, AZ::ConsoleFunctorFlags::Null,
"Add asset(s) to the list of assets to load with 'BenchmarkLoadAssetList'");
AZ_CONSOLEFREEFUNC(BenchmarkLoadAllAssets, AZ::ConsoleFunctorFlags::Null, "Time the loading of all assets in the catalog");
AZ_CONSOLEFREEFUNC(BenchmarkLoadAllAssetsSynchronous, AZ::ConsoleFunctorFlags::Null,
"Time the loading of all assets in the catalog synchronously");
}
namespace AssetSystem
{
void OnAssetSystemMessage(unsigned int /*typeId*/, const void* buffer, unsigned int bufferSize, AZ::SerializeContext* context)
{
AssetNotificationMessage message;
// note that we forbid asset loading and we set STRICT mode. These messages are all the kind of message that is supposed to be transmitted between the
// same version of software, and are created at runtime, not loaded from disk, so they should not contain errors - if they do, it requires investigation.
if (!AZ::Utils::LoadObjectFromBufferInPlace(buffer, bufferSize, message, context, AZ::ObjectStream::FilterDescriptor(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_STRICT)))
{
AZ_WarningOnce("AssetSystem", false, "AssetNotificationMessage received but unable to deserialize it. Is AssetProcessor.exe up to date?");
return;
}
if (message.m_data.length() > AZ_MAX_PATH_LEN)
{
auto maxPath = message.m_data.substr(0, AZ_MAX_PATH_LEN - 1);
AZ_Warning("AssetSystem", false, "HotUpdate: filename too long(%zd) : %s...", bufferSize, maxPath.c_str());
return;
}
switch (message.m_type)
{
case AssetNotificationMessage::AssetChanged:
{
// Used only to communicate to AssetCatalogs - no other system should rely on this
// Instead listen to AssetCatalogEventBus::OnAssetChanged
// This is a DIRECT call so that the catalog can update itself immediately so that it maintains as accurate a view of the current state of assets as possible.
// Attempting to queue this on the main thread has led to issues previously where systems start receiving
// asset change/remove notifications before all of the network catalog updates have been applied and start querying the state of other assets which haven't been updated yet.
AzFramework::AssetSystem::NetworkAssetUpdateInterface* notificationInterface = AZ::Interface<AzFramework::AssetSystem::NetworkAssetUpdateInterface>::Get();
if (notificationInterface)
{
notificationInterface->AssetChanged(message);
}
}
break;
case AssetNotificationMessage::AssetRemoved:
{
// Used only to communicate to AssetCatalogs - no other system should rely on this
// Instead listen to AssetCatalogEventBus::OnAssetRemoved
// This is a DIRECT call so that the catalog can update itself immediately so that it maintains as accurate a view of the current state of assets as possible.
// Attempting to queue this on the main thread has led to issues previously where systems start receiving
// asset change/remove notifications before all of the network catalog updates have been applied and start querying the state of other assets which haven't been updated yet.
AzFramework::AssetSystem::NetworkAssetUpdateInterface* notificationInterface = AZ::Interface<AzFramework::AssetSystem::NetworkAssetUpdateInterface>::Get();
if (notificationInterface)
{
notificationInterface->AssetRemoved(message);
}
}
break;
case AssetNotificationMessage::JobFileClaimed:
{
auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
if (streamer)
{
// The Asset Processor is about to write a product file that could have been loaded through AZ::IO::Streamer so
// flush it from any caches so stale data isn't used on reload and to make sure any file handles are released.
streamer->QueueRequest(streamer->FlushCache(message.m_data));
}
AssetSystemInfoBus::Broadcast(&AssetSystemInfoBus::Events::AssetFileClaimed, message.m_data);
}
break;
case AssetNotificationMessage::JobFileReleased:
{
AssetSystemInfoBus::Broadcast(&AssetSystemInfoBus::Events::AssetFileReleased, message.m_data);
}
break;
case AssetNotificationMessage::JobStarted:
{
AssetSystemInfoBus::Broadcast(&AssetSystemInfoBus::Events::AssetCompilationStarted, message.m_data);
}
break;
case AssetNotificationMessage::JobCompleted:
{
AssetSystemInfoBus::Broadcast(&AssetSystemInfoBus::Events::AssetCompilationSuccess, message.m_data);
}
break;
case AssetNotificationMessage::JobFailed:
{
AssetSystemInfoBus::Broadcast(&AssetSystemInfoBus::Events::AssetCompilationFailed, message.m_data);
}
break;
case AssetNotificationMessage::JobCount:
{
int numberOfAssets = atoi(message.m_data.c_str());
AssetSystemInfoBus::Broadcast(&AssetSystemInfoBus::Events::CountOfAssetsInQueue, numberOfAssets);
}
break;
default:
AZ_WarningOnce("AssetSystem", false, "Unknown AssetNotificationMessage type received from network. Is AssetProcessor.exe up to date?");
break;
}
}
void AssetSystemComponent::Init()
{
m_socketConn.reset(new AssetProcessorConnection());
}
void AssetSystemComponent::Activate()
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
EnableSocketConnection();
m_cbHandle = m_socketConn->AddMessageHandler(AZ_CRC("AssetProcessorManager::AssetNotification", 0xd6191df5),
[context](unsigned int typeId, unsigned int /*serial*/, const void* data, unsigned int dataLength)
{
if (dataLength)
{
OnAssetSystemMessage(typeId, data, dataLength, context);
}
});
AssetSystemRequestBus::Handler::BusConnect();
AZ::SystemTickBus::Handler::BusConnect();
AssetSystemStatusBus::Broadcast(&AssetSystemStatusBus::Events::AssetSystemAvailable);
}
void AssetSystemComponent::Deactivate()
{
AssetSystemStatusBus::Broadcast(&AssetSystemStatusBus::Events::AssetSystemUnavailable);
AZ::SystemTickBus::Handler::BusDisconnect();
AssetSystemRequestBus::Handler::BusDisconnect();
m_socketConn->RemoveMessageHandler(AZ_CRC("AssetProcessorManager::AssetNotification", 0xd6191df5), m_cbHandle);
m_socketConn->Disconnect(true);
DisableSocketConnection();
}
void AssetSystemComponent::Reflect(AZ::ReflectContext* context)
{
NegotiationMessage::Reflect(context);
BaseAssetProcessorMessage::Reflect(context);
RequestAssetStatus::Reflect(context);
RequestEscalateAsset::Reflect(context);
ResponseAssetProcessorStatus::Reflect(context);
RequestAssetProcessorStatus::Reflect(context);
ResponseAssetStatus::Reflect(context);
RequestPing::Reflect(context);
ResponsePing::Reflect(context);
// Requests
GetUnresolvedDependencyCountsRequest::Reflect(context);
GetRelativeProductPathFromFullSourceOrProductPathRequest::Reflect(context);
GetFullSourcePathFromRelativeProductPathRequest::Reflect(context);
SourceAssetInfoRequest::Reflect(context);
AssetInfoRequest::Reflect(context);
AssetDependencyInfoRequest::Reflect(context);
RegisterSourceAssetRequest::Reflect(context);
UnregisterSourceAssetRequest::Reflect(context);
ShowAssetProcessorRequest::Reflect(context);
ShowAssetInAssetProcessorRequest::Reflect(context);
FileOpenRequest::Reflect(context);
FileCloseRequest::Reflect(context);
FileReadRequest::Reflect(context);
FileWriteRequest::Reflect(context);
FileTellRequest::Reflect(context);
FileSeekRequest::Reflect(context);
FileIsReadOnlyRequest::Reflect(context);
PathIsDirectoryRequest::Reflect(context);
FileSizeRequest::Reflect(context);
FileModTimeRequest::Reflect(context);
FileExistsRequest::Reflect(context);
FileFlushRequest::Reflect(context);
PathCreateRequest::Reflect(context);
PathDestroyRequest::Reflect(context);
FileRemoveRequest::Reflect(context);
FileCopyRequest::Reflect(context);
FileRenameRequest::Reflect(context);
FindFilesRequest::Reflect(context);
FileTreeRequest::Reflect(context);
// Responses
GetUnresolvedDependencyCountsResponse::Reflect(context);
GetRelativeProductPathFromFullSourceOrProductPathResponse::Reflect(context);
GetFullSourcePathFromRelativeProductPathResponse::Reflect(context);
SourceAssetInfoResponse::Reflect(context);
AssetInfoResponse::Reflect(context);
AssetDependencyInfoResponse::Reflect(context);
FileOpenResponse::Reflect(context);
FileReadResponse::Reflect(context);
FileWriteResponse::Reflect(context);
FileTellResponse::Reflect(context);
FileSeekResponse::Reflect(context);
FileIsReadOnlyResponse::Reflect(context);
PathIsDirectoryResponse::Reflect(context);
FileSizeResponse::Reflect(context);
FileModTimeResponse::Reflect(context);
FileExistsResponse::Reflect(context);
FileFlushResponse::Reflect(context);
PathCreateResponse::Reflect(context);
PathDestroyResponse::Reflect(context);
FileRemoveResponse::Reflect(context);
FileCopyResponse::Reflect(context);
FileRenameResponse::Reflect(context);
FindFilesResponse::Reflect(context);
FileTreeResponse::Reflect(context);
SaveAssetCatalogRequest::Reflect(context);
SaveAssetCatalogResponse::Reflect(context);
AssetNotificationMessage::Reflect(context);
AssetSeedListReflector::Reflect(context);
SeedInfo::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<AssetSystemComponent, AZ::Component>()
;
}
}
void AssetSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("AssetProcessorConnection", 0xf0cd75cd));
}
void AssetSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("AssetProcessorConnection", 0xf0cd75cd));
}
void AssetSystemComponent::EnableSocketConnection()
{
AZ_Assert(!SocketConnection::GetInstance(), "You can only have one AZ::SocketConnection");
if (!SocketConnection::GetInstance())
{
SocketConnection::SetInstance(m_socketConn.get());
}
}
void AssetSystemComponent::DisableSocketConnection()
{
SocketConnection* socketConnection = SocketConnection::GetInstance();
if (socketConnection && socketConnection == m_socketConn.get())
{
SocketConnection::SetInstance(nullptr);
}
}
//////////////////////////////////////////////////////////////////////////
// SystemTickBus overrides
void AssetSystemComponent::OnSystemTick()
{
AZ_TRACE_METHOD();
LegacyAssetEventBus::ExecuteQueuedEvents();
}
bool AssetSystemComponent::SetAssetProcessorIP(AZStd::string_view ip)
{
if (ConnectedWithAssetProcessor())
{
AZ_Warning("AssetSystem", false, "Cannot change ip while already connected");
return false;
}
m_assetProcessorIP = ip;
return true;
}
bool AssetSystemComponent::SetAssetProcessorPort(AZ::u16 port)
{
if (ConnectedWithAssetProcessor())
{
AZ_Warning("AssetSystem", false, "Cannot change port while already connected");
return false;
}
m_assetProcessorPort = port;
return true;
}
void AssetSystemComponent::SetAssetProcessorBranchToken(AZStd::string_view branchToken)
{
if (m_assetProcessorBranchToken != branchToken)
{
m_configured = false;
m_assetProcessorBranchToken = branchToken;
}
}
void AssetSystemComponent::SetAssetProcessorProjectName(AZStd::string_view projectName)
{
if (m_assetProcessorProjectName != projectName)
{
m_configured = false;
m_assetProcessorProjectName = projectName;
}
}
void AssetSystemComponent::SetAssetProcessorPlatform(AZStd::string_view platform)
{
if (m_assetProcessorPlatform != platform)
{
m_configured = false;
m_assetProcessorPlatform = platform;
}
}
void AssetSystemComponent::SetAssetProcessorIdentifier(AZStd::string_view identifier)
{
if (m_assetProcessorIdentifier != identifier)
{
m_configured = false;
m_assetProcessorIdentifier = identifier;
}
}
bool AssetSystemComponent::ConfigureSocketConnection()
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
if (ConnectedWithAssetProcessor()) // Don't allow changing the IP while connected
{
AZ_Warning("AssetSystem", false, "Cannot configure while connected");
return false;
}
auto apConnection = azrtti_cast<AssetProcessorConnection*>(m_socketConn.get());
apConnection->Configure(m_assetProcessorBranchToken.c_str(),
m_assetProcessorPlatform.c_str(),
m_assetProcessorIdentifier.c_str(),
m_assetProcessorProjectName.c_str());
m_configured = true;
return true;
}
bool AssetSystemComponent::StartConnectToAssetProcessor()
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
if (ConnectedWithAssetProcessor())
{
AZ_Warning("AssetSystem", false, "Cannot connect while already connected.");
return false;
}
if (!m_configured)
{
AZ_Warning("AssetSystem", false, "SocketConnection was not configured before calling StartConnectToAssetProcessor!!! Ensure AssetSystemComponent::ConfigureSocketConnection was called after changing any setting.");
return false;
}
//connect is async
AZ_TracePrintf("Asset System Connection", "Asset Processor Connection IP: %s, port: %hu, branch token %s\n", m_assetProcessorIP.c_str(), m_assetProcessorPort, m_assetProcessorBranchToken.c_str());
auto apConnection = azrtti_cast<AssetProcessorConnection*>(m_socketConn.get());
apConnection->Connect(m_assetProcessorIP.c_str(), m_assetProcessorPort);
return true;
}
bool AssetSystemComponent::StartConnectFromAssetProcessor()
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
if (ConnectedWithAssetProcessor()) // Don't allow changing the IP while connected
{
AZ_Warning("AssetSystem", false, "Cannot connect while connected.");
return false;
}
if (!m_configured)
{
AZ_Warning("AssetSystem", false, "SocketConnection was not configured before calling StartConnectFromAssetProcessor!!! Ensure AssetSystemComponent::ConfigureSocketConnection was called after changing any setting.");
return false;
}
//listen is async
//instances always listen on port 22229, currently its not configurable
auto apConnection = azrtti_cast<AssetProcessorConnection*>(m_socketConn.get());
apConnection->Listen(22229);
return true;
}
bool AssetSystemComponent::ConnectToAssetProcessor(const ConnectionSettings& connectionSettings)
{
if (connectionSettings.m_loggingCallback)
{
connectionSettings.m_loggingCallback("Connecting to Asset Processor...\n");
}
if (!SetAssetProcessorIP(connectionSettings.m_assetProcessorIp))
{
AZ_Warning("AssetSystem", false, "SetAssetProcessorIP() has failed!!!.");
return false;
}
if (!SetAssetProcessorPort(connectionSettings.m_assetProcessorPort))
{
AZ_Warning("AssetSystem", false, "SetAssetProcessorPort() has failed!!!.");
return false;
}
SetAssetProcessorBranchToken(connectionSettings.m_branchToken);
SetAssetProcessorProjectName(connectionSettings.m_projectName);
SetAssetProcessorPlatform(connectionSettings.m_assetPlatform);
SetAssetProcessorIdentifier(connectionSettings.m_connectionIdentifier);
if (!ConfigureSocketConnection())
{
AZ_Warning("AssetSystem", false, "ConfigureSocketConnection() has failed!!!.");
return false;
}
if (!StartConnectToAssetProcessor())
{
AZ_Warning("AssetSystem", false, "StartConnectToAssetProcessor() has failed!!!");
return false;
}
return WaitUntilAssetProcessorConnected(connectionSettings.m_connectTimeout);
}
bool AssetSystemComponent::ConnectFromAssetProcessor(const ConnectionSettings& connectionSettings)
{
if (connectionSettings.m_loggingCallback)
{
connectionSettings.m_loggingCallback("Listening for Asset Processor connection...\n");
}
SetAssetProcessorBranchToken(connectionSettings.m_branchToken);
SetAssetProcessorProjectName(connectionSettings.m_projectName);
SetAssetProcessorPlatform(connectionSettings.m_assetPlatform);
SetAssetProcessorIdentifier(connectionSettings.m_connectionIdentifier);
if(!ConfigureSocketConnection())
{
AZ_Warning("AssetSystem", false, "ConfigureSocketConnection() has failed!!!");
return false;
}
if (!StartConnectFromAssetProcessor())
{
AZ_Warning("AssetSystem", false, "StartConnectFromAssetProcessor() has failed!!!");
return false;
}
return WaitUntilAssetProcessorConnected(connectionSettings.m_connectTimeout);
}
//////////////////////////////////////////////////////////////////////////
// AssetSystemRequestBus::Handler overrides
bool AssetSystemComponent::EstablishAssetProcessorConnection(const ConnectionSettings& connectionSettings)
{
bool connectionEstablished{};
if (connectionSettings.m_connectionDirection == ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor)
{
connectionEstablished = ConnectToAssetProcessor(connectionSettings);
}
else
{
connectionEstablished = ConnectFromAssetProcessor(connectionSettings);
}
if (!connectionEstablished)
{
bool failedNegotiation = NegotiationWithAssetProcessorFailed();
if (failedNegotiation)
{
AZ_Error(connectionSettings.m_connectionIdentifier.c_str(), false, "Negotiation with asset processor failed");
return false;
}
#if AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
if (connectionSettings.m_launchAssetProcessorOnFailedConnection)
{
if (!LaunchAssetProcessor())
{
if (connectionSettings.m_waitForConnect)
{
AZ_Error(connectionSettings.m_connectionIdentifier.c_str(), false, "Launch asset processor failed");
return false;
}
else
{
AZ_Warning(connectionSettings.m_connectionIdentifier.c_str(), false, "Launch asset processor failed");
}
}
else
{
AZStd::chrono::time_point startConnectFromLaunchTime = AZStd::chrono::system_clock::now();
connectionEstablished = WaitUntilAssetProcessorConnected(connectionSettings.m_launchTimeout);
AZStd::chrono::time_point endConnectFromLaunchTime = AZStd::chrono::system_clock::now();
if (!connectionEstablished && NegotiationWithAssetProcessorFailed())
{
AZ_Error(connectionSettings.m_connectionIdentifier.c_str(), false, "Negotiation with asset processor failed");
}
else
{
AZStd::chrono::duration<float> launchToConnectTime{ endConnectFromLaunchTime - startConnectFromLaunchTime };
if (connectionSettings.m_loggingCallback)
{
connectionSettings.m_loggingCallback(AZStd::fixed_string<128>::format("Launched Asset Processor and received connection in %f seconds\n",
launchToConnectTime.count()));
}
}
}
}
#endif
while (!connectionEstablished && connectionSettings.m_waitForConnect)
{
constexpr AZStd::chrono::seconds aSecond(1);
connectionEstablished = WaitUntilAssetProcessorConnected(aSecond);
if (!connectionEstablished && NegotiationWithAssetProcessorFailed())
{
AZ_Error(connectionSettings.m_connectionIdentifier.c_str(), false, "Negotiation with asset processor failed");
break;
}
}
}
// If the wait until asset processor is ready option is unset
// The return only whether a successful connection to the Asset Processor has taken place
if (connectionEstablished && connectionSettings.m_waitUntilAssetProcessorIsReady)
{
// regardless of what is set in the bootstrap wait for AP to be ready
// wait a maximum of 100 milliseconds and pump the system event loop until empty
struct AssetsInQueueNotification
: public AzFramework::AssetSystemInfoBus::Handler
{
AssetsInQueueNotification(const ConnectionSettings::LoggingCallback& callback)
: m_loggingCallback{ callback }
{
}
void CountOfAssetsInQueue(const int& count) override
{
// Pad to 7 digits as there should be any reasonable amount of jobs that are in the millions
// Carriage Return is used here to overwrite the current line of output
if (m_loggingCallback)
{
m_loggingCallback(AZStd::fixed_string<128>::format("Asset Processor working... %7d jobs remaining\r", count));
}
}
const ConnectionSettings::LoggingCallback& m_loggingCallback;
};
AssetsInQueueNotification assetsInQueueNotifcation(connectionSettings.m_loggingCallback);
assetsInQueueNotifcation.BusConnect();
if (connectionSettings.m_loggingCallback)
{
connectionSettings.m_loggingCallback("Asset Processor working...\r");
}
bool assetProcessorIsReady = AssetProcessorIsReady() || WaitUntilAssetProcessorReady(connectionSettings.m_waitForReadyTimeout);
assetsInQueueNotifcation.BusDisconnect();
return connectionEstablished && assetProcessorIsReady;
}
return connectionEstablished;
}
bool AssetSystemComponent::WaitUntilAssetProcessorConnected(AZStd::chrono::duration<float> timeout)
{
AZStd::chrono::system_clock::time_point start = AZStd::chrono::system_clock::now();
while (!ConnectedWithAssetProcessor() && AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(AZStd::chrono::system_clock::now() - start) < timeout)
{
if (NegotiationWithAssetProcessorFailed())
{
EBUS_EVENT(AzFramework::AssetSystemConnectionNotificationsBus, NegotiationFailed);
StartDisconnectingAssetProcessor();
return false;
}
//yield
AZStd::this_thread::yield();
}
return ConnectedWithAssetProcessor();
}
bool AssetSystemComponent::WaitUntilAssetProcessorReady(AZStd::chrono::duration<float> timeout)
{
if (!ConnectedWithAssetProcessor()) //don't wait if not connected
{
return false;
}
// while we wait, let's get some ping times.
float pingTime = GetAssetProcessorPingTimeMilliseconds();
if (pingTime > 0.0f)
{
AZ_TracePrintf("AssetSystem", "Ping time to asset processor: %0.2f milliseconds\n", pingTime);
}
AZStd::chrono::system_clock::time_point start = AZStd::chrono::system_clock::now();
bool isAssetProcessorReady = false;
while (!isAssetProcessorReady && (AZStd::chrono::system_clock::now() - start) < timeout)
{
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::PumpSystemEventLoopUntilEmpty);
if (!ConnectedWithAssetProcessor())
{
//If we are here than it means we were connected but have lost connection with AP
AZ_Warning("AssetSystem", false, "Lost the connection to the Asset Processor!\nMake sure the Asset Processor is running.");
return false;
}
//Keep asking the AP about its status, until it is ready
isAssetProcessorReady = AssetProcessorIsReady();
if(!isAssetProcessorReady)
{
// Throttle this, each loop actually sends network traffic to the AP and there's no point in running at 100x a second, but 10x is smooth.
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100)); // on some systems, PumpSystemEventLoopUntilEmpty may not sleep.
}
}
return isAssetProcessorReady;
}
bool AssetSystemComponent::ConnectedWithAssetProcessor()
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
auto apConnection = azrtti_cast<AssetProcessorConnection*>(m_socketConn.get());
return apConnection->IsConnected();
}
bool AssetSystemComponent::NegotiationWithAssetProcessorFailed()
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
auto apConnection = azrtti_cast<AssetProcessorConnection*>(m_socketConn.get());
return apConnection->NegotiationFailed();
}
bool AssetSystemComponent::AssetProcessorIsReady()
{
if (!ConnectedWithAssetProcessor()) //cant be ready if not connected
{
return false;
}
RequestAssetProcessorStatus request;
request.m_platform = m_assetProcessorPlatform;
ResponseAssetProcessorStatus response;
if (!SendRequest(request, response))
{
AZ_Warning("AssetSystem", false, "Failed to send Asset Processor Status request for platform %s.", m_assetProcessorPlatform.c_str());
return false;
}
return response.m_isAssetProcessorReady;
}
void AssetSystemComponent::StartDisconnectingAssetProcessor()
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
auto apConnection = azrtti_cast<AssetProcessorConnection*>(m_socketConn.get());
apConnection->Disconnect();
}
bool AssetSystemComponent::DisconnectedWithAssetProcessor()
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
auto apConnection = azrtti_cast<AssetProcessorConnection*>(m_socketConn.get());
return apConnection->IsDisconnected();
}
bool AssetSystemComponent::WaitUntilAssetProcessorDisconnected(AZStd::chrono::duration<float> timeout)
{
AZStd::chrono::system_clock::time_point start = AZStd::chrono::system_clock::now();
while (!DisconnectedWithAssetProcessor() && AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(AZStd::chrono::system_clock::now() - start) < timeout)
{
//yield
AZStd::this_thread::yield();
}
return DisconnectedWithAssetProcessor();
}
AssetStatus AssetSystemComponent::CompileAssetSync(const AZStd::string& assetPath)
{
return SendAssetStatusRequest(RequestAssetStatus(assetPath.c_str(), false, false));
}
AssetStatus AssetSystemComponent::CompileAssetSync_FlushIO(const AZStd::string& assetPath)
{
return SendAssetStatusRequest(RequestAssetStatus(assetPath.c_str(), false, true));
}
AssetStatus AssetSystemComponent::CompileAssetSyncById(const AZ::Data::AssetId& assetId)
{
return SendAssetStatusRequest(RequestAssetStatus(assetId, false, false));
}
AssetStatus AssetSystemComponent::CompileAssetSyncById_FlushIO(const AZ::Data::AssetId& assetId)
{
return SendAssetStatusRequest(RequestAssetStatus(assetId, false, true));
}
AssetStatus AssetSystemComponent::GetAssetStatus(const AZStd::string& assetPath)
{
return SendAssetStatusRequest(RequestAssetStatus(assetPath.c_str(), true, false));
}
AssetStatus AssetSystemComponent::GetAssetStatus_FlushIO(const AZStd::string& assetPath)
{
return SendAssetStatusRequest(RequestAssetStatus(assetPath.c_str(), true, true));
}
AssetStatus AssetSystemComponent::GetAssetStatusSearchType(const AZStd::string& assetPath, int searchType)
{
return SendAssetStatusRequest(RequestAssetStatus(assetPath.c_str(), true, false, searchType));
}
AssetStatus AssetSystemComponent::GetAssetStatusSearchType_FlushIO(const AZStd::string& assetPath, int searchType)
{
return SendAssetStatusRequest(RequestAssetStatus(assetPath.c_str(), true, true, searchType));
}
AssetStatus AssetSystemComponent::GetAssetStatusById(const AZ::Data::AssetId& assetId)
{
return SendAssetStatusRequest(RequestAssetStatus(assetId, true, false));
}
AssetStatus AssetSystemComponent::GetAssetStatusById_FlushIO(const AZ::Data::AssetId& assetId)
{
return SendAssetStatusRequest(RequestAssetStatus(assetId, true, true));
}
bool AssetSystemComponent::EscalateAssetByUuid(const AZ::Uuid& assetUuid)
{
if (ConnectedWithAssetProcessor())
{
RequestEscalateAsset request(assetUuid);
SendRequest(request);
return true;
}
return false; // not sent.
}
bool AssetSystemComponent::EscalateAssetBySearchTerm(AZStd::string_view searchTerm)
{
if (ConnectedWithAssetProcessor())
{
RequestEscalateAsset request(searchTerm.data());
SendRequest(request);
return true;
}
return false; // not sent.
}
void AssetSystemComponent::GetUnresolvedProductReferences(AZ::Data::AssetId assetId, AZ::u32& unresolvedAssetIdReferences, AZ::u32& unresolvedPathReferences)
{
AZ_Assert(m_socketConn.get(), "SocketConnection doesn't exist! Ensure AssetSystemComponent::Init was called");
unresolvedPathReferences = unresolvedAssetIdReferences = 0;
if (ConnectedWithAssetProcessor())
{
GetUnresolvedDependencyCountsRequest request(assetId);
GetUnresolvedDependencyCountsResponse response;
if (SendRequest(request, response))
{
unresolvedAssetIdReferences = response.m_unresolvedAssetIdReferences;
unresolvedPathReferences = response.m_unresolvedPathReferences;
}
}
}
AssetStatus AssetSystemComponent::SendAssetStatusRequest(const RequestAssetStatus& request)
{
AssetStatus eStatus = AssetStatus_Unknown;
if (ConnectedWithAssetProcessor())
{
ResponseAssetStatus response;
SendRequest(request, response);
eStatus = static_cast<AssetStatus>(response.m_assetStatus);
}
return eStatus;
}
float AssetSystemComponent::GetAssetProcessorPingTimeMilliseconds()
{
if (!ConnectedWithAssetProcessor())
{
return 0.0f;
}
AZStd::chrono::system_clock::time_point beforePing = AZStd::chrono::system_clock::now();
RequestPing pingeRequest;
ResponsePing pingRespose;
if (SendRequest(pingeRequest, pingRespose))
{
AZStd::chrono::duration<float, AZStd::milli> difference = AZStd::chrono::duration_cast<AZStd::chrono::duration<float, AZStd::milli> >(AZStd::chrono::system_clock::now() - beforePing);
return difference.count();
}
return 0.0f;
}
bool AssetSystemComponent::SaveCatalog()
{
if (!ConnectedWithAssetProcessor()) //cant be ready if not connected
{
return false;
}
SaveAssetCatalogRequest saveCatalogRequest;
SaveAssetCatalogResponse saveCatalogRespose;
if (SendRequest(saveCatalogRequest, saveCatalogRespose))
{
return saveCatalogRespose.m_saved;
}
return false;
}
} // namespace AssetSystem
} // namespace AzFramework
@@ -0,0 +1,178 @@
/*
* 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/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string_view.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/Network/SocketConnection.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
namespace AzFramework
{
namespace AssetSystem
{
constexpr char BranchToken[] = "assetProcessor_branch_token";
constexpr char ProjectName[] = "sys_game_folder";
constexpr char Assets[] = "assets";
constexpr char AssetProcessorRemoteIp[] = "remote_ip";
constexpr char AssetProcessorRemotePort[] = "remote_port";
constexpr char WaitForConnect[] = "wait_for_connect";
/**
* A game level component for interacting with the asset processor
*
* Currently used to request synchronous asset compilation, provide notifications
* when assets are updated, and to query asset status
*/
class AssetSystemComponent
: public AZ::Component
, private AssetSystemRequestBus::Handler
, private AZ::SystemTickBus::Handler
{
public:
AZ_COMPONENT(AssetSystemComponent, "{42C58BBF-0C15-4DF9-9351-4639B36F122A}")
AssetSystemComponent() = default;
virtual ~AssetSystemComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component overrides
void Init() override;
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// SystemTickBus overrides
void OnSystemTick() override;
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
private:
AssetSystemComponent(const AssetSystemComponent&) = delete;
void EnableSocketConnection();
void DisableSocketConnection();
public:
//////////////////////////////////////////////////////////////////////////
// AssetSystemRequestBus::Handler overrides
//! Uses the ConnectionsSettings ConnectionDirection field
//! to determine whether to connect to an AssetProcessor instance or to listen for
//! a connection
bool EstablishAssetProcessorConnection(const ConnectionSettings& connectionSettings) override;
bool WaitUntilAssetProcessorConnected(AZStd::chrono::duration<float> timeout) override;
bool WaitUntilAssetProcessorReady(AZStd::chrono::duration<float> timeout) override;
bool AssetProcessorIsReady() override;
bool ConnectedWithAssetProcessor() override;
bool NegotiationWithAssetProcessorFailed() override;
void StartDisconnectingAssetProcessor() override;
bool DisconnectedWithAssetProcessor() override;
bool WaitUntilAssetProcessorDisconnected(AZStd::chrono::duration<float> timeout) override;
private:
//! Sets the asset processor IP to use when connecting
//! \return Whether the ip was set, can fail if already connected
bool SetAssetProcessorIP(AZStd::string_view ip);
//! Sets the asset processor port to use when connecting
//! \return Whether the port was set, can fail if already connected
bool SetAssetProcessorPort(AZ::u16 port) ;
//! Sets the branchtoken that will be used for negotiating with the AssetProcessor
void SetAssetProcessorBranchToken(AZStd::string_view branchtoken);
//! Sets the (game) project name that will be used for negotiating with the AssetProcessor
void SetAssetProcessorProjectName(AZStd::string_view projectName);
//! Sets the platform that will be used for negotiating with the AssetProcessor
void SetAssetProcessorPlatform(AZStd::string_view platform);
//! Sets the identifier that will be used for negotiating with the AssetProcessor
void SetAssetProcessorIdentifier(AZStd::string_view identifier);
//! Configure the underlying socket connection
//! \return Whether the ip was set, can fail if already connected
bool ConfigureSocketConnection();
//! Start the connection thread that will try to initiate a connection to the Asset Processor.
//! Once successfully called, the connection thread will starts trying to connect to the ap and will keep trying to initiate a connection, until StartDiconnectingAssetProcessor is called
//! return of true does NOT mean it connected, only that it successfully started the connection thread. You have to keep checking and/or wait is desired
//! \return True if connect was called, NOT that it connected, so true means the connection thread was started. False means connect was not called and therefore
//! the connection thread was not started. It can fail if already connected or the connection was not configured prior to this call
bool StartConnectToAssetProcessor();
//! Convenience function that calls StartConnectToAssetProcessor and then waits for timeout seconds for a connection, if not connected before timeout, StartDiconnectingAssetProcessor is
//! called to stop the connection thread. A timeout of 0 means wait forever until connected, this is not recommended as you will have more control by doing that yourself
//! in the calling code.
//! returns true if successfully connected, false if not
bool ConnectToAssetProcessor(const ConnectionSettings& connectionSettings);
//! Start the connection thread that will try to listen for an Asset Processor to initiate a connection to us.
//! Once successfully called, the connection thread starts listening for an asset processor to initiate a connection to us and will keep trying to initiate a connection, until Disconnect is called
//! \return True if listen was called, NOT that it connected, so true means the connection thread was started. False means listen was not called and therefore
//! the connection thread was not started. It can fail if already connected or the connection was not configured prior to this call
bool StartConnectFromAssetProcessor();
//! Convenience function that calls StartConnectToAssetProcessor and then waits for timeout seconds for a connection, if not connected before timeout, StartDiconnectingAssetProcessor is
//! called to stop the connection thread. A timeout of 0 means wait forever until connected, this is not recommended as you will have more control by doing that yourself
//! in the calling code.
//! returns true if successfully connected, false if not
bool ConnectFromAssetProcessor(const ConnectionSettings& connectionSettings);
AssetStatus CompileAssetSync(const AZStd::string& assetPath) override;
AssetStatus CompileAssetSync_FlushIO(const AZStd::string& assetPath) override;
AssetStatus CompileAssetSyncById(const AZ::Data::AssetId& assetId) override;
AssetStatus CompileAssetSyncById_FlushIO(const AZ::Data::AssetId& assetId) override;
AssetStatus GetAssetStatusSearchType(const AZStd::string& assetPath, int searchType) override;
AssetStatus GetAssetStatusSearchType_FlushIO(const AZStd::string& searchTerm, int searchType) override;
AssetStatus GetAssetStatusById(const AZ::Data::AssetId& assetId) override;
AssetStatus GetAssetStatusById_FlushIO(const AZ::Data::AssetId& assetId) override;
AssetStatus GetAssetStatus(const AZStd::string& assetPath) override;
AssetStatus GetAssetStatus_FlushIO(const AZStd::string& assetPath) override;
bool EscalateAssetByUuid(const AZ::Uuid& assetUuid) override;
bool EscalateAssetBySearchTerm(AZStd::string_view searchTerm) override;
void ShowAssetProcessor() override;
void ShowInAssetProcessor(const AZStd::string& assetPath) override;
void GetUnresolvedProductReferences(AZ::Data::AssetId assetId, AZ::u32& unresolvedAssetIdReferences, AZ::u32& unresolvedPathReferences) override;
float GetAssetProcessorPingTimeMilliseconds() override;
bool SaveCatalog() override;
//////////////////////////////////////////////////////////////////////////
AssetStatus SendAssetStatusRequest(const RequestAssetStatus& request);
AZStd::unique_ptr<SocketConnection> m_socketConn = nullptr;
SocketConnection::TMessageCallbackHandle m_cbHandle = 0;
AZStd::string m_assetProcessorBranchToken;
AZStd::string m_assetProcessorProjectName;
AZStd::string m_assetProcessorPlatform;
AZStd::string m_assetProcessorIdentifier;
AZStd::string m_assetProcessorIP;
AZ::u16 m_assetProcessorPort = 45643;
bool m_configured = false;
};
} // namespace AssetSystem
} // namespace AzFramework
@@ -0,0 +1,266 @@
/*
* 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/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
#include <AzFramework/Platform/PlatformDefaults.h>
namespace AzFramework::AssetSystem::Platform
{
// Declare platform specific AllowAssetProcessorToForeground function
void AllowAssetProcessorToForeground();
// Declare platform specific LaunchAssetProcessor function
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view appRoot,
AZStd::string_view projectName);
}
namespace AzFramework
{
namespace AssetSystem
{
void AllowAssetProcessorToForeground()
{
Platform::AllowAssetProcessorToForeground();
}
// Do this here because including Windows.h causes problems with SetPort being a define
void AssetSystemComponent::ShowAssetProcessor()
{
AllowAssetProcessorToForeground();
ShowAssetProcessorRequest request;
SendRequest(request);
}
void AssetSystemComponent::ShowInAssetProcessor(const AZStd::string& assetPath)
{
AllowAssetProcessorToForeground();
ShowAssetInAssetProcessorRequest request;
request.m_assetPath = assetPath;
SendRequest(request);
}
bool LaunchAssetProcessor()
{
AZ::IO::FixedMaxPathString executableDirectory;
if (AZ::Utils::GetExecutableDirectory(executableDirectory.data(), executableDirectory.max_size()) == AZ::Utils::ExecutablePathResult::Success)
{
// Update the size member of the FixedString stored in the path class
executableDirectory.resize_no_construct(AZStd::char_traits<char>::length(executableDirectory.data()));
}
auto settingsRegistry = AZ::SettingsRegistry::Get();
AZ::SettingsRegistryInterface::FixedValueString engineRootFolder;
// Add the app-root to the launch command if available from the Settings Registry
if (settingsRegistry)
{
settingsRegistry->Get(engineRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
// Add the active game project to the launch from the Settings Registry
const auto gameProjectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/sys_game_folder",
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
AZ::SettingsRegistryInterface::FixedValueString gameProjectName;
if (settingsRegistry)
{
settingsRegistry->Get(gameProjectName, gameProjectKey);
}
if (!Platform::LaunchAssetProcessor(executableDirectory, engineRootFolder, gameProjectName))
{
// if we are unable to launch asset processor
AzFramework::AssetSystemInfoBus::Broadcast(&AzFramework::AssetSystem::AssetSystemInfoNotifications::OnError, AssetSystemErrors::ASSETSYSTEM_FAILED_TO_LAUNCH_ASSETPROCESSOR);
return false;
}
return true;
}
bool ReadConnectionSettingsFromSettingsRegistry(ConnectionSettings& outputConnectionSettings)
{
bool result = true;
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
{
// Read Asset Processor IP Address from Settings Registry
AZ::SettingsRegistryInterface::FixedValueString ip;
if (!AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, ip,
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::AssetProcessorRemoteIp))
{
AZ_TracePrintfOnce("AssetSystemComponent", "Failed to find ip, setting 127.0.0.1\n");
outputConnectionSettings.m_assetProcessorIp = "127.0.0.1";
}
else if (ip.empty())
{
AZ_TracePrintfOnce("AssetSystemComponent", "Ip is empty, setting 127.0.0.1\n");
outputConnectionSettings.m_assetProcessorIp = "127.0.0.1";
}
else
{
//check the ip for obvious things wrong
size_t iplen = ip.length();
int countseperators = 0;
bool isNumeric = true;
#if AZ_TRAIT_DENY_ASSETPROCESSOR_LOOPBACK
bool isIllegalLoopBack = ip == "127.0.0.1";
#endif
for (int i = 0; isNumeric && i < iplen; ++i)
{
if (ip[i] == '.')
{
countseperators++;
}
else if (!isdigit(ip[i]))
{
isNumeric = false;
}
}
if (iplen < 7 ||
countseperators != 3 ||
#if AZ_TRAIT_DENY_ASSETPROCESSOR_LOOPBACK
isIllegalLoopBack ||
#endif
!isNumeric)
{
AZ_Error("AssetSystemComponent", false, "IP address of the Asset Processor is invalid!\nMake sure the remote_ip in the bootstrap.cfg is correct.\n");
result = false;
}
outputConnectionSettings.m_assetProcessorIp = ip;
}
}
{
// Read AssetProcessor port from Settings Registry
AZ::s64 port64;
if (!AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, port64, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::AssetProcessorRemotePort))
{
AZ_TracePrintfOnce("AssetSystemComponent", "Failed to find port, setting 45643\n");
outputConnectionSettings.m_assetProcessorPort = 45643;
}
else
{
outputConnectionSettings.m_assetProcessorPort = aznumeric_cast<AZ::u16>(port64);
}
}
{
// Read the Asset Platform from the Settings Registry
AZ::SettingsRegistryInterface::FixedValueString assetsPlatform;
if (!AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, assetsPlatform, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::Assets))
{
AZ_TracePrintfOnce("AssetSystemComponent", "Failed to find asset platform, setting 'pc'\n");
outputConnectionSettings.m_assetPlatform = "pc";
}
outputConnectionSettings.m_assetPlatform = assetsPlatform;
if (outputConnectionSettings.m_assetPlatform.empty())
{
assetsPlatform = AzFramework::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME);
AZ_TracePrintfOnce("AssetSystemComponent", "Asset platform read from bootstrap is empty, setting %s\n", assetsPlatform.c_str());
}
}
{
// Read Branch Token from Settings Registry
AZ::s64 branchToken64;
if (!settingsRegistry->Get(branchToken64, AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::BranchToken)))
{
// The first time the AssetProcessor runs within a branch the bootstrap.cfg does not have a branch token set
// Therefore it is not an error for the branch token to not be in the bootstrap.cfg file
AZStd::string branchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, branchToken);
AZ_TracePrintfOnce("AssetSystemComponent", "Failed to read branch token from bootstrap. Calculating Branch Token: %s\n", branchToken.c_str());
outputConnectionSettings.m_branchToken = branchToken;
}
else
{
outputConnectionSettings.m_branchToken = AZStd::fixed_string<32>::format("0x%08X", aznumeric_cast<AZ::u32>(branchToken64));
if (outputConnectionSettings.m_branchToken.empty())
{
AZStd::string branchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, branchToken);
AZ_TracePrintfOnce("AssetSystemComponent", "Branch token read from bootstrap is empty. Calculating Branch Token: %s\n", branchToken.c_str());
outputConnectionSettings.m_branchToken = branchToken;
}
}
}
{
// Read Project Name from Settings Registry
AZ::SettingsRegistryInterface::FixedValueString projectName;
if (!settingsRegistry->Get(projectName, AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::ProjectName)))
{
AZ_Error("AssetSystemComponent", false, "Failed to read project name from bootstrap");
result = false;
}
outputConnectionSettings.m_projectName = projectName;
if (outputConnectionSettings.m_projectName.empty())
{
AZ_Error("AssetSystemComponent", false, "Project name read from bootstrap is empty");
result = false;
}
}
// Read the direction in which a connection to the Asset Processor should be from using the Settings Registry
// Determine whether to connect to the Asset Processor or whether the AssetProcessor
// connects to the game "listen" socket
AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, reinterpret_cast<AZ::s64&>(outputConnectionSettings.m_connectionDirection),
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "connect_to_remote");
{
// Read the wait for connection boolean from the Settings Registry
AZ::s64 waitForConnect64{};
if (!AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, waitForConnect64, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::WaitForConnect))
{
outputConnectionSettings.m_waitForConnect = waitForConnect64 != 0;
}
}
// Read timeout values from the Settings Registry
AZ::s64 timeoutValue{};
if (AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, timeoutValue,
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "connect_ap_timeout"))
{
outputConnectionSettings.m_connectTimeout = AZStd::chrono::seconds(timeoutValue);
}
// Reset timeout integer
timeoutValue = {};
if (AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, timeoutValue,
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "launch_ap_timeout"))
{
outputConnectionSettings.m_launchTimeout = AZStd::chrono::seconds(timeoutValue);
}
timeoutValue = {};
if (AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, timeoutValue,
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "wait_ap_ready_timeout"))
{
outputConnectionSettings.m_waitForReadyTimeout = AZStd::chrono::seconds(timeoutValue);
}
return result;
}
}
}
@@ -0,0 +1,49 @@
/*
* 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
namespace AzFramework
{
namespace AssetSystem
{
// Note: this must be kept in sync with the engine's AssetStatus enum
enum AssetStatus
{
AssetStatus_Unknown,
AssetStatus_Missing,
AssetStatus_Queued,
AssetStatus_Compiling,
AssetStatus_Compiled,
AssetStatus_Failed,
};
enum NegotiationInfo
{
NegotiationInfo_ProcessId,
NegotiationInfo_Platform,
NegotiationInfo_BranchIndentifier,
NegotiationInfo_ProjectName,
};
const unsigned int DEFAULT_SERIAL = 0;
const unsigned int NEGOTIATION_SERIAL = 0x0fffffff;
const unsigned int RESPONSE_SERIAL_FLAG = (1U << 31);
//This enum should have the list of all asset system errors
enum AssetSystemErrors
{
ASSETSYSTEM_FAILED_TO_LAUNCH_ASSETPROCESSOR, // not able to launch the AssetProcessor
ASSETSYSTEM_FAILED_TO_CONNECT_TO_ASSETPROCESSOR, // not able to connect to the AssetProcessor
};
} // namespace AssetMessage
} // namespace AzFramework
@@ -0,0 +1,44 @@
/*
* 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 <AzFramework/Asset/Benchmark/BenchmarkAsset.h>
namespace AzFramework
{
void BenchmarkAsset::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BenchmarkAsset, AZ::Data::AssetData>()
->Version(0)
->Field("BufferSize", &BenchmarkAsset::m_bufferSize)
->Field("Buffer", &BenchmarkAsset::m_buffer)
->Field("Dependencies", &BenchmarkAsset::m_assetReferences)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<BenchmarkAsset>(
"Benchmark Asset", "Generated benchmark asset")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &BenchmarkAsset::m_bufferSize, "Buffer Size", "Size of the buffer in bytes")
->DataElement(AZ::Edit::UIHandlers::Default, &BenchmarkAsset::m_buffer, "Buffer", "Aribtrary data buffer")
->DataElement(AZ::Edit::UIHandlers::Default, &BenchmarkAsset::m_assetReferences, "Dependencies", "Asset dependencies to load with this asset")
;
}
}
}
} // namespace AzFramework
@@ -0,0 +1,41 @@
/*
* 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/Asset/AssetCommon.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AzFramework
{
constexpr const char s_benchmarkAssetExtension[] = "benchmark";
//! BenchmarkAsset is a representative placeholder asset used for asset load benchmarks.
//! BenchmarkAsset is generated from a BenchmarkSettingsAsset asset. It is designed to
//! provide a variety of asset loading scenarios to benchmark by using different sizes
//! and combinations of dependent asset hierarchies.
class BenchmarkAsset
: public AZ::Data::AssetData
{
public:
AZ_RTTI(BenchmarkAsset, "{FEDD2FFE-C8E6-4627-9B88-C3A6E9BA8A98}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(BenchmarkAsset, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
uint64_t m_bufferSize;
AZStd::vector<uint8_t> m_buffer;
AZStd::vector<AZ::Data::Asset<BenchmarkAsset>> m_assetReferences;
};
}// namespace AzFramework
@@ -0,0 +1,301 @@
/*
* 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 <AzFramework/Asset/Benchmark/BenchmarkCommands.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/chrono/chrono.h>
namespace AzFramework::AssetBenchmark
{
//! Max time that the load asset benchmark is allowed to run.
AZ_CVAR(uint32_t, benchmarkLoadAssetTimeoutMs, 20000, nullptr, AZ::ConsoleFunctorFlags::Null,
"Timeout value for BenchmarkLoadAsset* commands, in milliseconds");
//! Polling frequency used by the load asset benchmark to detect load completion.
AZ_CVAR(uint32_t, benchmarkLoadAssetPollMs, 20, nullptr, AZ::ConsoleFunctorFlags::Null,
"Sleep value for BenchmarkLoadAsset* commands between asset completion polls, in milliseconds");
//! Optionally show progress messages during the load.
//! Printing these messages can affect the overall timing, so this is off by default.
AZ_CVAR(bool, benchmarkLoadAssetDisplayProgress, false, nullptr, AZ::ConsoleFunctorFlags::Null,
"Controls whether or not the BenchmarkLoadAsset* commands print progress messages while running");
//! Label to add to the BenchmarkLoadAsset log outputs.
AZ_CVAR(AZ::CVarFixedString, benchmarkLoadAssetLogLabel, "BenchmarkLoadAsset", nullptr, AZ::ConsoleFunctorFlags::Null,
"Provide a log label for tagging the BenchmarkLoadAsset* outputs to make it easier to distinguish different benchmark runs.");
static AZStd::vector<AZStd::pair<AZ::Data::AssetId, AZ::Data::AssetType>> s_benchmarkAssetList;
// Given a list of assets, load them and time the results.
void BenchmarkLoadAssetList(AZStd::vector<AZStd::pair<AZ::Data::AssetId, AZ::Data::AssetType>>&& sourceAssetList,
bool loadBlocking)
{
// Run the entire benchmark on its own thread so that the main thread can continue ticking along.
AZStd::thread benchmarkThread([assetList = AZStd::move(sourceAssetList), loadBlocking]()
{
// Define the set of loading stats to track
const size_t initialRequests = assetList.size();
size_t previouslyLoadedAssets = 0;
size_t newlyLoadedAssets = 0;
size_t loadErrors = 0;
// These are used to hold onto our Asset references until we're done to ensure that we aren't
// immediately unloading and possibly reloading any assets along the way.
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> requestedAssets;
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> processedAssets;
// Start timing.
auto start = AZStd::chrono::system_clock::now();
// Part 1: Queue up all the requested assets.
// If loadBlocking is true, each asset will be synchronously and serially loaded.
for (const auto& [assetId, assetType] : assetList)
{
// Check to see if the requested asset is already loaded or not.
// We'll track the stats appropriately, and queue it if it isn't already loaded.
AZ::Data::Asset<AZ::Data::AssetData> curAsset;
curAsset = AZ::Data::AssetManager::Instance().FindAsset(assetId, AZ::Data::AssetLoadBehavior::Default);
if (curAsset.IsReady())
{
// The asset is already loaded, so track it as processed.
previouslyLoadedAssets++;
processedAssets.emplace_back(AZStd::move(curAsset));
}
else
{
// The asset is *not* already loaded. Queue it for loading.
requestedAssets.emplace_back(AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default));
if(loadBlocking)
{
requestedAssets.back().BlockUntilLoadComplete();
}
}
}
// Part 2: All the assets are queued, now wait for them all to complete.
AZStd::chrono::milliseconds runMs{ 0 };
const AZStd::chrono::milliseconds maxWaitMs{ benchmarkLoadAssetTimeoutMs };
// Keep going until our assets are loaded or we time out.
while (!requestedAssets.empty() && runMs < maxWaitMs)
{
// For all remaining assets, remove any that have finished or errored out
// and track them in our stats.
requestedAssets.erase(
AZStd::remove_if(requestedAssets.begin(), requestedAssets.end(),
[&loadErrors, &newlyLoadedAssets, &processedAssets](auto requestedAsset)
{
if (!requestedAsset || !requestedAsset.GetId().IsValid())
{
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(),
"Asset %s type %s (%s) was not found",
requestedAsset.GetId().template ToString<AZStd::string>().c_str(),
requestedAsset.GetType().template ToString<AZStd::string>().c_str(),
requestedAsset.GetHint().c_str());
loadErrors++;
return true;
}
else if (requestedAsset.IsReady())
{
newlyLoadedAssets++;
processedAssets.emplace_back(AZStd::move(requestedAsset));
return true;
}
else if (requestedAsset.IsError())
{
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(),
"Asset %s type %s (%s) had an error while loading",
requestedAsset.GetId().template ToString<AZStd::string>().c_str(),
requestedAsset.GetType().template ToString<AZStd::string>().c_str(),
requestedAsset.GetHint().c_str());
loadErrors++;
return true;
}
return false;
}),
requestedAssets.end());
// Update our total running time so far.
runMs = AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(AZStd::chrono::system_clock::now() - start);
if (!requestedAssets.empty())
{
// Only display progress messages if requested. Beware, these can affect the benchmark timings.
if (benchmarkLoadAssetDisplayProgress)
{
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(),
"%zu / %zu processed after %lld ms",
(previouslyLoadedAssets + newlyLoadedAssets + loadErrors), initialRequests, runMs.count());
}
// If polling produces too unstable of results, this could eventually get changed to listen
// for all the OnAssetReady messages.
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(benchmarkLoadAssetPollMs));
}
}
// If we've timed out, provide some diagnostics on what went wrong.
if (runMs >= maxWaitMs)
{
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(),
"Request timed out, the following assets didn't load:\n");
for (auto& requestedAsset [[maybe_unused]]: requestedAssets)
{
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(),
"%s: id=%s type=%s\n",
requestedAsset.GetHint().c_str(),
requestedAsset.GetId().template ToString<AZStd::string>().c_str(),
requestedAsset.GetType().template ToString<AZStd::string>().c_str()
);
}
}
// Print out our benchmarking results.
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(),
"Results: LoadRequests=%zu "
"PreviouslyLoaded=%zu "
"NewlyLoaded=%zu "
"Errors=%zu "
"TotalProcessed=%zu "
"Time=%lld ms %s\n",
initialRequests,
previouslyLoadedAssets,
newlyLoadedAssets,
loadErrors,
(previouslyLoadedAssets + newlyLoadedAssets + loadErrors),
runMs.count(), (runMs >= maxWaitMs) ? "(request timed out)" : "");
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(), "Benchmark run complete.\n");
});
benchmarkThread.detach();
}
// Console command: Clear the list of assets to use with BenchmarkLoadAssetList
void BenchmarkClearAssetList([[maybe_unused]] const AZ::ConsoleCommandContainer& parameters)
{
s_benchmarkAssetList.clear();
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(), "Benchmark asset list cleared.\n");
}
// Console command: Add the given list of assets to the list of assets to load with BenchmarkLoadAssetList
void BenchmarkAddAssetsToList(const AZ::ConsoleCommandContainer& parameters)
{
bool allAssetsAdded = true;
for (auto& assetName : parameters)
{
AZ::Data::AssetId assetId;
AZ::Data::AssetInfo assetInfo;
// For each name passed in, look up the Asset ID and Type
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
AZStd::string(assetName).c_str(), AZ::Data::s_invalidAssetType, false);
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById,
assetId);
// Only add the asset to our list if it's valid and there's an AssetHandler that can load it.
if (assetId.IsValid())
{
if (AZ::Data::AssetManager::Instance().GetHandler(assetInfo.m_assetType))
{
s_benchmarkAssetList.emplace_back(assetId, assetInfo.m_assetType);
}
else
{
AZ_Error("AssetBenchmark", false, "No asset handler registered for asset: " AZ_STRING_FORMAT,
AZ_STRING_ARG(assetName));
allAssetsAdded = false;
}
}
else
{
AZ_Error("AssetBenchmark", false, "Could not find asset: " AZ_STRING_FORMAT, AZ_STRING_ARG(assetName));
allAssetsAdded = false;
}
}
AZ_TracePrintf(static_cast<AZ::CVarFixedString>(benchmarkLoadAssetLogLabel).c_str(),
allAssetsAdded ? "All requested assets added." : "One or more requested assets could not be added.");
}
// Console command: Given a list of assets, asynchronously load them and time the results.
void BenchmarkLoadAssetList(const AZ::ConsoleCommandContainer& parameters)
{
BenchmarkAddAssetsToList(parameters);
if (!s_benchmarkAssetList.empty())
{
constexpr bool loadBlocking = false;
// This intentionally does a 'move' of the list instead of a copy as a user convenience. The assumed common use case
// is that once the user has run a benchmark, they would like the asset list cleared before starting the next benchmark run.
BenchmarkLoadAssetList(AZStd::move(s_benchmarkAssetList), loadBlocking);
}
}
// Gather up all existing assets in the asset catalog and try to load them.
static void BenchmarkLoadAllAssetsInternal(bool loadBlocking)
{
AZStd::vector<AZStd::pair<AZ::Data::AssetId, AZ::Data::AssetType>> assetList;
// For each asset in the asset catalog, only add it to our list of there's an
// AssetHandler capable of loading it.
AZ::Data::AssetCatalogRequests::AssetEnumerationCB collectAssetsCb = [&]
(const AZ::Data::AssetId id, const AZ::Data::AssetInfo& info)
{
if (AZ::Data::AssetManager::Instance().GetHandler(info.m_assetType))
{
assetList.emplace_back(id, info.m_assetType);
}
};
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets,
nullptr, collectAssetsCb, nullptr);
BenchmarkLoadAssetList(AZStd::move(assetList), loadBlocking);
}
// Console command: Load all assets in the asset catalog asynchronously and time the results.
void BenchmarkLoadAllAssets([[maybe_unused]] const AZ::ConsoleCommandContainer& parameters)
{
constexpr bool loadBlocking = false;
BenchmarkLoadAllAssetsInternal(loadBlocking);
}
// Console command: Load all assets in the asset catalog synchronously and time the results.
void BenchmarkLoadAllAssetsSynchronous([[maybe_unused]] const AZ::ConsoleCommandContainer& parameters)
{
constexpr bool loadBlocking = true;
BenchmarkLoadAllAssetsInternal(loadBlocking);
}
// Normally, the commands above would be registered by AZ_CONSOLEFREEFUNC here. They specifically have been moved from
// here to AssetSystemComponent.cpp to circumvent dead code stripping. If they appear here, then the only references
// to code within this file is self-contained to the file. The C++ standard (3.6.2, 3.7.1) only guarantees that
// a static variable is initialized before any other function in the compilation unit (.cpp file) is called. If no other
// functions are called, it's not guaranteed to get initialized at all, which leads to this entire file getting stripped
// as unused. By moving these lines to a different compilation unit, the file has external references and is no longer
// unused.
} // namespace AzFramework::AssetBenchmark
@@ -0,0 +1,46 @@
/*
* 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/Console/IConsole.h>
#include <AzCore/Asset/AssetCommon.h>
namespace AzFramework::AssetBenchmark
{
//! Given a list of assets, load them and time the results.
//! @param assetList The list of assets to load (list ownership is handed off to this function)
//! @param loadBlocking true=load synchronously and sequentially, false=load asynchronously
void BenchmarkLoadAssetList(AZStd::vector<AZStd::pair<AZ::Data::AssetId, AZ::Data::AssetType>>&& assetList,
bool loadBlocking = false);
//! Clear the list of assets to use with BenchmarkLoadAssetList.
//! @param parameters The set of console command parameters that were passed in
void BenchmarkClearAssetList(const AZ::ConsoleCommandContainer& parameters);
//! Add one or more asset names to the list of assets to use with BenchmarkLoadAssetList.
//! @param parameters The set of console command parameters that were passed in
void BenchmarkAddAssetsToList(const AZ::ConsoleCommandContainer& parameters);
//! Given a list of assets, load them and time the results.
//! @param parameters The set of console command parameters that were passed in
void BenchmarkLoadAssetList(const AZ::ConsoleCommandContainer& parameters);
//! Load all assets that exist in the asset catalog and time the results.
//! @param parameters The set of console command parameters that were passed in
void BenchmarkLoadAllAssets(const AZ::ConsoleCommandContainer& parameters);
//! Synchronously load all assets that exist in the asset catalog and time the results.
//! @param parameters The set of console command parameters that were passed in
void BenchmarkLoadAllAssetsSynchronous(const AZ::ConsoleCommandContainer& parameters);
}// namespace AzFramework::AssetBenchmark
@@ -0,0 +1,52 @@
/*
* 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 <AzFramework/Asset/Benchmark/BenchmarkSettingsAsset.h>
namespace AzFramework
{
void BenchmarkSettingsAsset::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BenchmarkSettingsAsset, AZ::Data::AssetData>()
->Version(0)
->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)
->Field("PrimaryAssetByteSize", &BenchmarkSettingsAsset::m_primaryAssetByteSize)
->Field("DependentAssetByteSize", &BenchmarkSettingsAsset::m_dependentAssetByteSize)
->Field("DependencyDepth", &BenchmarkSettingsAsset::m_dependencyDepth)
->Field("NumAssetsPerDependency", &BenchmarkSettingsAsset::m_numAssetsPerDependency)
->Field("AssetStorageType", &BenchmarkSettingsAsset::m_assetStorageType)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<BenchmarkSettingsAsset>(
"Benchmark Settings Asset", "Settings file for generating assets for benchmark purposes")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &BenchmarkSettingsAsset::m_primaryAssetByteSize, "Asset Buffer Size", "Size of the test buffer in the primary asset in bytes")
->DataElement(AZ::Edit::UIHandlers::Default, &BenchmarkSettingsAsset::m_dependentAssetByteSize, "Dependent Asset Buffer Size", "Size of the test buffer in each dependent asset in bytes")
->DataElement(AZ::Edit::UIHandlers::Default, &BenchmarkSettingsAsset::m_dependencyDepth, "Dependency Depth", "Depth of the asset dependency tree")
->DataElement(AZ::Edit::UIHandlers::Default, &BenchmarkSettingsAsset::m_numAssetsPerDependency, "Assets Per Dependency", "Number of assets to generate for each dependency in the tree")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &BenchmarkSettingsAsset::m_assetStorageType, "Asset Storage", "Serializaton format to use for each asset (binary, text)")
->EnumAttribute(AZ::DataStream::StreamType::ST_BINARY, "Binary")
->EnumAttribute(AZ::DataStream::StreamType::ST_XML, "XML")
->EnumAttribute(AZ::DataStream::StreamType::ST_JSON, "JSON")
;
}
}
}
} // namespace AzFramework
@@ -0,0 +1,73 @@
/*
* 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/Asset/AssetCommon.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/ObjectStream.h>
namespace AzFramework
{
constexpr const char s_benchmarkSettingsAssetExtension[] = "benchmarksettings";
//! This provides a compact means of locally generating a large volume of test assets used for load benchmarks.
//! The generated assets can be used for benchmarking asset loads in a variety of situations.
//!
//! To use:
//! In the LY Editor, go to the Asset Editor and create a new "Benchmark Settings Asset" asset. Set the following
//! settings as appropriate for your test:
//! - "Asset Buffer Size": The number of bytes to save in the buffer for the top-level asset.
//! - "Dependent Asset Buffer Size": The number of bytes to save in the buffer for every asset below the top-level one.
//! - "Dependency Depth": The depth of the generated asset dependency tree.
//! - "Assets Per Dependency": The number of assets to generate per asset in the dependency tree.
//! - "Asset Storage Type": Whether to save the asset as JSON, XML, or BINARY.
//! NOTE: With text-based formats, every byte in the asset buffer takes up 2 bytes in
//! the final output.
//!
//! Example tests and their associated settings:
//! - Load 1 100 MB asset:
//! Asset Buffer Size: 100 MB
//! Dependent Asset Buffer Size: 0
//! Dependency Depth: 0
//! Assets Per Dependency: 0
//!
//! - Load 1 10 MB asset that directly depends on 100 1 MB assets:
//! Asset Buffer Size: 10 MB
//! Dependent Asset Buffer Size: 1 MB
//! Dependency Depth: 1
//! Assets Per Dependency: 100
//!
//! - Load 1 10 MB asset that depends on 5 1 MB assets, each of which also depends on 5 1 MB assets:
//! Asset Buffer Size: 10 MB
//! Dependent Asset Buffer Size: 1 MB
//! Dependency Depth: 2
//! Assets Per Dependency: 5
class BenchmarkSettingsAsset
: public AZ::Data::AssetData
{
public:
AZ_RTTI(BenchmarkSettingsAsset, "{D570D0DD-CE8D-4DF3-BC3E-77DB92D72626}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(BenchmarkSettingsAsset, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
uint64_t m_primaryAssetByteSize = 1024;
uint64_t m_dependentAssetByteSize = 0;
uint32_t m_numAssetsPerDependency = 0;
uint32_t m_dependencyDepth = 0;
AZ::DataStream::StreamType m_assetStorageType = AZ::DataStream::StreamType::ST_BINARY;
};
}// namespace AzFramework
@@ -0,0 +1,28 @@
/*
* 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/RTTI/TypeInfo.h>
#include <AzFramework/Asset/SimpleAsset.h>
namespace AzFramework
{
class CfgFileAsset
{
public:
AZ_TYPE_INFO(CfgFileAsset, "{117A80A5-206B-4D85-9445-33B446D94C35}")
static const char* GetFileFilter()
{
return "*.cfg";
}
};
}
@@ -0,0 +1,78 @@
/*
* 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 "CustomAssetTypeComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
namespace AzFramework
{
//=========================================================================
// Reflect
//=========================================================================
void CustomAssetTypeComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CustomAssetTypeComponent, AZ::Component>()
->Version(2);
VersionSearchRule::Reflect(context);
MatchingRule::Reflect(context);
XmlSchemaAttribute::Reflect(context);
XmlSchemaElement::Reflect(context);
SearchRuleDefinition::Reflect(context);
DependencySearchRule::Reflect(context);
XmlSchemaAsset::Reflect(context);
FileTag::FileTagAsset::Reflect(context);
BenchmarkAsset::Reflect(context);
BenchmarkSettingsAsset::Reflect(context);
}
}
//=========================================================================
// Activate
//=========================================================================
void CustomAssetTypeComponent::Activate()
{
using namespace FileTag;
m_schemaAssetHandler.reset(aznew AzFramework::GenericAssetHandler<XmlSchemaAsset>(XmlSchemaAsset::GetDisplayName(), XmlSchemaAsset::GetGroup(), XmlSchemaAsset::GetFileFilter()));
m_schemaAssetHandler->Register();
m_fileTagAssetHandler.reset(aznew AzFramework::GenericAssetHandler<FileTagAsset>(FileTagAsset::GetDisplayName(), FileTagAsset::GetGroup(), FileTagAsset::Extension()));
m_fileTagAssetHandler->Register();
m_benchmarkSettingsAssetAssetHandler.reset(aznew AzFramework::GenericAssetHandler<AzFramework::BenchmarkSettingsAsset>(
"Benchmark Settings Asset",
"Other",
AzFramework::s_benchmarkSettingsAssetExtension));
m_benchmarkSettingsAssetAssetHandler->Register();
m_benchmarkAssetAssetHandler.reset(aznew AzFramework::GenericAssetHandler<AzFramework::BenchmarkAsset>(
"Benchmark Asset",
"Other",
AzFramework::s_benchmarkAssetExtension));
m_benchmarkAssetAssetHandler->Register();
}
//=========================================================================
// Deactivate
//=========================================================================
void CustomAssetTypeComponent::Deactivate()
{
m_schemaAssetHandler.reset();
m_fileTagAssetHandler.reset();
m_benchmarkSettingsAssetAssetHandler.reset();
m_benchmarkAssetAssetHandler.reset();
}
} // namespace AzFramework
@@ -0,0 +1,48 @@
/*
* 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/Component/Component.h>
#include <AzFramework/Asset/FileTagAsset.h>
#include <AzFramework/Asset/GenericAssetHandler.h>
#include <AzFramework/Asset/XmlSchemaAsset.h>
#include <AzFramework/Asset/Benchmark/BenchmarkSettingsAsset.h>
#include <AzFramework/Asset/Benchmark/BenchmarkAsset.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
class CustomAssetTypeComponent
: public AZ::Component
{
public:
AZ_COMPONENT(CustomAssetTypeComponent, "{E19B2FA4-60F1-4B7C-AF14-7C7A7D8DCFC2}");
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
private:
AZStd::unique_ptr<AzFramework::GenericAssetHandler<XmlSchemaAsset>> m_schemaAssetHandler;
AZStd::unique_ptr<AzFramework::GenericAssetHandler<FileTag::FileTagAsset>> m_fileTagAssetHandler;
AZStd::unique_ptr<AzFramework::GenericAssetHandler<BenchmarkSettingsAsset>> m_benchmarkSettingsAssetAssetHandler;
AZStd::unique_ptr<AzFramework::GenericAssetHandler<BenchmarkAsset>> m_benchmarkAssetAssetHandler;
};
} // namespace AzFramework
@@ -0,0 +1,91 @@
/*
* 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 <AzFramework/Asset/FileTagAsset.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AzFramework
{
namespace FileTag
{
FileTagData::FileTagData(AZStd::set<AZStd::string> fileTags, FilePatternType filePatternType, const AZStd::string& comment)
: m_filePatternType(filePatternType)
, m_fileTags(fileTags)
, m_comment(AZStd::move(comment))
{
}
void FileTagData::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<FileTagData>()
->Version(2)
->Field("FilePatternType", &FileTagData::m_filePatternType)
->Field("FileTags", &FileTagData::m_fileTags)
->Field("Comment", &FileTagData::m_comment);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<FileTagData>("Definition", "Files/Patterns and their associated tags.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &FileTagData::m_filePatternType,
"File Pattern", "File Pattern can either be a regex or a wildcard.")
->Attribute(AZ::Edit::Attributes::EnumValues,
AZStd::vector<AZ::Edit::EnumConstant<FilePatternType>>
{
AZ::Edit::EnumConstant<FilePatternType>(FilePatternType::Exact, "Exact"),
AZ::Edit::EnumConstant<FilePatternType>(FilePatternType::Wildcard, "Wildcard"),
AZ::Edit::EnumConstant<FilePatternType>(FilePatternType::Regex, "Regex")
})
->DataElement(AZ::Edit::UIHandlers::Default, &FileTagData::m_fileTags, "File Tags", "List of tags associated with the file/pattern.")
->DataElement(AZ::Edit::UIHandlers::Default, &FileTagData::m_comment, "Comment", "Comment for the file tag definition");
}
}
}
void FileTagAsset::Reflect(AZ::ReflectContext* context)
{
FileTagData::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<FileTagAsset>()
->Version(1)
->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)
->Field("FileTagMap", &FileTagAsset::m_fileTagMap);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<FileTagAsset>("Definition", "Asset storing all the file/pattern tagging information.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &FileTagAsset::m_fileTagMap, "File Tag Map", "Container for storing file tagging information.");
}
}
}
const char* FileTagAsset::GetDisplayName()
{
return "File Tag";
}
const char* FileTagAsset::GetGroup()
{
return "FileTag";
}
const char* FileTagAsset::Extension()
{
return "filetag";
}
}
}
@@ -0,0 +1,71 @@
/*
* 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/Asset/AssetCommon.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
namespace FileTag
{
enum class FilePatternType : AZ::u8
{
//! The pattern is an eaxct match
Exact = 0,
//! The pattern is a file wildcard pattern (glob)
Wildcard,
//! The pattern is a regular expression pattern
Regex,
};
//! File Tag Data stores all the information related to the FileTagAsset.
struct FileTagData
{
AZ_TYPE_INFO(FileTagData, "{5F66E43B-548B-4AA8-8CD8-F6924F6031E6}");
FileTagData(AZStd::set<AZStd::string> fileTags, FilePatternType filePatternType = FilePatternType::Exact, const AZStd::string& comment = "");
FileTagData() = default;
static void Reflect(AZ::ReflectContext* context);
FilePatternType m_filePatternType = FilePatternType::Exact;
AZStd::set<AZStd::string> m_fileTags;
AZStd::string m_comment;
};
using FileTagMap = AZStd::map<AZStd::string, AzFramework::FileTag::FileTagData>;
class FileTagAsset
: public AZ::Data::AssetData
{
public:
AZ_RTTI(FileTagAsset, "{F3BE5CAB-85B7-44B7-9495-863863F6B267}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(FileTagAsset, AZ::SystemAllocator, 0);
static const char* GetDisplayName();
static const char* GetGroup();
static const char* Extension();
static void Reflect(AZ::ReflectContext* context);
FileTagMap m_fileTagMap;
};
}
}
@@ -0,0 +1,246 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_ASSET_GENERICASSETHANDLER_H
#define AZFRAMEWORK_ASSET_GENERICASSETHANDLER_H
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AzFramework
{
/**
* Generic implementation of an asset handler for any arbitrary type that is reflected for serialization.
*
* Simple or game specific assets that wish to make use of automated loading and editing facilities
* can use this handler with any asset type that's reflected for editing.
*
* Example:
*
* class MyAsset : public AZ::Data::AssetData
* {
* public:
* AZ_CLASS_ALLOCATOR(MyAsset, AZ::SystemAllocator, 0);
* AZ_RTTI(MyAsset, "{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}", AZ::Data::AssetData);
*
* static void Reflect(AZ::ReflectContext* context)
* {
* AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
* if (serialize)
* {
* serialize->Class<MyAsset>()
* ->Field("SomeField", &MyAsset::m_someField)
* ;
*
* AZ::EditContext* edit = serialize->GetEditContext();
* if (edit)
* {
* edit->Class<MyAsset>("My Asset", "Asset for representing X, Y, and Z")
* ->DataElement(0, &MyAsset::m_someField, "Some data field", "It's a float")
* ;
* }
* }
* }
*
* float m_someField;
* };
*
*
* using MyAssetHandler = GenericAssetHandler<MyAsset>;
*
*/
/**
* Just a base class to assign concrete RTTI to these classes - Don't derive from this - use GenericAssetHandler<T> instead.
* This being in the heirarchy allows you to easily ask whether a particular handler derives from this type and thus is a
* GenericAssetHandler.
*/
class GenericAssetHandlerBase : public AZ::Data::AssetHandler
{
public:
AZ_RTTI(GenericAssetHandlerBase, "{B153B8B5-25CC-4BB7-A2BD-9A47ECF4123C}", AZ::Data::AssetHandler);
virtual ~GenericAssetHandlerBase() {}
};
template <typename AssetType>
class GenericAssetHandler
: public GenericAssetHandlerBase
, private AZ::AssetTypeInfoBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(GenericAssetHandler<AssetType>, AZ::SystemAllocator, 0);
AZ_RTTI(GenericAssetHandler<AssetType>, "{8B36B3E8-8C0B-4297-BDA2-1648C155C78E}", GenericAssetHandlerBase);
GenericAssetHandler(const char* displayName,
const char* group,
const char* extension,
const AZ::Uuid& componentTypeId = AZ::Uuid::CreateNull(),
AZ::SerializeContext* serializeContext = nullptr)
: m_displayName(displayName)
, m_group(group)
, m_extension(extension)
, m_componentTypeId(componentTypeId)
, m_serializeContext(serializeContext)
{
AZ_Assert(extension, "Extension is required.");
if (extension[0] == '.')
{
++extension;
}
m_extension = extension;
AZ_Assert(!m_extension.empty(), "Invalid extension provided.");
if (!m_serializeContext)
{
EBUS_EVENT_RESULT(m_serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
}
AZ::AssetTypeInfoBus::Handler::BusConnect(AZ::AzTypeInfo<AssetType>::Uuid());
}
~GenericAssetHandler()
{
AZ::AssetTypeInfoBus::Handler::BusDisconnect();
}
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& /*id*/, const AZ::Data::AssetType& /*type*/) override
{
return aznew AssetType();
}
AZ::Data::AssetHandler::LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override
{
AssetType* assetData = asset.GetAs<AssetType>();
AZ_Assert(assetData, "Asset is of the wrong type.");
AZ_Assert(m_serializeContext, "Unable to retrieve serialize context.");
if (assetData)
{
return AZ::Utils::LoadObjectFromStreamInPlace<AssetType>(*stream, *assetData, m_serializeContext,
AZ::ObjectStream::FilterDescriptor(assetLoadFilterCB)) ?
AZ::Data::AssetHandler::LoadResult::LoadComplete :
AZ::Data::AssetHandler::LoadResult::Error;
}
return AZ::Data::AssetHandler::LoadResult::Error;
}
bool SaveAssetData(const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZ::IO::GenericStream* stream) override
{
AssetType* assetData = asset.GetAs<AssetType>();
AZ_Assert(assetData, "Asset is of the wrong type.");
if (assetData && m_serializeContext)
{
return AZ::Utils::SaveObjectToStream<AssetType>(*stream,
AZ::ObjectStream::ST_XML,
assetData,
m_serializeContext);
}
return false;
}
void DestroyAsset(AZ::Data::AssetPtr ptr) override
{
delete ptr;
}
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override
{
assetTypes.push_back(AZ::AzTypeInfo<AssetType>::Uuid());
}
void Register()
{
EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, EnableCatalogForAsset, AZ::AzTypeInfo<AssetType>::Uuid());
EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, AddExtension, m_extension.c_str());
AZ_Assert(AZ::Data::AssetManager::IsReady(), "AssetManager isn't ready!");
AZ::Data::AssetManager::Instance().RegisterHandler(this, AZ::AzTypeInfo<AssetType>::Uuid());
}
void Unregister()
{
if (AZ::Data::AssetManager::IsReady())
{
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
}
}
bool CanHandleAsset(const AZ::Data::AssetId& id) const
{
AZStd::string assetPath;
EBUS_EVENT_RESULT(assetPath, AZ::Data::AssetCatalogRequestBus, GetAssetPathById, id);
if (!assetPath.empty())
{
AZStd::string assetExtension;
if (AzFramework::StringFunc::Path::GetExtension(assetPath.c_str(), assetExtension, false))
{
return assetExtension == m_extension;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////
// AZ::AssetTypeInfoBus::Handler
AZ::Data::AssetType GetAssetType() const override
{
return AZ::AzTypeInfo<AssetType>::Uuid();
}
const char* GetAssetTypeDisplayName() const override
{
return m_displayName.c_str();
}
const char* GetGroup() const override
{
return m_group.c_str();
}
AZ::Uuid GetComponentTypeId() const override
{
return m_componentTypeId;
}
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override
{
extensions.push_back(m_extension);
}
//////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string m_displayName;
AZStd::string m_group;
AZStd::string m_extension;
AZ::Uuid m_componentTypeId = AZ::Uuid::CreateNull();
AZ::SerializeContext* m_serializeContext;
GenericAssetHandler(const GenericAssetHandler&) = delete;
};
} // namespace AzFramework
#endif // AZFRAMEWORK_ASSET_GENERICASSETHANDLER_H
@@ -0,0 +1,47 @@
/*
* 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/string/string.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
namespace AzFramework
{
namespace AssetSystem
{
//! This internal interface exists only to communicate to an Asset Catalog or manager of Asset Catalogs
//! such as the PlatformAddressedAssetCatlogManager
//! If you want to know when new assets are ready to load or have changed, listen to the AssetCatalogEventBus
//! which will notify you when assets have been updated on the main thread only, and only after the catalog itself has
//! updated with the new information.
class NetworkAssetUpdateInterface
{
public:
AZ_RTTI(NetworkAssetUpdateInterface, "{5041D165-41CF-4ED0-AD90-FBB7025AB2DC}");
NetworkAssetUpdateInterface() = default;
virtual ~NetworkAssetUpdateInterface() = default;
NetworkAssetUpdateInterface(NetworkAssetUpdateInterface&&) = delete;
NetworkAssetUpdateInterface& operator=(NetworkAssetUpdateInterface&&) = delete;
//! Called by the AssetProcessor when an asset in the cache has been modified.
virtual void AssetChanged(AzFramework::AssetSystem::AssetNotificationMessage /*message*/) = 0;
//! Called by the AssetProcessor when an asset in the cache has been removed.
virtual void AssetRemoved(AzFramework::AssetSystem::AssetNotificationMessage /*message*/) = 0;
//! If we want to hear about assets for multiple platforms or something other than the asset platform defined at startup
virtual AZStd::string GetSupportedPlatforms() { return {}; }
};
} // namespace AssetSystem
} // namespace AzFramework
@@ -0,0 +1,43 @@
/*
* 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/Serialization/EditContext.h>
#include <AzFramework/Asset/SimpleAsset.h>
namespace AzFramework
{
const char* SimpleAssetTypeGetName(const AZ::Data::AssetType& nameUuid)
{
char varName[SimpleAssetReferenceBase::kMaxVariableNameLength] = { 0 };
azsnprintf(varName, SimpleAssetReferenceBase::kMaxVariableNameLength, "assetname%s", nameUuid.ToString<AZStd::string>().c_str());
auto variable = AZ::Environment::FindVariable<AssetInfoString>(varName);
if (variable)
{
return (*variable).c_str();
}
return "";
}
const char* SimpleAssetTypeGetFileFilter(const AZ::Data::AssetType& nameUuid)
{
char varName[SimpleAssetReferenceBase::kMaxVariableNameLength] = { 0 };
azsnprintf(varName, SimpleAssetReferenceBase::kMaxVariableNameLength, "assetfilter%s", nameUuid.ToString<AZStd::string>().c_str());
auto variable = AZ::Environment::FindVariable<AssetInfoString>(varName);
if (variable)
{
return (*variable).c_str();
}
return "";
}
} // namespace AzFramework
@@ -0,0 +1,233 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_SIMPLEASSET_H
#define AZFRAMEWORK_SIMPLEASSET_H
#include <AzCore/base.h>
#pragma once
/*!
* Asset references are simply game-folder relative paths.
* This will change as the asset system comes online, but in the mean time
* we need something we can reflect and use intuitively in the editor.
*
* Asset types are a simple class with a required API, e.g.:
*
* class MyAsset
* {
* static const char* GetName() { return "MyAsset"; }
* static const char* GetFileFilter() { return "*.myasset;*.myasset2"; }
* static const char* GetUuid() { return "{00000000-0000-0000-0000-000000000000}"; }
* }
*
* You must register your asset type's information with the environment
* and serialization context:
* SimpleAssetReference<MyAsset>::Register(serializeContext);
*
* You can now reflect references to your asset from components, etc. e.g.:
* In class header:
* AzFramework::SimpleAssetReference<MyAsset> m_asset;
* In reflection:
* ->DataElement("SimpleAssetRef", &MeshComponent::m_meshAsset, "My Asset", "The asset to use")g
*
* "SimpleAssetRef" tells the UI to use the corresponding widget.
* UI code will make use of your registered asset information to browse for the correct file types.
*/
#include <AzCore/std/string/string.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Component/Component.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/typetraits/alignment_of.h>
#include <AzCore/Asset/AssetCommon.h>
namespace AzFramework
{
/*!
* Base class for templated asset reference types.
* - Handles storage of the game-relative asset path.
* - Handles reflection of reference type for use in serialization/editing.
*/
class SimpleAssetReferenceBase
{
public:
static const int kMaxVariableNameLength = 128;
virtual ~SimpleAssetReferenceBase() { }
AZ_CLASS_ALLOCATOR(SimpleAssetReferenceBase, AZ::SystemAllocator, 0);
AZ_RTTI(SimpleAssetReferenceBase, "{E16CA6C5-5C78-4AD9-8E9B-F8C1FB4D1DB8}");
const AZStd::string& GetAssetPath() const { return m_assetPath; }
void SetAssetPath(const char* path) { m_assetPath = path; }
virtual AZ::Data::AssetType GetAssetType() const = 0;
virtual const char* GetFileFilter() const = 0;
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SimpleAssetReferenceBase>()
->Version(1)
->Field("AssetPath", &SimpleAssetReferenceBase::m_assetPath);
AZ::EditContext* edit = serializeContext->GetEditContext();
if (edit)
{
edit->Class<SimpleAssetReferenceBase>("Asset path", "Asset reference as a project-relative path")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SimpleAssetReferenceBase>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Property("assetPath", &SimpleAssetReferenceBase::GetAssetPath, nullptr)
->Property("assetType", &SimpleAssetReferenceBase::GetAssetType, nullptr)
->Property("fileFilter", &SimpleAssetReferenceBase::GetFileFilter, nullptr)
->Method("SetAssetPath", &SimpleAssetReferenceBase::SetAssetPath)
->Attribute(AZ::Script::Attributes::Alias, "set_asset_path")
;
}
}
protected:
AZStd::string m_assetPath;
};
using AssetInfoString = AZStd::basic_string<
char,
AZStd::char_traits<char>,
AZStd::static_buffer_allocator<128, AZStd::alignment_of<char>::value> >;
inline const AZ::Uuid SimpleAssetReferenceTypeId = { "{D03D0CF6-9A61-4DBA-AC53-E62453CE940D}" };
/*!
* Templated asset reference type.
* This currently acts as a convenience helper for registering
* asset type information with the environment.
* e.g. SimpleAssetReference<Mesh>::Register();
*/
template<typename AssetType>
class SimpleAssetReference
: public SimpleAssetReferenceBase
{
public:
AZ_CLASS_ALLOCATOR(SimpleAssetReference<AssetType>, AZ::SystemAllocator, 0);
AZ_RTTI((SimpleAssetReference<AssetType>, SimpleAssetReferenceTypeId, AssetType), SimpleAssetReferenceBase);
static void Register(AZ::SerializeContext& context)
{
RegisterAssetTypeName();
RegisterAssetTypeFileFilter();
context.Class<SimpleAssetReference<AssetType>, SimpleAssetReferenceBase>()
->Version(1)
;
}
AZ::Data::AssetType GetAssetType() const override { return AZ::Data::AssetType(AZ::AzTypeInfo<AssetType>::Uuid()); }
const char* GetFileFilter() const override { return AssetType::GetFileFilter(); }
private:
static void RegisterAssetTypeName()
{
char varName[SimpleAssetReferenceBase::kMaxVariableNameLength];
azsnprintf(varName, SimpleAssetReferenceBase::kMaxVariableNameLength, "assetname%s",
AZ::AzTypeInfo<AssetType>::Uuid().template ToString<AZStd::string>().c_str());
s_name = AZ::Environment::FindVariable<AssetInfoString>(varName);
if (!s_name)
{
s_name = AZ::Environment::CreateVariable<AssetInfoString>(varName);
AZ_Assert(s_name, "Could not create an environmental variable with name '%s'", varName);
}
(*s_name) = AssetType::TYPEINFO_Name();
}
static void RegisterAssetTypeFileFilter()
{
char varName[SimpleAssetReferenceBase::kMaxVariableNameLength];
azsnprintf(varName, SimpleAssetReferenceBase::kMaxVariableNameLength, "assetfilter%s",
AZ::AzTypeInfo<AssetType>::Uuid().template ToString<AZStd::string>().c_str());
s_filter = AZ::Environment::FindVariable<AssetInfoString>(varName);
if (!s_filter)
{
s_filter = AZ::Environment::CreateVariable<AssetInfoString>(varName);
AZ_Assert(s_filter, "Could not create an environmental variable with name '%s'", varName);
}
(*s_filter) = AssetType::GetFileFilter();
}
static AZ::EnvironmentVariable<AssetInfoString> s_name;
static AZ::EnvironmentVariable<AssetInfoString> s_filter;
};
template<typename AssetType>
AZ::EnvironmentVariable<AssetInfoString> SimpleAssetReference<AssetType>::s_name;
template<typename AssetType>
AZ::EnvironmentVariable<AssetInfoString> SimpleAssetReference<AssetType>::s_filter;
/*!
* Retrieves the name of an asset by asset type (which is actually a name Crc).
* This information is stored in the environment, so it's accessible from any module.
*/
const char* SimpleAssetTypeGetName(const AZ::Data::AssetType& assetType);
/*!
* Retrieves the file filter for an asset type.
* This information is stored in the environment, so it's accessible from any module.
*/
const char* SimpleAssetTypeGetFileFilter(const AZ::Data::AssetType& assetType);
} // namespace AzFramework
namespace AZ
{
//! OnDemandReflection for any generic SimpleAssetReference<T>
template<typename T>
struct OnDemandReflection<AzFramework::SimpleAssetReference<T>>
{
using SimpleAssetReferenceType = AzFramework::SimpleAssetReference<T>;
static void Reflect(ReflectContext* context)
{
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SimpleAssetReferenceType>(SimpleAssetReferenceType::RTTI_TypeName())
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "asset")
;
}
}
};
//! This is being declared so that azrtti_typeid<AzFramework::SimpleAssetReference>() will work
AZ_TYPE_INFO_INTERNAL_VARIATION_GENERIC(AzFramework::SimpleAssetReference, AzFramework::SimpleAssetReferenceTypeId)
}
#endif // AZFRAMEWORK_SIMPLEASSET_H
@@ -0,0 +1,345 @@
/*
* 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 "XmlSchemaAsset.h"
namespace AzFramework
{
void VersionSearchRule::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<VersionSearchRule>()
->Version(1)
->Field("RootNodeAttributeName", &VersionSearchRule::m_rootNodeAttributeName);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<VersionSearchRule>("Version Search Rule", "Rule for getting the attribute of the root node which specifies the version")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &VersionSearchRule::m_rootNodeAttributeName, "Root Node Attribute Name", "Attribute name of the root node which specifies the version. Example: versionnumber");
}
}
}
AZStd::string VersionSearchRule::GetRootNodeAttributeName() const
{
return m_rootNodeAttributeName;
}
void MatchingRule::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<MatchingRule>()
->Version(1)
->Field("FilePathPattern", &MatchingRule::m_filePathPattern)
->Field("ExcludedFilePathPattern", &MatchingRule::m_excludedFilePathPattern)
->Field("VersionConstraints", &MatchingRule::m_versionConstraints);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<MatchingRule>("Matching Rules", "Rules for matchup")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &MatchingRule::m_filePathPattern, "File Path Pattern", "Pattern of the file path. Example: *Fonts/*.xml")
->DataElement(AZ::Edit::UIHandlers::Default, &MatchingRule::m_excludedFilePathPattern, "Excluded File Path Pattern", "Pattern of the excluded file path. Example: *Fonts/*.xml")
->DataElement(AZ::Edit::UIHandlers::Default, &MatchingRule::m_versionConstraints, "Version Constraints", "Data file versions these rules adapt to. These constraints follow the rules of Semantic Versioning. Example: >=1.2.3, ~>1.2.3");
}
}
}
bool MatchingRule::Valid() const
{
return !m_filePathPattern.empty();
}
AZStd::string MatchingRule::GetFilePathPattern() const
{
return m_filePathPattern;
}
AZStd::string MatchingRule::GetExcludedFilePathPattern() const
{
return m_excludedFilePathPattern;
}
AZStd::vector<AZStd::string> MatchingRule::GetVersionConstraints() const
{
return m_versionConstraints;
}
void XmlSchemaAttribute::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<XmlSchemaAttribute>()
->Version(6)
->Field("Name", &XmlSchemaAttribute::m_name)
->Field("ExpectedExtension", &XmlSchemaAttribute::m_expectedExtension)
->Field("MatchPattern", &XmlSchemaAttribute::m_matchPattern)
->Field("FindPattern", &XmlSchemaAttribute::m_findPattern)
->Field("ReplacePattern", &XmlSchemaAttribute::m_replacePattern)
->Field("Type", &XmlSchemaAttribute::m_type)
->Field("PathDependencyType", &XmlSchemaAttribute::m_pathDependencyType)
->Field("RelativeToSourceAssetFolder", &XmlSchemaAttribute::m_relativeToSourceAssetFolder)
->Field("Optional", &XmlSchemaAttribute::m_optional)
->Field("CacheRelativePath", &XmlSchemaAttribute::m_cacheRelativePath);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<XmlSchemaAttribute>("XmlSchemaAttribute", "XML Schema attribute")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_name, "Name", "Name of the attribute")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_expectedExtension, "Expected Extension", "Expected extension for the file name.")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_matchPattern, "Match Pattern", "(Optional) Values that don't match this regex pattern will be rejected. Case-insensitive.")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_findPattern, "Find Pattern", "(Optional) Regex pattern to use to match against the value for replacing. Case-insensitive.")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_replacePattern, "Replace Pattern", "(Optional) Regex pattern to use to replace the value.")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &XmlSchemaAttribute::m_type, "Type", "Type of the attribute. Select from RelativePath, AssetId, etc.")
->EnumAttribute(AttributeType::RelativePath, "RelativePath")
->EnumAttribute(AttributeType::Asset, "Asset")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &XmlSchemaAttribute::m_pathDependencyType, "Path Dependency Type", "Path dependency type of the attribute. Select from SourceFile and ProductFile")
->Attribute(AZ::Edit::Attributes::Visibility, &XmlSchemaAttribute::GetVisibilityProperty)
->EnumAttribute(AttributePathDependencyType::SourceFile, "SourceFile")
->EnumAttribute(AttributePathDependencyType::ProductFile, "ProductFile")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_relativeToSourceAssetFolder, "RelativeToSourceAssetFolder", "Whether the file path is relative to the source asset folder")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_optional, "Optional", "Whether the attribute is optional")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAttribute::m_cacheRelativePath, "CacheRelativePath", "CacheRelative allows dependent assets to be from other scan folders.");
}
}
}
AZStd::string XmlSchemaAttribute::GetName() const
{
return m_name;
}
AZStd::string XmlSchemaAttribute::GetExpectedExtension() const
{
return m_expectedExtension;
}
AZStd::string XmlSchemaAttribute::GetMatchPattern() const
{
return m_matchPattern;
}
AZStd::string XmlSchemaAttribute::GetFindPattern() const
{
return m_findPattern;
}
AZStd::string XmlSchemaAttribute::GetReplacePattern() const
{
return m_replacePattern;
}
XmlSchemaAttribute::AttributeType XmlSchemaAttribute::GetType() const
{
return m_type;
}
XmlSchemaAttribute::AttributePathDependencyType XmlSchemaAttribute::GetPathDependencyType() const
{
return m_pathDependencyType;
}
bool XmlSchemaAttribute::IsRelativeToSourceAssetFolder() const
{
return m_relativeToSourceAssetFolder;
}
bool XmlSchemaAttribute::CacheRelativePath() const
{
return m_cacheRelativePath;
}
bool XmlSchemaAttribute::IsOptional() const
{
return m_optional;
}
AZ::Crc32 XmlSchemaAttribute::GetVisibilityProperty() const
{
return m_type == AttributeType::RelativePath ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
void XmlSchemaElement::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<XmlSchemaElement>()
->Version(2)
->Field("Name", &XmlSchemaElement::m_name)
->Field("ChildElements", &XmlSchemaElement::m_childElements)
->Field("Attributes", &XmlSchemaElement::m_attributes)
->Field("Optional", &XmlSchemaElement::m_optional);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<XmlSchemaElement>("XmlSchemaElement", "XML Schema Element")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaElement::m_name, "Name", "Name of the element")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaElement::m_childElements, "Child Elements", "Children of the element")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaElement::m_attributes, "Attributes", "Attributes of the element")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaElement::m_optional, "Optional", "Whether the element is optional");
}
}
}
AZStd::string XmlSchemaElement::GetName() const
{
return m_name;
}
AZStd::vector<XmlSchemaElement> XmlSchemaElement::GetChildElements() const
{
return m_childElements;
}
AZStd::vector<XmlSchemaAttribute> XmlSchemaElement::GetAttributes() const
{
return m_attributes;
}
bool XmlSchemaElement::IsOptional() const
{
return m_optional;
}
bool XmlSchemaElement::Valid() const
{
if (m_name.empty())
{
return false;
}
for (const XmlSchemaElement& childElement : m_childElements)
{
if (!childElement.Valid())
{
return false;
}
}
return true;
}
void SearchRuleDefinition::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SearchRuleDefinition>()
->Version(1)
->Field("SearchRuleStructure", &SearchRuleDefinition::m_searchRuleStructure)
->Field("RelativeToXmlRoot", &SearchRuleDefinition::m_relativeToXmlRoot);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SearchRuleDefinition>("SearchRuleDefinition", "Definition for the dependency search rule")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchRuleDefinition::m_searchRuleStructure, "Search Rule Structure", "Search rule structure which contain element and attribute nodes")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchRuleDefinition::m_relativeToXmlRoot, "Relative to XML Root", "Whether the element is relative to XML root");
}
}
}
XmlSchemaElement SearchRuleDefinition::GetSearchRuleStructure() const
{
return m_searchRuleStructure;
}
bool SearchRuleDefinition::IsRelativeToXmlRoot() const
{
return m_relativeToXmlRoot;
}
void DependencySearchRule::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<DependencySearchRule>()
->Version(2)
->Field("SearchRuleDefinitions", &DependencySearchRule::m_searchRuleDefinitions)
->Field("VersionConstraints", &DependencySearchRule::m_versionConstraints);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<DependencySearchRule>("DependencySearchRule", "Dependency search rules")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &DependencySearchRule::m_searchRuleDefinitions, "Search Rule Definitions", "A list of Definitions for dependency search rules")
->DataElement(AZ::Edit::UIHandlers::Default, &DependencySearchRule::m_versionConstraints, "Version Constraints", "Data file versions these rules adapt to. These constraints follow the rules of Semantic Versioning. Example: >=1.2.3: Minimum: 1.2.3 Maximum: None");
}
}
}
AZStd::vector<SearchRuleDefinition> DependencySearchRule::GetSearchRules() const
{
return m_searchRuleDefinitions;
}
AZStd::vector<AZStd::string> DependencySearchRule::GetVersionConstraints() const
{
return m_versionConstraints;
}
bool DependencySearchRule::Valid() const
{
for (const SearchRuleDefinition& searchRuleDefinition : m_searchRuleDefinitions)
{
if (!searchRuleDefinition.GetSearchRuleStructure().Valid())
{
return false;
}
}
return true;
}
void XmlSchemaAsset::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<XmlSchemaAsset>()
->Version(3)
->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)
->Field("VersionSearchRule", &XmlSchemaAsset::m_versionSearchRule)
->Field("MatchingRules", &XmlSchemaAsset::m_matchingRules)
->Field("DependencySearchRules", &XmlSchemaAsset::m_dependencySearchRules)
->Field("useAZSerialization", &XmlSchemaAsset::m_useAZSerialization);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<XmlSchemaAsset>("Definition", "Definition of the schema asset")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAsset::m_versionSearchRule, "Version Search Rule", "VersionSearchRule")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAsset::m_matchingRules, "Matching Rules", "A list of matching rules defined by the current schema")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAsset::m_useAZSerialization, "Use AZ Serialization for dependencies", "Use AZ serialization to extract dependencies from matching files")
->DataElement(AZ::Edit::UIHandlers::Default, &XmlSchemaAsset::m_dependencySearchRules, "Dependency Search Rules", "A list of dependency search rules defined by the current schema");
}
}
}
VersionSearchRule XmlSchemaAsset::GetVersionSearchRule() const
{
return m_versionSearchRule;
}
AZStd::vector<MatchingRule> XmlSchemaAsset::GetMatchingRules() const
{
return m_matchingRules;
}
AZStd::vector<DependencySearchRule> XmlSchemaAsset::GetDependencySearchRules() const
{
return m_dependencySearchRules;
}
}
@@ -0,0 +1,172 @@
/*
* 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/containers/vector.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AzFramework
{
class VersionSearchRule
{
public:
AZ_TYPE_INFO(VersionSearchRule, "{0AC0D453-7F3E-4BA2-9D28-B39052361B0F}");
static void Reflect(AZ::ReflectContext* context);
AZStd::string GetRootNodeAttributeName() const;
private:
AZStd::string m_rootNodeAttributeName;
};
class MatchingRule
{
public:
AZ_TYPE_INFO(MatchingRule, "{0052598D-594B-44C7-8B0F-F268FBBA6E4F}");
static void Reflect(AZ::ReflectContext* context);
AZStd::string GetFilePathPattern() const;
AZStd::string GetExcludedFilePathPattern() const;
AZStd::vector<AZStd::string> GetVersionConstraints() const;
bool Valid() const;
private:
AZStd::string m_filePathPattern;
AZStd::string m_excludedFilePathPattern;
AZStd::vector<AZStd::string> m_versionConstraints;
};
class XmlSchemaAttribute
{
public:
AZ_TYPE_INFO(XmlSchemaAttribute, "{EE322552-0565-4D54-B022-9A9F134BF447}");
enum class AttributeType : AZ::u32
{
RelativePath,
Asset
};
enum class AttributePathDependencyType : AZ::u32
{
SourceFile,
ProductFile
};
static void Reflect(AZ::ReflectContext* context);
AZStd::string GetName() const;
AZStd::string GetExpectedExtension() const;
AZStd::string GetMatchPattern() const;
AZStd::string GetFindPattern() const;
AZStd::string GetReplacePattern() const;
AttributeType GetType() const;
AttributePathDependencyType GetPathDependencyType() const;
bool IsRelativeToSourceAssetFolder() const;
bool IsOptional() const;
bool CacheRelativePath() const;
private:
AZ::Crc32 GetVisibilityProperty() const;
AZStd::string m_name;
AZStd::string m_expectedExtension;
AZStd::string m_matchPattern;
AZStd::string m_findPattern;
AZStd::string m_replacePattern;
AttributeType m_type;
AttributePathDependencyType m_pathDependencyType;
bool m_relativeToSourceAssetFolder;
bool m_optional;
bool m_cacheRelativePath;
};
class XmlSchemaElement
{
public:
AZ_TYPE_INFO(XmlSchemaElement, "{DB03558D-9533-4426-B50F-3DB16F7AA686}");
static void Reflect(AZ::ReflectContext* context);
AZStd::string GetName() const;
AZStd::vector<XmlSchemaElement> GetChildElements() const;
AZStd::vector<XmlSchemaAttribute> GetAttributes() const;
bool IsOptional() const;
bool Valid() const;
private:
AZStd::vector<XmlSchemaElement> m_childElements;
AZStd::vector<XmlSchemaAttribute> m_attributes;
AZStd::string m_name;
bool m_optional;
};
class SearchRuleDefinition
{
public:
AZ_TYPE_INFO(SearchRuleDefinition, "{DA29525E-3032-4919-97B2-3FECCDFF06A6}");
static void Reflect(AZ::ReflectContext* context);
XmlSchemaElement GetSearchRuleStructure() const;
bool IsRelativeToXmlRoot() const;
private:
XmlSchemaElement m_searchRuleStructure;
bool m_relativeToXmlRoot;
};
class DependencySearchRule
{
public:
AZ_TYPE_INFO(DependencySearchRule, "{5A6EB7DE-A2EC-47F3-A2AB-91F0560C2E66}");
static void Reflect(AZ::ReflectContext* context);
AZStd::vector<SearchRuleDefinition> GetSearchRules() const;
AZStd::vector<AZStd::string> GetVersionConstraints() const;
bool Valid() const;
private:
AZStd::vector<SearchRuleDefinition> m_searchRuleDefinitions;
AZStd::vector<AZStd::string> m_versionConstraints;
};
class XmlSchemaAsset
: public AZ::Data::AssetData
{
public:
AZ_RTTI(XmlSchemaAsset, "{2DF35909-AF12-40A8-BED2-A033478D864D}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(XmlSchemaAsset, AZ::SystemAllocator, 0);
static const char* GetDisplayName() { return "XML Schema"; }
static const char* GetGroup() { return "XmlSchema"; }
static const char* GetFileFilter() { return "xmlschema"; }
static void Reflect(AZ::ReflectContext* context);
VersionSearchRule GetVersionSearchRule() const;
AZStd::vector<MatchingRule> GetMatchingRules() const;
AZStd::vector<DependencySearchRule> GetDependencySearchRules() const;
bool UseAZSerialization() const { return m_useAZSerialization; }
private:
VersionSearchRule m_versionSearchRule;
AZStd::vector<MatchingRule> m_matchingRules;
AZStd::vector<DependencySearchRule> m_dependencySearchRules;
bool m_useAZSerialization = false; // The XML file is in AZ serialization format, dependencies can be extracted with that.
};
}
@@ -0,0 +1,76 @@
/*
* 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 <AzFramework/AzFrameworkModule.h>
// Component includes
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/Asset/CustomAssetTypeComponent.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzFramework/Driller/RemoteDrillerInterface.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/FileTag/FileTagComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Script/ScriptComponent.h>
#include <AzFramework/Script/ScriptRemoteDebugging.h>
#include <AzFramework/StreamingInstall/StreamingInstall.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Visibility/OctreeSystemComponent.h>
namespace AzFramework
{
AzFrameworkModule::AzFrameworkModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
AzFramework::AssetCatalogComponent::CreateDescriptor(),
AzFramework::CustomAssetTypeComponent::CreateDescriptor(),
AzFramework::FileTag::ExcludeFileComponent::CreateDescriptor(),
AzFramework::NetBindingComponent::CreateDescriptor(),
AzFramework::NetBindingSystemComponent::CreateDescriptor(),
AzFramework::TransformComponent::CreateDescriptor(),
AzFramework::NonUniformScaleComponent::CreateDescriptor(),
AzFramework::GameEntityContextComponent::CreateDescriptor(),
AzFramework::RenderGeometry::GameIntersectorComponent::CreateDescriptor(),
#if !defined(_RELEASE)
AzFramework::TargetManagementComponent::CreateDescriptor(),
#endif
AzFramework::CreateScriptDebugAgentFactory(),
AzFramework::AssetSystem::AssetSystemComponent::CreateDescriptor(),
AzFramework::InputSystemComponent::CreateDescriptor(),
AzFramework::DrillerNetworkAgentComponent::CreateDescriptor(),
#if !defined(AZCORE_EXCLUDE_LUA)
AzFramework::ScriptComponent::CreateDescriptor(),
#endif
AzFramework::SceneSystemComponent::CreateDescriptor(),
AzFramework::StreamingInstall::StreamingInstallSystemComponent::CreateDescriptor(),
AzFramework::AzFrameworkConfigurationSystemComponent::CreateDescriptor(),
AzFramework::OctreeSystemComponent::CreateDescriptor(),
});
}
AZ::ComponentTypeList AzFrameworkModule::GetRequiredSystemComponents() const
{
return AZ::ComponentTypeList
{
azrtti_typeid<AzFramework::OctreeSystemComponent>(),
};
}
}
@@ -0,0 +1,30 @@
/*
* 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/Module/Module.h>
namespace AzFramework
{
class AzFrameworkModule
: public AZ::Module
{
public:
AZ_RTTI(AzFrameworkModule, "{FC9FEAC4-ADF5-426B-B26D-96A3413F3AF2}", AZ::Module);
AZ_CLASS_ALLOCATOR(AzFrameworkModule, AZ::OSAllocator, 0);
AzFrameworkModule();
~AzFrameworkModule() override = default;
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
}
@@ -0,0 +1,19 @@
/*
* 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/Settings/CommandLine.h>
namespace AzFramework
{
using CommandLine = AZ::CommandLine;
} // namespace AzFramework
@@ -0,0 +1,68 @@
/*
* 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/EBus/EBus.h>
namespace AzFramework
{
/** Standard results from a command callback function
*/
enum class CommandResult
{
Success,
Error,
ErrorWrongNumberOfArguments,
ErrorCommandNotFound,
};
/** The command callback signature
*/
using CommandFunction = AZStd::function<CommandResult(const AZStd::vector<AZStd::string_view>& args)>;
/** Flags to combine for a command to describe its behavior in the command callback system
* Note: the command flag values should match "enum EVarFlags" values inside IConsole.h
*/
enum CommandFlags : AZ::u32
{
NoValue = 0x00000000, // the default no flags value
Cheat = 0x00000002, // a command that is a game cheat
Development = 0x00000004, // registered only for non release builds
Restricted = 0x00080000, // a visible command and usable in restricted mode
Invisible = 0x00100000, // invisible to the user when listing commands
BlockFrame = 0x00400000, // blocks the execution of console commands for one frame
};
/** A command registration system to register console commands
*/
class CommandRegistration
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
/** Adds a command callback function to be executed with a string identifier
*/
virtual bool RegisterCommand(AZStd::string_view identifier, AZStd::string_view helpText, AZ::u32 commandFlags, CommandFunction callback) = 0;
/** Removes a command (using its identifier)
*/
virtual bool UnregisterCommand(AZStd::string_view identifier) = 0;
};
using CommandRegistrationBus = AZ::EBus<CommandRegistration>;
} // namespace AzFramework
@@ -0,0 +1,101 @@
/*
* 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 <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Debug/Trace.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemBus.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
namespace AzFramework
{
void AzFrameworkConfigurationSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AzFrameworkConfigurationSystemComponent, AZ::Component>();
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<AzFrameworkConfigurationSystemComponent>(
"AzFramework Configuration Component", "System component responsible for configuring AzFramework")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Editor")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Method("Terminate", &AZ::Debug::Trace::Terminate, nullptr, "Terminates the process with the specified exit code")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "framework")
;
}
}
void AzFrameworkConfigurationSystemComponent::Activate()
{
// Create the defaults scene and associate the GameEntityContext with it.
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("SceneSystemRequests bus not responding.");
SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequests::CreateScene, "default");
if (createSceneOutcome)
{
Scene* scene = createSceneOutcome.GetValue();
bool success = false;
EntityContextId gameEntityContextId;
GameEntityContextRequestBus::BroadcastResult(gameEntityContextId, &GameEntityContextRequests::GetGameEntityContextId);
if (!gameEntityContextId.IsNull())
{
SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequests::SetSceneForEntityContextId, gameEntityContextId, scene);
}
AZ_Assert(success, "The application was unable to setup a scene for the game entity context, this should always work");
}
else
{
AZ_Assert(false, "%s", createSceneOutcome.GetError().data());
}
}
void AzFrameworkConfigurationSystemComponent::Deactivate()
{
bool success = false;
SceneSystemRequestBus::BroadcastResult(
success, &AzFramework::SceneSystemRequestBus::Events::RemoveScene, "default");
AZ_Assert(success, "\"default\" scene was not removed");
}
void AzFrameworkConfigurationSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("AzFrameworkConfigurationSystemComponentService", 0xcc49c96e));
}
void AzFrameworkConfigurationSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("AzFrameworkConfigurationSystemComponentService", 0xcc49c96e));
}
void AzFrameworkConfigurationSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("SceneSystemComponentService", 0xd8975435));
dependent.push_back(AZ_CRC("GameEntityContextService", 0xa6f2c885));
}
} // AzFramework
@@ -0,0 +1,40 @@
/*
* 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/Component/Component.h>
namespace AzFramework
{
class AzFrameworkConfigurationSystemComponent
: public AZ::Component
{
public:
AZ_COMPONENT(AzFrameworkConfigurationSystemComponent, "{19BB423E-B7FD-45D3-8673-A7FA5A4C92F6}", AZ::Component);
AzFrameworkConfigurationSystemComponent() = default;
~AzFrameworkConfigurationSystemComponent() override = default;
//////////////////////////////////////////////////////////////////////////
// Component overrides
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
};
} // AzFramework
@@ -0,0 +1,254 @@
/*
* 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/Component/ComponentBus.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Transform.h>
namespace Camera
{
struct Configuration
{
float m_fovRadians = 0.f;
float m_nearClipDistance = 0.f;
float m_farClipDistance = 0.f;
float m_frustumWidth = 0.f;
float m_frustumHeight = 0.f;
};
/**
* Use this bus to send messages to a camera component on an entity
* If you create your own camera you should implement this bus
* Call like this:
* Camera::CameraRequestBus::Event(cameraEntityId, &Camera::CameraRequestBus::Events::SetFov, newFov);
*/
class CameraComponentRequests
: public AZ::ComponentBus
{
public:
virtual ~CameraComponentRequests() = default;
/**
* Gets the camera's field of view in degrees
* @return The camera's field of view in degrees
*/
virtual float GetFov()
{
AZ_WarningOnce("CameraBus", false, "GetFov is deprecated. Please use GetFovDegrees or GetFovRadians.");
return GetFovDegrees();
}
/**
* Gets the camera's field of view in degrees
* @return The camera's field of view in degrees
*/
virtual float GetFovDegrees() = 0;
/**
* Gets the camera's field of view in radians
* @return The camera's field of view in radians
*/
virtual float GetFovRadians() = 0;
/**
* Gets the camera's distance from the near clip plane in meters
* @return The camera's distance from the near clip plane in meters
*/
virtual float GetNearClipDistance() = 0;
/**
* Gets the camera's distance from the far clip plane in meters
* @return The camera's distance from the far clip plane in meters
*/
virtual float GetFarClipDistance() = 0;
/**
* Gets the camera frustum's width
* @return The camera frustum's width
*/
virtual float GetFrustumWidth() = 0;
/**
* Gets the camera frustum's height
* @return The camera frustum's height
*/
virtual float GetFrustumHeight() = 0;
/**
* Sets the camera's field of view in degrees between 0 < fov < 180 degrees
* @param fov The camera frustum's new field of view in degrees
*/
virtual void SetFov(float fov)
{
AZ_WarningOnce("CameraBus", false, "SetFov is deprecated. Please use SetFovDegrees or SetFovRadians.");
SetFovDegrees(fov);
}
/**
* Sets the camera's field of view in degrees between 0 < fov < 180 degrees
* @param fov The camera frustum's new field of view in degrees
*/
virtual void SetFovDegrees(float fovInDegrees) = 0;
/**
* Sets the camera's field of view in radians between 0 < fov < pi radians
* @param fov The camera frustum's new field of view in radians
*/
virtual void SetFovRadians(float fovInRadians) = 0;
/**
* Sets the near clip plane to a given distance from the camera in meters. Should be small, but greater than 0
* @param nearClipDistance The camera frustum's new near clip plane distance from camera
*/
virtual void SetNearClipDistance(float nearClipDistance) = 0;
/**
* Sets the far clip plane to a given distance from the camera in meters.
* @param farClipDistance The camera frustum's new far clip plane distance from camera
*/
virtual void SetFarClipDistance(float farClipDistance) = 0;
/**
* Sets the camera frustum's width
* @param width The camera frustum's new width
*/
virtual void SetFrustumWidth(float width) = 0;
/**
* Sets the camera frustum's height
* @param height The camera frustum's new height
*/
virtual void SetFrustumHeight(float height) = 0;
/**
* Makes the camera the active view
*/
virtual void MakeActiveView() = 0;
virtual Configuration GetCameraConfiguration()
{
return Configuration
{
GetFovRadians(),
GetNearClipDistance(),
GetFarClipDistance(),
GetFrustumWidth(),
GetFrustumHeight()
};
}
};
using CameraRequestBus = AZ::EBus<CameraComponentRequests>;
/**
* Use this broadcast bus to gather a list of all active cameras
* If you create your own camera you should handle this bus
* Call like this:
*
* AZ::EBusAggregateResults<AZ::EntityId> results;
* Camera::CameraBus::BroadcastResult(results, &Camera::CameraRequests::GetCameras);
*/
class CameraRequests
: public AZ::EBusTraits
{
public:
virtual ~CameraRequests() = default;
/// Get a list of all cameras
virtual AZ::EntityId GetCameras() = 0;
};
using CameraBus = AZ::EBus<CameraRequests>;
/**
* Use this system broadcast for things like getting the active camera
*/
class CameraSystemRequests
: public AZ::EBusTraits
{
public:
virtual ~CameraSystemRequests() = default;
/**
* returns the camera being used by the active view
*/
virtual AZ::EntityId GetActiveCamera() = 0;
};
using CameraSystemRequestBus = AZ::EBus<CameraSystemRequests>;
//! This system broadcast offer the active camera information
//! even when the camera is not attached to an entity.
class ActiveCameraRequests
: public AZ::EBusTraits
{
public:
virtual ~ActiveCameraRequests() = default;
//! This returns the transform of the active view
virtual const AZ::Transform& GetActiveCameraTransform() = 0;
//! This returns the configuration of the active camera.
virtual const Configuration& GetActiveCameraConfiguration() = 0;
};
using ActiveCameraRequestBus = AZ::EBus<ActiveCameraRequests>;
/**
* Handle this bus if you want to know when cameras are added or removed during edit or run time
* You will get an OnCameraAdded event for each camera that is already active
* If you create your own camera you should call this bus on activation/deactivation
* Connect to the bus like this
* Camera::CameraNotificationBus::Handler::Connect()
*/
class CameraNotifications
: public AZ::EBusTraits
{
public:
template<class Bus>
struct CameraNotificationConnectionPolicy
: public AZ::EBusConnectionPolicy<Bus>
{
static void Connect(typename Bus::BusPtr& busPtr, typename Bus::Context& context, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& connectLock, const typename Bus::BusIdType& id = 0)
{
AZ::EBusConnectionPolicy<Bus>::Connect(busPtr, context, handler, connectLock, id);
AZ::EBusAggregateResults<AZ::EntityId> results;
CameraBus::BroadcastResult(results, &CameraRequests::GetCameras);
for (const AZ::EntityId& cameraId : results.values)
{
handler->OnCameraAdded(cameraId);
}
}
};
/**
* If the camera is active when a handler connects to the bus,
* then OnCameraAdded() is immediately dispatched.
*/
template<class Bus>
using ConnectionPolicy = CameraNotificationConnectionPolicy<Bus>;
virtual ~CameraNotifications() = default;
/**
* Called whenever a camera entity is added
* @param cameraId The id of the camera added
*/
virtual void OnCameraAdded(const AZ::EntityId& cameraId) = 0;
/**
* Called whenever a camera entity is removed
* @param cameraId The id of the camera removed
*/
virtual void OnCameraRemoved(const AZ::EntityId& cameraId) = 0;
};
using CameraNotificationBus = AZ::EBus<CameraNotifications>;
#define CameraComponentTypeId "{E2DC7EB8-02D1-4E6D-BFE4-CE652FCB7C7F}"
#define EditorCameraComponentTypeId "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D}"
} // namespace Camera
@@ -0,0 +1,100 @@
/*
* 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/Component/Component.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
namespace AzFramework
{
namespace Components
{
/** ComponentAdapter is a utility base class that provides a consistent pattern for implementing components
that operate in the launcher but may need to share code in different contexts like the editor.
ComponentAdapter achieves this by delegating to a controller class that implements common behavior instead of
duplicating code between multiple components.
To use the ComponentAdapter, 2 classes are required for the template:
- a class that implements the functions required for TController (see below)
- a configuration struct/class which extends AZ::ComponentConfig
The concrete component extends the adapter and implements behavior which is unique to the component.
TController can handle any common functionality between the runtime and editor component and is where most of the code for the
component will live
TConfiguration is where any data that needs to be serialized out should live.
TController must implement certain functions to conform to the template. These functions mirror those in
AZ::Component and must be accesible to any adapter that follows this pattern:
@code
static void Reflect(AZ::ReflectContext* context);
void Activate(EntityId entityId);
void Deactivate();
void SetConfiguration(const ComponentConfigurationType& config);
const ComponentConfigurationType& GetConfiguration() const;
@endcode
In addition, certain functions will optionally be called if they are available:
@code
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& services);
void Init();
@endcode
It is recommended that TController handle the SerializeContext, but the editor components handle
the EditContext. TController can friend itself to the editor component to make this work if required.
*/
template<typename TController, typename TConfiguration = AZ::ComponentConfig>
class ComponentAdapter
: public AZ::Component
{
public:
AZ_RTTI((ComponentAdapter, "{644A9187-4FDB-42C1-9D59-DD75304B551A}", TController, TConfiguration), AZ::Component);
ComponentAdapter() = default;
ComponentAdapter(const TConfiguration& configuration);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& services);
// AZ::Component overrides ...
void Init() override;
void Activate() override;
void Deactivate() override;
protected:
static void Reflect(AZ::ReflectContext* context);
// AZ::Component overrides ...
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
TController m_controller;
};
} // namespace Components
} // namespace AzFramework
#include "ComponentAdapter.inl"
@@ -0,0 +1,112 @@
/*
* 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 <AzFramework/Components/ComponentAdapterHelpers.h>
namespace AzFramework
{
namespace Components
{
template<typename TController, typename TConfiguration>
ComponentAdapter<TController, TConfiguration>::ComponentAdapter(const TConfiguration& configuration)
: m_controller(configuration)
{
}
//////////////////////////////////////////////////////////////////////////
// Serialization and version conversion
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::Reflect(AZ::ReflectContext* context)
{
TController::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ComponentAdapter, Component>()
->Version(1)
->Field("Controller", &ComponentAdapter::m_controller)
;
}
}
//////////////////////////////////////////////////////////////////////////
// Get*Services functions
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
GetProvidedServicesHelper<TController>(services, typename AZ::HasComponentProvidedServices<TController>::type());
}
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
GetRequiredServicesHelper<TController>(services, typename AZ::HasComponentRequiredServices<TController>::type());
}
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
GetIncompatibleServicesHelper<TController>(services, typename AZ::HasComponentIncompatibleServices<TController>::type());
}
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
GetDependentServicesHelper<TController>(services, typename AZ::HasComponentDependentServices<TController>::type());
}
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::Init()
{
ComponentInitHelper<TController>::Init(m_controller);
}
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::Activate()
{
m_controller.Activate(GetEntityId());
}
template<typename TController, typename TConfiguration>
void ComponentAdapter<TController, TConfiguration>::Deactivate()
{
m_controller.Deactivate();
}
template<typename TController, typename TConfiguration>
bool ComponentAdapter<TController, TConfiguration>::ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (const auto config = azrtti_cast<const TConfiguration*>(baseConfig))
{
m_controller.SetConfiguration(*config);
return true;
}
return false;
}
template<typename TController, typename TConfiguration>
bool ComponentAdapter<TController, TConfiguration>::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto config = azrtti_cast<TConfiguration*>(outBaseConfig))
{
*config = m_controller.GetConfiguration();
return true;
}
return false;
}
} // namespace Components
} // namespace AzFramework
@@ -0,0 +1,83 @@
/*
* 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/Component/Component.h>
namespace AzFramework
{
namespace Components
{
//////////////////////////////////////////////////////////////////////////
// Helper functions to make certain functions optional in the class wrapped by
// EditorComponentAdapter and ComponentAdapter.
// Make Init() Optional
template<typename T, typename = void>
struct ComponentInitHelper
{
static void Init(T& common)
{
AZ_UNUSED(common);
}
};
template<typename T>
struct ComponentInitHelper<T, AZStd::void_t<decltype(AZStd::declval<T>().Init())>>
{
static void Init(T& common)
{
common.Init();
}
};
// Make GetProvidedServices, GetDependentServicesHelper, GetRequiredServices and GetIncompatibleServices optional.
template<typename T>
void GetProvidedServicesHelper(AZ::ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) {}
template<typename T>
void GetProvidedServicesHelper(AZ::ComponentDescriptor::DependencyArrayType& services, const AZStd::true_type&)
{
T::GetProvidedServices(services);
}
template<typename T>
void GetDependentServicesHelper(AZ::ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) {}
template<typename T>
void GetDependentServicesHelper(AZ::ComponentDescriptor::DependencyArrayType& services, const AZStd::true_type&)
{
T::GetDependentServices(services);
}
template<typename T>
void GetRequiredServicesHelper(AZ::ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) {}
template<typename T>
void GetRequiredServicesHelper(AZ::ComponentDescriptor::DependencyArrayType& services, const AZStd::true_type&)
{
T::GetRequiredServices(services);
}
template<typename T>
void GetIncompatibleServicesHelper(AZ::ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) {}
template<typename T>
void GetIncompatibleServicesHelper(AZ::ComponentDescriptor::DependencyArrayType& services, const AZStd::true_type&)
{
T::GetIncompatibleServices(services);
}
} // namespace Components
} // namespace AzFramework
@@ -0,0 +1,53 @@
/*
* 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 "ConsoleBus.h"
#include <AzCore/RTTI/BehaviorContext.h>
namespace AzFramework
{
void ConsoleRequests::Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<ConsoleRequestBus>("ConsoleRequestBus")
->Attribute(AZ::Script::Attributes::Category, "Utilities")
->Event("ExecuteConsoleCommand", &ConsoleRequestBus::Events::ExecuteConsoleCommand)
;
}
}
/**
* Behavior Context forwarder
*/
class ConsoleNotificationBusBehaviorHandler : public ConsoleNotificationBus::Handler, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(ConsoleNotificationBusBehaviorHandler, "{EE90D2DA-9339-4CE6-AF98-AF81E00E2AB3}", AZ::SystemAllocator, OnConsoleCommandExecuted);
void OnConsoleCommandExecuted(const char* command) override
{
Call(FN_OnConsoleCommandExecuted, command);
}
};
void ConsoleNotifications::Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<ConsoleNotificationBus>("ConsoleNotificationBus")
->Attribute(AZ::Script::Attributes::Category, "Utilities")
->Handler<ConsoleNotificationBusBehaviorHandler>()
;
}
}
}
@@ -0,0 +1,72 @@
#pragma once
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_CONSOLE_BUS_H
#define AZFRAMEWORK_CONSOLE_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/string/string.h>
namespace AzFramework
{
/**
* Event bus that can be used to request commands be executed by the console
* Only one console can exist at a time, which is why this bus
* supports only one listener.
*/
class ConsoleRequests
: public AZ::EBusTraits
{
public:
virtual ~ConsoleRequests() {}
//////////////////////////////////////////////////////////////////////////
/**
* Overrides the default AZ::EBusTraits handler policy to allow one
* listener only, because only one console can exist at a time.
*/
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual void ExecuteConsoleCommand(const char* command) = 0;
virtual void ResetCVarsToDefaults() = 0;
static void Reflect(AZ::ReflectContext* context);
};
typedef AZ::EBus<ConsoleRequests> ConsoleRequestBus;
/**
* Event bus that can be used to request commands be executed by the console
* Only one console can exist at a time, which is why this bus
* supports only one listener.
*/
class ConsoleNotifications
: public AZ::EBusTraits
{
public:
virtual ~ConsoleNotifications() {}
virtual void OnConsoleCommandExecuted(const char* command) = 0;
static void Reflect(AZ::ReflectContext* context);
};
typedef AZ::EBus<ConsoleNotifications> ConsoleNotificationBus;
} // namespace AzFramework
#endif // AZFRAMEWORK_CONSOLE_BUS_H
@@ -0,0 +1,42 @@
/*
* 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/EBus/EBus.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AzFramework
{
namespace Components
{
struct DeprecatedInfo
{
bool m_hideComponent = false;
AZStd::string m_deprecationString;
};
using DeprecatedComponentsList = AZStd::unordered_map<AZ::Uuid, DeprecatedInfo>;
//! Use this bus to get a list of the Uuid's of any class that is deprecated.
//! The list contains the Uuid's of all the deprecated classes,
//! a flag that the class should be hidden in the add component menu,
//! some text to be appended to the classes name in the add component menu.
//! List will be appended to by each handler of the bus.
class DeprecatedComponents
: public AZ::EBusTraits
{
public:
virtual void EnumerateDeprecatedComponents(DeprecatedComponentsList& list) const = 0;
};
using DeprecatedComponentsRequestBus = AZ::EBus<DeprecatedComponents>;
} // namespace Components
} // namespace AzFramework
@@ -0,0 +1,52 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_EDITORENTITYEVENTS_H
#define AZFRAMEWORK_EDITORENTITYEVENTS_H
#include <AzCore/Math/Transform.h>
namespace AzFramework
{
class DebugDisplayRequests;
/**
* Inherit the EditorEntityEvents interface to receive editor-time events on a non-editor
* component.
*
* The preferred model is to implement a separate editor component inheriting from
* AzToolsFramework::EditorComponentBase in order to provide richer in-editor functionality.
* This is _specifically_ to accelerate teams willing to blend editor and runtime component code,
* and/or activate components at edit time.
*/
class EditorEntityEvents
{
public:
AZ_RTTI(EditorEntityEvents, "{A6ECB561-1C69-4438-92E5-1CC4EC0C9E93}");
virtual ~EditorEntityEvents() {}
virtual void EditorInit(AZ::EntityId /*entityId*/) {}
virtual void EditorActivate(AZ::EntityId /*entityId*/) {}
virtual void EditorDeactivate(AZ::EntityId /*entityId*/) {}
virtual void EditorDisplay(
AZ::EntityId /*entityId*/, DebugDisplayRequests& /*displayInterface*/, const AZ::Transform& /*world*/) {}
/**
* This API allows a component associated with a primary asset to participate in drag and drop asset events without an editor counterpart
*/
virtual void EditorSetPrimaryAsset(const AZ::Data::AssetId& /*assetId*/) {}
};
} // namespace AzFramework
#endif // AZFRAMEWORK_EDITORENTITYEVENTS_H
@@ -0,0 +1,99 @@
/*
* 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 <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzFramework
{
void NonUniformScaleComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<NonUniformScaleComponent, AZ::Component>()
->Version(1)
->Field("NonUniformScale", &NonUniformScaleComponent::m_scale)
;
}
}
void NonUniformScaleComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC_CE("TransformService"));
}
void NonUniformScaleComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("SkyCloudService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawObbService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawService"));
incompatible.push_back(AZ_CRC_CE("EMotionFXActorService"));
incompatible.push_back(AZ_CRC_CE("EMotionFXSimpleMotionService"));
incompatible.push_back(AZ_CRC_CE("GradientTransformService"));
incompatible.push_back(AZ_CRC_CE("LegacyMeshService"));
incompatible.push_back(AZ_CRC_CE("LookAtService"));
incompatible.push_back(AZ_CRC_CE("SequenceService"));
incompatible.push_back(AZ_CRC_CE("ClothMeshService"));
incompatible.push_back(AZ_CRC_CE("PhysXColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXTriggerService"));
incompatible.push_back(AZ_CRC_CE("PhysXJointService"));
incompatible.push_back(AZ_CRC_CE("PhysXShapeColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXCharacterControllerService"));
incompatible.push_back(AZ_CRC_CE("PhysXRagdollService"));
incompatible.push_back(AZ_CRC_CE("TouchBendingPhysicsService"));
incompatible.push_back(AZ_CRC_CE("WaterVolumeService"));
incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
incompatible.push_back(AZ_CRC_CE("NavigationAreaService"));
incompatible.push_back(AZ_CRC_CE("GeometryService"));
incompatible.push_back(AZ_CRC_CE("CapsuleShapeService"));
incompatible.push_back(AZ_CRC_CE("CompoundShapeService"));
incompatible.push_back(AZ_CRC_CE("CylinderShapeService"));
incompatible.push_back(AZ_CRC_CE("DiskShapeService"));
incompatible.push_back(AZ_CRC_CE("FixedVertexContainerService"));
incompatible.push_back(AZ_CRC_CE("PolygonPrismShapeService"));
incompatible.push_back(AZ_CRC_CE("SphereShapeService"));
incompatible.push_back(AZ_CRC_CE("SplineService"));
incompatible.push_back(AZ_CRC_CE("TubeShapeService"));
incompatible.push_back(AZ_CRC_CE("VariableVertexContainerService"));
}
void NonUniformScaleComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NonUniformScaleService"));
}
void NonUniformScaleComponent::Activate()
{
AZ::NonUniformScaleRequestBus::Handler::BusConnect(GetEntityId());
}
void NonUniformScaleComponent::Deactivate()
{
AZ::NonUniformScaleRequestBus::Handler::BusDisconnect();
}
AZ::Vector3 NonUniformScaleComponent::GetScale() const
{
return m_scale;
}
void NonUniformScaleComponent::SetScale(const AZ::Vector3& scale)
{
m_scale = scale;
m_scaleChangedEvent.Signal(m_scale);
}
void NonUniformScaleComponent::RegisterScaleChangedEvent(AZ::NonUniformScaleChangedEvent::Handler& handler)
{
handler.Connect(m_scaleChangedEvent);
}
} // namespace AzFramework
@@ -0,0 +1,50 @@
/*
* 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/Component/Component.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzCore/Math/Vector3.h>
namespace AzFramework
{
class NonUniformScaleComponent
: public AZ::Component
, public AZ::NonUniformScaleRequestBus::Handler
{
public:
AZ_COMPONENT(NonUniformScaleComponent, "{077A7A44-BC44-4357-840D-E8E193ADE991}");
static void Reflect(AZ::ReflectContext* context);
NonUniformScaleComponent() = default;
~NonUniformScaleComponent() = default;
// AZ::NonUniformScaleRequests::Handler ...
AZ::Vector3 GetScale() const override;
void SetScale(const AZ::Vector3& scale) override;
void RegisterScaleChangedEvent(AZ::NonUniformScaleChangedEvent::Handler& handler);
protected:
// AZ::Component ...
void Activate() override;
void Deactivate() override;
private:
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
AZ::Vector3 m_scale = AZ::Vector3::CreateOne();
AZ::NonUniformScaleChangedEvent m_scaleChangedEvent;
};
} // namespace AzFramework
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,271 @@
/*
* 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/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/EBus/Event.h>
#include <AzFramework/Network/NetBindable.h>
namespace AzToolsFramework
{
namespace Components
{
class TransformComponent;
}
}
namespace AzFramework
{
class TransformReplicaChunk;
class GameEntityContextComponent;
/// @deprecated Use AZ::TransformConfig
using TransformComponentConfiguration = AZ::TransformConfig;
//! Fundamental component that describes the entity in 3D space.
//! It is net-bindable. Only local transform is synchronized, so when
//! parented, the parent must properly synchronize its transform as well.
class TransformComponent
: public AZ::Component
, public AZ::TransformBus::Handler
, public AZ::TransformNotificationBus::Handler
, public AZ::EntityBus::Handler
, public AZ::TickBus::Handler
, private AZ::TransformHierarchyInformationBus::Handler
, public NetBindable
{
friend class TransformReplicaChunk;
public:
AZ_COMPONENT(TransformComponent, AZ::TransformComponentTypeId, NetBindable, AZ::TransformInterface);
friend class AzToolsFramework::Components::TransformComponent;
using ParentActivationTransformMode = AZ::TransformConfig::ParentActivationTransformMode;
TransformComponent();
TransformComponent(const TransformComponent& copy);
virtual ~TransformComponent();
// TransformBus events (publicly accessible)
//! Returns true if the tm was set to the local transform.
const AZ::Transform& GetLocalTM() override { return m_localTM; }
//! Returns true if the tm was set to the world transform.
const AZ::Transform& GetWorldTM() override { return m_worldTM; }
//! Returns both local and world transforms.
void GetLocalAndWorld(AZ::Transform& localTM, AZ::Transform& worldTM) override { localTM = m_localTM; worldTM = m_worldTM; }
//! Returns parent EntityId.
AZ::EntityId GetParentId() override { return m_parentId; }
//! Returns parent interface if available.
AZ::TransformInterface* GetParent() override { return m_parentTM; }
//! Sets the local transform and notifies all interested parties.
void SetLocalTM(const AZ::Transform& tm) override;
//! Sets the world transform and notifies all interested parties.
void SetWorldTM(const AZ::Transform& tm) override;
//! Set parent entity and notifies all interested parties.
//! The object localTM will be moved into parent space so we will preserve the same worldTM.
void SetParent(AZ::EntityId id) override;
//! Set the parent entity and notifies all interested parties.
//! This will use worldTM as a localTM and move the transform relative to the parent.
void SetParentRelative(AZ::EntityId id) override;
protected:
// Component
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
// Translation modifiers
void SetWorldTranslation(const AZ::Vector3& newPosition) override;
void SetLocalTranslation(const AZ::Vector3& newPosition) override;
AZ::Vector3 GetWorldTranslation() override;
AZ::Vector3 GetLocalTranslation() override;
void MoveEntity(const AZ::Vector3& offset) override;
void SetWorldX(float x) override;
void SetWorldY(float y) override;
void SetWorldZ(float z) override;
float GetWorldX() override;
float GetWorldY() override;
float GetWorldZ() override;
void SetLocalX(float x) override;
void SetLocalY(float y) override;
void SetLocalZ(float z) override;
float GetLocalX() override;
float GetLocalY() override;
float GetLocalZ() override;
bool IsPositionInterpolated() override;
// Rotation modifiers
void SetRotation(const AZ::Vector3& eulerAnglesRadian) override;
void SetRotationQuaternion(const AZ::Quaternion& quaternion) override;
void SetRotationX(float eulerAngleRadian) override;
void SetRotationY(float eulerAngleRadian) override;
void SetRotationZ(float eulerAngleRadian) override;
void RotateByX(float eulerAngleRadian) override;
void RotateByY(float eulerAngleRadian) override;
void RotateByZ(float eulerAngleRadian) override;
AZ::Vector3 GetRotationEulerRadians() override;
AZ::Quaternion GetRotationQuaternion() override;
float GetRotationX() override;
float GetRotationY() override;
float GetRotationZ() override;
AZ::Vector3 GetWorldRotation() override;
AZ::Quaternion GetWorldRotationQuaternion() override;
void SetLocalRotation(const AZ::Vector3& eulerAnglesRadian) override;
void SetLocalRotationQuaternion(const AZ::Quaternion& quaternion) override;
void RotateAroundLocalX(float eulerAngleRadian) override;
void RotateAroundLocalY(float eulerAngleRadian) override;
void RotateAroundLocalZ(float eulerAngleRadian) override;
AZ::Vector3 GetLocalRotation() override;
AZ::Quaternion GetLocalRotationQuaternion() override;
bool IsRotationInterpolated() override;
// Scale Modifiers
void SetScale(const AZ::Vector3& scale) override;
void SetScaleX(float scaleX) override;
void SetScaleY(float scaleY) override;
void SetScaleZ(float scaleZ) override;
AZ::Vector3 GetScale() override;
float GetScaleX() override;
float GetScaleY() override;
float GetScaleZ() override;
void SetLocalScale(const AZ::Vector3& scale) override;
void SetLocalScaleX(float scaleX) override;
void SetLocalScaleY(float scaleY) override;
void SetLocalScaleZ(float scaleZ) override;
AZ::Vector3 GetLocalScale() override;
AZ::Vector3 GetWorldScale() override;
// Transform hierarchy
AZStd::vector<AZ::EntityId> GetChildren() override;
AZStd::vector<AZ::EntityId> GetAllDescendants() override;
AZStd::vector<AZ::EntityId> GetEntityAndAllDescendants() override;
bool IsStaticTransform() override;
//! Methods implementing parent support.
//! @{
// TransformNotificationBus - for parent entity
void OnTransformChanged(const AZ::Transform& parentLocalTM, const AZ::Transform& parentWorldTM) override;
// EntityBus
//! Called when the parent entity activates.
void OnEntityActivated(const AZ::EntityId& parentEntityId) override;
//! Called when the parent entity deactivates.
void OnEntityDeactivated(const AZ::EntityId& parentEntityId) override;
//! @}
//! Methods implementing NetBindable.
//! @{
GridMate::ReplicaChunkPtr GetNetworkBinding() override;
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk) override;
void UnbindFromNetwork() override;
//! Called by the net chunk when new transform data arrives from the network.
void OnNewNetTransformData(const AZ::Transform& transform, const GridMate::TimeContext& tc);
//! Called by the net chunk when new parent id arrives from the network.
void OnNewNetParentData(const AZ::u64& parentId, const GridMate::TimeContext& tc);
//! Returns true if this instance is non-authoritative.
bool IsNetworkControlled() const;
//! Triggers an update of the chunk data. Should only be called on the authoritative instance.
void UpdateReplicaChunk();
//! @}
// AZ::TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
//////////////////////////////////////////////////////////////////////////
// Actual Implementation Functions
// They are protected so we can gate them when network-controlled
void SetParentImpl(AZ::EntityId parentId, bool isKeepWorldTM);
void SetLocalTMImpl(const AZ::Transform& tm);
void SetWorldTMImpl(const AZ::Transform& tm);
void OnTransformChangedImpl(const AZ::Transform& parentLocalTM, const AZ::Transform& parentWorldTM);
void OnEntityActivatedImpl(const AZ::EntityId& parentEntityId);
void OnEntityDeactivateImpl(const AZ::EntityId& parentEntityId);
void ComputeLocalTM();
void ComputeWorldTM();
//////////////////////////////////////////////////////////////////////////
//! Returns whether external calls are currently allowed to move the transform.
bool AreMoveRequestsAllowed() const;
// TransformHierarchyInformationBus
void GatherChildren(AZStd::vector<AZ::EntityId>& children) override;
//! Feedback from corresponding replica chunk.
//! @{
void OnNewPositionData(const AZ::Vector3&, const GridMate::TimeContext&);
void OnNewRotationData(const AZ::Quaternion&, const GridMate::TimeContext&);
void OnNewScaleData(const AZ::Vector3&, const GridMate::TimeContext&);
//! @}
/// \ref ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
/// \ref ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* reflection);
AZ::Transform m_localTM; ///< Local transform relative to parent transform (same as worldTM if no parent).
AZ::Transform m_worldTM; ///< World transform including parent transform (same as localTM if no parent).
AZ::EntityId m_parentId; ///< If valid, this transform is parented to m_parentId.
AZ::TransformInterface* m_parentTM; ///< Cached - pointer to parent transform, to avoid extra calls. Valid only when if it's present.
bool m_parentActive; ///< Keeps track of the state of the parent entity.
AZ::TransformNotificationBus::BusPtr m_notificationBus; ///< Cached bus pointer to the notification bus.
bool m_onNewParentKeepWorldTM; ///< If set, recompute localTM instead of worldTM when parent becomes active.
ParentActivationTransformMode m_parentActivationTransformMode;
GridMate::ReplicaChunkPtr m_replicaChunk;
bool m_isStatic; ///< If true, the transform is static and doesn't move while entity is active.
AZ::InterpolationMode m_interpolatePosition; ///< Interpolation mode for net-synced position updates.
AZ::InterpolationMode m_interpolateRotation; ///< Interpolation mode for net-synced rotation updates.
private:
bool HasAnyInterpolation();
void CreateSamples();
void CreateTranslationSample();
void CreateRotationSample();
AZ::Transform GetInterpolatedTransform(unsigned int localTime);
AZStd::unique_ptr<AZ::Sample<AZ::Vector3>> m_netTargetTranslation;
AZStd::unique_ptr<AZ::Sample<AZ::Quaternion>> m_netTargetRotation;
AZ::Vector3 m_netTargetScale;
};
} // namespace AZ
@@ -0,0 +1,72 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace AZ
{
class Transform;
class Matrix3x3;
class Vector3;
}
namespace AzFramework
{
//! The debug camera allows the user control over the view through mouse + keyboard and/or
//! controller while the game still uses the view camera for everything else. This can for
//! instance be used to debug occlusion culling as all occlusion calculates will be done
//! from the view camera, so the debug camera makes it possible to check if hidden objects
//! are correctly culled.
//! This class can be useful to validate a hypothetical camera-based look-ahead asset streaming system.
//! The developer can update the camera location using this EBus, without requiring to move the viewport camera.
class DebugCameraInterface
: public AZ::EBusTraits
{
public:
enum class Mode
{
FreeFloating, //< Controls move the debug camera through the world.
Fixed, //< The debug camera stays in the position it was navigated to and control is handed back to the game.
Disabled, //< Debug camera is disabled.
Unknown
};
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Sets the debug camera in free floating, fixed or disabled mode.
virtual void SetMode(Mode mode) = 0;
//! Returns the current mode the debug camera is in.
virtual Mode GetMode() const = 0;
//! Retrieves the world position of the debug camera. This is the same position that can be retrieved
//! from GetTransform.
virtual void GetPosition(AZ::Vector3& result) const = 0;
//! Retrieves the view orientation of the debub camera. This is the same orientation that can be retrieved
//! from GetTransform.
virtual void GetView(AZ::Matrix3x3& result) const = 0;
//! Get the world transform for the debug camera.
virtual void GetTransform(AZ::Transform& result) const = 0;
};
using DebugCameraBus = AZ::EBus<DebugCameraInterface>;
//! The debug camera sends out notifications about some changes. This interface provides access to these.
class DebugCameraEventsInterface
: public AZ::EBusTraits
{
public:
//! Called when the debug camera moves, usually due to user interaction.
virtual void DebugCameraMoved(const AZ::Transform& world) {}
};
using DebugCameraEventsBus = AZ::EBus<DebugCameraEventsInterface>;
} // namespace AzFramework
@@ -0,0 +1,190 @@
/*
* 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/Math/Uuid.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/regex.h>
#include <AzFramework/Dependency/Version.h>
namespace AzFramework
{
/**
* Specifies a particular Gem instance (by ID and version).
*/
template <size_t N>
struct Specifier
{
AZ::Uuid m_id;
Version<N> m_version;
Specifier(const AZ::Uuid& id, const Version<N>& version);
virtual ~Specifier() = default;
};
/**
* Defines a Gem's dependency upon another Gem.
*/
template <size_t N>
class Dependency
{
public:
class Bound
{
public:
friend class Dependency<N>;
enum class Comparison : AZ::u8
{
// Don't compare against this version
None = 0,
// Traditional operators
GreaterThan = 1 << 0,
LessThan = 1 << 1,
EqualTo = 1 << 2,
// Special operators
TwiddleWakka = 1 << 3
};
/**
* Get a formatted string output of the Dependency's bounds.
*
* \returns The formatted output string.
*/
AZStd::string ToString() const;
/**
* Check if the bound is satisfied by the version input.
*
* \params[in] version The version to check against.
*
* \returns Returns true if the bound is satisfied.
*/
bool MatchesVersion(const Version<N>& version) const;
/**
* Set the version of this bound that will be used to check against
*
* \params[in] version The version to set this bound.
*/
void SetVersion(const Version<N>& version);
/**
* Get the version of this bound that will be used to check against
*
* \returns The version used by this bound.
*/
const Version<N>& GetVersion() const;
/**
* Set the comparison of this bound that will be used to check against
*
* \params[in] comp The comparison to set this bound.
*/
void SetComparison(Comparison comp);
/**
* Get the comparison operator of this bound that will be used to check against
*
* \returns The comparison used by this bound.
*/
Comparison GetComparison() const;
private:
AZStd::string m_parsedString;
Version<N> m_version;
Comparison m_comparison = Comparison::None;
AZ::u8 m_parseDepth;
};
Dependency();
Dependency(const Dependency& dep);
~Dependency() = default;
/**
* Gets the ID of the Gem depended on.
*
* \returns The ID of the Gem depended on.
*/
const AZ::Uuid& GetID() const;
/**
* Set the ID of the Gem depended on.
*
* \params[in] id The ID of the dependency
*/
void SetID(const AZ::Uuid& id);
/**
* Gets the bounds that the dependence's version must fulfill.
*
* \returns The list of bounds.
*/
const AZStd::vector<Bound>& GetBounds() const;
/**
* Checks if a specifier matches a dependency.
*
* Checks that the specifier's ID is the one depended on,
* and that the version matches the bounds (which can be retrieved by GetBounds()).
*
* \params[in] spec The specifier to test.
*
* \returns Whether or not the Specifier<N> fits the dependency.
*/
bool IsFullfilledBy(const Specifier<N>& spec) const;
/**
* Parses version bounds from a list of strings.
*
* Each string should fit the pattern [OPERATOR][VERSION],
* where [OPERATOR] is >, >=, <, <=, ==, or ~>,
* and [VERSION] is a valid version string, parsable by Gems::Version<N>.
*
* \params[in] deps The list of bound strings to parse.
*
* \returns True on success, false on failure.
*/
AZ::Outcome<void, AZStd::string> ParseVersions(const AZStd::vector<AZStd::string>& deps);
AZ::Uuid m_id = AZ::Uuid::CreateNull();
AZStd::vector<Bound> m_bounds;
private:
AZ::Outcome<AZ::u8> ParseVersion(AZStd::string str, Version<N>& ver);
AZStd::regex m_dependencyRegex;
AZStd::regex m_versionRegex;
};
#define BITMASK_OPS(Ty) \
inline Ty&operator&=(Ty & left, Ty right) { left = (Ty)((int)left & (int)right); return (left); } \
inline Ty& operator|=(Ty& left, Ty right) { left = (Ty)((int)left | (int)right); return (left); } \
inline Ty& operator^=(Ty& left, Ty right) { left = (Ty)((int)left ^ (int)right); return (left); } \
inline Ty operator&(Ty left, Ty right) { return ((Ty)((int)left & (int)right)); } \
inline Ty operator|(Ty left, Ty right) { return ((Ty)((int)left | (int)right)); } \
inline Ty operator^(Ty left, Ty right) { return ((Ty)((int)left ^ (int)right)); } \
inline Ty operator~(Ty left) { return ((Ty) ~(int)left); }
BITMASK_OPS(Dependency< Version<4>::parts_count>::Bound::Comparison)
BITMASK_OPS(Dependency<SemanticVersion::parts_count>::Bound::Comparison)
#undef BITMASK_OPS
} // namespace AzFramework
#include <AzFramework/Dependency/Dependency.inl>
@@ -0,0 +1,345 @@
/*
* 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/StringFunc/StringFunc.h>
namespace AzFramework
{
//////////////////////////////////////////////////////////////////////////
// Specifier
//////////////////////////////////////////////////////////////////////////
template <size_t N>
Specifier<N>::Specifier(const AZ::Uuid& id, const Version<N>& version)
: m_id(id)
, m_version(version)
{}
//////////////////////////////////////////////////////////////////////////
// Dependency::Bound
//////////////////////////////////////////////////////////////////////////
template <size_t N>
AZStd::string Dependency<N>::Bound::ToString() const
{
// use version string if it's set
// Dependency::ParseVersions set this value
// this is used to save user's version string configuration (eg ==1.8.01.001)
if (m_parsedString.empty() == false)
{
return m_parsedString;
}
// if there is no version string set, we generate it based on the m_version and m_comparison
if ((m_comparison& Comparison::TwiddleWakka) != Comparison::None)
{
AZ_Assert(m_parseDepth >= 2, "Internal Error: There should be "
"or more than 2 parts to a TwiddleWakka dependency.");
AZStd::string version = AZStd::string::format("~>%llu", m_version.m_parts[0]);
for (AZ::u8 i = 1; i < m_parseDepth; ++i)
{
version += AZStd::string::format(".%llu", m_version.m_parts[i]);
}
return version;
}
AZStd::string op = "";
if ((m_comparison& Comparison::GreaterThan) != Comparison::None)
{
op = ">";
}
else if ((m_comparison& Comparison::LessThan) != Comparison::None)
{
op = "<";
}
if ((m_comparison& Comparison::EqualTo) != Comparison::None)
{
op += op.length() == 0 ? "==" : "=";
}
return op + m_version.ToString();
}
template <size_t N>
bool Dependency<N>::Bound::MatchesVersion(const Version<N>& version) const
{
bool satisfies = false;
#define CHECK_OPERATOR(comp, OP) \
if (!satisfies && (m_comparison& Comparison::comp) != Comparison::None) \
{ \
satisfies = version OP m_version; \
}
CHECK_OPERATOR(EqualTo, == );
CHECK_OPERATOR(GreaterThan, > );
CHECK_OPERATOR(LessThan, < );
#undef CHECK_OPERATOR
return satisfies;
}
template <size_t N>
void Dependency<N>::Bound::SetVersion(const Version<N>& version)
{
m_parsedString = "";
m_version = version;
}
template <size_t N>
const Version<N>& Dependency<N>::Bound::GetVersion() const
{
return m_version;
}
template <size_t N>
void Dependency<N>::Bound::SetComparison(Comparison comp)
{
m_parsedString = "";
m_comparison = comp;
}
template <size_t N>
typename Dependency<N>::Bound::Comparison Dependency<N>::Bound::GetComparison() const
{
return m_comparison;
}
//////////////////////////////////////////////////////////////////////////
// Dependency
//////////////////////////////////////////////////////////////////////////
template <size_t N>
Dependency<N>::Dependency()
: m_dependencyRegex("(?:(~>|[>=<]{1,2}) *([0-9]+(?:\\.[0-9]+)*))")
, m_versionRegex("([0-9]+)(?:\\.(.*)){0,1}")
{
}
template <size_t N>
Dependency<N>::Dependency(const Dependency& dep)
: m_id(dep.m_id)
, m_bounds(dep.m_bounds)
, m_dependencyRegex(dep.m_dependencyRegex)
, m_versionRegex(dep.m_versionRegex)
{
}
template <size_t N>
const AZ::Uuid& Dependency<N>::GetID() const
{
return m_id;
}
template <size_t N>
void Dependency<N>::SetID(const AZ::Uuid& id)
{
m_id = id;
}
template <size_t N>
const AZStd::vector<typename Dependency<N>::Bound>& Dependency<N>::GetBounds() const
{
return m_bounds;
}
template <size_t N>
bool Dependency<N>::IsFullfilledBy(const Specifier<N>& spec) const
{
using Comp = typename Dependency::Bound::Comparison;
if (!m_id.IsNull() && !spec.m_id.IsNull())
{
if (spec.m_id != m_id)
{
return false;
}
}
for (auto && bound : m_bounds)
{
bool satisfies = false;
if (bound.m_comparison == Comp::TwiddleWakka)
{
// Lower bound
Bound lower;
lower.m_comparison = Comp::EqualTo | Comp::GreaterThan;
lower.m_version = bound.m_version;
lower.m_parseDepth = bound.m_parseDepth;
// Upper bound
Bound upper;
upper.m_comparison = Comp::LessThan;
upper.m_version = lower.m_version;
upper.m_parseDepth = bound.m_parseDepth;
// ~>1.0 becomes >=1.0 <2.0
// ~>1.2.0 becomes >=1.2.0 <1.3.0
// ~>1.2.3 becomes >=1.2.3 <1.3.0
upper.m_version.m_parts[lower.m_parseDepth - 1] = 0;
upper.m_version.m_parts[lower.m_parseDepth - 2]++;
if (!lower.MatchesVersion(spec.m_version)
|| !upper.MatchesVersion(spec.m_version))
{
return false;
}
}
else
{
if (!bound.MatchesVersion(spec.m_version))
{
return false;
}
}
}
return true;
}
template <size_t N>
AZ::Outcome<void, AZStd::string> Dependency<N>::ParseVersions(const AZStd::vector<AZStd::string>& deps)
{
using Comp = typename Dependency::Bound::Comparison;
AZStd::smatch match;
AZStd::string depStr;
for (const auto& depStrRaw : deps)
{
depStr = depStrRaw;
AZ::StringFunc::Strip(depStr, " \t");
if (depStr == "*")
{
// If * is in the constraints, allow ANY version of the dependency
m_bounds.clear();
return AZ::Success();
}
else if (AZStd::regex_match(depStr, match, m_dependencyRegex) && match.size() >= 3)
{
AZStd::string op = match[1].str();
AZStd::string versionStr = match[2].str();
static const AZStd::array<const char*, 4> invalid_operators{ {
"><", "<>", ">>", "<<"
} };
for (const char* invalid_op : invalid_operators)
{
if (op == invalid_op)
{
// invalid operators detected
goto invalid_version_str;
}
}
// Check for twiddle wakka, it's a special case
if (op == "~>")
{
// Lower bound
Bound bound;
bound.m_comparison = Comp::TwiddleWakka;
bound.m_parsedString = op + versionStr;
auto parseOutcome = ParseVersion(versionStr, bound.m_version);
if (!parseOutcome.IsSuccess() || parseOutcome.GetValue() < 2)
{
// ~>1 not allowed, must specifiy ~>1.0
goto invalid_version_str;
}
bound.m_parseDepth = parseOutcome.GetValue();
m_bounds.push_back(bound);
}
else
{
Bound current;
auto findOperator = [&op, &current](Comp comp, const char* str) -> void
{
if (op.find(str) != AZStd::string::npos)
{
current.m_comparison |= comp;
}
};
findOperator(Comp::EqualTo, "=");
findOperator(Comp::LessThan, "<");
findOperator(Comp::GreaterThan, ">");
auto parseOutcome = ParseVersion(versionStr, current.m_version);
if (!parseOutcome.IsSuccess())
{
goto invalid_version_str;
}
current.m_parseDepth = parseOutcome.GetValue();
// fix incomplete version parts
// 1.8 -> 1.8.0.0
for (AZ::u8 praseDepth = current.m_parseDepth; praseDepth < N; ++praseDepth)
{
versionStr += ".0";
}
// since "=" is a valid comparsion string, we want to standardize it to ==
if (current.m_comparison == Comp::EqualTo)
{
op = "==";
}
current.m_parsedString = op + versionStr;
m_bounds.push_back(current);
}
}
else
{
goto invalid_version_str;
}
}
return AZ::Success();
invalid_version_str:
// Error message
m_bounds.clear();
return AZ::Failure(AZStd::string::format(
"Failed to parse dependency version string \"%s\": invalid format.",
depStr.c_str()));
}
template <size_t N>
AZ::Outcome<AZ::u8> Dependency<N>::ParseVersion(AZStd::string str, Version<N>& ver)
{
AZStd::smatch match;
AZ::u8 depth = 0;
while (!str.empty() && depth < N)
{
if (AZStd::regex_match(str, match, m_versionRegex))
{
ver.m_parts[depth++] = static_cast<typename Version<N>::ComponentType>(AZStd::stoull(match[1].str()));
str = match[2].str();
}
else
{
break;
}
}
if (!str.empty())
{
return AZ::Failure();
}
return AZ::Success(depth);
}
}
@@ -0,0 +1,209 @@
/*
* 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/Outcome/Outcome.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/hash.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/containers/array.h>
#include <initializer_list>
#include <sstream>
namespace AzFramework
{
#define VERSION_SEPARATOR_CHAR '.'
#define VERSION_SEPARATOR_STR "."
template <size_t N>
struct Version
{
/// The type each component is stored as.
using ComponentType = AZ::u64;
/// Store size of Version object
enum
{
parts_count = N
};
Version()
{
static_assert(N > 0, "Size for Version must be more than 0.");
m_parts.fill(0);
}
Version(const Version& other)
: m_parts(other.m_parts)
{
}
Version(const AZStd::array<ComponentType, N>& parts)
: m_parts(parts) { }
Version(const std::initializer_list<ComponentType>& values)
{
AZ_Assert(values.size() == N,
"Initializer size does not matches Version size. "
"Expected: %u, got: %u",
N, values.size());
AZStd::transform(values.begin(), values.end(), m_parts.begin(), [](const ComponentType& c) { return c; });
}
/**
* Parses a version from a string in the format "[part].[part].[part] ...".
*
* \param[in] versionStr The string to parse for a version.
* \returns On success, the parsed Version; on failure, a message describing the error.
*/
static AZ::Outcome<Version, AZStd::string> ParseFromString(const AZStd::string& versionStr)
{
// there is 1 more part than there are dots in the string. (1.2.3 has 3 parts but 2 dots)
size_t partCount = AZStd::accumulate(versionStr.begin(), versionStr.end(), size_t(1),
[](size_t currentValue, const char elem) -> size_t
{
return currentValue + (elem == VERSION_SEPARATOR_CHAR ? 1 : 0);
});
if (N != partCount)
{
return AZ::Failure(AZStd::string::format(
"Failed to parse invalid version string \"%s\". "
"Number of parts in the string doesn't match the size. "
"Expected: %zu, got: %zu"
, versionStr.c_str(), N, partCount));
}
Version<N> result;
std::istringstream iss(versionStr.c_str());
for (int i = 0; i < partCount; ++i)
{
iss >> result.m_parts[i];
// remove the dot before the next iteration
char throwaway;
iss >> throwaway;
if (throwaway != VERSION_SEPARATOR_CHAR)
{
return AZ::Failure(AZStd::string::format(
"Failed to parse invalid version string \"%s\". "
"Unexpected separator character encountered. "
"Expected: \"%d\", got: \"%d\""
, versionStr.c_str(), VERSION_SEPARATOR_CHAR, throwaway));
}
}
if (!iss.eof())
{
return AZ::Failure(AZStd::string::format(
"Failed to parse invalid version string \"%s\". "
, versionStr.c_str()));
}
return AZ::Success(result);
}
/**
* Returns the version in string form.
*
* \returns The version as a string formatted as "[major].[minor].[patch]".
*/
AZStd::string ToString() const
{
std::stringstream ss;
const char* separator = "";
for (int i = 0; i < N; ++i)
{
ss << separator << m_parts[i];
separator = VERSION_SEPARATOR_STR;
}
return ss.str().c_str();
}
/**
* Compare two versions.
*
* Returns: 0 if a == b, <0 if a < b, and >0 if a > b.
*/
static int Compare(const Version& a, const Version& b)
{
for (int i = 0; i < N; ++i)
{
if (a.m_parts[i] != b.m_parts[i])
{
return static_cast<int>(a.m_parts[i]) - static_cast<int>(b.m_parts[i]);
}
}
return 0;
}
/**
* Check if current version is all zero-ed out
*
* Returns: True if the current version is all zero-ed out, else false
*/
bool IsZero() const
{
for (int i = 0; i < N; ++i)
{
if (m_parts[i] != 0)
{
return false;
}
}
return true;
}
AZStd::array<ComponentType, N> m_parts;
};
/**
* Represents a version conforming to the Semantic Versioning standard (http://semver.org/)
*/
struct SemanticVersion
: public Version<3>
{
SemanticVersion()
: Version<3>() { }
SemanticVersion(const Version<3>& other)
: Version<3>(other) { }
SemanticVersion(ComponentType major, ComponentType minor, ComponentType patch)
{
m_parts[0] = major;
m_parts[1] = minor;
m_parts[2] = patch;
}
ComponentType GetMajor() const { return m_parts[0]; }
ComponentType GetMinor() const { return m_parts[1]; }
ComponentType GetPatch() const { return m_parts[2]; }
};
template <size_t N>
inline bool operator< (const Version<N>& a, const Version<N>& b) { return Version<N>::Compare(a, b) < 0; }
template <size_t N>
inline bool operator> (const Version<N>& a, const Version<N>& b) { return Version<N>::Compare(a, b) > 0; }
template <size_t N>
inline bool operator<=(const Version<N>& a, const Version<N>& b) { return Version<N>::Compare(a, b) <= 0; }
template <size_t N>
inline bool operator>=(const Version<N>& a, const Version<N>& b) { return Version<N>::Compare(a, b) >= 0; }
template <size_t N>
inline bool operator==(const Version<N>& a, const Version<N>& b) { return Version<N>::Compare(a, b) == 0; }
template <size_t N>
inline bool operator!=(const Version<N>& a, const Version<N>& b) { return Version<N>::Compare(a, b) != 0; }
} // namespace AzFramework
@@ -0,0 +1,201 @@
/*
* 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 <AzFramework/Driller/DrillToFileComponent.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/IO/FileIO.h>
namespace AzFramework
{
void DrillToFileComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<DrillToFileComponent, AZ::Component>()
;
if (serialize->FindClassData(DrillerInfo::RTTI_Type()) == nullptr)
{
serialize->Class<DrillerInfo>()
->Field("Id", &DrillerInfo::m_id)
->Field("GroupName", &DrillerInfo::m_groupName)
->Field("Name", &DrillerInfo::m_name)
->Field("Description", &DrillerInfo::m_description);
}
}
}
void DrillToFileComponent::Activate()
{
m_drillerSession = nullptr;
DrillerConsoleCommandBus::Handler::BusConnect();
}
void DrillToFileComponent::Deactivate()
{
DrillerConsoleCommandBus::Handler::BusDisconnect();
StopDrillerSession(reinterpret_cast<AZ::u64>(this));
}
void DrillToFileComponent::WriteBinary(const void* data, unsigned int dataSize)
{
if (dataSize > 0)
{
m_frameBuffer.insert(m_frameBuffer.end(), reinterpret_cast<const AZ::u8*>(data), reinterpret_cast<const AZ::u8*>(data) + dataSize);
}
}
void DrillToFileComponent::OnEndOfFrame()
{
AZStd::lock_guard<AZStd::mutex> lock(m_writerMutex);
m_writeQueue.push_back();
m_writeQueue.back().swap(m_frameBuffer);
m_signal.notify_all();
}
void DrillToFileComponent::EnumerateAvailableDrillers()
{
DrillerInfoListType availableDrillers;
AZ::Debug::DrillerManager* mgr = NULL;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
if (mgr)
{
for (int i = 0; i < mgr->GetNumDrillers(); ++i)
{
AZ::Debug::Driller* driller = mgr->GetDriller(i);
AZ_Assert(driller, "DrillerManager returned a NULL driller. This is not legal!");
availableDrillers.push_back();
availableDrillers.back().m_id = driller->GetId();
availableDrillers.back().m_groupName = driller->GroupName();
availableDrillers.back().m_name = driller->GetName();
availableDrillers.back().m_description = driller->GetDescription();
}
}
EBUS_EVENT(DrillerConsoleEventBus, OnDrillersEnumerated, availableDrillers);
}
void DrillToFileComponent::StartDrillerSession(const AZ::Debug::DrillerManager::DrillerListType& requestedDrillers, AZ::u64 sessionId)
{
if (!m_drillerSession)
{
AZ_Assert(m_writeQueue.empty(), "write queue is not empty!");
m_sessionId = sessionId;
AZ::Debug::DrillerManager* mgr = nullptr;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
if (mgr)
{
SetStringPool(&m_stringPool);;
m_drillerSession = mgr->Start(*this, requestedDrillers);
AZStd::unique_lock<AZStd::mutex> signalLock(m_writerMutex);
m_isWriterEnabled = true;
AZStd::thread_desc td;
td.m_name = "DrillToFileComponent Writer Thread";
m_writerThread = AZStd::thread(AZStd::bind(&DrillToFileComponent::AsyncWritePump, this), &td);
m_signal.wait(signalLock);
EBUS_EVENT(DrillerConsoleEventBus, OnDrillerSessionStarted, sessionId);
}
}
}
void DrillToFileComponent::StopDrillerSession(AZ::u64 sessionId)
{
if (sessionId == m_sessionId)
{
if (m_drillerSession)
{
AZ::Debug::DrillerManager* mgr = NULL;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
if (mgr)
{
mgr->Stop(m_drillerSession);
}
m_drillerSession = nullptr;
EBUS_EVENT(DrillerConsoleEventBus, OnDrillerSessionStopped, reinterpret_cast<AZ::u64>(this));
}
m_isWriterEnabled = false;
if (m_writerThread.joinable())
{
m_writerMutex.lock();
m_signal.notify_all();
m_writerMutex.unlock();
m_writerThread.join();
}
SetStringPool(nullptr);
m_stringPool.Reset();
m_frameBuffer.clear(); // there may be pending data but we don't want to write it because it's an incomplete frame.
}
}
void DrillToFileComponent::AsyncWritePump()
{
AZStd::unique_lock<AZStd::mutex> signalLock(m_writerMutex);
AZStd::basic_string<char, AZStd::char_traits<char>, AZ::OSStdAllocator> drillerOutputPath;
// Try the log path first
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO)
{
const char* logLocation = fileIO->GetAlias("@log@");
if (logLocation)
{
drillerOutputPath = logLocation;
drillerOutputPath.append("/");
}
}
// Try the executable path
if (drillerOutputPath.empty())
{
EBUS_EVENT_RESULT(drillerOutputPath, AZ::ComponentApplicationBus, GetExecutableFolder);
drillerOutputPath.append("/");
}
drillerOutputPath.append("drillerdata.drl");
AZ::IO::SystemFile output;
output.Open(drillerOutputPath.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
AZ_Assert(output.IsOpen(), "Failed to open driller output file!");
m_signal.notify_all();
while (true)
{
while (!m_writeQueue.empty())
{
AZStd::vector<AZ::u8, AZ::OSStdAllocator> outBuffer;
outBuffer.swap(m_writeQueue.front());
m_writeQueue.pop_front();
signalLock.unlock();
output.Write(outBuffer.data(), outBuffer.size());
output.Flush();
signalLock.lock();
}
if (!m_isWriterEnabled)
{
break;
}
m_signal.wait(signalLock);
}
output.Close();
}
} // namespace AzFramework
@@ -0,0 +1,78 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Driller/DrillerConsoleAPI.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/Driller/DefaultStringPool.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/parallel/condition_variable.h>
//#define ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
namespace AZ
{
struct ClassDataReflection;
}
namespace AzFramework
{
/**
* Runs on the machine being drilled and is responsible for communications
* with the DrillerNetworkConsole running on the tool side as well as
* creating DrillerNetSessionStreams for each driller session being started.
*/
class DrillToFileComponent
: public AZ::Component
, public AZ::Debug::DrillerOutputStream
, public DrillerConsoleCommandBus::Handler
{
public:
AZ_COMPONENT(DrillToFileComponent, "{42BAA25D-7CEB-4A37-8BD4-4A1FE2253894}")
//////////////////////////////////////////////////////////////////////////
// AZ::Component
static void Reflect(AZ::ReflectContext* context);
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerOutputStream
void WriteBinary(const void* data, unsigned int dataSize) override;
void OnEndOfFrame() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerConsoleCommandBus
void EnumerateAvailableDrillers() override;
void StartDrillerSession(const AZ::Debug::DrillerManager::DrillerListType& requestedDrillers, AZ::u64 sessionId) override;
void StopDrillerSession(AZ::u64 sessionId) override;
//////////////////////////////////////////////////////////////////////////
protected:
void AsyncWritePump();
AZ::u64 m_sessionId;
AZ::Debug::DrillerSession* m_drillerSession;
AZ::Debug::DrillerDefaultStringPool m_stringPool;
AZStd::vector<AZ::u8, AZ::OSStdAllocator> m_frameBuffer;
AZStd::deque<AZStd::vector<AZ::u8, AZ::OSStdAllocator>, AZ::OSStdAllocator> m_writeQueue;
AZStd::mutex m_writerMutex;
AZStd::condition_variable m_signal;
AZStd::thread m_writerThread;
bool m_isWriterEnabled;
};
} // namespace AzFramework
@@ -0,0 +1,83 @@
/*
* 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/Driller/Driller.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/EBus/EBus.h>
namespace AzFramework
{
/*
* Descriptors for drillers available on the target machine.
*/
struct DrillerInfo final
{
AZ_RTTI(DrillerInfo, "{197AC318-B65C-4B36-A109-BD25422BF7D0}");
AZ::u32 m_id;
AZStd::string m_groupName;
AZStd::string m_name;
AZStd::string m_description;
};
typedef AZStd::vector<DrillerInfo> DrillerInfoListType;
typedef AZStd::vector<AZ::u32> DrillerListType;
/**
* Driller clients interested in receiving notification events from the
* console should implement this interface.
*/
class DrillerConsoleEvents
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
typedef AZ::OSStdAllocator AllocatorType;
//////////////////////////////////////////////////////////////////////////
virtual ~DrillerConsoleEvents() {}
// A list of available drillers has been received from the target machine.
virtual void OnDrillersEnumerated(const DrillerInfoListType& availableDrillers) = 0;
virtual void OnDrillerSessionStarted(AZ::u64 sessionId) = 0;
virtual void OnDrillerSessionStopped(AZ::u64 sessionId) = 0;
};
typedef AZ::EBus<DrillerConsoleEvents> DrillerConsoleEventBus;
/**
* Commands can be sent to the driller through this interface.
*/
class DrillerConsoleCommands
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
typedef AZ::OSStdAllocator AllocatorType;
// there's only one driller console instance allowed
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
virtual ~DrillerConsoleCommands() {}
// Request an enumeration of available drillers from the target machine
virtual void EnumerateAvailableDrillers() = 0;
// Start a drilling session. This function is normally called internally by DrillerRemoteSession
virtual void StartDrillerSession(const AZ::Debug::DrillerManager::DrillerListType& requestedDrillers, AZ::u64 sessionId) = 0;
// Stop a drilling session. This function is normally called internally by DrillerRemoteSession
virtual void StopDrillerSession(AZ::u64 sessionId) = 0;
};
typedef AZ::EBus<DrillerConsoleCommands> DrillerConsoleCommandBus;
} // namespace AzFramework
@@ -0,0 +1,744 @@
/*
* 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 <AzFramework/Driller/RemoteDrillerInterface.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/Driller/Stream.h>
#include <AzCore/Driller/DefaultStringPool.h>
#include <AzCore/std/containers/fixed_vector.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Component/TickBus.h>
namespace AzFramework
{
//---------------------------------------------------------------------
// TEMP FOR DEBUGGING ONLY!!!
//---------------------------------------------------------------------
class DebugDrillerRemoteSession
: public DrillerRemoteSession
{
public:
AZ_CLASS_ALLOCATOR(DebugDrillerRemoteSession, AZ::OSAllocator, 0);
DebugDrillerRemoteSession()
{
AZStd::string filename = AZStd::string::format("remotedrill_%llu", static_cast<AZ::u64>(reinterpret_cast<size_t>(static_cast<DrillerRemoteSession*>(this))));
m_file.Open(filename.c_str(), AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY | AZ::IO::SystemFile::SF_OPEN_CREATE);
}
~DebugDrillerRemoteSession()
{
m_file.Close();
}
virtual void ProcessIncomingDrillerData(const char* streamIdentifier, const void* data, size_t dataSize)
{
(void)streamIdentifier;
m_file.Write(data, dataSize);
}
virtual void OnDrillerConnectionLost()
{
delete this;
}
AZ::IO::SystemFile m_file;
};
//---------------------------------------------------------------------
/**
* These are the different synchronization messages that are used.
*/
namespace NetworkDrillerSyncMsgId
{
static const AZ::Crc32 NetDrillMsg_RequestDrillerEnum = AZ_CRC("NetDrillMsg_RequestEnum", 0x517cca25);
static const AZ::Crc32 NetDrillMsg_RequestStartSession = AZ_CRC("NetDrillMsg_RequestStartSession", 0x5238b5fe);
static const AZ::Crc32 NetDrillMsg_RequestStopSession = AZ_CRC("NetDrillMsg_RequestStopSession", 0x1abe6888);
static const AZ::Crc32 NetDrillMsg_DrillerEnum = AZ_CRC("NetDrillMsg_Enum", 0x3d0a0f76);
};
struct NetDrillerStartSessionRequest
: public TmMsg
{
AZ_CLASS_ALLOCATOR(NetDrillerStartSessionRequest, AZ::OSAllocator, 0);
AZ_RTTI(NetDrillerStartSessionRequest, "{FF899D61-A445-44B5-9B67-8319ACC8BB06}");
NetDrillerStartSessionRequest()
: TmMsg(NetworkDrillerSyncMsgId::NetDrillMsg_RequestStartSession) {}
// TODO: Replace this with the DrillerListType from driller.h
DrillerListType m_drillerIds;
AZ::u64 m_sessionId;
};
struct NetDrillerStopSessionRequest
: public TmMsg
{
AZ_CLASS_ALLOCATOR(NetDrillerStopSessionRequest, AZ::OSAllocator, 0);
AZ_RTTI(NetDrillerStopSessionRequest, "{BCC6524F-287F-48D2-A21A-029215DB24DD}");
NetDrillerStopSessionRequest(AZ::u64 sessionId = 0)
: TmMsg(NetworkDrillerSyncMsgId::NetDrillMsg_RequestStopSession)
, m_sessionId(sessionId) {}
AZ::u64 m_sessionId;
};
struct NetDrillerEnumeration
: public TmMsg
{
AZ_CLASS_ALLOCATOR(NetDrillerEnumeration, AZ::OSAllocator, 0);
AZ_RTTI(NetDrillerEnumeration, "{60E5BED2-F492-4A55-8EF6-2628CD390991}");
NetDrillerEnumeration()
: TmMsg(NetworkDrillerSyncMsgId::NetDrillMsg_DrillerEnum) {}
DrillerInfoListType m_enumeration;
};
//---------------------------------------------------------------------
// DrillerRemoteSession
//---------------------------------------------------------------------
DrillerRemoteSession::DrillerRemoteSession()
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
: m_decompressor(&AZ::AllocatorInstance<AZ::OSAllocator>::Get())
#endif
{
}
//---------------------------------------------------------------------
DrillerRemoteSession::~DrillerRemoteSession()
{
}
//---------------------------------------------------------------------
void DrillerRemoteSession::StartDrilling(const DrillerListType& drillers, const char* captureFile)
{
if (captureFile)
{
m_captureFile.Open(captureFile, AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
AZ_Warning("DrillerRemoteSession", m_captureFile.IsOpen(), "Failed to open %s. Driller data will not be saved.", captureFile);
}
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
m_decompressor.StartDecompressor();
#endif
BusConnect(static_cast<AZ::u64>(reinterpret_cast<size_t>(this)));
EBUS_EVENT(DrillerNetworkConsoleCommandBus, StartRemoteDrillerSession, drillers, this);
}
//---------------------------------------------------------------------
void DrillerRemoteSession::StopDrilling()
{
EBUS_EVENT(DrillerNetworkConsoleCommandBus, StopRemoteDrillerSession, static_cast<AZ::u64>(reinterpret_cast<size_t>(this)));
BusDisconnect();
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
if (m_decompressor.IsDecompressorStarted())
{
m_decompressor.StopDecompressor();
}
#endif
m_captureFile.Close();
}
//---------------------------------------------------------------------
void DrillerRemoteSession::LoadCaptureData(const char* fileName)
{
m_captureFile.Open(fileName, AZ::IO::SystemFile::SF_OPEN_READ_ONLY);
AZ_Warning("DrillerRemoteSession", m_captureFile.IsOpen(), "Failed to open %s. No driller data could be loaded.", fileName);
if (m_captureFile.IsOpen())
{
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
m_decompressor.StartDecompressor();
#endif
AZ::IO::SystemFile::SizeType bytesRemaining = m_captureFile.Length();
AZ::IO::SystemFile::SizeType maxReadChunkSize = 1024 * 1024;
AZStd::vector<char> readBuffer;
readBuffer.resize_no_construct(static_cast<size_t>(maxReadChunkSize));
while (bytesRemaining > 0)
{
AZ::IO::SystemFile::SizeType bytesToRead = bytesRemaining < maxReadChunkSize ? bytesRemaining : maxReadChunkSize;
if (m_captureFile.Read(bytesToRead, readBuffer.data()) != bytesToRead)
{
AZ_Warning("DrillerRemoteSession", false, "Failed reading driller data. No more driller data can be read.");
break;
}
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
Decompress(readBuffer.data(), static_cast<size_t>(bytesToRead));
ProcessIncomingDrillerData(fileName, m_uncompressedMsgBuffer.data(), m_uncompressedMsgBuffer.size());
#else
ProcessIncomingDrillerData(fileName, readBuffer.data(), readBuffer.size());
#endif
bytesRemaining -= bytesToRead;
}
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
m_decompressor.StopDecompressor();
#endif
m_captureFile.Close();
}
}
//---------------------------------------------------------------------
void DrillerRemoteSession::OnReceivedMsg(TmMsgPtr msg)
{
AZ_Assert(msg->GetCustomBlob(), "Missing driller frame data!");
if (msg->GetCustomBlobSize() == 0)
{
return;
}
if (m_captureFile.IsOpen())
{
if (m_captureFile.Write(msg->GetCustomBlob(), msg->GetCustomBlobSize()) != msg->GetCustomBlobSize())
{
AZ_Warning("DrillerRemoteSession", false, "Failed writing capture data to %s, no more data will be written out.", m_captureFile.Name());
m_captureFile.Close();
}
}
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
Decompress(msg->GetCustomBlob(), msg->GetCustomBlobSize());
ProcessIncomingDrillerData(m_captureFile.Name(),m_uncompressedMsgBuffer.data(), m_uncompressedMsgBuffer.size());
#else
ProcessIncomingDrillerData(m_captureFile.Name(),msg->GetCustomBlob(), msg->GetCustomBlobSize());
#endif
}
//---------------------------------------------------------------------
void DrillerRemoteSession::Decompress(const void* compressedBuffer, size_t compressedBufferSize)
{
m_uncompressedMsgBuffer.clear();
if (m_uncompressedMsgBuffer.capacity() < compressedBufferSize * 10)
{
m_uncompressedMsgBuffer.reserve(compressedBufferSize * 10);
}
#if defined(ENABLE_COMPRESSION_FOR_REMOTE_DRILLER)
unsigned int compressedBytesRemaining = static_cast<unsigned int>(compressedBufferSize);
unsigned int decompressedBytes = 0;
while (compressedBytesRemaining > 0)
{
unsigned int uncompressedBytes = c_decompressionBufferSize;
unsigned int bytesConsumed = m_decompressor.Decompress(reinterpret_cast<const char*>(compressedBuffer) + decompressedBytes, compressedBytesRemaining, m_decompressionBuffer, uncompressedBytes);
decompressedBytes += bytesConsumed;
compressedBytesRemaining -= bytesConsumed;
m_uncompressedMsgBuffer.insert(m_uncompressedMsgBuffer.end(), &m_decompressionBuffer[0], &m_decompressionBuffer[uncompressedBytes]);
}
#else
m_uncompressedMsgBuffer.insert(m_uncompressedMsgBuffer.end(), &((char*)compressedBuffer)[0], &((char*)compressedBuffer)[compressedBufferSize]);
#endif
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// DrillerNetSessionStream
//---------------------------------------------------------------------
/**
* Represents a driller session on the target machine.
* It is responsible for listening for driller events and forwarding
* them to the console machine.
*/
class DrillerNetSessionStream
: public AZ::Debug::DrillerOutputStream
, AZ::SystemTickBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(DrillerNetSessionStream, AZ::OSAllocator, 0);
DrillerNetSessionStream(AZ::u64 sessionId);
~DrillerNetSessionStream();
//---------------------------------------------------------------------
// DrillerOutputStream
//---------------------------------------------------------------------
virtual void WriteBinary(const void* data, unsigned int dataSize);
virtual void OnEndOfFrame();
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// AZ::SystemTickBus
//---------------------------------------------------------------------
void OnSystemTick() override;
//---------------------------------------------------------------------
static const size_t c_defaultUncompressedBufferSize = 256 * 1024;
static const size_t c_defaultCompressedBufferSize = 32 * 1024;
static const size_t c_bufferCount = 2;
AZ::Debug::DrillerSession* m_session;
AZ::u64 m_sessionId;
TargetInfo m_requestor;
size_t m_activeBuffer;
AZStd::vector<char, AZ::OSStdAllocator> m_uncompressedBuffer[c_bufferCount];
AZStd::vector<char, AZ::OSStdAllocator> m_compressedBuffer[c_bufferCount];
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
// Compression
AZ::ZLib m_compressor;
AZStd::fixed_vector<char, c_defaultCompressedBufferSize> m_compressionBuffer;
#endif
// String Pooling
AZ::Debug::DrillerDefaultStringPool m_stringPool;
// TEMP Debug
//AZ::IO::SystemFile m_file;
};
DrillerNetSessionStream::DrillerNetSessionStream(AZ::u64 sessionId)
: m_session(NULL)
, m_sessionId(sessionId)
, m_activeBuffer(0)
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
, m_compressor(&AZ::AllocatorInstance<AZ::OSAllocator>::Get())
#endif
{
for (size_t i = 0; i < c_bufferCount; ++i)
{
m_uncompressedBuffer[i].reserve(c_defaultUncompressedBufferSize);
m_compressedBuffer[i].reserve(c_defaultCompressedBufferSize);
}
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
// Level 3 compression seems to give pretty good compression at decent speed.
// Speed is paramount for us because initial driller packets can be huge and
// we need to be able to compress the data within the driller report call
// without blocking for too long.
m_compressor.StartCompressor(3);
#endif
SetStringPool(&m_stringPool);
AZ::SystemTickBus::Handler::BusConnect();
}
//---------------------------------------------------------------------
DrillerNetSessionStream::~DrillerNetSessionStream()
{
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
m_compressor.StopCompressor();
#endif
// Debug
//m_file.Close();
}
//---------------------------------------------------------------------
void DrillerNetSessionStream::WriteBinary(const void* data, unsigned int dataSize)
{
size_t activeBuffer = m_activeBuffer;
if (dataSize > 0)
{
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
// Only do the compression when the buffer is full so we don't run the compression all the time
if (m_uncompressedBuffer[activeBuffer].size() + dataSize > c_defaultUncompressedBufferSize)
{
// compress
unsigned int curDataSize = static_cast<unsigned int>(m_uncompressedBuffer[activeBuffer].size());
unsigned int remaining = curDataSize;
while (remaining > 0)
{
unsigned int processedBytes = curDataSize - remaining;
unsigned int compressedBytes = m_compressor.Compress(m_uncompressedBuffer[activeBuffer].data() + processedBytes, remaining, m_compressionBuffer.data(), static_cast<unsigned int>(c_defaultCompressedBufferSize));
if (compressedBytes > 0)
{
m_compressedBuffer[activeBuffer].insert(m_compressedBuffer[activeBuffer].end(), m_compressionBuffer.data(), m_compressionBuffer.data() + compressedBytes);
}
}
m_uncompressedBuffer[activeBuffer].clear();
}
m_uncompressedBuffer[activeBuffer].insert(m_uncompressedBuffer[activeBuffer].end(), reinterpret_cast<const char*>(data), reinterpret_cast<const char*>(data) + dataSize);
#else
// Since we are not compressing, transfer the input directly into our compressed buffer
m_compressedBuffer[activeBuffer].insert(m_compressedBuffer[activeBuffer].end(), reinterpret_cast<const char*>(data), reinterpret_cast<const char*>(data) + dataSize);
#endif
}
}
//---------------------------------------------------------------------
void DrillerNetSessionStream::OnEndOfFrame()
{
size_t activeBuffer = m_activeBuffer;
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
// Write whatever data has not yet been compressed and flush the compressor
unsigned int curDataSize = static_cast<unsigned int>(m_uncompressedBuffer[activeBuffer].size());
unsigned int remaining = curDataSize;
unsigned int compressedBytes = 0;
do
{
unsigned int processedBytes = curDataSize - remaining;
compressedBytes = m_compressor.Compress(m_uncompressedBuffer[activeBuffer].data() + processedBytes, remaining, m_compressionBuffer.data(), static_cast<unsigned int>(c_defaultCompressedBufferSize), AZ::ZLib::FT_SYNC_FLUSH);
if (compressedBytes > 0)
{
m_compressedBuffer[activeBuffer].insert(m_compressedBuffer[activeBuffer].end(), m_compressionBuffer.data(), m_compressionBuffer.data() + compressedBytes);
}
} while (compressedBytes > 0 || remaining > 0);
#endif
m_activeBuffer = (activeBuffer + 1) % 2; // switch buffers
}
//-------------------------------------------------------------------------
void DrillerNetSessionStream::OnSystemTick()
{
// The buffer index we want to send is the one we wrote to in the previous frame.
size_t bufferIndex = (m_activeBuffer + 1) % 2;
if (m_compressedBuffer[bufferIndex].empty())
{
return;
}
TmMsg msg(m_sessionId);
msg.AddCustomBlob(m_compressedBuffer[bufferIndex].data(), m_compressedBuffer[bufferIndex].size());
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_requestor, msg);
// Debug
//if (!m_file.IsOpen())
//{
// AZStd::string filename = AZStd::string::format("localdrill_%llu", m_sessionId);
// m_file.Open(filename.c_str(), AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY | AZ::IO::SystemFile::SF_OPEN_CREATE);
//}
//m_file.Write(msg.GetCustomBlob(), msg.GetCustomBlobSize());
// Reset buffers
m_uncompressedBuffer[bufferIndex].clear();
m_compressedBuffer[bufferIndex].clear();
// Buffers may grow during exceptional circumstances. Re-shrink them to their default sizes
// so we don't keep holding on to the memory.
m_uncompressedBuffer[bufferIndex].reserve(c_defaultUncompressedBufferSize);
m_compressedBuffer[bufferIndex].reserve(c_defaultCompressedBufferSize);
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// DrillerNetworkAgent
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::Init()
{
m_cbDrillerEnumRequest = TmMsgCallback(AZStd::bind(&DrillerNetworkAgentComponent::OnRequestDrillerEnum, this, AZStd::placeholders::_1));
m_cbDrillerStartRequest = TmMsgCallback(AZStd::bind(&DrillerNetworkAgentComponent::OnRequestDrillerStart, this, AZStd::placeholders::_1));
m_cbDrillerStopRequest = TmMsgCallback(AZStd::bind(&DrillerNetworkAgentComponent::OnRequestDrillerStop, this, AZStd::placeholders::_1));
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::Activate()
{
m_cbDrillerEnumRequest.BusConnect(NetworkDrillerSyncMsgId::NetDrillMsg_RequestDrillerEnum);
m_cbDrillerStartRequest.BusConnect(NetworkDrillerSyncMsgId::NetDrillMsg_RequestStartSession);
m_cbDrillerStopRequest.BusConnect(NetworkDrillerSyncMsgId::NetDrillMsg_RequestStopSession);
TargetManagerClient::Bus::Handler::BusConnect();
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::Deactivate()
{
TargetManagerClient::Bus::Handler::BusDisconnect();
m_cbDrillerEnumRequest.BusDisconnect(NetworkDrillerSyncMsgId::NetDrillMsg_RequestDrillerEnum);
m_cbDrillerStartRequest.BusDisconnect(NetworkDrillerSyncMsgId::NetDrillMsg_RequestStartSession);
m_cbDrillerStopRequest.BusDisconnect(NetworkDrillerSyncMsgId::NetDrillMsg_RequestStopSession);
AZ::Debug::DrillerManager* mgr = NULL;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
for (size_t i = 0; i < m_activeSessions.size(); ++i)
{
if (mgr)
{
mgr->Stop(m_activeSessions[i]->m_session);
}
delete m_activeSessions[i];
}
m_activeSessions.clear();
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("DrillerNetworkAgentService", 0xcd2ab821));
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("DrillerNetworkAgentService", 0xcd2ab821));
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<DrillerNetworkAgentComponent, AZ::Component>()
->Version(1)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<DrillerNetworkAgentComponent>(
"Driller Network Agent", "Runs on the machine being drilled and communicates with tools")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Profiling")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
ReflectNetDrillerClasses(context);
}
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::TargetLeftNetwork(TargetInfo info)
{
AZ::Debug::DrillerManager* mgr = NULL;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
for (AZStd::vector<DrillerNetSessionStream*>::iterator it = m_activeSessions.begin(); it != m_activeSessions.end(); )
{
if ((*it)->m_requestor.GetNetworkId() == info.GetNetworkId())
{
if (mgr)
{
mgr->Stop((*it)->m_session);
}
delete *it;
it = m_activeSessions.erase(it);
}
else
{
++it;
}
}
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::OnRequestDrillerEnum(TmMsgPtr msg)
{
AZ::Debug::DrillerManager* mgr = NULL;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
if (!mgr)
{
return;
}
TargetInfo sendTo;
EBUS_EVENT_RESULT(sendTo, TargetManager::Bus, GetTargetInfo, msg->GetSenderTargetId());
NetDrillerEnumeration drillerEnum;
for (int i = 0; i < mgr->GetNumDrillers(); ++i)
{
AZ::Debug::Driller* driller = mgr->GetDriller(i);
AZ_Assert(driller, "DrillerManager returned a NULL driller. This is not legal!");
drillerEnum.m_enumeration.push_back();
drillerEnum.m_enumeration.back().m_id = driller->GetId();
drillerEnum.m_enumeration.back().m_groupName = driller->GroupName();
drillerEnum.m_enumeration.back().m_name = driller->GetName();
drillerEnum.m_enumeration.back().m_description = driller->GetDescription();
}
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sendTo, drillerEnum);
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::OnRequestDrillerStart(TmMsgPtr msg)
{
AZ::Debug::DrillerManager* mgr = NULL;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
if (!mgr)
{
return;
}
NetDrillerStartSessionRequest* request = azdynamic_cast<NetDrillerStartSessionRequest*>(msg.get());
AZ_Assert(request, "Not a NetDrillerStartSessionRequest msg!");
AZ::Debug::DrillerManager::DrillerListType drillers;
for (size_t i = 0; i < request->m_drillerIds.size(); ++i)
{
AZ::Debug::DrillerManager::DrillerInfo di;
di.id = request->m_drillerIds[i];
drillers.push_back(di);
}
DrillerNetSessionStream* session = aznew DrillerNetSessionStream(request->m_sessionId);
EBUS_EVENT_RESULT(session->m_requestor, TargetManager::Bus, GetTargetInfo, msg->GetSenderTargetId());
m_activeSessions.push_back(session);
session->m_session = mgr->Start(*session, drillers);
}
//---------------------------------------------------------------------
void DrillerNetworkAgentComponent::OnRequestDrillerStop(TmMsgPtr msg)
{
NetDrillerStopSessionRequest* request = azdynamic_cast<NetDrillerStopSessionRequest*>(msg.get());
for (AZStd::vector<DrillerNetSessionStream*>::iterator it = m_activeSessions.begin(); it != m_activeSessions.end(); ++it)
{
if ((*it)->m_sessionId == request->m_sessionId)
{
AZ::Debug::DrillerManager* mgr = NULL;
EBUS_EVENT_RESULT(mgr, AZ::ComponentApplicationBus, GetDrillerManager);
if (mgr)
{
mgr->Stop((*it)->m_session);
}
delete *it;
m_activeSessions.erase(it);
return;
}
}
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// DrillerRemoteConsole
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::Init()
{
m_cbDrillerEnum = TmMsgCallback(AZStd::bind(&DrillerNetworkConsoleComponent::OnReceivedDrillerEnum, this, AZStd::placeholders::_1));
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::Activate()
{
m_cbDrillerEnum.BusConnect(NetworkDrillerSyncMsgId::NetDrillMsg_DrillerEnum);
DrillerNetworkConsoleCommandBus::Handler::BusConnect();
TargetManagerClient::Bus::Handler::BusConnect();
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::Deactivate()
{
TargetManagerClient::Bus::Handler::BusDisconnect();
DrillerNetworkConsoleCommandBus::Handler::BusDisconnect();
m_cbDrillerEnum.BusDisconnect(NetworkDrillerSyncMsgId::NetDrillMsg_DrillerEnum);
for (size_t i = 0; i < m_activeSessions.size(); ++i)
{
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_curTarget, NetDrillerStopSessionRequest(static_cast<AZ::u64>(reinterpret_cast<size_t>(m_activeSessions[i]))));
m_activeSessions[i]->OnDrillerConnectionLost();
}
m_activeSessions.clear();
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("DrillerNetworkConsoleService", 0x2286125d));
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("DrillerNetworkConsoleService", 0x2286125d));
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<DrillerNetworkConsoleComponent, AZ::Component>()
->Version(1)
;
if (AZ::EditContext* editContext = serialize->GetEditContext())
{
editContext->Class<DrillerNetworkConsoleComponent>(
"Driller Network Console", "Runs on the tool machine and is responsible for communications with the DrillerNetworkAgent")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Profiling")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
ReflectNetDrillerClasses(context);
}
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::EnumerateAvailableDrillers()
{
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_curTarget, TmMsg(NetworkDrillerSyncMsgId::NetDrillMsg_RequestDrillerEnum));
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::StartRemoteDrillerSession(const DrillerListType& drillers, DrillerRemoteSession* handler)
{
NetDrillerStartSessionRequest request;
request.m_drillerIds = drillers;
request.m_sessionId = static_cast<AZ::u64>(reinterpret_cast<size_t>(handler));
m_activeSessions.push_back(handler);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_curTarget, request);
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::StopRemoteDrillerSession(AZ::u64 sessionId)
{
for (size_t i = 0; i < m_activeSessions.size(); ++i)
{
if (sessionId == static_cast<AZ::u64>(reinterpret_cast<size_t>(m_activeSessions[i])))
{
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_curTarget, NetDrillerStopSessionRequest(sessionId));
m_activeSessions[i] = m_activeSessions.back();
m_activeSessions.pop_back();
}
}
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::DesiredTargetConnected(bool connected)
{
if (connected)
{
EBUS_EVENT_RESULT(m_curTarget, TargetManager::Bus, GetDesiredTarget);
EBUS_EVENT(DrillerNetworkConsoleCommandBus, EnumerateAvailableDrillers);
}
else
{
for (size_t i = 0; i < m_activeSessions.size(); ++i)
{
m_activeSessions[i]->OnDrillerConnectionLost();
}
m_activeSessions.clear();
EBUS_EVENT(DrillerNetworkConsoleEventBus, OnReceivedDrillerEnumeration, DrillerInfoListType());
}
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::DesiredTargetChanged(AZ::u32 newTargetID, AZ::u32 oldTargetID)
{
(void)oldTargetID;
(void)newTargetID;
EBUS_EVENT(DrillerNetworkConsoleEventBus, OnReceivedDrillerEnumeration, DrillerInfoListType());
for (size_t i = 0; i < m_activeSessions.size(); ++i)
{
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_curTarget, NetDrillerStopSessionRequest(static_cast<AZ::u64>(reinterpret_cast<size_t>(m_activeSessions[i]))));
m_activeSessions[i]->OnDrillerConnectionLost();
}
m_activeSessions.clear();
}
//---------------------------------------------------------------------
void DrillerNetworkConsoleComponent::OnReceivedDrillerEnum(TmMsgPtr msg)
{
NetDrillerEnumeration* drillerEnum = azdynamic_cast<NetDrillerEnumeration*>(msg.get());
AZ_Assert(drillerEnum, "No NetDrillerEnumeration message!");
EBUS_EVENT(DrillerNetworkConsoleEventBus, OnReceivedDrillerEnumeration, drillerEnum->m_enumeration);
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// ReflectNetDrillerClasses
//---------------------------------------------------------------------
void ReflectNetDrillerClasses(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
// Assume no one else will register our classes.
if (serialize->FindClassData(DrillerInfo::RTTI_Type()) == nullptr)
{
serialize->Class<DrillerInfo>()
->Field("Id", &DrillerInfo::m_id)
->Field("GroupName", &DrillerInfo::m_groupName)
->Field("Name", &DrillerInfo::m_name)
->Field("Description", &DrillerInfo::m_description);
serialize->Class<NetDrillerStartSessionRequest, TmMsg>()
->Field("DrillerIds", &NetDrillerStartSessionRequest::m_drillerIds)
->Field("SessionId", &NetDrillerStartSessionRequest::m_sessionId);
serialize->Class<NetDrillerStopSessionRequest, TmMsg>()
->Field("SessionId", &NetDrillerStopSessionRequest::m_sessionId);
serialize->Class<NetDrillerEnumeration, TmMsg>()
->Field("Enumeration", &NetDrillerEnumeration::m_enumeration);
}
}
}
//---------------------------------------------------------------------
} // namespace AzFramework
@@ -0,0 +1,221 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_REMOTE_DRILLER_INTERFACE_H
#define AZFRAMEWORK_REMOTE_DRILLER_INTERFACE_H
#include <AzCore/Driller/Driller.h>
#include <AzCore/Compression/Compression.h>
#include <AzCore/Component/Component.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Driller/DrillerConsoleAPI.h>
#include <AzFramework/TargetManagement/TargetManagementAPI.h>
//#define ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
namespace AZ
{
struct ClassDataReflection;
}
namespace AzFramework
{
/**
* Represents a remote driller session on the tool machine.
* It is responsible for receiving and processing remote driller data.
* Driller clients should derive from this class and implement the virtual interfaces.
*/
class DrillerRemoteSession
: public TmMsgBus::Handler
{
public:
DrillerRemoteSession();
~DrillerRemoteSession();
// Called when new driller data arrives
virtual void ProcessIncomingDrillerData(const char* streamIdentifier, const void* data, size_t dataSize) = 0;
// Called when the connection to the driller is lost. The session should be deleted in response to this message
virtual void OnDrillerConnectionLost() = 0;
// Start drilling the selected drillers as part of this session
void StartDrilling(const DrillerListType& drillers, const char* captureFile);
// Stop this drill session
void StopDrilling();
// Replay a previously captured driller session from file
void LoadCaptureData(const char* fileName);
protected:
//---------------------------------------------------------------------
// TmMsgBus
//---------------------------------------------------------------------
virtual void OnReceivedMsg(TmMsgPtr msg);
//---------------------------------------------------------------------
void Decompress(const void* compressedBuffer, size_t compressedBufferSize);
static const AZ::u32 c_decompressionBufferSize = 128 * 1024;
AZStd::vector<char> m_uncompressedMsgBuffer;
#ifdef ENABLE_COMPRESSION_FOR_REMOTE_DRILLER
AZ::ZLib m_decompressor;
char m_decompressionBuffer[c_decompressionBufferSize];
#endif
AZ::IO::SystemFile m_captureFile;
};
/**
* Driller clients interested in receiving notification events from the
* network console should implement this interface.
*/
class DrillerNetworkConsoleEvents
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
typedef AZ::OSStdAllocator AllocatorType;
//////////////////////////////////////////////////////////////////////////
virtual ~DrillerNetworkConsoleEvents() {}
// A list of available drillers has been received from the target machine.
virtual void OnReceivedDrillerEnumeration(const DrillerInfoListType& availableDrillers) = 0;
};
typedef AZ::EBus<DrillerNetworkConsoleEvents> DrillerNetworkConsoleEventBus;
/**
* The network driller console implements this interface.
* Commands can be sent to the network console through this interface.
*/
class DrillerNetworkConsoleCommands
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
typedef AZ::OSStdAllocator AllocatorType;
// there's only one driller console instance allowed
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
virtual ~DrillerNetworkConsoleCommands() {}
// Request an enumeration of available drillers from the target machine
virtual void EnumerateAvailableDrillers() = 0;
// Start a drilling session. This function is normally called internally by DrillerRemoteSession
virtual void StartRemoteDrillerSession(const DrillerListType& drillers, DrillerRemoteSession* handler) = 0;
// Stop a drilling session. This function is normally called internally by DrillerRemoteSession
virtual void StopRemoteDrillerSession(AZ::u64 sessionId) = 0;
};
typedef AZ::EBus<DrillerNetworkConsoleCommands> DrillerNetworkConsoleCommandBus;
class DrillerNetSessionStream;
/**
* Runs on the machine being drilled and is responsible for communications
* with the DrillerNetworkConsole running on the tool side as well as
* creating DrillerNetSessionStreams for each driller session being started.
*/
class DrillerNetworkAgentComponent
: public AZ::Component
, public TargetManagerClient::Bus::Handler
{
public:
AZ_COMPONENT(DrillerNetworkAgentComponent, "{B587A74D-6190-4149-91CB-0EA69936BD59}")
//////////////////////////////////////////////////////////////////////////
// AZ::Component
virtual void Init();
virtual void Activate();
virtual void Deactivate();
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void Reflect(AZ::ReflectContext* context);
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// TargetManagerClient
virtual void TargetLeftNetwork(TargetInfo info);
//////////////////////////////////////////////////////////////////////////
protected:
//////////////////////////////////////////////////////////////////////////
// TmMsg handlers
virtual void OnRequestDrillerEnum(TmMsgPtr msg);
virtual void OnRequestDrillerStart(TmMsgPtr msg);
virtual void OnRequestDrillerStop(TmMsgPtr msg);
//////////////////////////////////////////////////////////////////////////
TmMsgCallback m_cbDrillerEnumRequest;
TmMsgCallback m_cbDrillerStartRequest;
TmMsgCallback m_cbDrillerStopRequest;
AZStd::vector<DrillerNetSessionStream*> m_activeSessions;
};
/**
* Runs on the tool machine and is responsible for communications with the
* DrillerNetworkAgent.
*/
class DrillerNetworkConsoleComponent
: public AZ::Component
, public DrillerNetworkConsoleCommandBus::Handler
, public TargetManagerClient::Bus::Handler
{
public:
AZ_COMPONENT(DrillerNetworkConsoleComponent, "{78ACADA4-F2C7-4320-8E97-59DD8B9BE33A}")
//////////////////////////////////////////////////////////////////////////
// AZ::Component
virtual void Init();
virtual void Activate();
virtual void Deactivate();
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void Reflect(AZ::ReflectContext* context);
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerNetworkConsoleCommandBus
virtual void EnumerateAvailableDrillers();
virtual void StartRemoteDrillerSession(const DrillerListType& drillers, DrillerRemoteSession* handler);
virtual void StopRemoteDrillerSession(AZ::u64 sessionId);
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// TargetManagerClient
virtual void DesiredTargetConnected(bool connected);
virtual void DesiredTargetChanged(AZ::u32 newTargetID, AZ::u32 oldTargetID);
//////////////////////////////////////////////////////////////////////////
protected:
//////////////////////////////////////////////////////////////////////////
// TmMsg handlers
virtual void OnReceivedDrillerEnum(TmMsgPtr msg);
//////////////////////////////////////////////////////////////////////////
typedef AZStd::vector<DrillerRemoteSession*> ActiveSessionListType;
ActiveSessionListType m_activeSessions;
TargetInfo m_curTarget;
TmMsgCallback m_cbDrillerEnum;
};
void ReflectNetDrillerClasses(AZ::ReflectContext* context);
} // namespace AzFramework
#endif // AZFRAMEWORK_REMOTE_DRILLER_INTERFACE_H
#pragma once
@@ -0,0 +1,566 @@
/*
* 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 "BehaviorEntity.h"
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////
// BehaviorComponentId
void BehaviorComponentId::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BehaviorComponentId>()
->Version(1)
->Field("ComponentId", &BehaviorComponentId::m_id)
;
serializeContext->RegisterGenericType<AZStd::vector<BehaviorComponentId>>();
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<BehaviorComponentId>("ComponentId")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Constructor()
->Method("IsValid", &BehaviorComponentId::IsValid)
->Method("Equal", &BehaviorComponentId::operator==)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
->Method("ToString", &BehaviorComponentId::ToString)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)
;
}
}
BehaviorComponentId::BehaviorComponentId(AZ::ComponentId id)
: m_id(id)
{
}
BehaviorComponentId::operator AZ::ComponentId() const
{
return m_id;
}
bool BehaviorComponentId::operator==(const BehaviorComponentId& rhs) const
{
return m_id == rhs.m_id;
}
bool BehaviorComponentId::IsValid() const
{
return m_id != AZ::InvalidComponentId;
}
AZStd::string BehaviorComponentId::ToString() const
{
return AZStd::string::format("[%llu]", m_id);
}
////////////////////////////////////////////////////////////////////////////
// BehaviorEntity
namespace Internal
{
void BehaviorEntityScriptConstructor(BehaviorEntity* self, AZ::ScriptDataContext& dc)
{
if (dc.GetNumArguments() == 0)
{
*self = BehaviorEntity();
return;
}
else if (dc.GetNumArguments() == 1)
{
if (dc.IsClass<AZ::EntityId>(0))
{
AZ::EntityId entityId;
dc.ReadArg(0, entityId);
new(self) BehaviorEntity(entityId);
return;
}
else if (dc.IsNil(0))
{
new(self) BehaviorEntity(nullptr);
return;
}
// Constructor taking AZ::Entity* isn't supported.
// AZ::Entity is not exposed to BehaviorContext, so we can't detect args of that type.
}
dc.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true, "Invalid arguments passed to BehaviorEntity().");
new(self) BehaviorEntity();
}
const char* GetComponentName(const AZ::TypeId& componentTypeId)
{
AZ::ComponentDescriptor* descriptor = nullptr;
AZ::ComponentDescriptorBus::EventResult(descriptor, componentTypeId, &AZ::ComponentDescriptorBus::Events::GetDescriptor);
return descriptor ? descriptor->GetName() : "<unknown>";
}
}
void BehaviorEntity::Reflect(AZ::ReflectContext* context)
{
BehaviorComponentId::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BehaviorEntity>()
->Field("EntityId", &BehaviorEntity::m_entityId)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<BehaviorEntity>("Entity", "Entity")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &BehaviorEntity::m_entityId, "EntityId", "")
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<BehaviorEntity>("Entity")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::BehaviorEntityScriptConstructor)
->Constructor()
->Constructor<AZ::EntityId>()
->Constructor<AZ::Entity*>()
->Method("GetName", &BehaviorEntity::GetName)
->Method("SetName", &BehaviorEntity::SetName)
->Method("GetId", &BehaviorEntity::GetId)
->Method("GetOwningContextId", &BehaviorEntity::GetOwningContextId)
->Method("IsValid", &BehaviorEntity::IsValid)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::All)
->Method("Exists", &BehaviorEntity::Exists)
->Method("IsActivated", &BehaviorEntity::IsActivated)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::All)
->Method("Activate", &BehaviorEntity::Activate)
->Method("Deactivate", &BehaviorEntity::Deactivate)
->Method("CreateComponent", &BehaviorEntity::CreateComponent, behaviorContext->MakeDefaultValues(static_cast<const AZ::ComponentConfig*>(nullptr)))
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::List)
->Method("DestroyComponent", &BehaviorEntity::DestroyComponent)
->Method("GetComponents", &BehaviorEntity::GetComponents)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::All)
->Method("FindComponentOfType", &BehaviorEntity::FindComponentOfType)
->Method("FindAllComponentsOfType", &BehaviorEntity::FindAllComponentsOfType)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::All)
->Method("GetComponentType", &BehaviorEntity::GetComponentType)
->Method("GetComponentName", &BehaviorEntity::GetComponentName)
->Method("SetComponentConfiguration", &BehaviorEntity::SetComponentConfiguration)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::List)
->Method("GetComponentConfiguration", &BehaviorEntity::GetComponentConfiguration)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::List)
// Allow BehaviorEntity to be passed to functions expecting AZ::Entity*
->WrappingMember<AZ::Entity*>(&BehaviorEntity::GetRawEntityPtr)
;
}
}
BehaviorEntity::BehaviorEntity(AZ::EntityId entityId)
: m_entityId(entityId)
{
}
BehaviorEntity::BehaviorEntity(AZ::Entity* entity)
: m_entityId(entity ? entity->GetId() : AZ::EntityId())
{
}
AZStd::string BehaviorEntity::GetName() const
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot get entity name. %s", errorMessage.c_str());
return "";
}
return entity->GetName();
}
void BehaviorEntity::SetName(const char* name)
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot set entity name. %s", errorMessage.c_str());
return;
}
entity->SetName(name);
}
AzFramework::EntityContextId BehaviorEntity::GetOwningContextId() const
{
if (!m_entityId.IsValid())
{
AZ_Warning("Entity", false, "Cannot get entity context. Entity ID is invalid.");
return EntityContextId::CreateNull();
}
// no further warnings, iquerying missing entities is a valid use case
EntityContextId contextId = EntityContextId::CreateNull();
EntityIdContextQueryBus::EventResult(contextId, m_entityId, &EntityIdContextQueryBus::Events::GetOwningContextId);
return contextId;
}
bool BehaviorEntity::Exists() const
{
if (!m_entityId.IsValid())
{
AZ_Warning("Entity", false, "Cannot check entity existence. Entity ID is invalid.");
return false;
}
// no further warnings, querying missing entities is a valid use case
return GetValidEntity(nullptr, nullptr, nullptr);
}
bool BehaviorEntity::IsActivated() const
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot get entity activation. %s", errorMessage.c_str());
return false;
}
AZ::Entity::State state = entity->GetState();
return (state == AZ::Entity::State::Active || state == AZ::Entity::State::Activating);
}
void BehaviorEntity::Activate()
{
AZ::Entity* entity;
EntityContextId contextId;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, &contextId, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot activate entity. %s", errorMessage.c_str());
return;
}
if (entity->GetState() != AZ::Entity::State::Init)
{
AZ_Warning("Entity", false, "Cannot activate entity. Entity (id=%s name='%s') must be in the initialized state.", m_entityId.ToString().c_str(), entity->GetName().c_str());
return;
}
EntityContextRequestBus::Event(contextId, &EntityContextRequestBus::Events::ActivateEntity, m_entityId);
// don't warn if activation fails, Entity::Activate() already issues warnings
}
void BehaviorEntity::Deactivate()
{
AZ::Entity* entity;
EntityContextId contextId;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, &contextId, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot deactivate entity. %s", errorMessage.c_str());
return;
}
AZ::Entity::State state = entity->GetState();
if (state != AZ::Entity::State::Active && state != AZ::Entity::State::Activating)
{
AZ_Warning("Entity", false, "Cannot deactivate entity. Entity (id=%s name='%s') must be in the activated state.", m_entityId.ToString().c_str(), entity->GetName().c_str());
return;
}
EntityContextRequestBus::Event(contextId, &EntityContextRequestBus::Events::DeactivateEntity, m_entityId);
}
BehaviorComponentId BehaviorEntity::CreateComponent(const AZ::TypeId& componentTypeId, const AZ::ComponentConfig* componentConfig /*=nullptr*/)
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot create component. %s", errorMessage.c_str());
return AZ::InvalidComponentId;
}
// don't create component if an incompatible component exists on the entity
AZStd::vector<AZ::Component*> incompatibleComponents;
entity->IsComponentReadyToAdd(componentTypeId, nullptr, &incompatibleComponents);
if (!incompatibleComponents.empty())
{
AZ_Warning("Entity", false, "Cannot create component '%s' because it is incompatible with existing component '%s' on entity (id=%s name='%s').",
Internal::GetComponentName(componentTypeId), Internal::GetComponentName(azrtti_typeid(incompatibleComponents[0])), m_entityId.ToString().c_str(), entity->GetName().c_str());
return AZ::InvalidComponentId;
}
AZ::Component* component = entity->CreateComponent(componentTypeId);
if (!component)
{
AZ_Warning("Entity", false, "Failed to create component (type=%s) on entity (id=%s name='%s')", componentTypeId.ToString<AZStd::string>().c_str(), m_entityId.ToString().c_str(), entity->GetName().c_str());
return AZ::InvalidComponentId;
}
if (componentConfig)
{
component->SetConfiguration(*componentConfig);
// don't warn if configuration fails. Entity::SetConfiguration() already gives good warnings.
}
return component->GetId();
}
bool BehaviorEntity::DestroyComponent(BehaviorComponentId componentId)
{
AZ::Component* component;
AZStd::string errorMessage;
if (!GetValidComponent(componentId, &component, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot destroy component. %s", errorMessage.c_str());
return false;
}
if (!component->GetEntity()->RemoveComponent(component))
{
AZ_Warning("Entity", false, "Cannot destroy component. Failed to remove component (id=%llu) from entity (id=%s name='%s').", componentId, m_entityId.ToString().c_str(), component->GetEntity()->GetName().c_str());
return false;
}
delete component;
return true;
}
AZStd::vector<BehaviorComponentId> BehaviorEntity::GetComponents() const
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot get components. %s", errorMessage.c_str());
return AZStd::vector<BehaviorComponentId>();
}
AZStd::vector<BehaviorComponentId> components;
for (AZ::Component* component : entity->GetComponents())
{
components.emplace_back(component->GetId());
}
return components;
}
BehaviorComponentId BehaviorEntity::FindComponentOfType(const AZ::TypeId& componentTypeId) const
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot find component. %s", errorMessage.c_str());
return AZ::InvalidComponentId;
}
BehaviorComponentId componentId = AZ::InvalidComponentId;
if (const AZ::Component* component = entity->FindComponent(componentTypeId))
{
componentId = component->GetId();
}
return componentId;
}
AZStd::vector<BehaviorComponentId> BehaviorEntity::FindAllComponentsOfType(const AZ::TypeId& componentTypeId) const
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot find components. %s", errorMessage.c_str());
return AZStd::vector<BehaviorComponentId>();
}
AZStd::vector<BehaviorComponentId> components;
for (const AZ::Component* component : entity->FindComponents(componentTypeId))
{
components.emplace_back(component->GetId());
}
return components;
}
AZ::TypeId BehaviorEntity::GetComponentType(BehaviorComponentId componentId) const
{
AZ::Component* component;
AZStd::string errorMessage;
if (!GetValidComponent(componentId, &component, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot get component type. %s", errorMessage.c_str());
return AZ::TypeId::CreateNull();
}
return azrtti_typeid(component);
}
AZStd::string BehaviorEntity::GetComponentName(BehaviorComponentId componentId) const
{
AZ::Component* component;
AZStd::string errorMessage;
if (!GetValidComponent(componentId, &component, &errorMessage))
{
AZ_Warning("Entity", false, "Cannot get component name. %s", errorMessage.c_str());
return "";
}
return component->RTTI_GetTypeName();
}
bool BehaviorEntity::SetComponentConfiguration(BehaviorComponentId componentId, const AZ::ComponentConfig& componentConfig)
{
AZ::Component* component;
AZStd::string errorMessage;
if (!GetValidComponent(componentId, &component, &errorMessage))
{
AZ_Warning("Entity", false, "Failed to set component configuration. %s", errorMessage.c_str());
return false;
}
bool success = component->SetConfiguration(componentConfig);
// don't warn if configuration fails. Entity::SetConfiguration() already gives good warnings.
return success;
}
bool BehaviorEntity::GetComponentConfiguration(BehaviorComponentId componentId, AZ::ComponentConfig& outComponentConfig) const
{
AZ::Component* component;
AZStd::string errorMessage;
if (!GetValidComponent(componentId, &component, &errorMessage))
{
AZ_Warning("Entity", false, "Failed to get component configuration. %s", errorMessage.c_str());
return false;
}
bool success = component->GetConfiguration(outComponentConfig);
// don't warn if configuration fails. Entity::GetConfiguration() already gives good warnings.
return success;
}
AZ::Entity* BehaviorEntity::GetRawEntityPtr()
{
AZ::Entity* entity;
AZStd::string errorMessage;
if (!GetValidEntity(&entity, nullptr, &errorMessage))
{
AZ_Warning("Entity", false, errorMessage.c_str());
return nullptr;
}
return entity;
}
bool BehaviorEntity::GetValidEntity(AZ::Entity** outEntity, EntityContextId* outContextId, AZStd::string* outErrorMessage) const
{
AZ::Entity* entity = nullptr;
EntityContextId contextId = EntityContextId::CreateNull();
AZStd::string errorMessage;
bool success = false;
if (m_entityId.IsValid())
{
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, m_entityId);
if (entity)
{
EntityIdContextQueryBus::EventResult(contextId, m_entityId, &EntityIdContextQueryBus::Events::GetOwningContextId);
if (!contextId.IsNull())
{
success = true;
}
else
{
errorMessage = AZStd::string::format("Entity has no owning context (id=%s name='%s')", m_entityId.ToString().c_str(), entity->GetName().c_str());
}
}
else
{
errorMessage = AZStd::string::format("Entity does not exist (id=%s)", m_entityId.ToString().c_str());
}
}
else
{
errorMessage = "Entity ID is invalid";
}
if (outEntity)
{
*outEntity = success ? entity : nullptr;
}
if (outContextId)
{
*outContextId = success ? contextId : AZ::TypeId::CreateNull();
}
if (outErrorMessage)
{
if (success)
{
outErrorMessage->clear();
}
else
{
*outErrorMessage = AZStd::move(errorMessage);
}
}
return success;
}
bool BehaviorEntity::GetValidComponent(BehaviorComponentId componentId, AZ::Component** outComponent, AZStd::string* outErrorMessage) const
{
AZ::Entity* entity = nullptr;
AZ::Component* component = nullptr;
AZStd::string errorMessage;
bool success = false;
if (GetValidEntity(&entity, nullptr, &errorMessage))
{
component = entity->FindComponent(componentId);
if (component)
{
success = true;
}
else
{
errorMessage = AZStd::string::format("Component (id=%llu) not found on entity(id=%s name='%s').", static_cast<AZ::u64>(componentId), m_entityId.ToString().c_str(), entity->GetName().c_str());
}
}
if (outComponent)
{
*outComponent = success ? component : nullptr;
}
if (outErrorMessage)
{
if (success)
{
outErrorMessage->clear();
}
else
{
*outErrorMessage = AZStd::move(errorMessage);
}
}
return success;
}
} // namespace AzFramework
@@ -0,0 +1,243 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Entity/EntityContext.h>
namespace AzFramework
{
/**
* A wrapper around AZ::ComponentId, for use within the BehaviorContext.
* This wrapper is necessary because AZ::ComponentId is just a 64bit int and
* Lua cannot store the exact value of a 64bit int.
*
* BehaviorComponentId should only be used in coordination with the
* BehaviorEntity class to access components on deactivated entities.
* Other systems, which communicate with activated entities,
* should use the appropriate EBus to communicate with components.
*/
class BehaviorComponentId
{
public:
AZ_TYPE_INFO(BehaviorComponentId, "{60A9A069-9C3D-465A-B7AD-0D6CC803990A}");
AZ_CLASS_ALLOCATOR(BehaviorComponentId, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
BehaviorComponentId() = default;
BehaviorComponentId(AZ::ComponentId id);
operator AZ::ComponentId() const;
bool operator==(const BehaviorComponentId& rhs) const;
bool IsValid() const;
AZStd::string ToString() const;
private:
AZ::ComponentId m_id = AZ::InvalidComponentId;
};
/**
* A wrapper around calls to AZ::Entity, for use within the BehaviorContext.
* It is always safe to call functions on this class
* even if the entity it represents has been deleted from memory.
*/
class BehaviorEntity
{
public:
AZ_RTTI(BehaviorEntity, "{41CC88A4-FE07-48E6-943D-998DE68AFF5C}");
AZ_CLASS_ALLOCATOR(BehaviorEntity, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
virtual ~BehaviorEntity() = default;
/**
* Constructs an invalid BehaviorEntity.
* Any methods called on this instance will have no effect.
*/
BehaviorEntity() = default;
/**
* Constructs a BehaviorEntity with the given entity ID.
* @param entityId The ID of the entity.
*/
explicit BehaviorEntity(AZ::EntityId entityId);
/**
* Constructs a BehaviorEntity with the ID of the provided entity.
* @param entity Entity that this BehaviorEntity will represent.
* If nullptr is provided then an invalid BehaviorEntity is constructed.
*/
explicit BehaviorEntity(AZ::Entity* entity);
/**
* @copydoc AZ::Entity::GetName()
*/
AZStd::string GetName() const;
/**
* @copydoc AZ::Entity::SetName()
*/
void SetName(const char* name);
/**
* @copydoc AZ::Entity::GetId()
*/
AZ::EntityId GetId() const { return m_entityId; }
/**
* @copydoc EntityIdContextQueries::GetOwningContextId()
*/
EntityContextId GetOwningContextId() const;
/**
* Check whether this instance has a valid entity ID.
* Note that a valid entity ID does not indicate whether
* the entity it represents currently exists in memory.
* @return Returns true if the entity ID is valid. Otherwise, false.
*/
bool IsValid() const { return m_entityId.IsValid(); };
/**
* Check whether the entity exists in memory.
* Note that an entity which exists may or may not be activated.
* @return true if the entity exists in memory.
*/
bool Exists() const;
/**
* Check whether the entity is activated.
* @return Returns true if the entity is activated. Otherwise, false.
*/
bool IsActivated() const;
/**
* @copydoc AZ::Entity::Activate()
*/
void Activate();
/**
* @copydoc AZ::Entity::Deactivate()
*/
void Deactivate();
/**
* Creates a component and attaches it to the entity.
* You cannot add a component to an entity when the entity is activated.
* @param componentTypeId Type ID of component to create.
* For example, pass TransformComponentTypeId to create a TransformComponent.
* @param componentConfig (Optional) A configuration to apply to the new component.
* The configuration class must be of the appropriate type for this component.
* For example, use a TransformConfig with a TransformComponent.
* @return Returns the ID of the new component.
* If the component could not be created then AZ::InvalidEntityId is returned.
*/
BehaviorComponentId CreateComponent(const AZ::TypeId& componentTypeId, const AZ::ComponentConfig* componentConfig = nullptr);
/**
* Removes the component from the entity and destroys it.
* You cannot destroy a component while the entity is activated.
* @param componentId ID of the component to destroy.
* @return True if the component was destroyed. Otherwise, false.
*/
bool DestroyComponent(BehaviorComponentId componentId);
/**
* Gets all components registered with the entity.
* @return A vector with the IDs of all components registered with the entity.
*/
AZStd::vector<BehaviorComponentId> GetComponents() const;
/**
* Finds the first component of the requested component type.
* @param componentTypeId The type of component to find.
* @return The ID of the first component of the requested type.
* Returns invalid component ID if a component of the requested type cannot be found.
*/
BehaviorComponentId FindComponentOfType(const AZ::TypeId& componentTypeId) const;
/**
* Gets all components of a specified type registered with the entity.
* @param componentTypeId The type of component to find.
* @return A vector with the IDs of all components of a specified type registered with the entity.
*/
AZStd::vector<BehaviorComponentId> FindAllComponentsOfType(const AZ::TypeId& componentTypeId) const;
/**
* Get the type of a specific component on the entity.
* @param componentId The ID of the component to query.
* @return The type of the specified component.
* Returns an invalid type ID if the component is not found.
*/
AZ::TypeId GetComponentType(BehaviorComponentId componentId) const;
/**
* Get the name of a specific component on the entity.
* @param componentId the ID of the component to query.
* @return The name of the component.
*/
AZStd::string GetComponentName(BehaviorComponentId componentId) const;
/**
* Set the component's configuration.
* You cannot configure a component while the entity is activated.
* @param componentId The ID of the component to configure.
* @param componentConfig The component will set its properties based on this configuration.
* The configuration class must be of the appropriate type for this component.
* For example, use a TransformConfig with a TransformComponent.
* @return True if the configuration was successfully copied to the component.
* Returns false if the component was not found, or the component was not
* compatible with the provided configuration class.
*/
bool SetComponentConfiguration(BehaviorComponentId componentId, const AZ::ComponentConfig& componentConfig);
/**
* Get a component's configuration.
* @param componentId The ID of the component to query.
* @param outComponentConfig[out] The component will copy its properties into this configuration class.
* The configuration class must be of the appropriate type for this component.
* For example, use a TransformConfig with a TransformComponent.
* @return True if the configuration was successfully copied from the component.
* Returns false if the component was not found, or the component was not
* compatible with the provided configuration class.
*/
bool GetComponentConfiguration(BehaviorComponentId componentId, AZ::ComponentConfig& outComponentConfig) const;
private:
/**
* Get a pointer to the entity.
* @return Return a pointer to the entity if it exists. Otherwise, nullptr.
*/
AZ::Entity* GetRawEntityPtr();
/**
* Attempts to retrieve valid entity values.
* If anything is invalid, an error message describes the issue.
* @param[out] outEntity (Optional) On success, the valid entity pointer.
* @param[out] outEntityContextId (Optional) On success, the valid entity context ID.
* @param[out] outErrorMessage (Optional) On failure, an error message describing what went wrong.
* @return True if successful and all values are valid. Otherwise, false.
*/
bool GetValidEntity(AZ::Entity** outEntity, EntityContextId* outContextId, AZStd::string* outErrorMessage) const;
/**
* Attempt to retrieve a valid component.
* If anything is invalid, an error message describes the issue.
* @param componentId component ID to retrieve
* @param outComponent (Optional On success, the valid component pointer.
* @param outErrorMessage (Optional) On failure, an error message describing what went wrong.
* @return True if successful and component was valid. Otherwise, false.
*/
bool GetValidComponent(BehaviorComponentId componentId, AZ::Component** outComponent, AZStd::string* outErrorMessage) const;
AZ::EntityId m_entityId;
};
} // namespace AzFramework
@@ -0,0 +1,380 @@
/*
* 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/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/std/containers/stack.h>
#include "EntityContext.h"
namespace AzFramework
{
//=========================================================================
// Reflect
//=========================================================================
void EntityContext::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
// EntityContext entity data is serialized through streams / Ebus messages.
serializeContext->Class<EntityContext>()
->Version(1)
;
}
}
//=========================================================================
// EntityContext ctor
//=========================================================================
EntityContext::EntityContext(AZ::SerializeContext* serializeContext /*= nullptr*/)
: EntityContext(EntityContextId::CreateRandom(), serializeContext)
{
EntityContextRequestBus::Handler::BusConnect(m_contextId);
}
//=========================================================================
// EntityContext ctor
//=========================================================================
EntityContext::EntityContext(const AZ::Uuid& contextId, AZ::SerializeContext* serializeContext /*= nullptr*/)
: EntityContext(contextId, nullptr, serializeContext)
{
}
EntityContext::EntityContext(const EntityContextId& contextId, AZStd::unique_ptr<EntityOwnershipService> entityOwnershipService,
AZ::SerializeContext* serializeContext)
: m_serializeContext(serializeContext)
, m_contextId(contextId)
{
if (nullptr == serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
AZ_Assert(m_serializeContext, "Failed to retrieve application serialization context.");
}
if (m_contextId.IsNull())
{
m_contextId = EntityContextId::CreateRandom();
AZ_Assert(m_contextId.IsNull(), "Failed to create an entity context id.");
}
if (nullptr == entityOwnershipService)
{
m_entityOwnershipService = AZStd::make_unique<AzFramework::SliceEntityOwnershipService>(m_contextId, m_serializeContext);
AZ_Assert(m_entityOwnershipService, "Failed to create an entity ownership service.");
}
else
{
m_entityOwnershipService = AZStd::move(entityOwnershipService);
}
EntityContextRequestBus::Handler::BusConnect(m_contextId);
EntityContextEventBus::Bind(m_eventBusPtr, m_contextId);
}
//=========================================================================
// EntityContext dtor
//=========================================================================
EntityContext::~EntityContext()
{
m_eventBusPtr = nullptr;
DestroyContext();
}
//=========================================================================
// InitContext
//=========================================================================
void EntityContext::InitContext()
{
AZ_Assert(m_entityOwnershipService, "Entity Ownership Service has not been created yet");
EntityOwnershipServiceNotificationBus::Handler::BusConnect(m_contextId);
m_entityOwnershipService->Initialize();
// If any of the entity contexts that extend the base entity context override these handler functions, those overriden functions
// will be set as the callbacks.
m_entityOwnershipService->SetEntitiesAddedCallback([this](const EntityList& entityList)
{
this->HandleEntitiesAdded(entityList);
});
m_entityOwnershipService->SetEntitiesRemovedCallback([this](const EntityIdList& entityIds)
{
this->HandleEntitiesRemoved(entityIds);
});
m_entityOwnershipService->SetValidateEntitiesCallback([this](const EntityList& entities)
{
return this->ValidateEntitiesAreValidForContext(entities);
});
}
//=========================================================================
// DestroyContext
//=========================================================================
void EntityContext::DestroyContext()
{
if (m_entityOwnershipService)
{
m_entityOwnershipService->Reset();
EntityOwnershipServiceNotificationBus::Handler::BusDisconnect(m_contextId);
m_entityOwnershipService->Destroy();
}
}
//=========================================================================
// ResetContext
//=========================================================================
void EntityContext::ResetContext()
{
m_entityOwnershipService->Reset();
}
//=========================================================================
// HandleEntitiesAdded
//=========================================================================
void EntityContext::HandleEntitiesAdded(const EntityList& entities)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
for (AZ::Entity* entity : entities)
{
AZ::EntityBus::MultiHandler::BusConnect(entity->GetId());
EntityIdContextQueryBus::MultiHandler::BusConnect(entity->GetId());
EntityContextEventBus::Event(m_eventBusPtr, &EntityContextEventBus::Events::OnEntityContextCreateEntity, *entity);
}
OnContextEntitiesAdded(entities);
}
//=========================================================================
// HandleEntitiesRemoved
//=========================================================================
void EntityContext::HandleEntitiesRemoved(const EntityIdList& entityIds)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
for (AZ::EntityId id : entityIds)
{
OnContextEntityRemoved(id);
EntityContextEventBus::Event(m_eventBusPtr, &EntityContextEventBus::Events::OnEntityContextDestroyEntity, id);
EntityIdContextQueryBus::MultiHandler::BusDisconnect(id);
AZ::EntityBus::MultiHandler::BusDisconnect(id);
}
}
//=========================================================================
// ValidateEntitiesAreValidForContext
//=========================================================================
bool EntityContext::ValidateEntitiesAreValidForContext(const EntityList&)
{
return true;
}
//=========================================================================
// IsOwnedByThisContext
//=========================================================================
bool EntityContext::IsOwnedByThisContext(const AZ::EntityId& entityId)
{
// Get ID of the owning context of the incoming entity ID and compare it to
// the id of this context.
EntityContextId owningContextId = EntityContextId::CreateNull();
EntityIdContextQueryBus::EventResult(owningContextId, entityId, &EntityIdContextQueryBus::Events::GetOwningContextId);
return owningContextId == m_contextId;
}
//=========================================================================
// CreateEntity
//=========================================================================
AZ::Entity* EntityContext::CreateEntity(const char* name)
{
AZ::Entity* entity = aznew AZ::Entity(name);
AddEntity(entity);
return entity;
}
//=========================================================================
// AddEntity
//=========================================================================
void EntityContext::AddEntity(AZ::Entity* entity)
{
AZ_Assert(!EntityIdContextQueryBus::FindFirstHandler(entity->GetId()), "Entity already belongs to a context.");
m_entityOwnershipService->AddEntity(entity);
}
//=========================================================================
// ActivateEntity
//=========================================================================
void EntityContext::ActivateEntity(AZ::EntityId entityId)
{
AZ_ASSET_ATTACH_TO_SCOPE(this);
// Verify that this context has the right to perform operations on the entity
bool validEntity = IsOwnedByThisContext(entityId);
AZ_Warning("GameEntityContext", validEntity, "Entity with id %llu does not belong to the game context.", entityId);
if (validEntity)
{
// Look up the entity and activate it.
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
if (entity)
{
// Safety Check: Is the entity initialized?
if (entity->GetState() == AZ::Entity::State::Constructed)
{
AZ_Warning("GameEntityContext", false, "Entity with id %llu was not initialized before activation requested.", entityId);
entity->Init();
}
if (entity->GetState() == AZ::Entity::State::Init)
{
entity->Activate();
}
}
}
}
//=========================================================================
// DeactivateEntity
//=========================================================================
void EntityContext::DeactivateEntity(AZ::EntityId entityId)
{
// Verify that this context has the right to perform operations on the entity
bool validEntity = IsOwnedByThisContext(entityId);
AZ_Warning("GameEntityContext", validEntity, "Entity with id %llu does not belong to the game context.", entityId);
if (validEntity)
{
// Then look up the entity and deactivate it.
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
if (entity)
{
switch (entity->GetState())
{
case AZ::Entity::State::Activating:
// Queue deactivate to trigger next frame
AZ::TickBus::QueueFunction(&AZ::Entity::Deactivate, entity);
break;
case AZ::Entity::State::Active:
// Deactivate immediately
entity->Deactivate();
break;
default:
// Don't do anything, it's not even active.
break;
}
}
}
}
//=========================================================================
// DestroyEntity
//=========================================================================
bool EntityContext::DestroyEntity(AZ::Entity* entity)
{
AZ_Assert(entity, "Invalid entity passed to DestroyEntity");
EntityContextId owningContextId = EntityContextId::CreateNull();
EntityIdContextQueryBus::EventResult(owningContextId, entity->GetId(), &EntityIdContextQueryBus::Events::GetOwningContextId);
AZ_Assert(owningContextId == m_contextId, "Entity does not belong to this context, and therefore can not be safely destroyed by this context.");
if (owningContextId == m_contextId)
{
return m_entityOwnershipService->DestroyEntity(entity);
}
return false;
}
//=========================================================================
// DestroyEntity
//=========================================================================
bool EntityContext::DestroyEntityById(AZ::EntityId entityId)
{
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
if (entity)
{
return DestroyEntity(entity);
}
return false;
}
//=========================================================================
// CloneEntity
//=========================================================================
AZ::Entity* EntityContext::CloneEntity(const AZ::Entity& sourceEntity)
{
AZ_Assert(m_entityOwnershipService->IsInitialized(), "The context has not been initialized.");
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
AZ_Assert(serializeContext, "Failed to retrieve application serialization context.");
AZ::Entity* entity = serializeContext->CloneObject(&sourceEntity);
AZ_Error("EntityContext", entity != nullptr, "Failed to clone source entity.");
if (entity)
{
entity->SetId(AZ::Entity::MakeId());
AddEntity(entity);
}
return entity;
}
//=========================================================================
// EntityBus::OnEntityDestruction
//=========================================================================
void EntityContext::OnEntityDestruction(const AZ::EntityId& entityId)
{
EntityContextId owningContextId = EntityContextId::CreateNull();
EntityIdContextQueryBus::EventResult(owningContextId, entityId, &EntityIdContextQueryBus::Events::GetOwningContextId);
if (owningContextId == m_contextId)
{
m_entityOwnershipService->DestroyEntityById(entityId);
}
}
AZ::SerializeContext* EntityContext::GetSerializeContext() const
{
return m_serializeContext;
}
void EntityContext::PrepareForEntityOwnershipServiceReset()
{
PrepareForContextReset();
}
void EntityContext::OnEntityOwnershipServiceReset()
{
OnContextReset();
EntityContextEventBus::Event(m_contextId, &EntityContextEventBus::Events::OnEntityContextReset);
}
void EntityContext::OnEntitiesReloadedFromStream(const EntityList& entities)
{
OnRootEntityReloaded();
EntityContextEventBus::Event(m_contextId, &EntityContextEventBus::Events::OnEntityContextLoadedFromStream, entities);
}
} // namespace AzFramework
@@ -0,0 +1,136 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_ENTITYCONTEXT_H
#define AZFRAMEWORK_ENTITYCONTEXT_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/IdUtils.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Entity/SliceEntityOwnershipService.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
class EntityContext;
/**
* Provides services for a group of entities under the umbrella of a given context.
*
* e.g. Edit-time entities and runtime entities would belong to separate contexts.
*
* A context owns a root entity, which can be serialized in or out. Interfaces are
* provided for creating entities owned by the context.
*
* Entity contexts are not required to use entities, but provide a package for managing
* independent prefab hierarchies (i.e. a level, a world, etc).
*/
class EntityContext
: public EntityIdContextQueryBus::MultiHandler
, public AZ::EntityBus::MultiHandler
, public EntityContextRequestBus::Handler
, public EntityOwnershipServiceNotificationBus::Handler
{
public:
AZ_TYPE_INFO(EntityContext, "{4F98A6B9-C7B5-450E-8A8A-30EEFC411EF5}");
EntityContext(AZ::SerializeContext* serializeContext = nullptr);
EntityContext(const EntityContextId& contextId, AZ::SerializeContext* serializeContext = nullptr);
EntityContext(const EntityContextId& contextId, AZStd::unique_ptr<EntityOwnershipService> entityOwnershipService,
AZ::SerializeContext* serializeContext = nullptr);
virtual ~EntityContext();
void InitContext();
void DestroyContext();
/// \return the context's Id, which is used to listen on a given context's request or event bus.
const EntityContextId& GetContextId() const { return m_contextId; }
//////////////////////////////////////////////////////////////////////////
// EntityContextRequestBus
AZ::Entity* CreateEntity(const char* name) override;
void AddEntity(AZ::Entity* entity) override;
void ActivateEntity(AZ::EntityId entityId) override;
void DeactivateEntity(AZ::EntityId entityId) override;
bool DestroyEntity(AZ::Entity* entity) override;
bool DestroyEntityById(AZ::EntityId entityId) override;
AZ::Entity* CloneEntity(const AZ::Entity& sourceEntity) override;
void ResetContext() override;
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* context);
protected:
//////////////////////////////////////////////////////////////////////////
// EntityIdContextQueryBus
EntityContextId GetOwningContextId() override { return m_contextId; }
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// EntityOwnershipServiceNotificationBus
void PrepareForEntityOwnershipServiceReset() override;
void OnEntityOwnershipServiceReset() override;
void OnEntitiesReloadedFromStream(const EntityList& entities) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// EntityBus
void OnEntityDestruction(const AZ::EntityId& entityId) override;
//////////////////////////////////////////////////////////////////////////
void HandleEntitiesAdded(const EntityList& entities);
void HandleEntitiesRemoved(const EntityIdList& entityIds);
AZ::SerializeContext* GetSerializeContext() const;
/// Entity context derived implementations can conduct specialized actions when internal events occur, such as adds/removals/resets.
virtual void OnContextEntitiesAdded(const EntityList& /*entities*/) {}
virtual void OnContextEntityRemoved(const AZ::EntityId& /*id*/) {}
virtual void OnRootEntityReloaded() {}
virtual void PrepareForContextReset() { m_contextIsResetting = true; }
virtual void OnContextReset() { m_contextIsResetting = false; }
/// Used to validate that the given list of entities are valid for this context
/// For example they could be non-UI entities being instantiated in a UI context
virtual bool ValidateEntitiesAreValidForContext(const EntityList& entities);
/// Determine if the entity with the given ID is owned by this Entity Context
/// \param entityId An entity ID to check
/// \return true if this context owns the entity with the given id.
bool IsOwnedByThisContext(const AZ::EntityId& entityId);
AZ::SerializeContext* m_serializeContext;
//! Id of the context, used to address bus messages
EntityContextId m_contextId;
//! Pre-bound event bus for the context.
EntityContextEventBus::BusPtr m_eventBusPtr;
//! EntityOwnershipService is responsible for the management of entities used by this context. Such as loading, creation, etc.
AZStd::unique_ptr<EntityOwnershipService> m_entityOwnershipService;
// Tracks if the context is currently being reset.
// This allows systems to skip steps during teardown that will be handled in bulk by the reset.
bool m_contextIsResetting = false;
};
} // namespace AzFramework
#endif // AZFRAMEWORK_ENTITYCONTEXT_H
@@ -0,0 +1,239 @@
/*
* 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.
*
*/
/**
* @file
* Header file for buses that dispatch and receive events from an entity context.
* Entity contexts are collections of entities. Examples of entity contexts are
* the editor context, game context, a custom context, and so on.
*/
#ifndef AZFRAMEWORK_ENTITYCONTEXTBUS_H
#define AZFRAMEWORK_ENTITYCONTEXTBUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/ComponentBus.h>
namespace AZ
{
class Entity;
class EntityId;
}
namespace AzFramework
{
class EntityContext;
/**
* Unique ID for an entity context.
*/
using EntityContextId = AZ::Uuid;
using EntityList = AZStd::vector<AZ::Entity*>;
/**
* Interface for AzFramework::EntityContextRequestBus, which is
* the EBus that makes requests to a given entity context.
* If you want to make requests to a specific entity context, such
* as the game entity context, use the interface specific to that
* context. If you want to make requests to multiple types of entity
* contexts, use this interface.
*/
class EntityContextRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
/**
* Overrides the default AZ::EBusAddressPolicy so that the EBus has
* multiple addresses. Events that are addressed to an ID are received
* by all handlers that are connected to that ID.
*/
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
/**
* Specifies that events are addressed by entity context ID.
*/
typedef EntityContextId BusIdType;
//////////////////////////////////////////////////////////////////////////
/**
* Creates an entity and adds it to the entity context.
* This operation does not activate the entity by default.
* @param name A name for the entity.
* @return A pointer to a new entity.
* This operation succeeds unless the system is completely out of memory.
*/
virtual AZ::Entity* CreateEntity(const char* name) = 0;
/**
* Adds an entity to the entity context.
* This operation does not activate the entity by default.
* Derived classes might choose to set the entity to another state.
* @param entity A pointer to the entity to add.
*/
virtual void AddEntity(AZ::Entity* entity) = 0;
/**
* Activates an entity that is owned by the entity context.
* @param id The ID of the entity to activate.
*/
virtual void ActivateEntity(AZ::EntityId entityId) = 0;
/**
* Deactivates an entity that is owned by the entity context.
* @param id The ID of the entity to deactivate.
*/
virtual void DeactivateEntity(AZ::EntityId entityId) = 0;
/**
* Removes an entity from the entity context and destroys the entity.
* @param entity A pointer to the entity to destroy.
* @return If the entity context does not own the entity,
* this returns false and does not destroy the entity.
*/
virtual bool DestroyEntity(AZ::Entity* entity) = 0;
/**
* Removes an entity from the entity context and destroys the entity.
* @param entityId The ID of the entity to destroy.
* @return If the entity context does not own the entity,
* this returns false and does not destroy the entity.
*/
virtual bool DestroyEntityById(AZ::EntityId entityId) = 0;
/**
* Creates a copy of the entity in the entity context.
* The cloned copy is assigned a unique entity ID.
* @param sourceEntity A reference to the entity to clone.
* @return A pointer to the cloned copy of the entity. This operation
* can fail if serialization data fails to interpret the source entity.
*/
virtual AZ::Entity* CloneEntity(const AZ::Entity& sourceEntity) = 0;
/**
* Clears the entity context by destroying all entities and prefab instances
* that the entity context owns.
*/
virtual void ResetContext() = 0;
};
/**
* The EBus for requests to the entity context.
* The events are defined in the AzFramework::EntityContextRequests class.
* If you want to make requests to a specific entity context, such
* as the game entity context, use the bus specific to that context.
* If you want to make requests to multiple types of entity contexts,
* use this bus.
*/
using EntityContextRequestBus = AZ::EBus<EntityContextRequests>;
/**
* Interface for the AzFramework::EntityContextEventBus, which is the EBus
* that dispatches notification events from the global entity context.
* If you want to receive notification events from a specific entity context,
* such as the game entity context, use the interface specific to that context.
* If you want to receive notification events from multiple types of entity
* contexts, use this interface.
*/
class EntityContextEvents
: public AZ::EBusTraits
{
public:
/**
* Destroys the instance of the class.
*/
virtual ~EntityContextEvents() {}
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
/**
* Overrides the default AZ::EBusAddressPolicy to specify that the EBus
* has multiple addresses. Events that are addressed to an ID are received
* by all handlers connected to that ID.
*/
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
/**
* Specifies that events are addressed by entity context ID.
*/
typedef EntityContextId BusIdType;
//////////////////////////////////////////////////////////////////////////
/**
* Signals that an entity context was loaded from a stream.
* @param contextEntities A reference to a list of entities that
* are owned by the entity context that was loaded.
*/
virtual void OnEntityContextLoadedFromStream(const EntityList& /*contextEntities*/) {}
/**
* Signals that the entity context was reset.
*/
virtual void OnEntityContextReset() {}
/**
* Signals that the entity context created an entity.
* @param entity A reference to the entity that was created.
*/
virtual void OnEntityContextCreateEntity(AZ::Entity& /*entity*/) {}
/**
* Signals that the entity context is about to destroy an entity.
* @param id A reference to the ID of the entity that will be destroyed.
*/
virtual void OnEntityContextDestroyEntity(const AZ::EntityId& /*id*/) {}
};
/**
* The EBus for entity context events.
* The events are defined in the AzFramework::EntityContextEvents class.
* If you want to receive event notifications from a specific entity context,
* such as the game entity context, use the bus specific to that context.
* If you want to receive event notifications from multiple types of entity
* contexts, use this bus.
*/
using EntityContextEventBus = AZ::EBus<EntityContextEvents>;
/**
* Interface for AzFramework::EntityIdContextQueryBus, which is
* the EBus that queries an entity about its context.
*/
class EntityIdContextQueries
: public AZ::ComponentBus
{
public:
/**
* Destroys the instance of the class.
*/
virtual ~EntityIdContextQueries() {}
/**
* Gets the ID of the entity context that the entity belongs to.
* @return The ID of the entity context that the entity belongs to.
*/
virtual EntityContextId GetOwningContextId() = 0;
};
/**
* The EBus for querying an entity about its context.
* The events are defined in the AzFramework::EntityIdContextQueries class.
*/
using EntityIdContextQueryBus = AZ::EBus<EntityIdContextQueries>;
} // namespace AzFramework
#endif // AZFRAMEWORK_ENTITYCONTEXTBUS_H
@@ -0,0 +1,186 @@
/*
* 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/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzCore/Math/Color.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Viewport/CameraState.h>
namespace AZ
{
class Entity;
}
struct DisplayContext;
class ITexture;
namespace AzFramework
{
/// DebugDisplayRequests provides a debug draw api to be used by components and viewport features.
class DebugDisplayRequests
: public AZ::EBusTraits
{
public:
// EBusTraits overrides
using BusIdType = AZ::s32;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual void SetColor(float r, float g, float b, float a = 1.f) { (void)r; (void)g; (void)b; (void)a; }
virtual void SetColor(const AZ::Color& color) { (void)color; }
virtual void SetColor(const AZ::Vector4& color) { (void)color; }
virtual void SetAlpha(float a) { (void)a; }
virtual void DrawQuad(const AZ::Vector3& p1, const AZ::Vector3& p2, const AZ::Vector3& p3, const AZ::Vector3& p4) { (void)p1; (void)p2; (void)p3; (void)p4; }
virtual void DrawQuad(float width, float height) { (void)width; (void)height; }
virtual void DrawWireQuad(const AZ::Vector3& p1, const AZ::Vector3& p2, const AZ::Vector3& p3, const AZ::Vector3& p4) { (void)p1; (void)p2; (void)p3; (void)p4; }
virtual void DrawWireQuad(float width, float height) { (void)width; (void)height; }
virtual void DrawQuadGradient(const AZ::Vector3& p1, const AZ::Vector3& p2, const AZ::Vector3& p3, const AZ::Vector3& p4, const AZ::Vector4& firstColor, const AZ::Vector4& secondColor) { (void)p1; (void)p2; (void)p3; (void)p4; (void)firstColor; (void)secondColor; }
virtual void DrawTri(const AZ::Vector3& p1, const AZ::Vector3& p2, const AZ::Vector3& p3) { (void)p1; (void)p2; (void)p3; }
virtual void DrawTriangles(const AZStd::vector<AZ::Vector3>& vertices, const AZ::Color& color) { (void)vertices; (void)color; }
virtual void DrawTrianglesIndexed(const AZStd::vector<AZ::Vector3>& vertices, const AZStd::vector<AZ::u32>& indices, const AZ::Color& color) { (void)vertices; (void)indices, (void)color; }
virtual void DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max) { (void)min; (void)max; }
virtual void DrawSolidBox(const AZ::Vector3& min, const AZ::Vector3& max) { (void)min; (void)max; }
virtual void DrawSolidOBB(const AZ::Vector3& center, const AZ::Vector3& axisX, const AZ::Vector3& axisY, const AZ::Vector3& axisZ, const AZ::Vector3& halfExtents) { (void)center; (void)axisX; (void)axisY; (void)axisZ; (void)halfExtents; }
virtual void DrawPoint(const AZ::Vector3& p, int nSize = 1) { (void)p; (void)nSize; }
virtual void DrawLine(const AZ::Vector3& p1, const AZ::Vector3& p2) { (void)p1; (void)p2; }
virtual void DrawLine(const AZ::Vector3& p1, const AZ::Vector3& p2, const AZ::Vector4& col1, const AZ::Vector4& col2) { (void)p1; (void)p2; (void)col1; (void)col2; }
virtual void DrawLines(const AZStd::vector<AZ::Vector3>& lines, const AZ::Color& color) { (void)lines; (void)color; }
virtual void DrawPolyLine(const AZ::Vector3* pnts, int numPoints, bool cycled = true) { (void)pnts; (void)numPoints; (void)cycled; }
virtual void DrawWireQuad2d(const AZ::Vector2& p1, const AZ::Vector2& p2, float z) { (void)p1; (void)p2; (void)z; }
virtual void DrawLine2d(const AZ::Vector2& p1, const AZ::Vector2& p2, float z) { (void)p1; (void)p2; (void)z; }
virtual void DrawLine2dGradient(const AZ::Vector2& p1, const AZ::Vector2& p2, float z, const AZ::Vector4& firstColor, const AZ::Vector4& secondColor) { (void)p1; (void)p2; (void)z; (void)firstColor; (void)secondColor; }
virtual void DrawWireCircle2d(const AZ::Vector2& center, float radius, float z) { (void)center; (void)radius; (void)z; }
virtual void DrawTerrainCircle(const AZ::Vector3& worldPos, float radius, float height) { (void)worldPos; (void)radius; (void)height; }
virtual void DrawTerrainCircle(const AZ::Vector3& center, float radius, float angle1, float angle2, float height) { (void)center; (void)radius; (void)angle1; (void)angle2; (void)height; }
virtual void DrawArc(const AZ::Vector3& pos, float radius, float startAngleDegrees, float sweepAngleDegrees, float angularStepDegrees, int referenceAxis = 2) { (void)pos; (void)radius; (void)startAngleDegrees; (void)sweepAngleDegrees; (void)angularStepDegrees; (void)referenceAxis; }
virtual void DrawArc(const AZ::Vector3& pos, float radius, float startAngleDegrees, float sweepAngleDegrees, float angularStepDegrees, const AZ::Vector3& fixedAxis) { (void)pos; (void)radius; (void)startAngleDegrees; (void)sweepAngleDegrees; (void)angularStepDegrees; (void)fixedAxis; }
virtual void DrawCircle(const AZ::Vector3& pos, float radius, int nUnchangedAxis = 2 /*z axis*/) { (void)pos; (void)radius; (void)nUnchangedAxis; }
virtual void DrawHalfDottedCircle(const AZ::Vector3& pos, float radius, const AZ::Vector3& viewPos, int nUnchangedAxis = 2 /*z axis*/) { (void)pos; (void)radius; (void)viewPos; (void)nUnchangedAxis; }
virtual void DrawCone(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius, float height, bool drawShaded = true) { (void)pos; (void)dir; (void)radius; (void)height; (void)drawShaded; }
virtual void DrawWireCylinder(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height) { (void)center; (void)axis; (void)radius; (void)height; }
virtual void DrawSolidCylinder(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float height, bool drawShaded = true) { (void)center; (void)axis; (void)radius; (void)height; (void)drawShaded; }
virtual void DrawWireCapsule(const AZ::Vector3& center, const AZ::Vector3& axis, float radius, float heightStraightSection) { (void)center; (void)axis; (void)radius; (void)heightStraightSection; }
virtual void DrawTerrainRect(float x1, float y1, float x2, float y2, float height) { (void)x1; (void)y1; (void)x2; (void)y2; (void)height; }
virtual void DrawTerrainLine(AZ::Vector3 worldPos1, AZ::Vector3 worldPos2) { (void)worldPos1; (void)worldPos2; }
virtual void DrawWireSphere(const AZ::Vector3& pos, float radius) { (void)pos; (void)radius; }
virtual void DrawWireSphere(const AZ::Vector3& pos, const AZ::Vector3 radius) { (void)pos; (void)radius; }
virtual void DrawWireDisk(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius) { (void)pos; (void)dir; (void)radius; }
virtual void DrawBall(const AZ::Vector3& pos, float radius, bool drawShaded = true) { (void)pos; (void)radius; (void)drawShaded; }
virtual void DrawDisk(const AZ::Vector3& pos, const AZ::Vector3& dir, float radius) { (void)pos; (void)dir; (void)radius; }
virtual void DrawArrow(const AZ::Vector3& src, const AZ::Vector3& trg, float fHeadScale = 1, bool b2SidedArrow = false) { (void)src; (void)trg; (void)fHeadScale; (void)b2SidedArrow; }
virtual void DrawTextLabel(const AZ::Vector3& pos, float size, const char* text, const bool bCenter = false, int srcOffsetX = 0, int srcOffsetY = 0) { (void)pos; (void)size; (void)text; (void)bCenter; (void)srcOffsetX; (void)srcOffsetY; }
virtual void Draw2dTextLabel(float x, float y, float size, const char* text, bool bCenter = false) { (void)x; (void)y; (void)size; (void)text; (void)bCenter; }
virtual void DrawTextOn2DBox(const AZ::Vector3& pos, const char* text, float textScale, const AZ::Vector4& TextColor, const AZ::Vector4& TextBackColor) { (void)pos; (void)text; (void)textScale; (void)TextColor; (void)TextBackColor; }
virtual void DrawTextureLabel(ITexture* texture, const AZ::Vector3& pos, float sizeX, float sizeY, int texIconFlags) { (void)texture; (void)pos; (void)sizeX; (void)sizeY; (void)texIconFlags; }
virtual void DrawTextureLabel(int textureId, const AZ::Vector3& pos, float sizeX, float sizeY, int texIconFlags) { (void)textureId; (void)pos; (void)sizeX; (void)sizeY; (void)texIconFlags; }
virtual void SetLineWidth(float width) { (void)width; }
virtual bool IsVisible(const AZ::Aabb& bounds) { (void)bounds; return false; }
virtual int SetFillMode(int nFillMode) { (void)nFillMode; return 0; }
virtual float GetLineWidth() { return 0.0f; }
virtual float GetAspectRatio() { return 0.0f; }
virtual void DepthTestOff() {}
virtual void DepthTestOn() {}
virtual void DepthWriteOff() {}
virtual void DepthWriteOn() {}
virtual void CullOff() {}
virtual void CullOn() {}
virtual bool SetDrawInFrontMode(bool bOn) { (void)bOn; return false; }
virtual AZ::u32 GetState() { return 0; }
virtual AZ::u32 SetState(AZ::u32 state) { (void)state; return 0; }
virtual AZ::u32 SetStateFlag(AZ::u32 state) { (void)state; return 0; }
virtual AZ::u32 ClearStateFlag(AZ::u32 state) { (void)state; return 0; }
virtual void PushMatrix(const AZ::Transform& tm) { (void)tm; }
virtual void PopMatrix() {}
protected:
~DebugDisplayRequests() = default;
};
/// Inherit from DebugDisplayRequestBus::Handler to implement the DebugDisplayRequests interface.
using DebugDisplayRequestBus = AZ::EBus<DebugDisplayRequests>;
/// Structure to hold information relevant to a given viewport.
struct ViewportInfo
{
int m_viewportId; ///< Unique way to identify a given viewport.
};
/// Provide viewport drawing tied to a specific entity. Components can listen
/// to EntityDebugDisplayEvents in order to draw debug visuals in the viewport
/// for a given entity/component at the correct point in the frame.
class EntityDebugDisplayEvents
: public AZ::ComponentBus
{
public:
using Bus = AZ::EBus<EntityDebugDisplayEvents>;
/// Provide viewport drawing for a particular entity.
/// @param viewportInfo Can be used to determine information such as the camera position.
/// @param debugDisplay Contains interface for debug draw/display commands.
virtual void DisplayEntityViewport(
const ViewportInfo& /*viewportInfo*/,
DebugDisplayRequests& /*debugDisplay*/) {}
protected:
~EntityDebugDisplayEvents() = default;
};
// Inherit from this type to implement EntityDebugDisplayEvents.
using EntityDebugDisplayEventBus = AZ::EBus<EntityDebugDisplayEvents>;
/// Provide viewport drawing not tied to a specific entity. Any type can
/// implement this bus to provide drawing commands from DebugDisplayRequests.
class ViewportDebugDisplayEvents
: public AZ::EBusTraits
{
public:
using BusIdType = EntityContextId;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
/// Display drawing in world space.
virtual void DisplayViewport(
const ViewportInfo& /*viewportInfo*/,
DebugDisplayRequests& /*debugDisplay*/) {}
/// Display drawing in screen space.
virtual void DisplayViewport2d(
const ViewportInfo& /*viewportInfo*/,
DebugDisplayRequests& /*debugDisplay*/) {}
protected:
~ViewportDebugDisplayEvents() = default;
};
// Inherit from this type to implement ViewportDebugDisplayEvents.
using ViewportDebugDisplayEventBus = AZ::EBus<ViewportDebugDisplayEvents>;
class DebugDisplayEvents
: public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<DebugDisplayEvents>;
virtual void DrawGlobalDebugInfo() = 0;
};
using DebugDisplayEventBus = AZ::EBus<DebugDisplayEvents>;
} // namespace AzFramework

Some files were not shown because too many files have changed in this diff Show More