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,105 @@
/*
* 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 <CustomAssetExample/Builder/CustomAssetExampleBuilderComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
namespace CustomAssetExample
{
// AZ Components should only initialize their members to null and empty in constructor
// Allocation of data should occur in Init(), once we can guarantee reflection and registration of types
ExampleBuilderComponent::ExampleBuilderComponent()
{
}
// Handle deallocation of your memory allocated in Init()
ExampleBuilderComponent::~ExampleBuilderComponent()
{
}
// Init is where you'll actually allocate memory or create objects
// This ensures that any dependency components will have been been created and serialized
void ExampleBuilderComponent::Init()
{
}
// Activate is where you'd perform registration with other objects and systems.
// All builder classes owned by this component should be registered here
// Any EBuses for the builder classes should also be connected at this point
void ExampleBuilderComponent::Activate()
{
AssetBuilderSDK::AssetBuilderDesc builderDescriptor;
builderDescriptor.m_name = "Example Worker Builder";
builderDescriptor.m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern("*.example", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern("*.exampleinclude", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern("*.examplesource", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern("*.examplejob", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_busId = azrtti_typeid<ExampleBuilderWorker>();
builderDescriptor.m_version = 1; // if you change this, all assets will automatically rebuild
builderDescriptor.m_analysisFingerprint = ""; // if you change this, all assets will re-analyze but not necessarily rebuild.
builderDescriptor.m_createJobFunction = AZStd::bind(&ExampleBuilderWorker::CreateJobs, &m_exampleBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_processJobFunction = AZStd::bind(&ExampleBuilderWorker::ProcessJob, &m_exampleBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
// note that this particualar builder does in fact emit various kinds of dependencies (as an example).
// if your builder is simple and emits no dependencies (for example, it just processes a single file and that file
// doesn't really depend on any other files or jobs), setting the BF_EmitsNoDependencies flag
// will improve "fast analysis" scan performance.
builderDescriptor.m_flags = AssetBuilderSDK::AssetBuilderDesc::BF_None;
m_exampleBuilder.BusConnect(builderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDescriptor);
}
// Disconnects from any EBuses we connected to in Activate()
// Unregisters from objects and systems we register with in Activate()
void ExampleBuilderComponent::Deactivate()
{
m_exampleBuilder.BusDisconnect();
// We don't need to unregister the builder - the AP will handle this for us, because it is managing the lifecycle of this component
}
// This is your opportunity to perform static reflection or type registration of any types you need the serializer to know about
void ExampleBuilderComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<ExampleBuilderComponent, AZ::Component>()
->Version(0)
->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }))
;
}
}
void ExampleBuilderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ExampleBuilderPluginService", 0x1380f480));
}
void ExampleBuilderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ExampleBuilderPluginService", 0x1380f480));
}
void ExampleBuilderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
AZ_UNUSED(required);
}
void ExampleBuilderComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
AZ_UNUSED(dependent);
}
}
@@ -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/Component/Component.h>
#include <CustomAssetExample/Builder/CustomAssetExampleBuilderWorker.h>
namespace CustomAssetExample
{
//! Here's an example of the lifecycle Component you must implement.
//! You must have at least one component to handle the lifecycle of your builder classes.
//! This could be a builder class if you implement the the builder bus handler, and register itself as the builder class
//! But for the purposes of clarity, we will make it just be the lifecycle component in this example
class ExampleBuilderComponent
: public AZ::Component
{
public:
AZ_COMPONENT(ExampleBuilderComponent, "{8872211E-F704-48A9-B7EB-7B80596D871D}");
ExampleBuilderComponent();
~ExampleBuilderComponent() override;
void Init() override;
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 GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
private:
ExampleBuilderWorker m_exampleBuilder;
};
} // namespace CustomAssetExample
@@ -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 <CustomAssetExample/Builder/CustomAssetExampleBuilderWorker.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace CustomAssetExample
{
// Note - Shutdown will be called on a different thread than your process job thread
void ExampleBuilderWorker::ShutDown()
{
m_isShuttingDown = true;
}
// CreateJobs will be called early on in the file scanning pass from the Asset Processor.
// You should create the same jobs, and avoid checking whether the job is up to date or not. The Asset Processor will manage this for you
void ExampleBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
{
if (m_isShuttingDown)
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
return;
}
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.c_str(), ext, false);
// Our *.example extension details a source file with NO dependencies.
// Here we simply create the JobDescriptors for each enabled platform in order to process the source file.
if (AzFramework::StringFunc::Equal(ext.c_str(), "example"))
{
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
// We create a simple job here which only contains the identifying job key and the platform to process the file on
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Compile Example";
descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
// Note that there are additional parameters for the JobDescriptor which may be beneficial in your use case.
// Notable ones include:
// * m_critical - a boolean that flags this job as one which must complete before the Editor will start up.
// * m_priority - an integer where larger values signify that the job should be processed with higher priority than those with lower values.
// Please see the JobDescriptor for the full complement of configuration parameters.
response.m_createJobOutputs.push_back(descriptor);
// One builder can make multiple jobs for the same source file, for the same platform, as long as it emits a different job key per job.
// This allows you to break large compilations up into smaller jobs. Jobs emitted in this manner may be run in parallel
descriptor.m_jobKey = "Second Compile Example";
// Custom parameters that you may need to know about when the job processes can be added to m_jobParameters
descriptor.m_jobParameters[AZ_CRC("hello", 0x3610a686)] = "World";
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
// Our *.examplesource extension details a source file with dependencies.
// Here we declare source file dependencies and forward the info to the Asset Processor
// This example creates the following dependencies:
// * the source file .../test.examplesource depends on the source file .../test.exampleinclude
// * the source file .../test.exampleinclude depends on the source file .../common.exampleinclude
// * the source file .../common.exampleinclude depends on the non-source file .../common.examplefile
// Note - both file extensions "exampleinclude" and "examplesource" are handled by this builder class.
// However, files with extension "exampleinclude" do not create JobDescriptors, so they are not actually being processed by this builder.
// We are only collecting their dependencies here.
AssetBuilderSDK::SourceFileDependency sourceFileDependencyInfo;
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullPath, false);
AzFramework::StringFunc::Path::Normalize(fullPath);
AZStd::string relPath = request.m_sourceFile;
// Source files in this example generate dependencies on a file with the same name, but having "exampleinclude" extensions
if (AzFramework::StringFunc::Equal(ext.c_str(), "examplesource"))
{
AzFramework::StringFunc::Path::ReplaceExtension(relPath, "exampleinclude");
// Declare and add the dependency on the *.exampleinclude file:
sourceFileDependencyInfo.m_sourceFileDependencyPath = relPath;
response.m_sourceFileDependencyList.push_back(sourceFileDependencyInfo);
// Since we're a source file, we also add a job to do the actual compilation (for each enabled platform)
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Compile Example";
descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
// you can also place whatever parameters you want to save for later into this map:
descriptor.m_jobParameters[AZ_CRC("hello", 0x3610a686)] = "World";
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
if (AzFramework::StringFunc::Equal(ext.c_str(), "exampleinclude"))
{
if (AzFramework::StringFunc::Find(request.m_sourceFile.c_str(), "common.exampleinclude") != AZStd::string::npos)
{
// Add any dependencies that common.exampleinclude would like to depend on here, we can also add a non source file as a dependency like we are doing here
AzFramework::StringFunc::Path::ReplaceFullName(fullPath, "common.examplefile");
sourceFileDependencyInfo.m_sourceFileDependencyPath = fullPath;
}
else
{
AzFramework::StringFunc::Path::ReplaceFullName(fullPath, "common.exampleinclude");
// Assigning full path to sourceFileDependency path
sourceFileDependencyInfo.m_sourceFileDependencyPath = fullPath;
}
response.m_sourceFileDependencyList.push_back(sourceFileDependencyInfo);
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
// This example shows how you would be able to declare job dependencies on source files inside a builder and
// forward that info to the asset processor.
// Basically here we are creating a job dependency such that the job with source file ./test.examplejob and
// jobKey "Compile Example" depends on the fingerprint of the job with source file ./test.examplesource and jobkey "Compile Example".
else if (AzFramework::StringFunc::Equal(ext.c_str(), "examplejob"))
{
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Compile Example";
descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
AssetBuilderSDK::SourceFileDependency sourceFile;
sourceFile.m_sourceFileDependencyPath = "test.examplesource";
AssetBuilderSDK::JobDependency jobDependency("Compile Example", platformInfo.m_identifier.c_str(), AssetBuilderSDK::JobDependencyType::Fingerprint, sourceFile);
descriptor.m_jobDependencyList.push_back(jobDependency);
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
AZ_Assert(false, "Unhandled extension type in CustomExampleAssetBuilderWorker.");
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
}
// In this example builder class, we just copy the source file to a modified destination path in the temp directory.
// Later on, this function will be called for jobs the Asset Processor has determined need to be run.
// The request will contain the CreateJobResponse you constructed earlier, including any key value pairs you placed into m_jobParameters
void ExampleBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
{
// This is the most basic example of handling for cancellation requests.
// If possible, you should listen for cancellation requests and then cancel processing work to facilitate faster shutdown of the Asset Processor
// If you need to do more things such as signal a semaphore or other threading work, derive from the Job Cancel Listener and reimplement Cancel()
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
// Use AZ_TracePrintf to communicate job details. The logging system will automatically file the text under the appropriate log file and category.
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Starting Job.\n");
AZStd::string fileName;
AzFramework::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), fileName);
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.c_str(), ext, false);
if (AzFramework::StringFunc::Equal(ext.c_str(), "example"))
{
if (AzFramework::StringFunc::Equal(request.m_jobDescription.m_jobKey.c_str(), "Compile Example"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "example1");
}
else if (AzFramework::StringFunc::Equal(request.m_jobDescription.m_jobKey.c_str(), "Second Compile Example"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "example2");
}
}
else if (AzFramework::StringFunc::Equal(ext.c_str(), "examplesource"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "examplesourceprocessed");
}
else if (AzFramework::StringFunc::Equal(ext.c_str(), "examplejob"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "examplejobprocessed");
}
// All your work should happen inside the tempDirPath.
// The Asset Processor will handle taking the completed files you specify in JobProduct.m_outputProducts from the temp directory into the cache.
AZStd::string destPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), fileName.c_str(), destPath, true);
// Check if we are cancelled or shutting down before doing intensive processing on this source file
if (jobCancelListener.IsCancelled())
{
AZ_TracePrintf(AssetBuilderSDK::WarningWindow, "Cancel was requested for job %s.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
if (m_isShuttingDown)
{
AZ_TracePrintf(AssetBuilderSDK::WarningWindow, "Cancelled job %s because shutdown was requested.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
AZ::IO::LocalFileIO fileIO;
if (fileIO.Copy(request.m_fullPath.c_str(), destPath.c_str()) != AZ::IO::ResultCode::Success)
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Error during processing job %s.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Push all products successfully built into the JobProduct.m_outputProducts.
// Filepaths can be absolute, or relative to your temporary directory.
// The job request struct has the temp directory, so it will be properly reconstructed to an absolute path later.
AssetBuilderSDK::JobProduct jobProduct(fileName);
// Note - you must also add the asset type to the JobProduct.
// If you have direct access to the type within your gem, you can grab the asset type directly:
// jobProduct.m_productAssetType = return AZ::AzTypeInfo<CustomAssetExample>::Uuid();
// If you need to cross a gem boundary, you can use the AssetTypeInfo EBus and EBusFindAssetTypeByName:
// EBusFindAssetTypeByName assetType("customassetexample");
// AssetTypeInfoBus::BroadcastResult(assetType, &AssetTypeInfo::GetAssetType);
// jobProduct.m_productAssetType = assetType.GetAssetType();
// You should also pick a unique "SubID" for each product. The final address of an asset (the AssetId) is the combination
// of the subId you choose here and the source file's UUID, so if it is not unique, errors will be generated since your
// assets will shadow each other's address, and not be accessible via AssetId.
// you can pick whatever scheme you want but you should ensure stablility in your choice.
// For example, do not use random numbers - ideally no matter what happens, each time you run this process, the same
// subIds are chosen for the same logical asset (even if your builder starts emitting more or different assets out of the same source)
// You can use AssetBuilderSDK::ConstructSubID(...) helper function if you want to use various bits of the subID for things like LOD level
// or you can come up with your own scheme to ensure stability, using the 32-bit address space as you see fit. It only has to be unique
// and stable within the confines of a single source file, it is not globally unique.
jobProduct.m_productSubID = 0;
// once you've filled up the details of the product in jobProduct, add it to the result list:
response.m_outputProducts.push_back(jobProduct);
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
}
@@ -0,0 +1,45 @@
/*
* 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>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
namespace CustomAssetExample
{
//! Here is an example of a builder worker that actually performs the building of assets.
//! In this example, we only register one, but you can have as many different builders in a single builder module as you want.
class ExampleBuilderWorker
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler // this will deliver you the "shut down!" message on another thread.
{
public:
AZ_RTTI(ExampleBuilderWorker, "{C163F950-BF25-4D60-90D7-8E181E25A9EA}");
ExampleBuilderWorker() = default;
~ExampleBuilderWorker() = default;
//! Asset Builder Callback Functions
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response);
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response);
//////////////////////////////////////////////////////////////////////////
//!AssetBuilderSDK::AssetBuilderCommandBus interface
void ShutDown() override; // if you get this you must fail all existing jobs and return.
//////////////////////////////////////////////////////////////////////////
private:
bool m_isShuttingDown = false;
};
}
@@ -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 <AzCore/RTTI/RTTI.h>
#include <AzCore/Module/Module.h>
#include <CustomAssetExample/Builder/CustomAssetExampleBuilderComponent.h>
namespace CustomAssetExample
{
class CustomAssetExampleModule
: public AZ::Module
{
public:
AZ_RTTI(CustomAssetExampleModule, "{AZ082DD5-0C65-4584-9729-E9AFEAAEAA1D}", AZ::Module);
CustomAssetExampleModule()
: AZ::Module()
{
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
ExampleBuilderComponent::CreateDescriptor(),
});
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList();
}
};
} // namespace CustomAssetExample
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_CustomAssetExample, CustomAssetExample::CustomAssetExampleModule)
@@ -0,0 +1,37 @@
/*
* 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.
*
*/
#if !defined(CUSTOM_ASSET_EXAMPLE_EDITOR)
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Module/Module.h>
/**
* Currently, our example asset does not have any runtime components.
* Therefore, this is just a stub module with the minimum amount of initialization to register properly in the AZ::Module system
*/
namespace CustomAssetExample
{
class CustomAssetExampleModule
: public AZ::Module
{
public:
AZ_RTTI(CustomAssetExampleModule, "{B986F478-FDC1-4A7A-A7D5-D72246C47017}", AZ::Module);
CustomAssetExampleModule() = default;
};
} // namespace CustomAssetExample
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_CustomAssetExample, CustomAssetExample::CustomAssetExampleModule)
#endif // !defined(CUSTOM_ASSET_EXAMPLE_EDITOR)