PrefabInstanceSpawner.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Vegetation/PrefabInstanceSpawner.h>
  9. #include <AzCore/Asset/AssetManager.h>
  10. #include <AzCore/Asset/AssetSerializer.h>
  11. #include <AzCore/Math/Transform.h>
  12. #include <AzCore/RTTI/BehaviorContext.h>
  13. #include <AzCore/Serialization/EditContext.h>
  14. #include <AzFramework/Components/TransformComponent.h>
  15. #include <AzFramework/StringFunc/StringFunc.h>
  16. #include <Vegetation/AreaComponentBase.h>
  17. #include <Vegetation/InstanceData.h>
  18. #include <Vegetation/Ebuses/DescriptorNotificationBus.h>
  19. #include <Vegetation/Ebuses/InstanceSystemRequestBus.h>
  20. namespace Vegetation
  21. {
  22. PrefabInstanceSpawner::PrefabInstanceSpawner()
  23. {
  24. UnloadAssets();
  25. }
  26. PrefabInstanceSpawner::~PrefabInstanceSpawner()
  27. {
  28. UnloadAssets();
  29. AZ_Assert(m_instanceTickets.empty(), "Destroying spawner while %u spawn tickets still exist!", m_instanceTickets.size());
  30. }
  31. void PrefabInstanceSpawner::Reflect(AZ::ReflectContext* context)
  32. {
  33. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  34. if (serialize)
  35. {
  36. serialize->Class<PrefabInstanceSpawner, InstanceSpawner>()
  37. ->Version(0)->Field(
  38. "SpawnableAsset", &PrefabInstanceSpawner::m_spawnableAsset)
  39. ;
  40. AZ::EditContext* edit = serialize->GetEditContext();
  41. if (edit)
  42. {
  43. edit->Class<PrefabInstanceSpawner>(
  44. "Prefab", "Prefab Instance")
  45. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  46. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  47. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  48. ->DataElement(AZ::Edit::UIHandlers::Default, &PrefabInstanceSpawner::m_spawnableAsset, "Prefab Asset", "Prefab asset")
  49. ->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, false)
  50. ->Attribute(AZ::Edit::Attributes::HideProductFilesInAssetPicker, true)
  51. ->Attribute(AZ::Edit::Attributes::AssetPickerTitle, "a Prefab")
  52. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &PrefabInstanceSpawner::SpawnableAssetChanged)
  53. ;
  54. }
  55. }
  56. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  57. {
  58. behaviorContext->Class<PrefabInstanceSpawner>()
  59. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  60. ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
  61. ->Attribute(AZ::Script::Attributes::Module, "vegetation")
  62. ->Constructor()
  63. ->Method("GetPrefabAssetPath", &PrefabInstanceSpawner::GetSpawnableAssetPath)
  64. ->Method("SetPrefabAssetPath", &PrefabInstanceSpawner::SetSpawnableAssetPath)
  65. ->Method("GetPrefabAssetId", &PrefabInstanceSpawner::GetSpawnableAssetId)
  66. ->Method("SetPrefabAssetId", &PrefabInstanceSpawner::SetSpawnableAssetId);
  67. }
  68. }
  69. bool PrefabInstanceSpawner::DataIsEquivalent(const InstanceSpawner& baseRhs) const
  70. {
  71. if (const auto* rhs = azrtti_cast<const PrefabInstanceSpawner*>(&baseRhs))
  72. {
  73. return m_spawnableAsset == rhs->m_spawnableAsset;
  74. }
  75. // Not the same subtypes, so definitely not a data match.
  76. return false;
  77. }
  78. void PrefabInstanceSpawner::LoadAssets()
  79. {
  80. UnloadAssets();
  81. // Note that the spawnable tickets manage and track asset loading as well. We *could* just rely on that and mark
  82. // the spawner as immediately ready for use (i.e. always return "true" in IsLoaded() and IsSpawnable() ), but this
  83. // would cause us to wait until the first instance is spawned to load the asset, creating a delay right at the point
  84. // that the vegetation is becoming visible. It would also cause the asset to get auto-unloaded every time all the
  85. // instances using it are despawned. By loading it *prior* to marking things as ready, we can ensure that we have the
  86. // asset at the point that the first instance is spawned, and that it won't get auto-unloaded every time the instances
  87. // are despawned.
  88. m_spawnableAsset.QueueLoad();
  89. AZ::Data::AssetBus::MultiHandler::BusConnect(m_spawnableAsset.GetId());
  90. }
  91. void PrefabInstanceSpawner::UnloadAssets()
  92. {
  93. // It's possible under some circumstances that we might unload assets before destroying all spawned instances
  94. // due to the way the vegetation system queues up delete requests and descriptor unregistrations. If so,
  95. // despawn the actual spawned instances here, but leave the ticket entries in the instance ticket map and don't
  96. // delete the ticket pointers. The tickets will get cleaned up when the vegetation system gets around to requesting
  97. // the instance destroy.
  98. if (!m_instanceTickets.empty())
  99. {
  100. for (auto& ticket : m_instanceTickets)
  101. {
  102. DespawnAssetInstance(ticket);
  103. }
  104. }
  105. ResetSpawnableAsset();
  106. NotifyOnAssetsUnloaded();
  107. }
  108. void PrefabInstanceSpawner::ResetSpawnableAsset()
  109. {
  110. AZ::Data::AssetBus::MultiHandler::BusDisconnect();
  111. m_spawnableAsset.Release();
  112. UpdateCachedValues();
  113. m_spawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::QueueLoad);
  114. }
  115. void PrefabInstanceSpawner::UpdateCachedValues()
  116. {
  117. // Once our assets are loaded and at the point that they're getting registered,
  118. // cache off the spawnable state for use from multiple threads.
  119. m_assetLoadedAndSpawnable = m_spawnableAsset.IsReady();
  120. }
  121. void PrefabInstanceSpawner::OnRegisterUniqueDescriptor()
  122. {
  123. UpdateCachedValues();
  124. }
  125. void PrefabInstanceSpawner::OnReleaseUniqueDescriptor()
  126. {
  127. }
  128. bool PrefabInstanceSpawner::HasEmptyAssetReferences() const
  129. {
  130. // If we don't have a valid Spawnable Asset, then that means we're expecting to spawn empty instances.
  131. return !m_spawnableAsset.GetId().IsValid();
  132. }
  133. bool PrefabInstanceSpawner::IsLoaded() const
  134. {
  135. return m_assetLoadedAndSpawnable;
  136. }
  137. bool PrefabInstanceSpawner::IsSpawnable() const
  138. {
  139. return m_assetLoadedAndSpawnable;
  140. }
  141. AZStd::string PrefabInstanceSpawner::GetName() const
  142. {
  143. AZStd::string assetName;
  144. if (!HasEmptyAssetReferences())
  145. {
  146. // Get the asset file name
  147. assetName = m_spawnableAsset.GetHint();
  148. if (!m_spawnableAsset.GetHint().empty())
  149. {
  150. AzFramework::StringFunc::Path::GetFileName(m_spawnableAsset.GetHint().c_str(), assetName);
  151. }
  152. }
  153. else
  154. {
  155. assetName = "<asset name>";
  156. }
  157. return assetName;
  158. }
  159. bool PrefabInstanceSpawner::ValidateAssetContents(const AZ::Data::Asset<AZ::Data::AssetData> asset) const
  160. {
  161. bool validAsset = true;
  162. // Basic safety check: Make sure the asset is a spawnable.
  163. auto spawnableAsset = azrtti_cast<AzFramework::Spawnable*>(asset.GetData());
  164. if (!spawnableAsset)
  165. {
  166. return false;
  167. }
  168. // Loop through all the components on all the entities in the spawnable, looking for any type of Vegetation Area.
  169. // If we try to dynamically spawn vegetation areas, as they spawn in they will non-deterministically start spawning
  170. // (or blocking) other vegetation while we're in the midst of spawning the higher-level vegetation area. Threading
  171. // and timing affects which one wins out. It may also cause other bugs.
  172. const AzFramework::Spawnable::EntityList& entities = spawnableAsset->GetEntities();
  173. for (auto& entity : entities)
  174. {
  175. auto components = entity->GetComponents();
  176. for (auto component : components)
  177. {
  178. if (azrtti_istypeof<AreaComponentBase*>(component))
  179. {
  180. validAsset = false;
  181. AZ_Error("Vegetation", false,
  182. "Vegetation system cannot spawn prefabs containing a component of type '%s'",
  183. component->RTTI_GetTypeName());
  184. }
  185. }
  186. }
  187. return validAsset;
  188. }
  189. void PrefabInstanceSpawner::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
  190. {
  191. if (m_spawnableAsset.GetId() == asset.GetId())
  192. {
  193. // Make sure that the spawnable asset we're loading doesn't contain any data incompatible with
  194. // the dynamic vegetation system.
  195. // This check needs to be performed at asset loading time as opposed to authoring / configuration
  196. // time because the spawnable asset can be changed independently from the authoring of this component.
  197. bool validAsset = ValidateAssetContents(asset);
  198. ResetSpawnableAsset();
  199. if (validAsset)
  200. {
  201. m_spawnableAsset = asset;
  202. }
  203. UpdateCachedValues();
  204. NotifyOnAssetsLoaded();
  205. }
  206. }
  207. void PrefabInstanceSpawner::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
  208. {
  209. OnAssetReady(asset);
  210. }
  211. AZStd::string PrefabInstanceSpawner::GetSpawnableAssetPath() const
  212. {
  213. AZStd::string assetPathString;
  214. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  215. assetPathString, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_spawnableAsset.GetId());
  216. return assetPathString;
  217. }
  218. void PrefabInstanceSpawner::SetSpawnableAssetPath(const AZStd::string& assetPath)
  219. {
  220. if (!assetPath.empty())
  221. {
  222. AZ::Data::AssetId assetId;
  223. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  224. assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, assetPath.c_str(),
  225. AZ::Data::s_invalidAssetType, false);
  226. if (assetId.IsValid())
  227. {
  228. SetSpawnableAssetId(assetId);
  229. }
  230. else
  231. {
  232. AZ_Error("Vegetation", false, "Asset '%s' is invalid.", assetPath.c_str());
  233. }
  234. }
  235. else
  236. {
  237. SetSpawnableAssetId(AZ::Data::AssetId());
  238. }
  239. }
  240. AZ::Data::AssetId PrefabInstanceSpawner::GetSpawnableAssetId() const
  241. {
  242. return m_spawnableAsset.GetId();
  243. }
  244. void PrefabInstanceSpawner::SetSpawnableAssetId(const AZ::Data::AssetId& assetId)
  245. {
  246. if (assetId.IsValid())
  247. {
  248. AZ::Data::AssetInfo assetInfo;
  249. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  250. assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, assetId);
  251. if (assetInfo.m_assetType == m_spawnableAsset.GetType())
  252. {
  253. m_spawnableAsset.Create(assetId, false);
  254. LoadAssets();
  255. }
  256. else
  257. {
  258. AZ_Error(
  259. "Vegetation", false, "Asset '%s' is of type %s, but expected a Spawnable type.",
  260. assetId.ToString<AZStd::string>().c_str(), assetInfo.m_assetType.ToString<AZStd::string>().c_str());
  261. }
  262. }
  263. else
  264. {
  265. // An invalid asset ID is treated as a valid way to spawn "empty" instances, so don't print an error, just clear out
  266. // the asset to that it has an invalid asset reference. (See also HasEmptyAssetReferences() above)
  267. m_spawnableAsset = AZ::Data::Asset<AzFramework::Spawnable>();
  268. LoadAssets();
  269. }
  270. }
  271. AZ::u32 PrefabInstanceSpawner::SpawnableAssetChanged()
  272. {
  273. // Whenever we change the spawnable asset, force a refresh of the Entity Inspector
  274. // since we want the Descriptor List to refresh the name of the entry.
  275. NotifyOnAssetsUnloaded();
  276. return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
  277. }
  278. InstancePtr PrefabInstanceSpawner::CreateInstance(const InstanceData& instanceData)
  279. {
  280. InstancePtr opaqueInstanceData = nullptr;
  281. // Create a Transform that represents our instance.
  282. AZ::Transform world = AZ::Transform::CreateFromQuaternionAndTranslation(
  283. instanceData.m_alignment * instanceData.m_rotation, instanceData.m_position);
  284. world.MultiplyByUniformScale(instanceData.m_scale);
  285. // Create a callback for SpawnAllEntities that will set the transform of the root entity to the correct position / rotation / scale
  286. // for our spawned instance.
  287. auto preSpawnCB = [world](
  288. [[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, AzFramework::SpawnableEntityContainerView view)
  289. {
  290. AZ::Entity* rootEntity = *view.begin();
  291. AzFramework::TransformComponent* entityTransform = rootEntity->FindComponent<AzFramework::TransformComponent>();
  292. if (entityTransform)
  293. {
  294. entityTransform->SetWorldTM(world);
  295. }
  296. };
  297. // Create the EntitySpawnTicket here. This pointer is going to get handed off to the vegetation system as opaque instance data,
  298. // where it will be tracked and held onto for the lifetime of the vegetation instance. The vegetation system will pass it back
  299. // in to DestroyInstance at the end of the lifetime, so that's the one place where we will delete the ticket pointers.
  300. AzFramework::EntitySpawnTicket* ticket = aznew AzFramework::EntitySpawnTicket(m_spawnableAsset);
  301. if (ticket->IsValid())
  302. {
  303. // Track the ticket that we've created.
  304. m_instanceTickets.emplace(ticket);
  305. AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
  306. optionalArgs.m_preInsertionCallback = AZStd::move(preSpawnCB);
  307. AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs));
  308. opaqueInstanceData = ticket;
  309. }
  310. else
  311. {
  312. // Something went wrong!
  313. AZ_Assert(ticket->IsValid(), "Unable to instantiate spawnable asset");
  314. delete ticket;
  315. }
  316. return opaqueInstanceData;
  317. }
  318. void PrefabInstanceSpawner::DespawnAssetInstance(AzFramework::EntitySpawnTicket* ticket)
  319. {
  320. if (ticket->IsValid())
  321. {
  322. AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(*ticket);
  323. }
  324. }
  325. void PrefabInstanceSpawner::DestroyInstance([[maybe_unused]] InstanceId id, InstancePtr instance)
  326. {
  327. if (instance)
  328. {
  329. auto ticket = reinterpret_cast<AzFramework::EntitySpawnTicket*>(instance);
  330. // If the spawnable asset instantiated successfully, we should have a record of it.
  331. auto foundInstance = m_instanceTickets.find(ticket);
  332. AZ_Assert(foundInstance != m_instanceTickets.end(), "Couldn't find CreateInstance entry for the EntitySpawnTicket.");
  333. if (foundInstance != m_instanceTickets.end())
  334. {
  335. // The call to DespawnAssetInstance above is technically redundant right now, because when we delete the ticket pointer
  336. // below it will automatically despawn everything anyways. However, it's nice to have a single explicit call to despawn,
  337. // in case we ever need a place to add logging, or have a callback when despawning is complete, etc.
  338. DespawnAssetInstance(ticket);
  339. m_instanceTickets.erase(foundInstance);
  340. }
  341. // The vegetation system has stopped tracking this instance, so it's now safe to delete the ticket pointer.
  342. delete ticket;
  343. }
  344. }
  345. } // namespace Vegetation